@wrongstack/cli 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +398 -67
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, DefaultLogger, DefaultModelsRegistry, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader,
|
|
2
|
+
import { color, DefaultLogger, DefaultModelsRegistry, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, FLEET_ROSTER, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, makeDirectorSessionFactory, makeAgentSubagentRunner, Director, DefaultMultiAgentCoordinator, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader, decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core';
|
|
3
3
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
4
4
|
import * as fs6 from 'fs/promises';
|
|
5
5
|
import { writeFileSync } from 'fs';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
|
-
import * as
|
|
7
|
+
import * as path6 from 'path';
|
|
8
8
|
import { MCPRegistry } from '@wrongstack/mcp';
|
|
9
9
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
10
10
|
import { rememberTool, forgetTool } from '@wrongstack/tools';
|
|
@@ -541,7 +541,7 @@ var ReadlineInputReader = class {
|
|
|
541
541
|
history = [];
|
|
542
542
|
pending = false;
|
|
543
543
|
constructor(opts = {}) {
|
|
544
|
-
this.historyFile = opts.historyFile ??
|
|
544
|
+
this.historyFile = opts.historyFile ?? path6.join(os3.homedir(), ".wrongstack", "history");
|
|
545
545
|
}
|
|
546
546
|
async loadHistory() {
|
|
547
547
|
try {
|
|
@@ -553,7 +553,7 @@ var ReadlineInputReader = class {
|
|
|
553
553
|
}
|
|
554
554
|
async saveHistory() {
|
|
555
555
|
try {
|
|
556
|
-
await fs6.mkdir(
|
|
556
|
+
await fs6.mkdir(path6.dirname(this.historyFile), { recursive: true });
|
|
557
557
|
await fs6.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
558
558
|
} catch {
|
|
559
559
|
}
|
|
@@ -1046,6 +1046,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
1046
1046
|
statsCommand(opts),
|
|
1047
1047
|
spawnCommand(opts),
|
|
1048
1048
|
agentsCommand(opts),
|
|
1049
|
+
fleetCommand(opts),
|
|
1049
1050
|
metricsCommand(opts),
|
|
1050
1051
|
healthCommand(opts),
|
|
1051
1052
|
memoryCommand(opts),
|
|
@@ -1167,8 +1168,8 @@ function initCommand(opts) {
|
|
|
1167
1168
|
description: "Scaffold .wrongstack/AGENTS.md in the current project.",
|
|
1168
1169
|
async run(args, ctx) {
|
|
1169
1170
|
const force = args.trim() === "--force";
|
|
1170
|
-
const dir =
|
|
1171
|
-
const file =
|
|
1171
|
+
const dir = path6.join(ctx.projectRoot, ".wrongstack");
|
|
1172
|
+
const file = path6.join(dir, "AGENTS.md");
|
|
1172
1173
|
try {
|
|
1173
1174
|
await fs6.access(file);
|
|
1174
1175
|
if (!force) {
|
|
@@ -1199,7 +1200,7 @@ No project type auto-detected. Edit the file to add build/test commands and conv
|
|
|
1199
1200
|
async function detectProjectFacts(root) {
|
|
1200
1201
|
const facts = { hints: [] };
|
|
1201
1202
|
try {
|
|
1202
|
-
const pkg = JSON.parse(await fs6.readFile(
|
|
1203
|
+
const pkg = JSON.parse(await fs6.readFile(path6.join(root, "package.json"), "utf8"));
|
|
1203
1204
|
const scripts = pkg.scripts ?? {};
|
|
1204
1205
|
const pm = (pkg.packageManager ?? "npm").split("@")[0] ?? "npm";
|
|
1205
1206
|
if (scripts["build"]) facts.build = `${pm} run build`;
|
|
@@ -1210,28 +1211,28 @@ async function detectProjectFacts(root) {
|
|
|
1210
1211
|
} catch {
|
|
1211
1212
|
}
|
|
1212
1213
|
try {
|
|
1213
|
-
await fs6.access(
|
|
1214
|
+
await fs6.access(path6.join(root, "pyproject.toml"));
|
|
1214
1215
|
facts.test ??= "pytest";
|
|
1215
1216
|
facts.lint ??= "ruff check .";
|
|
1216
1217
|
facts.hints.push("pyproject.toml");
|
|
1217
1218
|
} catch {
|
|
1218
1219
|
}
|
|
1219
1220
|
try {
|
|
1220
|
-
await fs6.access(
|
|
1221
|
+
await fs6.access(path6.join(root, "go.mod"));
|
|
1221
1222
|
facts.build ??= "go build ./...";
|
|
1222
1223
|
facts.test ??= "go test ./...";
|
|
1223
1224
|
facts.hints.push("go.mod");
|
|
1224
1225
|
} catch {
|
|
1225
1226
|
}
|
|
1226
1227
|
try {
|
|
1227
|
-
await fs6.access(
|
|
1228
|
+
await fs6.access(path6.join(root, "Cargo.toml"));
|
|
1228
1229
|
facts.build ??= "cargo build";
|
|
1229
1230
|
facts.test ??= "cargo test";
|
|
1230
1231
|
facts.hints.push("Cargo.toml");
|
|
1231
1232
|
} catch {
|
|
1232
1233
|
}
|
|
1233
1234
|
try {
|
|
1234
|
-
await fs6.access(
|
|
1235
|
+
await fs6.access(path6.join(root, "Makefile"));
|
|
1235
1236
|
facts.build ??= "make";
|
|
1236
1237
|
facts.test ??= "make test";
|
|
1237
1238
|
facts.hints.push("Makefile");
|
|
@@ -1673,18 +1674,46 @@ function statusIcon(status) {
|
|
|
1673
1674
|
if (status === "degraded") return color.yellow("\u25CF");
|
|
1674
1675
|
return color.red("\u25CF");
|
|
1675
1676
|
}
|
|
1677
|
+
function parseSpawnFlags(input) {
|
|
1678
|
+
const opts = {};
|
|
1679
|
+
let rest = input;
|
|
1680
|
+
const consume = (re) => {
|
|
1681
|
+
const m = rest.match(re);
|
|
1682
|
+
if (m) {
|
|
1683
|
+
rest = rest.slice(m[0].length).replace(/^\s+/, "");
|
|
1684
|
+
return m;
|
|
1685
|
+
}
|
|
1686
|
+
return null;
|
|
1687
|
+
};
|
|
1688
|
+
while (rest.length > 0) {
|
|
1689
|
+
let m;
|
|
1690
|
+
if (m = consume(/^--provider=(\S+)\s*/)) opts.provider = m[1];
|
|
1691
|
+
else if (m = consume(/^--model=(\S+)\s*/)) opts.model = m[1];
|
|
1692
|
+
else if (m = consume(/^--name=("([^"]+)"|(\S+))\s*/)) opts.name = m[2] ?? m[3];
|
|
1693
|
+
else if (m = consume(/^--tools=(\S+)\s*/)) opts.tools = m[1].split(",").map((t) => t.trim()).filter(Boolean);
|
|
1694
|
+
else if (m = consume(/^-p\s+(\S+)\s*/)) opts.provider = m[1];
|
|
1695
|
+
else if (m = consume(/^-m\s+(\S+)\s*/)) opts.model = m[1];
|
|
1696
|
+
else if (m = consume(/^-n\s+("([^"]+)"|(\S+))\s*/)) opts.name = m[2] ?? m[3];
|
|
1697
|
+
else break;
|
|
1698
|
+
}
|
|
1699
|
+
return { description: rest.trim(), opts };
|
|
1700
|
+
}
|
|
1676
1701
|
function spawnCommand(opts) {
|
|
1677
1702
|
return {
|
|
1678
1703
|
name: "spawn",
|
|
1679
|
-
description: "Spawn an isolated subagent to handle a task. Usage: /spawn <task description>",
|
|
1704
|
+
description: "Spawn an isolated subagent to handle a task. Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>",
|
|
1680
1705
|
async run(args) {
|
|
1681
|
-
const description = args.trim();
|
|
1682
|
-
if (!description)
|
|
1706
|
+
const { description, opts: parsed } = parseSpawnFlags(args.trim());
|
|
1707
|
+
if (!description) {
|
|
1708
|
+
return {
|
|
1709
|
+
message: "Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>"
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1683
1712
|
if (!opts.onSpawn) {
|
|
1684
1713
|
return { message: "Multi-agent is not enabled in this session." };
|
|
1685
1714
|
}
|
|
1686
1715
|
try {
|
|
1687
|
-
const summary = await opts.onSpawn(description);
|
|
1716
|
+
const summary = Object.keys(parsed).length > 0 ? await opts.onSpawn(description, parsed) : await opts.onSpawn(description);
|
|
1688
1717
|
return { message: summary };
|
|
1689
1718
|
} catch (err) {
|
|
1690
1719
|
return {
|
|
@@ -1706,6 +1735,63 @@ function agentsCommand(opts) {
|
|
|
1706
1735
|
}
|
|
1707
1736
|
};
|
|
1708
1737
|
}
|
|
1738
|
+
function fleetCommand(opts) {
|
|
1739
|
+
return {
|
|
1740
|
+
name: "fleet",
|
|
1741
|
+
description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|help]",
|
|
1742
|
+
help: [
|
|
1743
|
+
"Usage:",
|
|
1744
|
+
" /fleet Show fleet status (alias for /fleet status).",
|
|
1745
|
+
" /fleet status Pending + completed subagent task table.",
|
|
1746
|
+
" /fleet usage Per-subagent runtime cost \u2014 iterations, tool calls, duration.",
|
|
1747
|
+
" /fleet kill <id> Terminate a running subagent by id (or prefix).",
|
|
1748
|
+
" /fleet manifest Print the director manifest (only with --director).",
|
|
1749
|
+
" /fleet help Show this help.",
|
|
1750
|
+
"",
|
|
1751
|
+
"Subagent ids are returned by /spawn and listed in /fleet status."
|
|
1752
|
+
].join("\n"),
|
|
1753
|
+
async run(args) {
|
|
1754
|
+
if (!opts.onFleet) {
|
|
1755
|
+
return { message: "Multi-agent is not enabled in this session." };
|
|
1756
|
+
}
|
|
1757
|
+
const trimmed = args.trim();
|
|
1758
|
+
const [verb, ...rest] = trimmed.length === 0 ? ["status"] : trimmed.split(/\s+/);
|
|
1759
|
+
const target = rest.join(" ").trim() || void 0;
|
|
1760
|
+
switch (verb) {
|
|
1761
|
+
case "status":
|
|
1762
|
+
case "usage":
|
|
1763
|
+
case "manifest": {
|
|
1764
|
+
const out = await opts.onFleet(verb, void 0);
|
|
1765
|
+
return { message: out };
|
|
1766
|
+
}
|
|
1767
|
+
case "kill": {
|
|
1768
|
+
if (!target) {
|
|
1769
|
+
return { message: "Usage: /fleet kill <subagent-id>" };
|
|
1770
|
+
}
|
|
1771
|
+
const out = await opts.onFleet("kill", target);
|
|
1772
|
+
return { message: out };
|
|
1773
|
+
}
|
|
1774
|
+
case "help":
|
|
1775
|
+
case "?":
|
|
1776
|
+
return {
|
|
1777
|
+
message: [
|
|
1778
|
+
"/fleet \u2014 inspect or control the subagent fleet",
|
|
1779
|
+
"",
|
|
1780
|
+
" /fleet \u2192 status (default)",
|
|
1781
|
+
" /fleet status pending + completed tasks per subagent",
|
|
1782
|
+
" /fleet usage iterations, tool calls, duration roll-up",
|
|
1783
|
+
" /fleet kill <id> terminate a subagent",
|
|
1784
|
+
" /fleet manifest director manifest (requires --director)"
|
|
1785
|
+
].join("\n")
|
|
1786
|
+
};
|
|
1787
|
+
default:
|
|
1788
|
+
return {
|
|
1789
|
+
message: `Unknown subcommand "${verb}". Try: status | usage | kill <id> | manifest | help`
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1709
1795
|
|
|
1710
1796
|
// src/pre-launch.ts
|
|
1711
1797
|
var MANIFESTS = [
|
|
@@ -1722,13 +1808,13 @@ var MANIFESTS = [
|
|
|
1722
1808
|
];
|
|
1723
1809
|
async function detectProjectKind(projectRoot) {
|
|
1724
1810
|
try {
|
|
1725
|
-
await fs6.access(
|
|
1811
|
+
await fs6.access(path6.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
1726
1812
|
return "initialized";
|
|
1727
1813
|
} catch {
|
|
1728
1814
|
}
|
|
1729
1815
|
for (const m of MANIFESTS) {
|
|
1730
1816
|
try {
|
|
1731
|
-
await fs6.access(
|
|
1817
|
+
await fs6.access(path6.join(projectRoot, m));
|
|
1732
1818
|
return "project";
|
|
1733
1819
|
} catch {
|
|
1734
1820
|
}
|
|
@@ -1736,8 +1822,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
1736
1822
|
return "empty";
|
|
1737
1823
|
}
|
|
1738
1824
|
async function scaffoldAgentsMd(projectRoot) {
|
|
1739
|
-
const dir =
|
|
1740
|
-
const file =
|
|
1825
|
+
const dir = path6.join(projectRoot, ".wrongstack");
|
|
1826
|
+
const file = path6.join(dir, "AGENTS.md");
|
|
1741
1827
|
const facts = await detectProjectFacts(projectRoot);
|
|
1742
1828
|
const body = renderAgentsTemplate(facts);
|
|
1743
1829
|
await fs6.mkdir(dir, { recursive: true });
|
|
@@ -1750,7 +1836,7 @@ async function runProjectCheck(opts) {
|
|
|
1750
1836
|
if (kind === "initialized") {
|
|
1751
1837
|
renderer.write(
|
|
1752
1838
|
`
|
|
1753
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
1839
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path6.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
1754
1840
|
`
|
|
1755
1841
|
);
|
|
1756
1842
|
return true;
|
|
@@ -2412,7 +2498,7 @@ function renderProgress2(ratio, width) {
|
|
|
2412
2498
|
return FILLED2.repeat(capped) + EMPTY2.repeat(width - capped);
|
|
2413
2499
|
}
|
|
2414
2500
|
async function bootConfig(flags) {
|
|
2415
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
2501
|
+
const cwd = typeof flags["cwd"] === "string" ? path6.resolve(flags["cwd"]) : process.cwd();
|
|
2416
2502
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
2417
2503
|
const projectRoot = pathResolver.projectRoot;
|
|
2418
2504
|
const userHome = os3.homedir();
|
|
@@ -2474,31 +2560,71 @@ async function ensureProjectMeta(paths, projectRoot) {
|
|
|
2474
2560
|
}
|
|
2475
2561
|
}
|
|
2476
2562
|
var MultiAgentHost = class {
|
|
2477
|
-
constructor(deps) {
|
|
2563
|
+
constructor(deps, opts = {}) {
|
|
2478
2564
|
this.deps = deps;
|
|
2565
|
+
this.opts = opts;
|
|
2479
2566
|
}
|
|
2480
2567
|
deps;
|
|
2481
2568
|
coordinator;
|
|
2569
|
+
/** Lazily built when `opts.directorMode` is set. Owns its own internal
|
|
2570
|
+
* coordinator; the host's `coordinator` field still points at it so
|
|
2571
|
+
* the rest of the methods don't need to branch. */
|
|
2572
|
+
director;
|
|
2573
|
+
/** Lazily built alongside the director — produces per-subagent JSONL
|
|
2574
|
+
* writers under `<sessionsRoot>/<runId>/`. Null in non-director mode. */
|
|
2575
|
+
sessionFactory;
|
|
2482
2576
|
pending = /* @__PURE__ */ new Map();
|
|
2483
2577
|
results = [];
|
|
2578
|
+
opts;
|
|
2579
|
+
/**
|
|
2580
|
+
* Force the lazy build path to run *now* and return the live Director,
|
|
2581
|
+
* or null when director mode is off. Used by the CLI to register the
|
|
2582
|
+
* fleet's LLM-callable orchestration tools (spawn_subagent,
|
|
2583
|
+
* assign_task, await_tasks, ask_subagent, roll_up, terminate_subagent,
|
|
2584
|
+
* fleet_status, fleet_usage) into the leader's ToolRegistry before the
|
|
2585
|
+
* agent starts — without this, the leader literally cannot see the
|
|
2586
|
+
* orchestration tools and `--director` becomes a no-op.
|
|
2587
|
+
*/
|
|
2588
|
+
async ensureDirector() {
|
|
2589
|
+
if (!this.opts.directorMode) return null;
|
|
2590
|
+
await this.ensureCoordinator();
|
|
2591
|
+
return this.director ?? null;
|
|
2592
|
+
}
|
|
2484
2593
|
async ensureCoordinator() {
|
|
2485
2594
|
if (this.coordinator) return this.coordinator;
|
|
2486
2595
|
const config = this.deps.configStore.get();
|
|
2596
|
+
if (this.opts.directorMode && this.opts.sessionsRoot && !this.sessionFactory) {
|
|
2597
|
+
this.sessionFactory = makeDirectorSessionFactory({
|
|
2598
|
+
sessionsRoot: this.opts.sessionsRoot,
|
|
2599
|
+
directorRunId: this.opts.directorRunId
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2487
2602
|
const factory = async (subCfg) => {
|
|
2488
2603
|
const events = new EventBus();
|
|
2489
|
-
const provider = await this.buildSubagentProvider(config);
|
|
2604
|
+
const provider = await this.buildSubagentProvider(config, subCfg.provider);
|
|
2490
2605
|
const baseSystem = await this.deps.systemPromptBuilder.build({
|
|
2491
2606
|
cwd: this.deps.cwd,
|
|
2492
2607
|
projectRoot: this.deps.projectRoot,
|
|
2493
2608
|
tools: this.filterTools(subCfg.tools),
|
|
2494
2609
|
model: subCfg.model ?? config.model,
|
|
2495
|
-
provider: config.provider
|
|
2610
|
+
provider: subCfg.provider ?? config.provider
|
|
2496
2611
|
});
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
id
|
|
2500
|
-
|
|
2501
|
-
|
|
2612
|
+
let subSession;
|
|
2613
|
+
if (this.sessionFactory) {
|
|
2614
|
+
const subagentName = subCfg.name ?? subCfg.id ?? `sub_${randomUUID().slice(0, 8)}`;
|
|
2615
|
+
subSession = await this.sessionFactory.createSubagentSession({
|
|
2616
|
+
subagentId: subagentName,
|
|
2617
|
+
provider: subCfg.provider ?? config.provider,
|
|
2618
|
+
model: subCfg.model ?? config.model,
|
|
2619
|
+
title: `subagent: ${subagentName}`
|
|
2620
|
+
});
|
|
2621
|
+
} else {
|
|
2622
|
+
const parentSession = this.deps.session;
|
|
2623
|
+
subSession = {
|
|
2624
|
+
id: parentSession.id,
|
|
2625
|
+
append: (ev) => parentSession.append({ ...ev })
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2502
2628
|
const ctx = new Context({
|
|
2503
2629
|
systemPrompt: baseSystem,
|
|
2504
2630
|
provider,
|
|
@@ -2526,15 +2652,27 @@ var MultiAgentHost = class {
|
|
|
2526
2652
|
return { agent, events };
|
|
2527
2653
|
};
|
|
2528
2654
|
const runner = makeAgentSubagentRunner({ factory });
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2655
|
+
const coordinatorConfig = {
|
|
2656
|
+
coordinatorId: randomUUID(),
|
|
2657
|
+
doneCondition: { type: "all_tasks_done" },
|
|
2658
|
+
maxConcurrent: 2,
|
|
2659
|
+
defaultBudget: { maxToolCalls: 20, maxIterations: 20, timeoutMs: 12e4 }
|
|
2660
|
+
};
|
|
2661
|
+
if (this.opts.directorMode) {
|
|
2662
|
+
this.director = new Director({
|
|
2663
|
+
config: coordinatorConfig,
|
|
2664
|
+
runner,
|
|
2665
|
+
manifestPath: this.opts.manifestPath,
|
|
2666
|
+
sharedScratchpadPath: this.opts.sharedScratchpadPath
|
|
2667
|
+
});
|
|
2668
|
+
this.director.on("task.completed", ({ task, result }) => {
|
|
2669
|
+
this.results.push(result);
|
|
2670
|
+
this.pending.delete(task.id);
|
|
2671
|
+
});
|
|
2672
|
+
this.coordinator = this.director.coordinator;
|
|
2673
|
+
return this.coordinator;
|
|
2674
|
+
}
|
|
2675
|
+
this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, { runner });
|
|
2538
2676
|
this.coordinator.on(
|
|
2539
2677
|
"task.completed",
|
|
2540
2678
|
({ task, result }) => {
|
|
@@ -2544,15 +2682,24 @@ var MultiAgentHost = class {
|
|
|
2544
2682
|
);
|
|
2545
2683
|
return this.coordinator;
|
|
2546
2684
|
}
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2685
|
+
/**
|
|
2686
|
+
* Build a Provider for a subagent. When `overrideId` is supplied (from
|
|
2687
|
+
* `SubagentConfig.provider`), looks that provider up in
|
|
2688
|
+
* `config.providers` and constructs it with its own apiKey/baseUrl.
|
|
2689
|
+
* Falls back to the leader's provider when `overrideId` is absent or
|
|
2690
|
+
* not configured (so a typo doesn't crash the whole run — we just
|
|
2691
|
+
* use the leader and the calling code can decide to error later).
|
|
2692
|
+
*/
|
|
2693
|
+
async buildSubagentProvider(config, overrideId) {
|
|
2694
|
+
const providerId = overrideId && config.providers?.[overrideId] ? overrideId : config.provider;
|
|
2695
|
+
const newCfg = config.providers?.[providerId] ?? {
|
|
2696
|
+
type: providerId,
|
|
2550
2697
|
apiKey: config.apiKey,
|
|
2551
2698
|
baseUrl: config.baseUrl
|
|
2552
2699
|
};
|
|
2553
|
-
return makeProviderFromConfig(
|
|
2700
|
+
return makeProviderFromConfig(providerId, {
|
|
2554
2701
|
...newCfg,
|
|
2555
|
-
type:
|
|
2702
|
+
type: providerId
|
|
2556
2703
|
});
|
|
2557
2704
|
}
|
|
2558
2705
|
/** Returns a tool slice for the subagent — full set unless restricted. */
|
|
@@ -2569,15 +2716,40 @@ var MultiAgentHost = class {
|
|
|
2569
2716
|
for (const t of this.filterTools(allow)) sub.register(t);
|
|
2570
2717
|
return sub;
|
|
2571
2718
|
}
|
|
2572
|
-
/**
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2719
|
+
/**
|
|
2720
|
+
* Spawn a fresh subagent and assign a single task. Returns task id.
|
|
2721
|
+
*
|
|
2722
|
+
* Optional `opts` lets the caller (a `/spawn` slash command or the
|
|
2723
|
+
* future director surface) override the subagent's provider, model,
|
|
2724
|
+
* and tool slice on a per-spawn basis. Without options, the legacy
|
|
2725
|
+
* behavior holds: the subagent uses the leader's provider/model and
|
|
2726
|
+
* the full tool registry.
|
|
2727
|
+
*/
|
|
2728
|
+
async spawn(description, opts) {
|
|
2729
|
+
await this.ensureCoordinator();
|
|
2730
|
+
const subagentConfig = {
|
|
2731
|
+
name: opts?.name ?? "adhoc",
|
|
2577
2732
|
role: "general",
|
|
2578
2733
|
maxToolCalls: 20,
|
|
2579
|
-
maxIterations: 20
|
|
2580
|
-
|
|
2734
|
+
maxIterations: 20,
|
|
2735
|
+
provider: opts?.provider,
|
|
2736
|
+
model: opts?.model,
|
|
2737
|
+
tools: opts?.tools
|
|
2738
|
+
};
|
|
2739
|
+
if (this.director) {
|
|
2740
|
+
const subagentId = await this.director.spawn(subagentConfig);
|
|
2741
|
+
const taskId2 = randomUUID();
|
|
2742
|
+
this.pending.set(taskId2, { description, subagentId });
|
|
2743
|
+
await this.director.assign({
|
|
2744
|
+
id: taskId2,
|
|
2745
|
+
description,
|
|
2746
|
+
subagentId,
|
|
2747
|
+
maxToolCalls: 20
|
|
2748
|
+
});
|
|
2749
|
+
return { subagentId, taskId: taskId2 };
|
|
2750
|
+
}
|
|
2751
|
+
const coord = this.coordinator;
|
|
2752
|
+
const spawned = await coord.spawn(subagentConfig);
|
|
2581
2753
|
const taskId = randomUUID();
|
|
2582
2754
|
this.pending.set(taskId, { description, subagentId: spawned.subagentId });
|
|
2583
2755
|
await coord.assign({
|
|
@@ -2597,6 +2769,79 @@ var MultiAgentHost = class {
|
|
|
2597
2769
|
const summary = !this.coordinator ? "No subagents have been spawned." : `${pending.length} pending, ${this.results.length} completed.`;
|
|
2598
2770
|
return { pending, completed: this.results, summary };
|
|
2599
2771
|
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Roll up per-subagent runtime cost from completed TaskResults. We don't
|
|
2774
|
+
* yet have FleetUsageAggregator wired into the simple MultiAgentHost
|
|
2775
|
+
* path (that lives on `Director`), so this aggregates iterations / tool
|
|
2776
|
+
* calls / duration which we *do* have — enough to spot a thrashing
|
|
2777
|
+
* worker without paying for a heavier orchestrator on every /spawn.
|
|
2778
|
+
*
|
|
2779
|
+
* Returns rows sorted by total duration descending (slowest first) so
|
|
2780
|
+
* the table renders the most interesting subagent at the top.
|
|
2781
|
+
*/
|
|
2782
|
+
usage() {
|
|
2783
|
+
const bySubagent = /* @__PURE__ */ new Map();
|
|
2784
|
+
for (const r of this.results) {
|
|
2785
|
+
const cur = bySubagent.get(r.subagentId) ?? { tasks: 0, iterations: 0, toolCalls: 0, durationMs: 0, lastStatus: "unknown" };
|
|
2786
|
+
cur.tasks += 1;
|
|
2787
|
+
cur.iterations += r.iterations;
|
|
2788
|
+
cur.toolCalls += r.toolCalls;
|
|
2789
|
+
cur.durationMs += r.durationMs;
|
|
2790
|
+
cur.lastStatus = r.status;
|
|
2791
|
+
bySubagent.set(r.subagentId, cur);
|
|
2792
|
+
}
|
|
2793
|
+
const rows = Array.from(bySubagent.entries()).map(([subagentId, v]) => ({
|
|
2794
|
+
subagentId,
|
|
2795
|
+
tasks: v.tasks,
|
|
2796
|
+
iterations: v.iterations,
|
|
2797
|
+
toolCalls: v.toolCalls,
|
|
2798
|
+
durationMs: v.durationMs,
|
|
2799
|
+
status: v.lastStatus
|
|
2800
|
+
})).sort((a, b) => b.durationMs - a.durationMs);
|
|
2801
|
+
const totals = rows.reduce(
|
|
2802
|
+
(acc, r) => ({
|
|
2803
|
+
tasks: acc.tasks + r.tasks,
|
|
2804
|
+
iterations: acc.iterations + r.iterations,
|
|
2805
|
+
toolCalls: acc.toolCalls + r.toolCalls,
|
|
2806
|
+
durationMs: acc.durationMs + r.durationMs
|
|
2807
|
+
}),
|
|
2808
|
+
{ tasks: 0, iterations: 0, toolCalls: 0, durationMs: 0 }
|
|
2809
|
+
);
|
|
2810
|
+
return { rows, totals };
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Force the director to write its manifest to disk and return the path,
|
|
2814
|
+
* or `null` when director mode is off (the simple coordinator path has
|
|
2815
|
+
* no manifest). Callers should fall back to a friendly user message
|
|
2816
|
+
* when `null` is returned — e.g. `/fleet manifest` does this already.
|
|
2817
|
+
*
|
|
2818
|
+
* The returned string is the absolute path of the manifest file. The
|
|
2819
|
+
* file contents are JSON; readers can `JSON.parse(fs.readFileSync(...))`
|
|
2820
|
+
* to consume.
|
|
2821
|
+
*/
|
|
2822
|
+
async manifest() {
|
|
2823
|
+
if (!this.director) return null;
|
|
2824
|
+
return this.director.writeManifest();
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* True when this host is running in director mode. Surfaces the mode
|
|
2828
|
+
* to slash commands and tests without exposing the underlying Director
|
|
2829
|
+
* (which would let callers bypass the host's coordination layer).
|
|
2830
|
+
*/
|
|
2831
|
+
isDirectorMode() {
|
|
2832
|
+
return !!this.director;
|
|
2833
|
+
}
|
|
2834
|
+
/**
|
|
2835
|
+
* Terminate a single subagent. Returns true when the subagent existed
|
|
2836
|
+
* (regardless of whether stop() succeeded or it was already idle),
|
|
2837
|
+
* false when no coordinator has been created yet — meaning the user
|
|
2838
|
+
* called /fleet kill before any /spawn, and there's nothing to do.
|
|
2839
|
+
*/
|
|
2840
|
+
async kill(subagentId) {
|
|
2841
|
+
if (!this.coordinator) return false;
|
|
2842
|
+
await this.coordinator.stop(subagentId);
|
|
2843
|
+
return true;
|
|
2844
|
+
}
|
|
2600
2845
|
async stopAll() {
|
|
2601
2846
|
if (this.coordinator) {
|
|
2602
2847
|
await this.coordinator.stopAll();
|
|
@@ -3367,8 +3612,8 @@ async function initCmd(_args, deps) {
|
|
|
3367
3612
|
};
|
|
3368
3613
|
if (apiKey) config.apiKey = apiKey;
|
|
3369
3614
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(config, null, 2));
|
|
3370
|
-
await fs6.mkdir(
|
|
3371
|
-
const agentsFile =
|
|
3615
|
+
await fs6.mkdir(path6.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
3616
|
+
const agentsFile = path6.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
3372
3617
|
try {
|
|
3373
3618
|
await fs6.access(agentsFile);
|
|
3374
3619
|
} catch {
|
|
@@ -3776,7 +4021,7 @@ async function doctorCmd(_args, deps) {
|
|
|
3776
4021
|
}
|
|
3777
4022
|
try {
|
|
3778
4023
|
await fs6.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
3779
|
-
const probe =
|
|
4024
|
+
const probe = path6.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
3780
4025
|
await fs6.writeFile(probe, "");
|
|
3781
4026
|
await fs6.unlink(probe);
|
|
3782
4027
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -3879,8 +4124,8 @@ async function exportCmd(args, deps) {
|
|
|
3879
4124
|
return 1;
|
|
3880
4125
|
}
|
|
3881
4126
|
if (output) {
|
|
3882
|
-
await fs6.mkdir(
|
|
3883
|
-
await fs6.writeFile(
|
|
4127
|
+
await fs6.mkdir(path6.dirname(path6.resolve(deps.cwd, output)), { recursive: true });
|
|
4128
|
+
await fs6.writeFile(path6.resolve(deps.cwd, output), rendered, "utf8");
|
|
3884
4129
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
3885
4130
|
`);
|
|
3886
4131
|
} else {
|
|
@@ -3933,13 +4178,14 @@ async function helpCmd(_args, deps) {
|
|
|
3933
4178
|
" wstack version Print version",
|
|
3934
4179
|
"",
|
|
3935
4180
|
"Global flags:",
|
|
3936
|
-
" --provider, --model, --cwd, --log-level, --yolo, --verbose, --trace, --config"
|
|
4181
|
+
" --provider, --model, --cwd, --log-level, --yolo, --verbose, --trace, --config",
|
|
4182
|
+
" --director Run with Director-backed orchestration (writes fleet manifest)"
|
|
3937
4183
|
];
|
|
3938
4184
|
deps.renderer.write(lines.join("\n") + "\n");
|
|
3939
4185
|
return 0;
|
|
3940
4186
|
}
|
|
3941
4187
|
async function projectsCmd(_args, deps) {
|
|
3942
|
-
const projectsRoot =
|
|
4188
|
+
const projectsRoot = path6.join(deps.paths.globalRoot, "projects");
|
|
3943
4189
|
try {
|
|
3944
4190
|
const entries = await fs6.readdir(projectsRoot);
|
|
3945
4191
|
if (entries.length === 0) {
|
|
@@ -3949,7 +4195,7 @@ async function projectsCmd(_args, deps) {
|
|
|
3949
4195
|
for (const hash of entries) {
|
|
3950
4196
|
try {
|
|
3951
4197
|
const meta = JSON.parse(
|
|
3952
|
-
await fs6.readFile(
|
|
4198
|
+
await fs6.readFile(path6.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
3953
4199
|
);
|
|
3954
4200
|
deps.renderer.write(
|
|
3955
4201
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -4040,7 +4286,7 @@ function resolveBundledSkillsDir() {
|
|
|
4040
4286
|
try {
|
|
4041
4287
|
const req2 = createRequire(import.meta.url);
|
|
4042
4288
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
4043
|
-
return
|
|
4289
|
+
return path6.join(path6.dirname(corePkg), "skills");
|
|
4044
4290
|
} catch {
|
|
4045
4291
|
return void 0;
|
|
4046
4292
|
}
|
|
@@ -4309,7 +4555,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4309
4555
|
const dumpMetrics = () => {
|
|
4310
4556
|
if (!metricsSink) return;
|
|
4311
4557
|
try {
|
|
4312
|
-
const out =
|
|
4558
|
+
const out = path6.join(wpaths.projectSessions, "metrics.json");
|
|
4313
4559
|
const snap = metricsSink.snapshot();
|
|
4314
4560
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
4315
4561
|
} catch {
|
|
@@ -4466,10 +4712,10 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4466
4712
|
}
|
|
4467
4713
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
4468
4714
|
const attachments = new DefaultAttachmentStore({
|
|
4469
|
-
spoolDir:
|
|
4715
|
+
spoolDir: path6.join(wpaths.projectSessions, session.id, "attachments")
|
|
4470
4716
|
});
|
|
4471
4717
|
const queueStore = new QueueStore({
|
|
4472
|
-
dir:
|
|
4718
|
+
dir: path6.join(wpaths.projectSessions, session.id)
|
|
4473
4719
|
});
|
|
4474
4720
|
const tokenCounter = container.resolve(TOKENS.TokenCounter);
|
|
4475
4721
|
const stats = new SessionStats(events, tokenCounter);
|
|
@@ -4677,6 +4923,11 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4677
4923
|
return err instanceof Error ? err.message : String(err);
|
|
4678
4924
|
}
|
|
4679
4925
|
};
|
|
4926
|
+
const directorMode = flags["director"] === true;
|
|
4927
|
+
const fleetRoot = directorMode ? path6.join(wpaths.projectSessions, session.id) : void 0;
|
|
4928
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path6.join(fleetRoot, "fleet.json") : void 0;
|
|
4929
|
+
const sharedScratchpadPath = directorMode ? path6.join(fleetRoot, "shared") : void 0;
|
|
4930
|
+
const subagentSessionsRoot = directorMode ? path6.join(fleetRoot, "subagents") : void 0;
|
|
4680
4931
|
const multiAgentHost = new MultiAgentHost({
|
|
4681
4932
|
container,
|
|
4682
4933
|
toolRegistry,
|
|
@@ -4688,7 +4939,28 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4688
4939
|
tokenCounter,
|
|
4689
4940
|
projectRoot,
|
|
4690
4941
|
cwd
|
|
4942
|
+
}, {
|
|
4943
|
+
directorMode,
|
|
4944
|
+
manifestPath,
|
|
4945
|
+
sharedScratchpadPath,
|
|
4946
|
+
sessionsRoot: subagentSessionsRoot,
|
|
4947
|
+
directorRunId: session.id
|
|
4691
4948
|
});
|
|
4949
|
+
if (directorMode) {
|
|
4950
|
+
const director = await multiAgentHost.ensureDirector();
|
|
4951
|
+
if (director) {
|
|
4952
|
+
for (const tool of director.tools(FLEET_ROSTER)) {
|
|
4953
|
+
toolRegistry.register(tool);
|
|
4954
|
+
}
|
|
4955
|
+
renderer.writeInfo(`Director mode enabled. Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`);
|
|
4956
|
+
renderer.writeInfo(` fleet root \u2192 ${fleetRoot}`);
|
|
4957
|
+
renderer.writeInfo(` manifest \u2192 ${manifestPath}`);
|
|
4958
|
+
renderer.writeInfo(` scratchpad \u2192 ${sharedScratchpadPath}`);
|
|
4959
|
+
renderer.writeInfo(` subagents \u2192 ${subagentSessionsRoot}`);
|
|
4960
|
+
} else {
|
|
4961
|
+
renderer.writeInfo(`Director mode enabled. Fleet manifest \u2192 ${manifestPath}`);
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4692
4964
|
const slashCmds = buildBuiltinSlashCommands({
|
|
4693
4965
|
registry: slashRegistry,
|
|
4694
4966
|
toolRegistry,
|
|
@@ -4701,9 +4973,14 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4701
4973
|
context,
|
|
4702
4974
|
metricsSink,
|
|
4703
4975
|
healthRegistry,
|
|
4704
|
-
onSpawn: async (description) => {
|
|
4705
|
-
const { subagentId, taskId } = await multiAgentHost.spawn(description);
|
|
4706
|
-
|
|
4976
|
+
onSpawn: async (description, spawnOpts) => {
|
|
4977
|
+
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
4978
|
+
const tags = [];
|
|
4979
|
+
if (spawnOpts?.provider) tags.push(spawnOpts.provider);
|
|
4980
|
+
if (spawnOpts?.model) tags.push(spawnOpts.model);
|
|
4981
|
+
if (spawnOpts?.name) tags.push(`"${spawnOpts.name}"`);
|
|
4982
|
+
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
4983
|
+
return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
|
|
4707
4984
|
},
|
|
4708
4985
|
onAgents: () => {
|
|
4709
4986
|
const s = multiAgentHost.status();
|
|
@@ -4718,6 +4995,60 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4718
4995
|
}
|
|
4719
4996
|
return lines.join("\n");
|
|
4720
4997
|
},
|
|
4998
|
+
onFleet: async (action, target) => {
|
|
4999
|
+
if (action === "status") {
|
|
5000
|
+
const s = multiAgentHost.status();
|
|
5001
|
+
const lines = [color.bold("Fleet status"), ` ${s.summary}`];
|
|
5002
|
+
if (s.pending.length > 0) {
|
|
5003
|
+
lines.push("", color.dim(" Pending"));
|
|
5004
|
+
for (const p of s.pending) {
|
|
5005
|
+
lines.push(` ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`);
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
if (s.completed.length > 0) {
|
|
5009
|
+
lines.push("", color.dim(" Completed"));
|
|
5010
|
+
for (const r of s.completed) {
|
|
5011
|
+
const mark = r.status === "success" ? color.green("\u2713") : color.red("\u2717");
|
|
5012
|
+
lines.push(` ${mark} ${r.taskId.slice(0, 8)} \u2192 ${r.subagentId.slice(0, 8)} \xB7 ${r.iterations}it ${r.toolCalls}tc ${r.durationMs}ms`);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
return lines.join("\n");
|
|
5016
|
+
}
|
|
5017
|
+
if (action === "usage") {
|
|
5018
|
+
const u = multiAgentHost.usage();
|
|
5019
|
+
if (u.rows.length === 0) return "No completed subagent tasks yet.";
|
|
5020
|
+
const lines = [
|
|
5021
|
+
color.bold("Fleet usage"),
|
|
5022
|
+
color.dim(" subagent tasks iter tools ms status")
|
|
5023
|
+
];
|
|
5024
|
+
for (const r of u.rows) {
|
|
5025
|
+
lines.push(
|
|
5026
|
+
` ${r.subagentId.slice(0, 14).padEnd(14)} ${String(r.tasks).padStart(5)} ${String(r.iterations).padStart(4)} ${String(r.toolCalls).padStart(5)} ${String(r.durationMs).padStart(5)} ${r.status}`
|
|
5027
|
+
);
|
|
5028
|
+
}
|
|
5029
|
+
lines.push(
|
|
5030
|
+
color.dim(" \u2500".repeat(28)),
|
|
5031
|
+
` ${"TOTAL".padEnd(14)} ${String(u.totals.tasks).padStart(5)} ${String(u.totals.iterations).padStart(4)} ${String(u.totals.toolCalls).padStart(5)} ${String(u.totals.durationMs).padStart(5)}`
|
|
5032
|
+
);
|
|
5033
|
+
return lines.join("\n");
|
|
5034
|
+
}
|
|
5035
|
+
if (action === "kill") {
|
|
5036
|
+
if (!target) return "Usage: /fleet kill <subagent-id>";
|
|
5037
|
+
const ok = await multiAgentHost.kill(target);
|
|
5038
|
+
return ok ? `Sent stop signal to ${target}.` : "No coordinator is running yet \u2014 nothing to kill.";
|
|
5039
|
+
}
|
|
5040
|
+
if (action === "manifest") {
|
|
5041
|
+
if (!multiAgentHost.isDirectorMode()) {
|
|
5042
|
+
return "Manifest is only available when the run was started with --director.";
|
|
5043
|
+
}
|
|
5044
|
+
const p = await multiAgentHost.manifest();
|
|
5045
|
+
if (!p) {
|
|
5046
|
+
return "Director is active but no subagents have been spawned \u2014 nothing to record yet.";
|
|
5047
|
+
}
|
|
5048
|
+
return `Manifest written \u2192 ${p}`;
|
|
5049
|
+
}
|
|
5050
|
+
return `Unknown fleet action: ${action}`;
|
|
5051
|
+
},
|
|
4721
5052
|
onExit: () => {
|
|
4722
5053
|
void mcpRegistry.stopAll();
|
|
4723
5054
|
},
|
|
@@ -4882,7 +5213,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4882
5213
|
tokenCounter,
|
|
4883
5214
|
attachments,
|
|
4884
5215
|
effectiveMaxContext,
|
|
4885
|
-
projectName:
|
|
5216
|
+
projectName: path6.basename(projectRoot) || void 0
|
|
4886
5217
|
});
|
|
4887
5218
|
await webuiPromise;
|
|
4888
5219
|
} else {
|
|
@@ -4894,7 +5225,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4894
5225
|
tokenCounter,
|
|
4895
5226
|
attachments,
|
|
4896
5227
|
effectiveMaxContext,
|
|
4897
|
-
projectName:
|
|
5228
|
+
projectName: path6.basename(projectRoot) || void 0
|
|
4898
5229
|
});
|
|
4899
5230
|
}
|
|
4900
5231
|
} finally {
|