@wrongstack/cli 1.0.3 → 1.0.4

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.
Files changed (74) hide show
  1. package/dist/{acp-JK4ZQO3R.js → acp-AB54FWM2.js} +120 -38
  2. package/dist/acp-agent-list.d.ts +14 -0
  3. package/dist/acp-hq-telemetry.d.ts +9 -2
  4. package/dist/acp-mcp-servers.d.ts +49 -0
  5. package/dist/acp-server-agent.d.ts +18 -6
  6. package/dist/{auth-XD3GN5RJ.js → auth-WDMSNZ3S.js} +2 -2
  7. package/dist/boot/short-circuit-hq.d.ts +2 -0
  8. package/dist/boot/tool-registry.d.ts +18 -1
  9. package/dist/{chunk-C2E24IXZ.js → chunk-3I5ZJXM5.js} +147 -241
  10. package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
  11. package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
  12. package/dist/chunk-AUQDGU5D.js +32 -0
  13. package/dist/{chunk-IUEKNHPT.js → chunk-BIWE22O5.js} +10 -10
  14. package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
  15. package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
  16. package/dist/chunk-I7V2OYVH.js +242 -0
  17. package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
  18. package/dist/{chunk-GMY45LO6.js → chunk-KEPABQYT.js} +101 -2
  19. package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
  20. package/dist/chunk-Q7E3BPDU.js +62 -0
  21. package/dist/{chunk-4NHD5OXJ.js → chunk-U6PGWDMZ.js} +5 -5
  22. package/dist/{chunk-U6KCWRMC.js → chunk-XJ7K67PP.js} +5 -1
  23. package/dist/{cli-context-Q6QP534K.js → cli-context-WFC3BNGM.js} +116 -21
  24. package/dist/{cli-main-JQXCDZEA.js → cli-main-GU3YZLMO.js} +1221 -694
  25. package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
  26. package/dist/{execution-KH7WJNAX.js → execution-RAZDDQFC.js} +107 -15
  27. package/dist/execution-specialist-trigger.d.ts +28 -0
  28. package/dist/fleet/acp-live-progress.d.ts +19 -0
  29. package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
  30. package/dist/fleet/host-acp.d.ts +14 -2
  31. package/dist/fleet/host.d.ts +1 -0
  32. package/dist/{hq-IG3YRGCO.js → hq-5XHEIIH6.js} +127 -8
  33. package/dist/hq-command-controller.d.ts +31 -2
  34. package/dist/hq-server/client-address.d.ts +2 -0
  35. package/dist/hq-server/ip-allowlist.d.ts +16 -0
  36. package/dist/hq-server/preflight.d.ts +1 -0
  37. package/dist/hq-server/routes/auth/password-routes.d.ts +1 -1
  38. package/dist/hq-server/routes.d.ts +5 -4
  39. package/dist/hq-server/secret-input.d.ts +7 -0
  40. package/dist/hq-server/types.d.ts +3 -2
  41. package/dist/hq-server/upgrade-handler.d.ts +2 -0
  42. package/dist/hq-server/utils.d.ts +6 -0
  43. package/dist/{hq-server-OILU3NPI.js → hq-server-CNUTBWTK.js} +4 -3
  44. package/dist/hq-server.d.ts +4 -0
  45. package/dist/hq-service-L5INQFM4.js +507 -0
  46. package/dist/hq-service.d.ts +22 -0
  47. package/dist/index.js +4 -4
  48. package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
  49. package/dist/live-settings-input.d.ts +2 -0
  50. package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
  51. package/dist/{plugins-U7KGQAUG.js → plugins-WJLW6ULV.js} +2 -2
  52. package/dist/projects-LMNAGGEU.js +36 -0
  53. package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
  54. package/dist/short-circuit-flags-YDMLAQ5R.js +10 -0
  55. package/dist/slash-commands/acp.d.ts +3 -0
  56. package/dist/slash-commands/command-context.d.ts +22 -0
  57. package/dist/slash-commands/ensemble.d.ts +1 -1
  58. package/dist/slash-commands/profile.d.ts +7 -0
  59. package/dist/subcommands/handlers/hq.d.ts +1 -0
  60. package/dist/subcommands/handlers/projects.d.ts +14 -0
  61. package/dist/{subcommands-CUTGBDKP.js → subcommands-7N4RIK6H.js} +2 -2
  62. package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
  63. package/dist/{webui-server-QWBUT7BO.js → webui-server-OBV5JYJV.js} +9 -3
  64. package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
  65. package/dist/wiring/codebase-embeddings.d.ts +2 -2
  66. package/dist/wiring/command-host-state.d.ts +7 -0
  67. package/dist/wiring/pipeline.d.ts +2 -0
  68. package/dist/wiring/provider-runtime-setup.d.ts +1 -1
  69. package/dist/wiring/session-command-handlers.d.ts +2 -1
  70. package/dist/wiring/slash-commands.d.ts +1 -0
  71. package/package.json +28 -28
  72. package/dist/chunk-Q5GTM25S.js +0 -17
  73. package/dist/projects-L6C6XLDY.js +0 -37
  74. package/dist/short-circuit-flags-ZNZX44MC.js +0 -10
@@ -1,19 +1,3 @@
1
- import {
2
- createHqCommandDispatcher
3
- } from "./chunk-IMWXXF4J.js";
4
- import {
5
- runProjectPicker
6
- } from "./chunk-WQZW3RDP.js";
7
- import {
8
- ensureProjectDataDir,
9
- findProject,
10
- generateSlug,
11
- loadManifest,
12
- saveManifest
13
- } from "./chunk-IS2PPBQV.js";
14
- import {
15
- getWrongTrace
16
- } from "./chunk-ERX4BLKP.js";
17
1
  import {
18
2
  MAX_TUI_THINKING_WORD_LENGTH,
19
3
  clearSuggestions,
@@ -23,6 +7,9 @@ import {
23
7
  runGit,
24
8
  setSuggestions
25
9
  } from "./chunk-SXJTVNWF.js";
10
+ import {
11
+ setupPlugins
12
+ } from "./chunk-XJ7K67PP.js";
26
13
  import {
27
14
  deriveFsAccessPair,
28
15
  filterSafeForProject,
@@ -31,8 +18,37 @@ import {
31
18
  persistTelegramConfig
32
19
  } from "./chunk-JACOMTIH.js";
33
20
  import {
34
- setupPlugins
35
- } from "./chunk-U6KCWRMC.js";
21
+ awaitFirstWrongProxyProbe,
22
+ bootstrapWrongProxy
23
+ } from "./chunk-CDY4D5QK.js";
24
+ import {
25
+ advanceToNextTask,
26
+ findSpec,
27
+ formatElapsed,
28
+ gatherProjectContext,
29
+ getSessionState,
30
+ getTaskProgress,
31
+ matchTaskNode,
32
+ sddState
33
+ } from "./chunk-JHUJUIOH.js";
34
+ import {
35
+ createHqCommandDispatcher,
36
+ createProjectKanbanAssignHandler,
37
+ createProjectKanbanTransitionHandler
38
+ } from "./chunk-KNMIBKLV.js";
39
+ import {
40
+ runProjectPicker
41
+ } from "./chunk-WQZW3RDP.js";
42
+ import {
43
+ ensureProjectDataDir,
44
+ findProject,
45
+ generateSlug,
46
+ loadManifest,
47
+ saveManifest
48
+ } from "./chunk-IS2PPBQV.js";
49
+ import {
50
+ getWrongTrace
51
+ } from "./chunk-ERX4BLKP.js";
36
52
  import {
37
53
  configureSimpleUiRuntimeContext,
38
54
  detectProjectFacts,
@@ -48,26 +64,12 @@ import {
48
64
  import {
49
65
  renderDeepHelpToString,
50
66
  renderFocusedHelpToString
51
- } from "./chunk-MXEJEYT7.js";
52
- import {
53
- awaitFirstWrongProxyProbe,
54
- bootstrapWrongProxy
55
- } from "./chunk-VOTTENR5.js";
56
- import {
57
- advanceToNextTask,
58
- findSpec,
59
- formatElapsed,
60
- gatherProjectContext,
61
- getSessionState,
62
- getTaskProgress,
63
- matchTaskNode,
64
- sddState
65
- } from "./chunk-JHUJUIOH.js";
67
+ } from "./chunk-JX6KL7OF.js";
66
68
  import {
67
69
  PLUGIN_AUDIT_ENTRIES,
68
70
  runPluginManagementCommand
69
71
  } from "./chunk-SIS37L3W.js";
70
- import "./chunk-JHPXJRRI.js";
72
+ import "./chunk-4XCBEUBX.js";
71
73
  import {
72
74
  recordGateDecision
73
75
  } from "./chunk-D3K4NYQL.js";
@@ -80,11 +82,12 @@ import {
80
82
  import {
81
83
  buildProviderForId,
82
84
  createGracefulShutdown,
85
+ formatAcpAgentList,
83
86
  loadCachedAcpRegistry,
84
87
  refreshAcpRegistry,
85
88
  resolveProviderCfg,
86
89
  setupProvider
87
- } from "./chunk-GMY45LO6.js";
90
+ } from "./chunk-KEPABQYT.js";
88
91
  import {
89
92
  startCliHqConnection
90
93
  } from "./chunk-TE3Q32JP.js";
@@ -113,7 +116,7 @@ import {
113
116
  } from "./chunk-YMXXOOFN.js";
114
117
  import {
115
118
  parseSpawnFlags
116
- } from "./chunk-E6RFXHWD.js";
119
+ } from "./chunk-EK2P53GL.js";
117
120
  import "./chunk-SIGUWXAA.js";
118
121
  import "./chunk-DAKVBMD4.js";
119
122
  import {
@@ -1250,7 +1253,7 @@ function createSubagentWrongTraceHookRunner(sessionId, emit) {
1250
1253
 
1251
1254
  // src/fleet/host.ts
1252
1255
  import { randomUUID as randomUUID5 } from "node:crypto";
1253
- import { createProjectAgentRoster } from "@wrongstack/core/agent-catalog";
1256
+ import { createProjectAgentRoster, recordDispatch } from "@wrongstack/core/agent-catalog";
1254
1257
  import {
1255
1258
  AdaptiveConcurrencyController,
1256
1259
  areSubagentsAllowedForSession as areSubagentsAllowedForSession2,
@@ -1269,27 +1272,352 @@ import {
1269
1272
  } from "@wrongstack/core/types";
1270
1273
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
1271
1274
 
1275
+ // src/fleet/host-event-bridge.ts
1276
+ import * as path2 from "node:path";
1277
+ var BRIDGE_TEXT_CAP = 360;
1278
+ var BRIDGE_OUTPUT_CAP = 4096;
1279
+ function compactText(value, cap = BRIDGE_TEXT_CAP) {
1280
+ return value.length <= cap ? value : `${value.slice(0, cap - 1)}\u2026`;
1281
+ }
1282
+ function lineCount(value) {
1283
+ return value.length === 0 ? 0 : value.split(/\r?\n/).length;
1284
+ }
1285
+ function patchDelta(value) {
1286
+ let addedLines = 0;
1287
+ let removedLines = 0;
1288
+ const hunkStart = value.indexOf("@@");
1289
+ if (hunkStart === -1) return { addedLines, removedLines };
1290
+ for (const line of value.slice(hunkStart).split(/\r?\n/)) {
1291
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
1292
+ if (line.startsWith("+")) addedLines += 1;
1293
+ else if (line.startsWith("-")) removedLines += 1;
1294
+ }
1295
+ return { addedLines, removedLines };
1296
+ }
1297
+ function compactBridgeToolInput(input) {
1298
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
1299
+ return typeof input === "string" ? compactText(input) : input;
1300
+ }
1301
+ const source = input;
1302
+ const compact = {};
1303
+ for (const key of [
1304
+ "file_path",
1305
+ "filePath",
1306
+ "path",
1307
+ "filename",
1308
+ "target_file",
1309
+ "targetFile",
1310
+ "line",
1311
+ "start_line",
1312
+ "startLine",
1313
+ "end_line",
1314
+ "endLine",
1315
+ "offset",
1316
+ "limit",
1317
+ "command",
1318
+ "cmd",
1319
+ "url",
1320
+ "href",
1321
+ "uri",
1322
+ "query",
1323
+ "pattern"
1324
+ ]) {
1325
+ const value = source[key];
1326
+ if (typeof value === "string") compact[key] = compactText(value);
1327
+ else if (typeof value === "number" || typeof value === "boolean") compact[key] = value;
1328
+ }
1329
+ const content = [source["content"], source["text"], source["data"]].find(
1330
+ (value) => typeof value === "string"
1331
+ );
1332
+ const oldText = [source["old_string"], source["oldString"], source["search"]].find(
1333
+ (value) => typeof value === "string"
1334
+ );
1335
+ const newText = [source["new_string"], source["newString"], source["replacement"]].find(
1336
+ (value) => typeof value === "string"
1337
+ );
1338
+ const patch = [source["patch"], source["diff"]].find(
1339
+ (value) => typeof value === "string"
1340
+ );
1341
+ const bodies = [content, oldText, newText, patch].filter(
1342
+ (value) => value !== void 0
1343
+ );
1344
+ if (bodies.length > 0) {
1345
+ compact["_bodyBytes"] = bodies.reduce(
1346
+ (total, value) => total + Buffer.byteLength(value, "utf8"),
1347
+ 0
1348
+ );
1349
+ }
1350
+ if (content !== void 0) compact["inputLines"] = lineCount(content);
1351
+ if (oldText !== void 0) compact["oldLines"] = lineCount(oldText);
1352
+ if (newText !== void 0) compact["newLines"] = lineCount(newText);
1353
+ if (patch !== void 0) {
1354
+ const delta = patchDelta(patch);
1355
+ compact["addedLines"] = delta.addedLines;
1356
+ compact["removedLines"] = delta.removedLines;
1357
+ }
1358
+ return compact;
1359
+ }
1360
+ function compactBridgeToolOutput(output) {
1361
+ return output === void 0 ? void 0 : compactText(output, BRIDGE_OUTPUT_CAP);
1362
+ }
1363
+ function installSubagentEventBridge(opts) {
1364
+ const {
1365
+ events,
1366
+ hostEvents,
1367
+ hostSessionId,
1368
+ projectRoot,
1369
+ effectiveCfg,
1370
+ subCfg,
1371
+ tokenCounter,
1372
+ subagentProvider,
1373
+ subagentModel
1374
+ } = opts;
1375
+ const subagentId = () => effectiveCfg.id ?? effectiveCfg.name ?? "subagent";
1376
+ const agentName = (name) => name ?? subCfg.name;
1377
+ const offToolStartedBridge = events.on("tool.started", (e) => {
1378
+ hostEvents.emit("subagent.tool_started", {
1379
+ sessionId: hostSessionId,
1380
+ agentSessionId: e.sessionId,
1381
+ subagentId: subagentId(),
1382
+ agentName: agentName(e.agentName),
1383
+ traceId: e.traceId,
1384
+ id: e.id,
1385
+ name: e.name,
1386
+ input: compactBridgeToolInput(e.input)
1387
+ });
1388
+ });
1389
+ const offToolProgressBridge = events.on("tool.progress", (e) => {
1390
+ if (e.event.type !== "file_changed") return;
1391
+ const rawPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
1392
+ if (!rawPath) return;
1393
+ const filePath = path2.normalize(
1394
+ path2.isAbsolute(rawPath) ? rawPath : path2.resolve(projectRoot, rawPath)
1395
+ );
1396
+ hostEvents.emit("file.activity", {
1397
+ filePath,
1398
+ operation: e.event.operation ?? "edit",
1399
+ phase: "changed",
1400
+ source: "deterministic",
1401
+ at: Date.now(),
1402
+ sessionId: e.sessionId,
1403
+ traceId: e.traceId,
1404
+ agentId: subagentId(),
1405
+ agentName: agentName(e.agentName),
1406
+ toolUseId: e.id,
1407
+ toolName: e.name,
1408
+ line: e.event.line,
1409
+ endLine: e.event.endLine
1410
+ });
1411
+ });
1412
+ const offToolBridge = events.on("tool.executed", (e) => {
1413
+ hostEvents.emit("subagent.tool_executed", {
1414
+ sessionId: hostSessionId,
1415
+ agentSessionId: e.sessionId,
1416
+ subagentId: subagentId(),
1417
+ agentName: agentName(e.agentName),
1418
+ traceId: e.traceId,
1419
+ id: e.id,
1420
+ name: e.name,
1421
+ durationMs: e.durationMs,
1422
+ ok: e.ok,
1423
+ input: compactBridgeToolInput(e.input),
1424
+ output: compactBridgeToolOutput(e.output),
1425
+ outputBytes: e.outputBytes,
1426
+ outputTokens: e.outputTokens,
1427
+ outputLines: e.outputLines
1428
+ });
1429
+ });
1430
+ const offSummaryBridge = events.on("subagent.iteration_summary", (e) => {
1431
+ hostEvents.emit("subagent.iteration_summary", {
1432
+ ...e,
1433
+ sessionId: hostSessionId,
1434
+ subagentId: subagentId()
1435
+ });
1436
+ });
1437
+ const offCtxBridge = events.on("ctx.pct", (e) => {
1438
+ hostEvents.emit("subagent.ctx_pct", {
1439
+ sessionId: hostSessionId,
1440
+ subagentId: subagentId(),
1441
+ load: e.load,
1442
+ tokens: e.tokens,
1443
+ maxContext: e.maxContext
1444
+ });
1445
+ });
1446
+ const offTokenBridge = events.on("token.accounted", (e) => {
1447
+ hostEvents.emit("subagent.token_accounted", {
1448
+ sessionId: hostSessionId,
1449
+ subagentId: subagentId(),
1450
+ agentName: agentName(),
1451
+ provider: e.provider ?? subagentProvider,
1452
+ model: e.model ?? subagentModel,
1453
+ // The subagent counter's own running total, not the leader's.
1454
+ usage: tokenCounter ? tokenCounter.total() : e.usage,
1455
+ deltaUsage: e.deltaUsage,
1456
+ cost: e.cost
1457
+ });
1458
+ });
1459
+ const bridgeAttempt = (outcome, e) => {
1460
+ hostEvents.emit("subagent.provider_attempt", {
1461
+ sessionId: hostSessionId,
1462
+ subagentId: subagentId(),
1463
+ agentName: agentName(),
1464
+ outcome,
1465
+ provider: e.providerId ?? subagentProvider,
1466
+ model: e.model ?? subagentModel,
1467
+ attempt: e.attempt,
1468
+ durationMs: e.durationMs,
1469
+ stopReason: e.stopReason,
1470
+ usage: e.usage,
1471
+ description: e.description,
1472
+ status: e.status,
1473
+ failureKind: e.failureKind,
1474
+ retryable: e.retryable,
1475
+ traceId: e.traceId,
1476
+ logicalRequestId: e.logicalRequestId,
1477
+ promptManifestId: e.promptManifestId,
1478
+ attemptId: e.attemptId
1479
+ });
1480
+ };
1481
+ const offAttemptStarted = events.on(
1482
+ "provider.attempt.started",
1483
+ (e) => bridgeAttempt("started", e)
1484
+ );
1485
+ const offAttemptCompleted = events.on(
1486
+ "provider.attempt.completed",
1487
+ (e) => bridgeAttempt("completed", e)
1488
+ );
1489
+ const offAttemptFailed = events.on("provider.attempt.failed", (e) => bridgeAttempt("failed", e));
1490
+ return () => {
1491
+ offToolStartedBridge();
1492
+ offToolProgressBridge();
1493
+ offToolBridge();
1494
+ offSummaryBridge();
1495
+ offCtxBridge();
1496
+ offTokenBridge();
1497
+ offAttemptStarted();
1498
+ offAttemptCompleted();
1499
+ offAttemptFailed();
1500
+ };
1501
+ }
1502
+
1503
+ // src/fleet/acp-live-progress.ts
1504
+ function toolName(title, kind) {
1505
+ const t = title?.trim();
1506
+ if (t) return t;
1507
+ return kind && kind.length > 0 ? kind : "tool";
1508
+ }
1509
+ function publishAcpLiveProgress(opts) {
1510
+ const { event, subagentId, agentName, sessionId, taskId, fleet, hostEvents } = opts;
1511
+ const ts = Date.now();
1512
+ switch (event.type) {
1513
+ case "message": {
1514
+ if (!event.text) return;
1515
+ fleet?.emit({
1516
+ subagentId,
1517
+ taskId,
1518
+ ts,
1519
+ type: "provider.text_delta",
1520
+ payload: { text: event.text }
1521
+ });
1522
+ return;
1523
+ }
1524
+ case "thought": {
1525
+ if (!event.text) return;
1526
+ fleet?.emit({
1527
+ subagentId,
1528
+ taskId,
1529
+ ts,
1530
+ type: "provider.thinking_delta",
1531
+ payload: { text: event.text }
1532
+ });
1533
+ return;
1534
+ }
1535
+ case "tool_call": {
1536
+ const name = toolName(event.toolCall.title, event.toolCall.kind);
1537
+ const id = event.toolCall.toolCallId;
1538
+ const input = compactBridgeToolInput(event.toolCall.rawInput);
1539
+ fleet?.emit({
1540
+ subagentId,
1541
+ taskId,
1542
+ ts,
1543
+ type: "tool.started",
1544
+ payload: { id, name, input }
1545
+ });
1546
+ hostEvents?.emit("subagent.tool_started", {
1547
+ sessionId,
1548
+ subagentId,
1549
+ agentName,
1550
+ taskId,
1551
+ id,
1552
+ name,
1553
+ input
1554
+ });
1555
+ return;
1556
+ }
1557
+ case "tool_call_update": {
1558
+ const status = event.toolCall.status;
1559
+ if (status !== "completed" && status !== "failed") return;
1560
+ const name = toolName(event.toolCall.title, event.toolCall.kind);
1561
+ const id = event.toolCall.toolCallId;
1562
+ const ok = status === "completed";
1563
+ const output = event.toolCall.rawOutput !== void 0 ? safeJson(event.toolCall.rawOutput) : void 0;
1564
+ fleet?.emit({
1565
+ subagentId,
1566
+ taskId,
1567
+ ts,
1568
+ type: "tool.executed",
1569
+ payload: { id, name, ok, output, durationMs: 0 }
1570
+ });
1571
+ hostEvents?.emit("subagent.tool_executed", {
1572
+ sessionId,
1573
+ subagentId,
1574
+ agentName,
1575
+ taskId,
1576
+ id,
1577
+ name,
1578
+ ok,
1579
+ durationMs: 0,
1580
+ output
1581
+ });
1582
+ return;
1583
+ }
1584
+ case "diff": {
1585
+ const filePath = event.diff.path;
1586
+ if (!filePath) return;
1587
+ hostEvents?.emit("file.activity", {
1588
+ filePath,
1589
+ operation: event.diff.oldText === null ? "write" : "edit",
1590
+ phase: "changed",
1591
+ source: "deterministic",
1592
+ at: ts,
1593
+ sessionId,
1594
+ agentId: subagentId,
1595
+ agentName
1596
+ });
1597
+ return;
1598
+ }
1599
+ default:
1600
+ return;
1601
+ }
1602
+ }
1603
+ function safeJson(value) {
1604
+ try {
1605
+ const text = JSON.stringify(value);
1606
+ return text.length > 4096 ? `${text.slice(0, 4095)}\u2026` : text;
1607
+ } catch {
1608
+ return void 0;
1609
+ }
1610
+ }
1611
+
1272
1612
  // src/fleet/host-acp.ts
1273
1613
  import {
1274
- ACP_AGENT_COMMANDS,
1275
1614
  defaultPermissionPolicy,
1276
- findAgentDescriptor,
1277
- makeACPSubagentRunner
1615
+ makeACPSubagentRunner,
1616
+ resolveAcpAgentCommand
1278
1617
  } from "@wrongstack/acp";
1279
1618
  import { ToolValidationError } from "@wrongstack/core/types";
1280
- function buildAcpSubagentRunner(subagentId) {
1281
- let cmd = Object.hasOwn(ACP_AGENT_COMMANDS, subagentId) ? ACP_AGENT_COMMANDS[subagentId] : void 0;
1282
- if (!cmd) {
1283
- const desc = findAgentDescriptor(subagentId);
1284
- if (desc) {
1285
- cmd = {
1286
- command: desc.acp.command,
1287
- args: [...desc.acp.args ?? []],
1288
- role: subagentId,
1289
- ...desc.acp.env ? { env: desc.acp.env } : {}
1290
- };
1291
- }
1292
- }
1619
+ function buildAcpSubagentRunner(subagentId, options) {
1620
+ const cmd = resolveAcpAgentCommand(subagentId, options?.overrides, options?.live);
1293
1621
  if (!cmd) {
1294
1622
  throw new ToolValidationError({
1295
1623
  message: `Unknown ACP agent: ${subagentId}`,
@@ -1297,11 +1625,15 @@ function buildAcpSubagentRunner(subagentId) {
1297
1625
  context: { requested: subagentId }
1298
1626
  });
1299
1627
  }
1300
- return makeACPSubagentRunner({ ...cmd, permissionPolicy: defaultPermissionPolicy });
1628
+ return makeACPSubagentRunner({
1629
+ ...cmd,
1630
+ permissionPolicy: defaultPermissionPolicy,
1631
+ ...options?.publishLive ? { publishLive: options.publishLive } : {}
1632
+ });
1301
1633
  }
1302
1634
 
1303
1635
  // src/fleet/host-helpers.ts
1304
- import * as path2 from "node:path";
1636
+ import * as path3 from "node:path";
1305
1637
  import {
1306
1638
  makeSessionNoteTool,
1307
1639
  makeSubagentResultTool
@@ -1313,8 +1645,8 @@ import {
1313
1645
  } from "@wrongstack/core/security";
1314
1646
  import { makePreferSideConflictResolver } from "@wrongstack/sdd";
1315
1647
  function isInsideDirectory(root, candidate) {
1316
- const relative5 = path2.relative(path2.resolve(root), path2.resolve(candidate));
1317
- return relative5 === "" || !relative5.startsWith("..") && !path2.isAbsolute(relative5);
1648
+ const relative5 = path3.relative(path3.resolve(root), path3.resolve(candidate));
1649
+ return relative5 === "" || !relative5.startsWith("..") && !path3.isAbsolute(relative5);
1318
1650
  }
1319
1651
  function isAgentAvailable(schedule, at = /* @__PURE__ */ new Date()) {
1320
1652
  const parts = new Intl.DateTimeFormat("en-US", {
@@ -1467,10 +1799,12 @@ function makeFleetWorktreeConflictResolver() {
1467
1799
 
1468
1800
  // src/fleet/host-acp-runner-cache.ts
1469
1801
  var HostAcpRunnerCache = class {
1470
- constructor(maxEntries = 20) {
1802
+ constructor(maxEntries = 20, resolveOpts) {
1471
1803
  this.maxEntries = maxEntries;
1804
+ this.resolveOpts = resolveOpts;
1472
1805
  }
1473
1806
  maxEntries;
1807
+ resolveOpts;
1474
1808
  runners = /* @__PURE__ */ new Map();
1475
1809
  accessOrder = [];
1476
1810
  get(subagentId) {
@@ -1479,7 +1813,7 @@ var HostAcpRunnerCache = class {
1479
1813
  touchLruKey(this.accessOrder, subagentId);
1480
1814
  return cached;
1481
1815
  }
1482
- const runner = buildAcpSubagentRunner(subagentId);
1816
+ const runner = buildAcpSubagentRunner(subagentId, this.resolveOpts?.());
1483
1817
  setBoundedLruEntry(this.runners, this.accessOrder, subagentId, runner, this.maxEntries);
1484
1818
  return runner;
1485
1819
  }
@@ -1500,7 +1834,7 @@ function normalizeMaxConcurrent(value) {
1500
1834
 
1501
1835
  // src/fleet/host-director-builder.ts
1502
1836
  import { randomUUID } from "node:crypto";
1503
- import * as path3 from "node:path";
1837
+ import * as path4 from "node:path";
1504
1838
  import {
1505
1839
  DEFAULT_MAX_FLEET_SPAWNS,
1506
1840
  FleetManager,
@@ -1532,7 +1866,7 @@ function prepareHostDirectorRuntime(input) {
1532
1866
  const fleetLifecycle = config.fleet?.lifecycle;
1533
1867
  const configuredIdleTimeoutMs = opts.subagentIdleTimeoutMs ?? fleetLifecycle?.idleTimeoutMs;
1534
1868
  const subagentIdleTimeoutMs = typeof configuredIdleTimeoutMs === "number" && Number.isFinite(configuredIdleTimeoutMs) && configuredIdleTimeoutMs >= 0 ? configuredIdleTimeoutMs : 3e5;
1535
- const defaultScratchpad = opts.sharedScratchpadPath || (opts.sessionsRoot && opts.directorRunId ? path3.join(opts.sessionsRoot, opts.directorRunId, "shared") : void 0);
1869
+ const defaultScratchpad = opts.sharedScratchpadPath || (opts.sessionsRoot && opts.directorRunId ? path4.join(opts.sessionsRoot, opts.directorRunId, "shared") : void 0);
1536
1870
  const worktreePolicy = resolveFleetWorktreePolicy(config);
1537
1871
  const worktrees = worktreePolicy.enabled === false || worktreePolicy.mode === "off" ? void 0 : new WorktreeManager({
1538
1872
  projectRoot: deps.projectRoot,
@@ -1912,67 +2246,6 @@ function createHostStatusBroadcaster(input) {
1912
2246
  });
1913
2247
  }
1914
2248
 
1915
- // src/fleet/host-explore-companion-registry.ts
1916
- var DEFAULT_MAX_COMPANIONS = 4;
1917
- var DEFAULT_IDLE_MS = 15 * 6e4;
1918
- function createExploreCompanionRegistry(input) {
1919
- const max = Math.max(1, input.max ?? DEFAULT_MAX_COMPANIONS);
1920
- const idleMs = input.idleMs ?? DEFAULT_IDLE_MS;
1921
- const now = input.now ?? Date.now;
1922
- const live = /* @__PURE__ */ new Map();
1923
- const stop = (sessionId) => {
1924
- const entry = live.get(sessionId);
1925
- if (!entry) return;
1926
- live.delete(sessionId);
1927
- try {
1928
- entry.companion.stop();
1929
- } catch {
1930
- }
1931
- };
1932
- const sweepIdle = (keep) => {
1933
- const cutoff = now() - idleMs;
1934
- for (const [sessionId, entry] of [...live]) {
1935
- if (sessionId === keep) continue;
1936
- if (entry.lastActiveAt > cutoff) continue;
1937
- stop(sessionId);
1938
- }
1939
- };
1940
- const evictOldest = (keep) => {
1941
- for (const sessionId of live.keys()) {
1942
- if (sessionId === keep) continue;
1943
- stop(sessionId);
1944
- return;
1945
- }
1946
- };
1947
- return {
1948
- ensure(sessionId) {
1949
- if (!sessionId) return;
1950
- const existing = live.get(sessionId);
1951
- if (existing) {
1952
- live.delete(sessionId);
1953
- existing.lastActiveAt = now();
1954
- live.set(sessionId, existing);
1955
- return;
1956
- }
1957
- sweepIdle(sessionId);
1958
- while (live.size >= max) {
1959
- const before = live.size;
1960
- evictOldest(sessionId);
1961
- if (live.size === before) return;
1962
- }
1963
- const companion = input.create(sessionId);
1964
- if (!companion) return;
1965
- live.set(sessionId, { companion, lastActiveAt: now() });
1966
- },
1967
- peek: (sessionId) => live.get(sessionId)?.companion,
1968
- ids: () => [...live.keys()],
1969
- release: stop,
1970
- disposeAll() {
1971
- for (const sessionId of [...live.keys()]) stop(sessionId);
1972
- }
1973
- };
1974
- }
1975
-
1976
2249
  // src/fleet/host-explore-companion.ts
1977
2250
  import { randomUUID as randomUUID2 } from "node:crypto";
1978
2251
  import {
@@ -2071,6 +2344,67 @@ function forwardExploreFindings(input) {
2071
2344
  }
2072
2345
  }
2073
2346
 
2347
+ // src/fleet/host-explore-companion-registry.ts
2348
+ var DEFAULT_MAX_COMPANIONS = 4;
2349
+ var DEFAULT_IDLE_MS = 15 * 6e4;
2350
+ function createExploreCompanionRegistry(input) {
2351
+ const max = Math.max(1, input.max ?? DEFAULT_MAX_COMPANIONS);
2352
+ const idleMs = input.idleMs ?? DEFAULT_IDLE_MS;
2353
+ const now = input.now ?? Date.now;
2354
+ const live = /* @__PURE__ */ new Map();
2355
+ const stop = (sessionId) => {
2356
+ const entry = live.get(sessionId);
2357
+ if (!entry) return;
2358
+ live.delete(sessionId);
2359
+ try {
2360
+ entry.companion.stop();
2361
+ } catch {
2362
+ }
2363
+ };
2364
+ const sweepIdle = (keep) => {
2365
+ const cutoff = now() - idleMs;
2366
+ for (const [sessionId, entry] of [...live]) {
2367
+ if (sessionId === keep) continue;
2368
+ if (entry.lastActiveAt > cutoff) continue;
2369
+ stop(sessionId);
2370
+ }
2371
+ };
2372
+ const evictOldest = (keep) => {
2373
+ for (const sessionId of live.keys()) {
2374
+ if (sessionId === keep) continue;
2375
+ stop(sessionId);
2376
+ return;
2377
+ }
2378
+ };
2379
+ return {
2380
+ ensure(sessionId) {
2381
+ if (!sessionId) return;
2382
+ const existing = live.get(sessionId);
2383
+ if (existing) {
2384
+ live.delete(sessionId);
2385
+ existing.lastActiveAt = now();
2386
+ live.set(sessionId, existing);
2387
+ return;
2388
+ }
2389
+ sweepIdle(sessionId);
2390
+ while (live.size >= max) {
2391
+ const before = live.size;
2392
+ evictOldest(sessionId);
2393
+ if (live.size === before) return;
2394
+ }
2395
+ const companion = input.create(sessionId);
2396
+ if (!companion) return;
2397
+ live.set(sessionId, { companion, lastActiveAt: now() });
2398
+ },
2399
+ peek: (sessionId) => live.get(sessionId)?.companion,
2400
+ ids: () => [...live.keys()],
2401
+ release: stop,
2402
+ disposeAll() {
2403
+ for (const sessionId of [...live.keys()]) stop(sessionId);
2404
+ }
2405
+ };
2406
+ }
2407
+
2074
2408
  // src/fleet/host-learning-scheduler.ts
2075
2409
  import {
2076
2410
  LearningOptimizationScheduler,
@@ -2378,19 +2712,19 @@ function emitHostLifecycleCompleted(events, sessionId, taskId, result) {
2378
2712
  }
2379
2713
 
2380
2714
  // src/fleet/host-paths.ts
2381
- import * as path4 from "node:path";
2715
+ import * as path5 from "node:path";
2382
2716
  function applyFleetRootDefaults(opts) {
2383
2717
  if (opts.fleetRoot && !opts.manifestPath) {
2384
- opts.manifestPath = path4.join(opts.fleetRoot, "fleet.json");
2718
+ opts.manifestPath = path5.join(opts.fleetRoot, "fleet.json");
2385
2719
  }
2386
2720
  if (opts.fleetRoot && !opts.sharedScratchpadPath) {
2387
- opts.sharedScratchpadPath = path4.join(opts.fleetRoot, "shared");
2721
+ opts.sharedScratchpadPath = path5.join(opts.fleetRoot, "shared");
2388
2722
  }
2389
2723
  if (opts.fleetRoot && !opts.sessionsRoot) {
2390
- opts.sessionsRoot = path4.join(opts.fleetRoot, "subagents");
2724
+ opts.sessionsRoot = path5.join(opts.fleetRoot, "subagents");
2391
2725
  }
2392
2726
  if (opts.fleetRoot && !opts.stateCheckpointPath) {
2393
- opts.stateCheckpointPath = path4.join(opts.fleetRoot, "director-state.json");
2727
+ opts.stateCheckpointPath = path5.join(opts.fleetRoot, "director-state.json");
2394
2728
  }
2395
2729
  }
2396
2730
 
@@ -3013,234 +3347,6 @@ async function retrieveHostSubagentMemory(deps, getLeaderMode, subCfg, taskConte
3013
3347
  }
3014
3348
  }
3015
3349
 
3016
- // src/fleet/host-event-bridge.ts
3017
- import * as path5 from "node:path";
3018
- var BRIDGE_TEXT_CAP = 360;
3019
- var BRIDGE_OUTPUT_CAP = 4096;
3020
- function compactText(value, cap = BRIDGE_TEXT_CAP) {
3021
- return value.length <= cap ? value : `${value.slice(0, cap - 1)}\u2026`;
3022
- }
3023
- function lineCount(value) {
3024
- return value.length === 0 ? 0 : value.split(/\r?\n/).length;
3025
- }
3026
- function patchDelta(value) {
3027
- let addedLines = 0;
3028
- let removedLines = 0;
3029
- const hunkStart = value.indexOf("@@");
3030
- if (hunkStart === -1) return { addedLines, removedLines };
3031
- for (const line of value.slice(hunkStart).split(/\r?\n/)) {
3032
- if (line.startsWith("+++") || line.startsWith("---")) continue;
3033
- if (line.startsWith("+")) addedLines += 1;
3034
- else if (line.startsWith("-")) removedLines += 1;
3035
- }
3036
- return { addedLines, removedLines };
3037
- }
3038
- function compactBridgeToolInput(input) {
3039
- if (!input || typeof input !== "object" || Array.isArray(input)) {
3040
- return typeof input === "string" ? compactText(input) : input;
3041
- }
3042
- const source = input;
3043
- const compact = {};
3044
- for (const key of [
3045
- "file_path",
3046
- "filePath",
3047
- "path",
3048
- "filename",
3049
- "target_file",
3050
- "targetFile",
3051
- "line",
3052
- "start_line",
3053
- "startLine",
3054
- "end_line",
3055
- "endLine",
3056
- "offset",
3057
- "limit",
3058
- "command",
3059
- "cmd",
3060
- "url",
3061
- "href",
3062
- "uri",
3063
- "query",
3064
- "pattern"
3065
- ]) {
3066
- const value = source[key];
3067
- if (typeof value === "string") compact[key] = compactText(value);
3068
- else if (typeof value === "number" || typeof value === "boolean") compact[key] = value;
3069
- }
3070
- const content = [source["content"], source["text"], source["data"]].find(
3071
- (value) => typeof value === "string"
3072
- );
3073
- const oldText = [source["old_string"], source["oldString"], source["search"]].find(
3074
- (value) => typeof value === "string"
3075
- );
3076
- const newText = [source["new_string"], source["newString"], source["replacement"]].find(
3077
- (value) => typeof value === "string"
3078
- );
3079
- const patch = [source["patch"], source["diff"]].find(
3080
- (value) => typeof value === "string"
3081
- );
3082
- const bodies = [content, oldText, newText, patch].filter(
3083
- (value) => value !== void 0
3084
- );
3085
- if (bodies.length > 0) {
3086
- compact["_bodyBytes"] = bodies.reduce(
3087
- (total, value) => total + Buffer.byteLength(value, "utf8"),
3088
- 0
3089
- );
3090
- }
3091
- if (content !== void 0) compact["inputLines"] = lineCount(content);
3092
- if (oldText !== void 0) compact["oldLines"] = lineCount(oldText);
3093
- if (newText !== void 0) compact["newLines"] = lineCount(newText);
3094
- if (patch !== void 0) {
3095
- const delta = patchDelta(patch);
3096
- compact["addedLines"] = delta.addedLines;
3097
- compact["removedLines"] = delta.removedLines;
3098
- }
3099
- return compact;
3100
- }
3101
- function compactBridgeToolOutput(output) {
3102
- return output === void 0 ? void 0 : compactText(output, BRIDGE_OUTPUT_CAP);
3103
- }
3104
- function installSubagentEventBridge(opts) {
3105
- const {
3106
- events,
3107
- hostEvents,
3108
- hostSessionId,
3109
- projectRoot,
3110
- effectiveCfg,
3111
- subCfg,
3112
- tokenCounter,
3113
- subagentProvider,
3114
- subagentModel
3115
- } = opts;
3116
- const subagentId = () => effectiveCfg.id ?? effectiveCfg.name ?? "subagent";
3117
- const agentName = (name) => name ?? subCfg.name;
3118
- const offToolStartedBridge = events.on("tool.started", (e) => {
3119
- hostEvents.emit("subagent.tool_started", {
3120
- sessionId: hostSessionId,
3121
- agentSessionId: e.sessionId,
3122
- subagentId: subagentId(),
3123
- agentName: agentName(e.agentName),
3124
- traceId: e.traceId,
3125
- id: e.id,
3126
- name: e.name,
3127
- input: compactBridgeToolInput(e.input)
3128
- });
3129
- });
3130
- const offToolProgressBridge = events.on("tool.progress", (e) => {
3131
- if (e.event.type !== "file_changed") return;
3132
- const rawPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
3133
- if (!rawPath) return;
3134
- const filePath = path5.normalize(
3135
- path5.isAbsolute(rawPath) ? rawPath : path5.resolve(projectRoot, rawPath)
3136
- );
3137
- hostEvents.emit("file.activity", {
3138
- filePath,
3139
- operation: e.event.operation ?? "edit",
3140
- phase: "changed",
3141
- source: "deterministic",
3142
- at: Date.now(),
3143
- sessionId: e.sessionId,
3144
- traceId: e.traceId,
3145
- agentId: subagentId(),
3146
- agentName: agentName(e.agentName),
3147
- toolUseId: e.id,
3148
- toolName: e.name,
3149
- line: e.event.line,
3150
- endLine: e.event.endLine
3151
- });
3152
- });
3153
- const offToolBridge = events.on("tool.executed", (e) => {
3154
- hostEvents.emit("subagent.tool_executed", {
3155
- sessionId: hostSessionId,
3156
- agentSessionId: e.sessionId,
3157
- subagentId: subagentId(),
3158
- agentName: agentName(e.agentName),
3159
- traceId: e.traceId,
3160
- id: e.id,
3161
- name: e.name,
3162
- durationMs: e.durationMs,
3163
- ok: e.ok,
3164
- input: compactBridgeToolInput(e.input),
3165
- output: compactBridgeToolOutput(e.output),
3166
- outputBytes: e.outputBytes,
3167
- outputTokens: e.outputTokens,
3168
- outputLines: e.outputLines
3169
- });
3170
- });
3171
- const offSummaryBridge = events.on("subagent.iteration_summary", (e) => {
3172
- hostEvents.emit("subagent.iteration_summary", {
3173
- ...e,
3174
- sessionId: hostSessionId,
3175
- subagentId: subagentId()
3176
- });
3177
- });
3178
- const offCtxBridge = events.on("ctx.pct", (e) => {
3179
- hostEvents.emit("subagent.ctx_pct", {
3180
- sessionId: hostSessionId,
3181
- subagentId: subagentId(),
3182
- load: e.load,
3183
- tokens: e.tokens,
3184
- maxContext: e.maxContext
3185
- });
3186
- });
3187
- const offTokenBridge = events.on("token.accounted", (e) => {
3188
- hostEvents.emit("subagent.token_accounted", {
3189
- sessionId: hostSessionId,
3190
- subagentId: subagentId(),
3191
- agentName: agentName(),
3192
- provider: e.provider ?? subagentProvider,
3193
- model: e.model ?? subagentModel,
3194
- // The subagent counter's own running total, not the leader's.
3195
- usage: tokenCounter ? tokenCounter.total() : e.usage,
3196
- deltaUsage: e.deltaUsage,
3197
- cost: e.cost
3198
- });
3199
- });
3200
- const bridgeAttempt = (outcome, e) => {
3201
- hostEvents.emit("subagent.provider_attempt", {
3202
- sessionId: hostSessionId,
3203
- subagentId: subagentId(),
3204
- agentName: agentName(),
3205
- outcome,
3206
- provider: e.providerId ?? subagentProvider,
3207
- model: e.model ?? subagentModel,
3208
- attempt: e.attempt,
3209
- durationMs: e.durationMs,
3210
- stopReason: e.stopReason,
3211
- usage: e.usage,
3212
- description: e.description,
3213
- status: e.status,
3214
- failureKind: e.failureKind,
3215
- retryable: e.retryable,
3216
- traceId: e.traceId,
3217
- logicalRequestId: e.logicalRequestId,
3218
- promptManifestId: e.promptManifestId,
3219
- attemptId: e.attemptId
3220
- });
3221
- };
3222
- const offAttemptStarted = events.on(
3223
- "provider.attempt.started",
3224
- (e) => bridgeAttempt("started", e)
3225
- );
3226
- const offAttemptCompleted = events.on(
3227
- "provider.attempt.completed",
3228
- (e) => bridgeAttempt("completed", e)
3229
- );
3230
- const offAttemptFailed = events.on("provider.attempt.failed", (e) => bridgeAttempt("failed", e));
3231
- return () => {
3232
- offToolStartedBridge();
3233
- offToolProgressBridge();
3234
- offToolBridge();
3235
- offSummaryBridge();
3236
- offCtxBridge();
3237
- offTokenBridge();
3238
- offAttemptStarted();
3239
- offAttemptCompleted();
3240
- offAttemptFailed();
3241
- };
3242
- }
3243
-
3244
3350
  // src/fleet/host-session-writer.ts
3245
3351
  import { stampAgentId } from "@wrongstack/core/storage";
3246
3352
  function createParentSubagentSessionWriter(parentSession, agentId) {
@@ -3620,7 +3726,14 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
3620
3726
  context: ctx,
3621
3727
  permissionPolicy: new AutoApprovePermissionPolicy(subAllowedCaps),
3622
3728
  toolExecutor,
3623
- loopDetection: config.tools?.loopDetection
3729
+ loopDetection: config.tools?.loopDetection,
3730
+ // `tools.maxIterations` was never read on this host, so an operator who
3731
+ // set a turn budget got it honoured on the CLI and WebUI hosts and
3732
+ // silently ignored here. Only a POSITIVE value is forwarded: the shipped
3733
+ // default is `0` ("no hard limit"), and handing that to the agent loop
3734
+ // would trade a missing setting for a missing safety net. Absent an
3735
+ // explicit budget the Agent keeps its own DEFAULT_MAX_ITERATIONS.
3736
+ ...typeof config.tools?.maxIterations === "number" && Number.isFinite(config.tools.maxIterations) && config.tools.maxIterations > 0 ? { maxIterations: config.tools.maxIterations } : {}
3624
3737
  });
3625
3738
  agent.extensions.register(
3626
3739
  createFallbackModelExtension({
@@ -3856,6 +3969,7 @@ var MultiAgentHost = class {
3856
3969
  this.deps = deps;
3857
3970
  this.opts = opts;
3858
3971
  this.roster = createProjectAgentRoster(FLEET_ROSTER2, deps.projectRoot);
3972
+ this.acpRunnerCache = new HostAcpRunnerCache(20, () => this.acpCommandOpts());
3859
3973
  this.learningScheduler = new HostLearningScheduler(deps);
3860
3974
  this.shadowManager = new HostShadowManager({
3861
3975
  deps,
@@ -3891,7 +4005,7 @@ var MultiAgentHost = class {
3891
4005
  * creating a new transport process on every ACP task dispatch. Stores the
3892
4006
  * pending promise so concurrent calls for the same subagentId share one spawn.
3893
4007
  * Bounded to 20 entries with LRU eviction to prevent unbounded memory growth. */
3894
- acpRunnerCache = new HostAcpRunnerCache();
4008
+ acpRunnerCache;
3895
4009
  learningRoles = new HostLearningRoleTracker();
3896
4010
  learningScheduler;
3897
4011
  /** Adaptive concurrency controller — created in buildDirector() when config has
@@ -4027,6 +4141,10 @@ var MultiAgentHost = class {
4027
4141
  brain: this.opts.brain,
4028
4142
  roster: this.roster,
4029
4143
  dispatchClassifier: (task, candidates) => this.learningScheduler.classifyDispatch(task, candidates),
4144
+ // Routing telemetry. Sibling of `dispatchClassifier` on purpose: both are
4145
+ // seams the host fills so core keeps no provider and no filesystem
4146
+ // dependency. See `.wrongstack/agents/dispatch-log.jsonl`.
4147
+ onSpawnRouted: (entry) => recordDispatch(entry, this.deps.projectRoot),
4030
4148
  taskResultNotifier: (n) => this.reportTaskResultToLeader(n),
4031
4149
  subagentIdleTimeoutMs,
4032
4150
  ...this.opts.statusTracker ? { statusTracker: this.opts.statusTracker } : {},
@@ -4159,6 +4277,28 @@ var MultiAgentHost = class {
4159
4277
  async buildACPRunner(subagentId) {
4160
4278
  return this.acpRunnerCache.get(subagentId);
4161
4279
  }
4280
+ acpCommandOpts() {
4281
+ let overrides;
4282
+ try {
4283
+ overrides = this.deps.configStore.get()?.acp?.agents;
4284
+ } catch {
4285
+ overrides = void 0;
4286
+ }
4287
+ return {
4288
+ ...overrides ? { overrides } : {},
4289
+ publishLive: (ctx, task, event) => {
4290
+ publishAcpLiveProgress({
4291
+ event,
4292
+ subagentId: ctx.subagentId,
4293
+ agentName: ctx.config.name ?? ctx.config.role ?? ctx.subagentId,
4294
+ sessionId: ctx.sessionId,
4295
+ taskId: task.id,
4296
+ fleet: this.getDirector()?.fleet,
4297
+ hostEvents: this.deps.events
4298
+ });
4299
+ }
4300
+ };
4301
+ }
4162
4302
  async spawnACP(subagentId, task, config) {
4163
4303
  const taskId = randomUUID5();
4164
4304
  await this.ensureCoordinator(config);
@@ -4253,9 +4393,12 @@ var MultiAgentHost = class {
4253
4393
  taskId: this.shadowManager.getTaskId(originSessionId) ?? "shadow-active"
4254
4394
  };
4255
4395
  }
4396
+ const isAcp = opts?.provider === "acp";
4256
4397
  const subagentConfig = {
4257
4398
  name: opts?.name ?? "adhoc",
4258
- role: isShadowSpawn ? "shadow-agent" : "general",
4399
+ // ACP `--bg` passes the catalog id as `name`. Routing looks up the
4400
+ // runner by role, so that id must be the role — not 'general'.
4401
+ role: isShadowSpawn ? "shadow-agent" : isAcp ? opts?.name ?? "general" : "general",
4259
4402
  provider: opts?.provider,
4260
4403
  model: opts?.model,
4261
4404
  fallbackModels: opts?.fallbackModels,
@@ -4782,6 +4925,7 @@ function setupBrainAndOrchestration(deps) {
4782
4925
  })
4783
4926
  );
4784
4927
  toolRegistry.exposeToProvider("delegate");
4928
+ toolRegistry.exposeToProvider("define_subagent");
4785
4929
  toolRegistry.register(
4786
4930
  createMcpControlTool({
4787
4931
  getConfig: () => configStore.get(),
@@ -6039,7 +6183,7 @@ async function runCliExecution(params) {
6039
6183
  governanceHandle,
6040
6184
  setConfig
6041
6185
  } = params;
6042
- const { execute } = await import("./execution-KH7WJNAX.js");
6186
+ const { execute } = await import("./execution-RAZDDQFC.js");
6043
6187
  return execute(
6044
6188
  toExecuteDeps({
6045
6189
  core: {
@@ -6246,6 +6390,7 @@ async function runCliExecution(params) {
6246
6390
  import * as path7 from "node:path";
6247
6391
  import { TOKENS as TOKENS8 } from "@wrongstack/core/kernel";
6248
6392
  import { ToolRegistry as ToolRegistry2 } from "@wrongstack/core/registry";
6393
+ import { resolveTokenSavingTier } from "@wrongstack/core/types";
6249
6394
 
6250
6395
  // src/boot/system-prompt-builder.ts
6251
6396
  import { DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
@@ -7143,7 +7288,7 @@ ${commit.out}`;
7143
7288
 
7144
7289
  // src/boot/tool-registry.ts
7145
7290
  function registerBuiltinTools(deps) {
7146
- const tier = normalizeTokenSavingTier4(deps.config.features.tokenSavingMode);
7291
+ const tier = deps.tier ?? normalizeTokenSavingTier4(deps.config.features.tokenSavingMode);
7147
7292
  registerCanonicalHostTools({
7148
7293
  registry: deps.toolRegistry,
7149
7294
  tier,
@@ -7241,6 +7386,10 @@ async function setupCliPromptAndTools(params) {
7241
7386
  events,
7242
7387
  vectorMemoryStore
7243
7388
  } = params;
7389
+ const tier = resolveTokenSavingTier(
7390
+ config.features.tokenSavingMode,
7391
+ modelCapabilitiesRef.current?.maxContextTokens
7392
+ );
7244
7393
  bindSystemPromptBuilder({
7245
7394
  container,
7246
7395
  modeStore,
@@ -7255,7 +7404,7 @@ async function setupCliPromptAndTools(params) {
7255
7404
  skillsEnabled: config.features.skills,
7256
7405
  skillMode: config.skills?.mode,
7257
7406
  skillEagerMaxChars: config.skills?.eagerMaxChars,
7258
- tokenSavingMode: config.features.tokenSavingMode,
7407
+ tokenSavingMode: tier,
7259
7408
  systemPromptVariant: config.systemPrompt?.variant,
7260
7409
  paths: {
7261
7410
  projectGoal: wpaths.projectGoal,
@@ -7274,6 +7423,7 @@ async function setupCliPromptAndTools(params) {
7274
7423
  toolRegistry,
7275
7424
  compactor: container.resolve(TOKENS8.Compactor),
7276
7425
  config,
7426
+ tier,
7277
7427
  memoryStore,
7278
7428
  vectorMemoryStore,
7279
7429
  events,
@@ -7333,6 +7483,134 @@ function createCommandHostAdapters(input) {
7333
7483
  };
7334
7484
  }
7335
7485
 
7486
+ // src/wiring/concept-summarizer.ts
7487
+ import { OneShotOrchestrator } from "@wrongstack/core/execution";
7488
+ import { CONCEPT_RELATIONS, MAX_CRUX_LINES } from "@wrongstack/tools";
7489
+ var FILE_MAX_TOKENS = 400;
7490
+ var SUBSYSTEM_MAX_TOKENS = 500;
7491
+ var CALL_TIMEOUT_MS = 45e3;
7492
+ var FILE_SYSTEM_PROMPT = [
7493
+ "You describe one source file for a code search index.",
7494
+ "",
7495
+ 'Answer with JSON only: {"summary": string, "cruxStart": number, "cruxEnd": number}.',
7496
+ "",
7497
+ "- `summary`: one or two sentences on what this file is FOR \u2014 the job it does in the",
7498
+ " system, in plain English. Do not list its exports; the index already has those.",
7499
+ " Prefer the domain vocabulary the code itself uses. Never speculate about code you",
7500
+ " were not shown.",
7501
+ `- \`cruxStart\`/\`cruxEnd\`: the 1-based, inclusive line span of the few lines that`,
7502
+ ` actually carry the file's meaning \u2014 the core decision, the central type, the loop`,
7503
+ ` everything else serves. At most ${MAX_CRUX_LINES} lines. Omit both if nothing stands out.`
7504
+ ].join("\n");
7505
+ var SUBSYSTEM_SYSTEM_PROMPT = [
7506
+ "You describe one subsystem of a codebase, given summaries of its most central files.",
7507
+ "",
7508
+ "Answer with JSON only:",
7509
+ '{"summary": string, "relations": [{"to": string, "relation": string}]}',
7510
+ "",
7511
+ "- `summary`: two or three sentences on what this subsystem is responsible for.",
7512
+ `- \`relations\`: how it relates to OTHER named subsystems, using only these verbs:`,
7513
+ ` ${CONCEPT_RELATIONS.join(", ")}. Omit the field entirely if you are not confident.`,
7514
+ " Never invent a subsystem name that was not given to you."
7515
+ ].join("\n");
7516
+ function parseJsonObject(text) {
7517
+ const trimmed = text.trim();
7518
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(trimmed);
7519
+ const candidate = fenced?.[1]?.trim() ?? trimmed;
7520
+ const start = candidate.indexOf("{");
7521
+ const end = candidate.lastIndexOf("}");
7522
+ if (start === -1 || end <= start) return null;
7523
+ try {
7524
+ const parsed = JSON.parse(candidate.slice(start, end + 1));
7525
+ return parsed !== null && typeof parsed === "object" ? parsed : null;
7526
+ } catch {
7527
+ return null;
7528
+ }
7529
+ }
7530
+ function asString(value) {
7531
+ return typeof value === "string" ? value.trim() : "";
7532
+ }
7533
+ function asLine(value) {
7534
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
7535
+ }
7536
+ function numberLines(source) {
7537
+ return source.split("\n").map((line, index) => `${index + 1} ${line}`).join("\n");
7538
+ }
7539
+ function createConceptSummarizer(deps) {
7540
+ const orchestrator = new OneShotOrchestrator({
7541
+ buildProvider: deps.buildProvider,
7542
+ getConfig: deps.getConfig,
7543
+ fallbackProfileManager: deps.fallbackProfileManager,
7544
+ ...deps.statusTracker ? { statusTracker: deps.statusTracker } : {},
7545
+ ...deps.wrapProviderCall ? { wrapProviderCall: deps.wrapProviderCall } : {}
7546
+ });
7547
+ const model = deps.model ?? deps.getConfig().indexing?.concepts?.model;
7548
+ return {
7549
+ async describeFile(input) {
7550
+ const userPrompt = [
7551
+ `File: ${input.file}`,
7552
+ input.declarations.length > 0 ? `Indexed declarations: ${input.declarations.map((d) => `${d.name} (${d.kind}, L${d.line})`).join(", ")}` : "",
7553
+ input.staleSummary !== void 0 ? `A previous description of an older version of this file \u2014 verify it against the source rather than trusting it: ${input.staleSummary}` : "",
7554
+ input.truncated ? "(source truncated)" : "",
7555
+ "",
7556
+ numberLines(input.source)
7557
+ ].filter(Boolean).join("\n");
7558
+ const result = await orchestrator.call({
7559
+ system: FILE_SYSTEM_PROMPT,
7560
+ userPrompt,
7561
+ responseFormat: { type: "json_object" },
7562
+ maxTokens: FILE_MAX_TOKENS,
7563
+ timeoutMs: CALL_TIMEOUT_MS,
7564
+ ...model ? { model } : { role: "summarizer" },
7565
+ ...input.signal ? { signal: input.signal } : {}
7566
+ });
7567
+ if (result.error || !result.text) return null;
7568
+ const parsed = parseJsonObject(result.text);
7569
+ if (parsed === null) return null;
7570
+ const summary = asString(parsed["summary"]);
7571
+ if (summary === "") return null;
7572
+ return {
7573
+ summary,
7574
+ cruxStart: asLine(parsed["cruxStart"]),
7575
+ cruxEnd: asLine(parsed["cruxEnd"]),
7576
+ model: result.model
7577
+ };
7578
+ },
7579
+ async describeSubsystem(input) {
7580
+ if (input.files.length === 0) return null;
7581
+ const userPrompt = [
7582
+ `Subsystem: ${input.name}`,
7583
+ "",
7584
+ "Its most central files, with what each is for:",
7585
+ ...input.files.map((f) => `- ${f.file}: ${f.summary}`)
7586
+ ].join("\n");
7587
+ const result = await orchestrator.call({
7588
+ system: SUBSYSTEM_SYSTEM_PROMPT,
7589
+ userPrompt,
7590
+ responseFormat: { type: "json_object" },
7591
+ maxTokens: SUBSYSTEM_MAX_TOKENS,
7592
+ timeoutMs: CALL_TIMEOUT_MS,
7593
+ ...model ? { model } : { role: "summarizer" },
7594
+ ...input.signal ? { signal: input.signal } : {}
7595
+ });
7596
+ if (result.error || !result.text) return null;
7597
+ const parsed = parseJsonObject(result.text);
7598
+ if (parsed === null) return null;
7599
+ const summary = asString(parsed["summary"]);
7600
+ if (summary === "") return null;
7601
+ const rawRelations = parsed["relations"];
7602
+ const relations = Array.isArray(rawRelations) ? rawRelations.flatMap((entry) => {
7603
+ if (entry === null || typeof entry !== "object") return [];
7604
+ const record = entry;
7605
+ const to = asString(record["to"]);
7606
+ const relation = asString(record["relation"]);
7607
+ return to !== "" && relation !== "" ? [{ to, relation }] : [];
7608
+ }) : [];
7609
+ return { summary, relations, model: result.model };
7610
+ }
7611
+ };
7612
+ }
7613
+
7336
7614
  // src/wiring/eternal-command-handlers.ts
7337
7615
  import { EternalAutonomyEngine as EternalAutonomyEngine2, ParallelEternalEngine } from "@wrongstack/core/execution";
7338
7616
  function createEternalCommandHandlers(input) {
@@ -8382,6 +8660,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
8382
8660
  toolCount: server.toolCount
8383
8661
  })),
8384
8662
  onYolo: input.onYolo,
8663
+ onYoloConfirm: input.onYoloConfirm,
8385
8664
  onNextPredict: (enabled) => {
8386
8665
  if (enabled !== void 0) {
8387
8666
  input.setNextPredict(enabled);
@@ -8499,134 +8778,6 @@ function formatDiagnostics(input) {
8499
8778
  ].filter((line) => line !== null).join("\n");
8500
8779
  }
8501
8780
 
8502
- // src/wiring/concept-summarizer.ts
8503
- import { OneShotOrchestrator } from "@wrongstack/core/execution";
8504
- import { CONCEPT_RELATIONS, MAX_CRUX_LINES } from "@wrongstack/tools";
8505
- var FILE_MAX_TOKENS = 400;
8506
- var SUBSYSTEM_MAX_TOKENS = 500;
8507
- var CALL_TIMEOUT_MS = 45e3;
8508
- var FILE_SYSTEM_PROMPT = [
8509
- "You describe one source file for a code search index.",
8510
- "",
8511
- 'Answer with JSON only: {"summary": string, "cruxStart": number, "cruxEnd": number}.',
8512
- "",
8513
- "- `summary`: one or two sentences on what this file is FOR \u2014 the job it does in the",
8514
- " system, in plain English. Do not list its exports; the index already has those.",
8515
- " Prefer the domain vocabulary the code itself uses. Never speculate about code you",
8516
- " were not shown.",
8517
- `- \`cruxStart\`/\`cruxEnd\`: the 1-based, inclusive line span of the few lines that`,
8518
- ` actually carry the file's meaning \u2014 the core decision, the central type, the loop`,
8519
- ` everything else serves. At most ${MAX_CRUX_LINES} lines. Omit both if nothing stands out.`
8520
- ].join("\n");
8521
- var SUBSYSTEM_SYSTEM_PROMPT = [
8522
- "You describe one subsystem of a codebase, given summaries of its most central files.",
8523
- "",
8524
- "Answer with JSON only:",
8525
- '{"summary": string, "relations": [{"to": string, "relation": string}]}',
8526
- "",
8527
- "- `summary`: two or three sentences on what this subsystem is responsible for.",
8528
- `- \`relations\`: how it relates to OTHER named subsystems, using only these verbs:`,
8529
- ` ${CONCEPT_RELATIONS.join(", ")}. Omit the field entirely if you are not confident.`,
8530
- " Never invent a subsystem name that was not given to you."
8531
- ].join("\n");
8532
- function parseJsonObject(text) {
8533
- const trimmed = text.trim();
8534
- const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(trimmed);
8535
- const candidate = fenced?.[1]?.trim() ?? trimmed;
8536
- const start = candidate.indexOf("{");
8537
- const end = candidate.lastIndexOf("}");
8538
- if (start === -1 || end <= start) return null;
8539
- try {
8540
- const parsed = JSON.parse(candidate.slice(start, end + 1));
8541
- return parsed !== null && typeof parsed === "object" ? parsed : null;
8542
- } catch {
8543
- return null;
8544
- }
8545
- }
8546
- function asString(value) {
8547
- return typeof value === "string" ? value.trim() : "";
8548
- }
8549
- function asLine(value) {
8550
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
8551
- }
8552
- function numberLines(source) {
8553
- return source.split("\n").map((line, index) => `${index + 1} ${line}`).join("\n");
8554
- }
8555
- function createConceptSummarizer(deps) {
8556
- const orchestrator = new OneShotOrchestrator({
8557
- buildProvider: deps.buildProvider,
8558
- getConfig: deps.getConfig,
8559
- fallbackProfileManager: deps.fallbackProfileManager,
8560
- ...deps.statusTracker ? { statusTracker: deps.statusTracker } : {},
8561
- ...deps.wrapProviderCall ? { wrapProviderCall: deps.wrapProviderCall } : {}
8562
- });
8563
- const model = deps.model ?? deps.getConfig().indexing?.concepts?.model;
8564
- return {
8565
- async describeFile(input) {
8566
- const userPrompt = [
8567
- `File: ${input.file}`,
8568
- input.declarations.length > 0 ? `Indexed declarations: ${input.declarations.map((d) => `${d.name} (${d.kind}, L${d.line})`).join(", ")}` : "",
8569
- input.staleSummary !== void 0 ? `A previous description of an older version of this file \u2014 verify it against the source rather than trusting it: ${input.staleSummary}` : "",
8570
- input.truncated ? "(source truncated)" : "",
8571
- "",
8572
- numberLines(input.source)
8573
- ].filter(Boolean).join("\n");
8574
- const result = await orchestrator.call({
8575
- system: FILE_SYSTEM_PROMPT,
8576
- userPrompt,
8577
- responseFormat: { type: "json_object" },
8578
- maxTokens: FILE_MAX_TOKENS,
8579
- timeoutMs: CALL_TIMEOUT_MS,
8580
- ...model ? { model } : { role: "summarizer" },
8581
- ...input.signal ? { signal: input.signal } : {}
8582
- });
8583
- if (result.error || !result.text) return null;
8584
- const parsed = parseJsonObject(result.text);
8585
- if (parsed === null) return null;
8586
- const summary = asString(parsed["summary"]);
8587
- if (summary === "") return null;
8588
- return {
8589
- summary,
8590
- cruxStart: asLine(parsed["cruxStart"]),
8591
- cruxEnd: asLine(parsed["cruxEnd"]),
8592
- model: result.model
8593
- };
8594
- },
8595
- async describeSubsystem(input) {
8596
- if (input.files.length === 0) return null;
8597
- const userPrompt = [
8598
- `Subsystem: ${input.name}`,
8599
- "",
8600
- "Its most central files, with what each is for:",
8601
- ...input.files.map((f) => `- ${f.file}: ${f.summary}`)
8602
- ].join("\n");
8603
- const result = await orchestrator.call({
8604
- system: SUBSYSTEM_SYSTEM_PROMPT,
8605
- userPrompt,
8606
- responseFormat: { type: "json_object" },
8607
- maxTokens: SUBSYSTEM_MAX_TOKENS,
8608
- timeoutMs: CALL_TIMEOUT_MS,
8609
- ...model ? { model } : { role: "summarizer" },
8610
- ...input.signal ? { signal: input.signal } : {}
8611
- });
8612
- if (result.error || !result.text) return null;
8613
- const parsed = parseJsonObject(result.text);
8614
- if (parsed === null) return null;
8615
- const summary = asString(parsed["summary"]);
8616
- if (summary === "") return null;
8617
- const rawRelations = parsed["relations"];
8618
- const relations = Array.isArray(rawRelations) ? rawRelations.flatMap((entry) => {
8619
- if (entry === null || typeof entry !== "object") return [];
8620
- const record = entry;
8621
- const to = asString(record["to"]);
8622
- const relation = asString(record["relation"]);
8623
- return to !== "" && relation !== "" ? [{ to, relation }] : [];
8624
- }) : [];
8625
- return { summary, relations, model: result.model };
8626
- }
8627
- };
8628
- }
8629
-
8630
8781
  // src/services/statusline-config.ts
8631
8782
  import * as fs3 from "node:fs/promises";
8632
8783
  import * as os from "node:os";
@@ -8867,7 +9018,7 @@ import {
8867
9018
  probeAcpAgents,
8868
9019
  renderAcpBenchText,
8869
9020
  renderEnsembleText,
8870
- resolveAcpAgentCommand,
9021
+ resolveAcpAgentCommand as resolveAcpAgentCommand2,
8871
9022
  runAcpBench,
8872
9023
  runEnsemble,
8873
9024
  runOneAcpTask
@@ -8878,7 +9029,7 @@ function buildAcpCommand(opts) {
8878
9029
  name: "acp",
8879
9030
  category: "Agent",
8880
9031
  description: "Discover and assign tasks to installed ACP coding agents (claude-code, codex-cli, gemini-cli, \u2026).",
8881
- argsHint: "[list | probe | <agent-id> [--bg] <task> | parallel <csv> <task>]",
9032
+ argsHint: "[list | probe | <agent-id> [--bg] <task> | parallel <csv> [--bg] <task>]",
8882
9033
  help: [
8883
9034
  "Use the ACP-supporting CLIs already installed on this machine as",
8884
9035
  "subagents \u2014 they run with their own login, so no API key is spent here.",
@@ -8888,6 +9039,7 @@ function buildAcpCommand(opts) {
8888
9039
  " /acp <agent-id> <task> Run a task on ONE agent, inline + streamed",
8889
9040
  " /acp <agent-id> --bg <task> Run it as a background fleet subagent (/agents)",
8890
9041
  " /acp parallel <csv> <task> Fan one task out to several agents at once",
9042
+ " /acp parallel <csv> --bg <task> Same, as background fleet subagents",
8891
9043
  " /acp probe [csv] Handshake-test agents \u2014 shows what truly works",
8892
9044
  " /acp bench [csv] [--fs] End-to-end verify each agent + graded report",
8893
9045
  " /acp sync Pull the official agentclientprotocol/registry",
@@ -8899,6 +9051,7 @@ function buildAcpCommand(opts) {
8899
9051
  ' /acp gemini-cli "explain src/agent.ts"',
8900
9052
  ' /acp claude-code --bg "refactor auth/session.ts and run the tests"',
8901
9053
  ' /acp parallel claude-code,gemini-cli,codex-cli "review this diff"',
9054
+ ' /acp parallel claude-code,gemini-cli --bg "review this diff"',
8902
9055
  " /acp probe",
8903
9056
  "",
8904
9057
  "If an agent is detected but `probe` shows it failing, its catalog entry",
@@ -8918,7 +9071,7 @@ function buildAcpCommand(opts) {
8918
9071
  const rest = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1).trim();
8919
9072
  if (head === "probe") return probeAgents(rest, overrides, live, opts);
8920
9073
  if (head === "bench") return benchAgents(rest, overrides, live, opts);
8921
- if (head === "parallel") return parallelAgents(rest, overrides, live);
9074
+ if (head === "parallel") return parallelAgents(rest, overrides, live, opts);
8922
9075
  return runSingle(head, rest, overrides, live, opts);
8923
9076
  }
8924
9077
  };
@@ -8950,42 +9103,7 @@ The bundled offline catalog is still available via \`/acp\`.`
8950
9103
  }
8951
9104
  async function listAgents(live) {
8952
9105
  const detected = await new EnsembleRegistry().list();
8953
- const installed = detected.filter((a) => a.installed);
8954
- const missing = detected.filter((a) => !a.installed);
8955
- const lines = ["Detected ACP agents (local installs):", ""];
8956
- for (const a of installed) {
8957
- const ver = a.version ? ` (${a.version.split("\n")[0]})` : "";
8958
- lines.push(` \u2713 ${a.id.padEnd(16)} ${a.displayName}${ver}`);
8959
- }
8960
- for (const a of missing) {
8961
- lines.push(` \u2717 ${a.id.padEnd(16)} ${a.displayName} (${a.reason ?? "not installed"})`);
8962
- }
8963
- lines.push("");
8964
- lines.push(`${installed.length} of ${detected.length} bundled agents installed locally.`);
8965
- if (live && live.agents.length > 0) {
8966
- const localIds = new Set(detected.map((a) => a.id));
8967
- const aliases = {
8968
- "claude-acp": "claude-code",
8969
- gemini: "gemini-cli",
8970
- "codex-acp": "codex-cli",
8971
- "github-copilot-cli": "copilot"
8972
- };
8973
- const extra = live.agents.filter(
8974
- (a) => !localIds.has(a.id) && !localIds.has(aliases[a.id] ?? "")
8975
- );
8976
- lines.push("");
8977
- lines.push(
8978
- `Synced registry: ${live.agents.length} agents available (use \`/acp <id> <task>\`).`
8979
- );
8980
- if (extra.length > 0) {
8981
- lines.push(` more ids: ${extra.map((a) => a.id).join(", ")}`);
8982
- }
8983
- } else {
8984
- lines.push("");
8985
- lines.push("Run `/acp sync` to pull the full official registry (37+ agents).");
8986
- }
8987
- lines.push("Run `/acp <agent-id> <task>` to assign a task, or `/acp probe` to test handshakes.");
8988
- return { message: lines.join("\n") };
9106
+ return { message: formatAcpAgentList({ live, detected }) };
8989
9107
  }
8990
9108
  async function probeAgents(csv, overrides, live, opts) {
8991
9109
  let ids;
@@ -9007,7 +9125,7 @@ async function probeAgents(csv, overrides, live, opts) {
9007
9125
  const liveById = live?.byId;
9008
9126
  const results = await probeAcpAgents({
9009
9127
  agentIds: ids,
9010
- resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, liveById),
9128
+ resolveCmd: (id) => resolveAcpAgentCommand2(id, overrides, liveById),
9011
9129
  projectRoot: opts.projectRoot,
9012
9130
  onProgress: (id, r) => opts.renderer.writeInfo(` ${r.ok ? "\u2713" : "\u2717"} ${id} (${r.ms}ms)
9013
9131
  `)
@@ -9053,7 +9171,7 @@ async function benchAgents(rest, overrides, live, opts) {
9053
9171
  const liveById = live?.byId;
9054
9172
  const result = await runAcpBench({
9055
9173
  agentIds: ids,
9056
- resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, liveById),
9174
+ resolveCmd: (id) => resolveAcpAgentCommand2(id, overrides, liveById),
9057
9175
  projectRoot: opts.projectRoot,
9058
9176
  checkFs,
9059
9177
  onProgress: (agentId, phase, r) => {
@@ -9065,22 +9183,62 @@ async function benchAgents(rest, overrides, live, opts) {
9065
9183
  });
9066
9184
  return { message: renderAcpBenchText(result) };
9067
9185
  }
9068
- async function parallelAgents(rest, overrides, live) {
9069
- const spaceIdx = rest.search(/\s/);
9186
+ async function parallelAgents(rest, overrides, live, opts) {
9187
+ const tokens = rest.split(/\s+/).filter(Boolean);
9188
+ const bg = tokens.includes("--bg");
9189
+ const withoutFlag = tokens.filter((t) => t !== "--bg").join(" ");
9190
+ const spaceIdx = withoutFlag.search(/\s/);
9070
9191
  if (spaceIdx === -1) {
9071
9192
  return {
9072
- message: 'Usage: /acp parallel <agent-ids-csv> <task>\nExample: /acp parallel claude-code,gemini-cli "review this diff"'
9193
+ message: 'Usage: /acp parallel <agent-ids-csv> [--bg] <task>\nExample: /acp parallel claude-code,gemini-cli --bg "review this diff"'
9073
9194
  };
9074
9195
  }
9075
- const agentIds = rest.slice(0, spaceIdx);
9076
- const task = stripQuotes(rest.slice(spaceIdx + 1).trim());
9077
- if (!task) return { message: "Task description is required." };
9196
+ const idsCsv = withoutFlag.slice(0, spaceIdx).trim();
9197
+ const agentIds = dedup(
9198
+ idsCsv.split(",").map((s) => s.trim()).filter(Boolean)
9199
+ );
9200
+ const task = stripQuotes(withoutFlag.slice(spaceIdx + 1).trim());
9201
+ if (agentIds.length === 0 || !task) {
9202
+ return { message: "Task description is required." };
9203
+ }
9078
9204
  const liveById = live?.byId;
9205
+ if (bg) {
9206
+ if (!opts.onSpawn) {
9207
+ return {
9208
+ message: "Background mode needs the fleet (director). Director Mode is always active."
9209
+ };
9210
+ }
9211
+ const dispatched = [];
9212
+ const skipped = [];
9213
+ for (const id of agentIds) {
9214
+ if (!resolveAcpAgentCommand2(id, overrides, liveById)) {
9215
+ skipped.push({ id, reason: "unknown agent" });
9216
+ continue;
9217
+ }
9218
+ try {
9219
+ const summary = await opts.onSpawn(task, { provider: "acp", name: id });
9220
+ dispatched.push(` \u2022 ${id}: ${summary}`);
9221
+ } catch (err) {
9222
+ skipped.push({ id, reason: toErrorMessage4(err) });
9223
+ }
9224
+ }
9225
+ const lines = [
9226
+ dispatched.length === 0 ? "No ACP agents dispatched." : `Dispatched ${dispatched.length} ACP agent${dispatched.length === 1 ? "" : "s"} as background subagents.`,
9227
+ ...dispatched
9228
+ ];
9229
+ if (skipped.length > 0) {
9230
+ lines.push(
9231
+ `Skipped ${skipped.length}: ${skipped.map((s) => `${s.id} (${s.reason})`).join(", ")}`
9232
+ );
9233
+ }
9234
+ lines.push("Live progress is visible in /agents, the TUI fleet panel and the WebUI Agents panel.");
9235
+ return { message: lines.join("\n") };
9236
+ }
9079
9237
  try {
9080
9238
  const result = await runEnsemble({
9081
- agentIds,
9239
+ agentIds: idsCsv,
9082
9240
  task,
9083
- resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, liveById)
9241
+ resolveCmd: (id) => resolveAcpAgentCommand2(id, overrides, liveById)
9084
9242
  });
9085
9243
  return { message: renderEnsembleText(result) };
9086
9244
  } catch (err) {
@@ -9099,7 +9257,7 @@ async function runSingle(agentId, rest, overrides, live, opts) {
9099
9257
  Task description is required.`
9100
9258
  };
9101
9259
  }
9102
- const cmd = resolveAcpAgentCommand(agentId, overrides, live?.byId);
9260
+ const cmd = resolveAcpAgentCommand2(agentId, overrides, live?.byId);
9103
9261
  if (!cmd) {
9104
9262
  return {
9105
9263
  message: `Unknown ACP agent: ${agentId}
@@ -9289,6 +9447,16 @@ ${lines.join("\n")}`;
9289
9447
  lines.push(
9290
9448
  ` directives: ${stats.entryCount} buffered \xB7 ${hitRate} \xB7 ${stats.deadEntryCount} never used`
9291
9449
  );
9450
+ if (stats.directiveLift === null) {
9451
+ lines.push(
9452
+ ` measured lift: none yet ${color10.dim("(needs a directive with trials both with and without it)")}`
9453
+ );
9454
+ } else {
9455
+ const points = (stats.directiveLift * 100).toFixed(1);
9456
+ lines.push(
9457
+ ` measured lift: ${stats.directiveLift >= 0 ? "+" : ""}${points} pts over baseline, across ${stats.measuredEntryCount} directive(s)`
9458
+ );
9459
+ }
9292
9460
  }
9293
9461
  if (cfg) lines.push(` config: ${JSON.stringify(cfg)}`);
9294
9462
  if (kn)
@@ -13328,7 +13496,7 @@ function buildDoctorCommand(opts) {
13328
13496
  ].join("\n");
13329
13497
  async function loadPluginSchemas() {
13330
13498
  try {
13331
- const { BUILTIN_PLUGIN_FACTORIES } = await import("./plugins-U7KGQAUG.js");
13499
+ const { BUILTIN_PLUGIN_FACTORIES } = await import("./plugins-WJLW6ULV.js");
13332
13500
  const settled = await Promise.allSettled(BUILTIN_PLUGIN_FACTORIES.map((f) => f()));
13333
13501
  const loaded = [];
13334
13502
  for (const s of settled) {
@@ -13803,9 +13971,13 @@ function buildEnhanceCommand(opts) {
13803
13971
  }
13804
13972
 
13805
13973
  // src/slash-commands/ensemble.ts
13806
- import { renderEnsembleText as renderEnsembleText2, runEnsemble as runEnsemble2 } from "@wrongstack/acp";
13974
+ import {
13975
+ renderEnsembleText as renderEnsembleText2,
13976
+ resolveAcpAgentCommand as resolveAcpAgentCommand3,
13977
+ runEnsemble as runEnsemble2
13978
+ } from "@wrongstack/acp";
13807
13979
  import { toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
13808
- function buildEnsembleCommand(_opts) {
13980
+ function buildEnsembleCommand(opts) {
13809
13981
  return {
13810
13982
  name: "ensemble",
13811
13983
  category: "Agent",
@@ -13830,6 +14002,11 @@ function buildEnsembleCommand(_opts) {
13830
14002
  " --timeout <sec> Per-agent hard timeout (default 300s).",
13831
14003
  " --concurrency <n> Max agents running at once (default 4).",
13832
14004
  "",
14005
+ "This command is blocking: you wait for every agent, then get a text dump.",
14006
+ "To watch Claude Code / Gemini / Kimi live in the TUI fleet panel or WebUI",
14007
+ "Agents panel while they run in the background, use:",
14008
+ ' /acp parallel claude-code,gemini-cli,kimi --bg "the task"',
14009
+ "",
13833
14010
  "Use /acp list (or wstack acp list) to see which agents are detected."
13834
14011
  ].join("\n"),
13835
14012
  async run(args) {
@@ -13862,9 +14039,12 @@ function buildEnsembleCommand(_opts) {
13862
14039
  return { message: "Task description is required." };
13863
14040
  }
13864
14041
  try {
14042
+ const overrides = readEnsembleOverrides(opts);
14043
+ const live = opts.paths ? await loadCachedAcpRegistry(opts.paths) : null;
13865
14044
  const result = await runEnsemble2({
13866
14045
  agentIds,
13867
14046
  task,
14047
+ resolveCmd: (id) => resolveAcpAgentCommand3(id, overrides, live?.byId),
13868
14048
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
13869
14049
  ...maxConcurrency !== void 0 ? { maxConcurrency } : {}
13870
14050
  });
@@ -13875,6 +14055,13 @@ function buildEnsembleCommand(_opts) {
13875
14055
  }
13876
14056
  };
13877
14057
  }
14058
+ function readEnsembleOverrides(opts) {
14059
+ try {
14060
+ return opts.configStore?.get()?.acp?.agents;
14061
+ } catch {
14062
+ return void 0;
14063
+ }
14064
+ }
13878
14065
  function stripSurroundingQuotes(s) {
13879
14066
  if (s.length < 2) return s;
13880
14067
  const first = s[0];
@@ -15494,7 +15681,7 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
15494
15681
  }
15495
15682
 
15496
15683
  // src/slash-commands/fleet.ts
15497
- import { AGENTS_BY_PHASE as AGENTS_BY_PHASE2 } from "@wrongstack/core/agent-catalog";
15684
+ import { AGENTS_BY_PHASE as AGENTS_BY_PHASE2, summarizeDispatchLog } from "@wrongstack/core/agent-catalog";
15498
15685
  import { dispatchAgent as dispatchAgent3 } from "@wrongstack/core/coordination";
15499
15686
  import { color as color29, toErrorMessage as toErrorMessage12 } from "@wrongstack/core/utils";
15500
15687
  var PHASE_ORDER = [
@@ -15519,6 +15706,7 @@ function buildFleetCommand(opts) {
15519
15706
  " /fleet status Same as /fleet (verbose status)",
15520
15707
  " /fleet list List the agent roster grouped by phase",
15521
15708
  " /fleet dispatch <task> Route a task to the best agent and spawn it",
15709
+ " /fleet routing How past spawns chose their role (roster coverage)",
15522
15710
  " /fleet spawn <role> [count] Spawn N subagents of a role (default 1)",
15523
15711
  " /fleet terminate <subagentId> Stop a specific subagent by id",
15524
15712
  " /fleet kill Stop all running subagents",
@@ -15574,6 +15762,10 @@ function buildFleetCommand(opts) {
15574
15762
  case "dispatch":
15575
15763
  case "route":
15576
15764
  return await handleDispatch(opts, subargs);
15765
+ // ── /fleet routing ───────────────────────────────────────────────────
15766
+ case "routing":
15767
+ case "coverage":
15768
+ return handleRouting(opts, subargs);
15577
15769
  // ── /fleet concurrency [n] ───────────────────────────────────────────
15578
15770
  case "concurrency":
15579
15771
  case "slots":
@@ -15589,6 +15781,7 @@ function buildFleetCommand(opts) {
15589
15781
  "status",
15590
15782
  "list",
15591
15783
  "dispatch",
15784
+ "routing",
15592
15785
  "usage",
15593
15786
  "spawn",
15594
15787
  "terminate",
@@ -15826,6 +16019,48 @@ function handleList() {
15826
16019
  }
15827
16020
  return { message: lines.join("\n") };
15828
16021
  }
16022
+ function handleRouting(opts, subargs) {
16023
+ const summary = summarizeDispatchLog(opts.projectRoot);
16024
+ if (summary.entries === 0) {
16025
+ const msg = "No spawn routing recorded yet. Every `spawn_subagent` appends one line to .wrongstack/agents/dispatch-log.jsonl; the log starts empty on a fresh checkout.";
16026
+ opts.renderer.writeInfo?.(msg);
16027
+ return { message: msg };
16028
+ }
16029
+ const { bySource, byMethod, rosterMisses } = summary.totals;
16030
+ const window = summary.since && summary.until ? `${summary.since.slice(0, 10)} \u2192 ${summary.until.slice(0, 10)}` : "unknown window";
16031
+ const lines = [
16032
+ `${color29.bold("Spawn routing")} ${color29.dim(`(${summary.entries} spawns, ${window})`)}`,
16033
+ "",
16034
+ ` ${color29.cyan("addressed by")} description ${bySource.description} \xB7 explicit role ${bySource["explicit-role"]} \xB7 name only ${bySource["name-only"]}`,
16035
+ ` ${color29.cyan("decided by ")} heuristic ${byMethod.heuristic} \xB7 classifier ${byMethod.llm} \xB7 ${color29.bold(`fallback ${byMethod.fallback}`)}`,
16036
+ ` ${color29.cyan("roster miss ")} ${rosterMisses} ${color29.dim("(dispatched to a catalog role with no roster entry)")}`,
16037
+ ""
16038
+ ];
16039
+ const showAll = subargs.includes("--all");
16040
+ const spawned = summary.roles.filter((row) => row.spawns > 0);
16041
+ const shown = showAll ? spawned : spawned.slice(0, 15);
16042
+ lines.push(color29.cyan(" Roles spawned"));
16043
+ for (const row of shown) {
16044
+ const confidence = row.avgConfidence === null ? " \u2014" : row.avgConfidence.toFixed(2);
16045
+ lines.push(
16046
+ ` ${color29.bold(row.role.padEnd(20))} ${String(row.spawns).padStart(5)} ${color29.dim(`conf ${confidence} last ${row.lastAt?.slice(0, 10) ?? "\u2014"}`)}`
16047
+ );
16048
+ }
16049
+ if (!showAll && spawned.length > shown.length) {
16050
+ lines.push(color29.dim(` \u2026and ${spawned.length - shown.length} more (/fleet routing --all)`));
16051
+ }
16052
+ const nearMisses = summary.roles.filter((row) => row.spawns === 0 && row.runnerUp > 0).slice(0, 15);
16053
+ if (nearMisses.length > 0) {
16054
+ lines.push("");
16055
+ lines.push(color29.cyan(" Considered but never chosen"));
16056
+ for (const row of nearMisses) {
16057
+ lines.push(
16058
+ ` ${color29.bold(row.role.padEnd(20))} ${String(row.runnerUp).padStart(5)} ${color29.dim("runner-up")}`
16059
+ );
16060
+ }
16061
+ }
16062
+ return { message: lines.join("\n") };
16063
+ }
15829
16064
  async function handleDispatch(opts, subargs) {
15830
16065
  const skipConfirm = subargs.includes("--yes") || subargs.includes("-y");
15831
16066
  const task = subargs.filter((t) => t !== "--yes" && t !== "-y").join(" ").trim();
@@ -22614,6 +22849,7 @@ function buildProfileCommand(opts) {
22614
22849
  const wpaths = opts.paths;
22615
22850
  const configStore = opts.configStore;
22616
22851
  const renderer = opts.renderer;
22852
+ const syncConfig = opts.onActiveProfileChange;
22617
22853
  return {
22618
22854
  name: "profile",
22619
22855
  category: "Config",
@@ -22626,8 +22862,10 @@ function buildProfileCommand(opts) {
22626
22862
  "",
22627
22863
  "Profiles store all your settings (provider, model, fallbacks, features, etc.)",
22628
22864
  "in ~/.wrongstack/profiles/<name>/config.json.",
22629
- "A successful /profile switch closes the current session so every service",
22630
- "restarts against the newly selected profile."
22865
+ "A successful /profile switch rebinds the live config watchers to the newly",
22866
+ "selected profile, so provider credentials and routing apply immediately.",
22867
+ "Profile-owned state resolved at boot (memory, skills, prompts, statusline)",
22868
+ "still needs a restart to follow the new profile."
22631
22869
  ].join("\n"),
22632
22870
  async run(args) {
22633
22871
  if (!wpaths || !configStore) {
@@ -22644,7 +22882,7 @@ function buildProfileCommand(opts) {
22644
22882
  return listProfiles(wpaths, activeProfile, renderer);
22645
22883
  }
22646
22884
  if (sub === "switch") {
22647
- return switchProfile(wpaths, rest, renderer);
22885
+ return switchProfile(wpaths, rest, renderer, configStore, syncConfig);
22648
22886
  }
22649
22887
  if (sub === "copy") {
22650
22888
  return copyProfile(wpaths, rest, activeProfile, renderer);
@@ -22688,7 +22926,7 @@ async function listProfiles(wpaths, activeProfile, renderer) {
22688
22926
  renderer.write(msg);
22689
22927
  return { message: msg };
22690
22928
  }
22691
- async function switchProfile(wpaths, nameArg, renderer) {
22929
+ async function switchProfile(wpaths, nameArg, renderer, configStore, syncConfig) {
22692
22930
  const safe = sanitizeProfileName(nameArg);
22693
22931
  if (!safe) {
22694
22932
  const msg2 = formatNameError(nameArg);
@@ -22716,10 +22954,57 @@ async function switchProfile(wpaths, nameArg, renderer) {
22716
22954
  renderer.writeWarning(msg2);
22717
22955
  return { message: msg2 };
22718
22956
  }
22957
+ const WATCHER_OWNED = [
22958
+ "providers",
22959
+ "apiKey",
22960
+ "baseUrl",
22961
+ "fallbackModels",
22962
+ "fallbackBridge",
22963
+ "fallbackProfiles",
22964
+ "fallbackProfile",
22965
+ "favoriteModels",
22966
+ "favoriteModelsOnly",
22967
+ "modelAvailabilitySchedule",
22968
+ "modelMatrix",
22969
+ "fallbackAuto",
22970
+ "fallbackStickiness",
22971
+ "fallbackMaxLastResortCandidates",
22972
+ "uiLocale"
22973
+ ];
22974
+ const KEEP = ["version", "activeProfile", ...WATCHER_OWNED];
22975
+ const incoming = { ...profileData };
22976
+ delete incoming["version"];
22977
+ const patch = { ...incoming, activeProfile: safe };
22978
+ const previous = configStore.get();
22979
+ const previousProfileRaw = await readJsonObjectFile2(
22980
+ wpaths.profileConfig(previous.activeProfile ?? "default")
22981
+ );
22982
+ for (const key of Object.keys(previousProfileRaw)) {
22983
+ if (KEEP.includes(key) || key in patch) continue;
22984
+ patch[key] = null;
22985
+ }
22986
+ let next;
22987
+ try {
22988
+ next = configStore.update(patch);
22989
+ } catch (err) {
22990
+ return finishSwitchFailure(renderer, safe, err);
22991
+ }
22992
+ try {
22993
+ syncConfig?.(next);
22994
+ } catch (err) {
22995
+ return finishSwitchFailure(renderer, safe, err);
22996
+ }
22719
22997
  const msg = `${color45.green("\u2713")} Switched to profile "${color45.bold(safe)}"
22720
- ${color45.dim("Closing this session; restart WrongStack to activate it.")}`;
22998
+ ${color45.dim("Provider credentials and routing reloaded; boot-resolved profile state (memory, skills, prompts) applies on next start.")}`;
22721
22999
  renderer.write(msg);
22722
- return { message: msg, exit: true };
23000
+ return { message: msg };
23001
+ }
23002
+ function finishSwitchFailure(renderer, name, err) {
23003
+ const detail = err instanceof Error ? err.message : String(err);
23004
+ const msg = `${color45.red("\u26A0")} Profile "${color45.bold(name)}" is selected on disk, but it could not be applied to this session: ${detail}
23005
+ ${color45.dim("Restart WrongStack to finish the switch.")}`;
23006
+ renderer.writeWarning(msg);
23007
+ return { message: msg };
22723
23008
  }
22724
23009
  async function copyProfile(wpaths, nameArg, activeProfile, renderer) {
22725
23010
  const safe = sanitizeProfileName(nameArg);
@@ -22978,16 +23263,49 @@ function buildProviderStatusCommand(tracker) {
22978
23263
 
22979
23264
  // src/slash-commands/prune.ts
22980
23265
  import { color as color47 } from "@wrongstack/core/utils";
23266
+ function parseMaxAgeDays(parts) {
23267
+ const numPart = parts.find((p) => /^\d+$/.test(p));
23268
+ if (!numPart) return 30;
23269
+ return Math.max(1, Math.min(365, Number.parseInt(numPart, 10)));
23270
+ }
22981
23271
  function buildPruneCommand(opts) {
22982
23272
  return {
22983
23273
  name: "prune",
22984
23274
  category: "Session",
22985
23275
  description: "Delete old sessions. /prune (default 30d). To compress instead of delete, use /sessions archive.",
22986
- help: "Usage:\n /prune Delete sessions older than 30 days.\n /prune 14 Delete sessions older than 14 days.\n /prune --dry-run Show what would be deleted without deleting.\n /prune --rebuild-index Rebuild the session index from disk.",
23276
+ help: "Usage:\n /prune Delete sessions older than 30 days.\n /prune 14 Delete sessions older than 14 days.\n /prune --dry-run Show what would be deleted without deleting.\n /prune --rebuild-index Rebuild the session index from disk.\n /prune --checkpoints Reclaim workspace checkpoints no session references.",
22987
23277
  async run(args) {
22988
23278
  const parts = args.split(/\s+/).filter(Boolean);
22989
23279
  const rebuildIndex = parts.includes("--rebuild-index") || parts.includes("--rebuild");
22990
23280
  const dryRun = parts.includes("--dry-run");
23281
+ const checkpoints = parts.includes("--checkpoints");
23282
+ if (checkpoints) {
23283
+ const store = opts.sessionStore;
23284
+ if (!store?.collectCheckpointGarbage) {
23285
+ return { message: color47.yellow("Session store does not support checkpoint GC.") };
23286
+ }
23287
+ const ageDays = parseMaxAgeDays(parts);
23288
+ const gc = await store.collectCheckpointGarbage(ageDays);
23289
+ if (gc.manifestsDeleted === 0 && gc.objectsDeleted === 0) {
23290
+ return {
23291
+ message: color47.dim(
23292
+ `No unreferenced checkpoints older than ${ageDays} day${ageDays === 1 ? "" : "s"} (${gc.manifestsScanned} manifest${gc.manifestsScanned === 1 ? "" : "s"}, ${gc.objectsScanned} object${gc.objectsScanned === 1 ? "" : "s"} checked).`
23293
+ )
23294
+ };
23295
+ }
23296
+ const mib = (gc.bytesReclaimed / (1024 * 1024)).toFixed(1);
23297
+ const lines = [
23298
+ `Reclaimed ${color47.green(`${mib} MiB`)} from the checkpoint store: ${color47.cyan(String(gc.manifestsDeleted))}/${gc.manifestsScanned} manifests and ${color47.cyan(String(gc.objectsDeleted))}/${gc.objectsScanned} objects.`
23299
+ ];
23300
+ if (gc.errors.length > 0) {
23301
+ lines.push(
23302
+ color47.dim(
23303
+ `${gc.errors.length} item${gc.errors.length === 1 ? "" : "s"} were kept because they could not be read.`
23304
+ )
23305
+ );
23306
+ }
23307
+ return { message: lines.join("\n") };
23308
+ }
22991
23309
  if (rebuildIndex) {
22992
23310
  if (!opts.sessionStore?.rebuildIndex) {
22993
23311
  return {
@@ -22999,11 +23317,7 @@ function buildPruneCommand(opts) {
22999
23317
  message: count === 0 ? color47.dim("No sessions found to index.") : `Session index rebuilt: ${color47.green(String(count))} session${count === 1 ? "" : "s"} indexed.`
23000
23318
  };
23001
23319
  }
23002
- let maxAgeDays = 30;
23003
- const numPart = parts.find((p) => /^\d+$/.test(p));
23004
- if (numPart) {
23005
- maxAgeDays = Math.max(1, Math.min(365, Number.parseInt(numPart, 10)));
23006
- }
23320
+ const maxAgeDays = parseMaxAgeDays(parts);
23007
23321
  if (dryRun) {
23008
23322
  if (!opts.sessionStore) {
23009
23323
  return { message: color47.yellow("No session store configured.") };
@@ -28218,10 +28532,10 @@ async function executeSettingsSubcommand(sub, rest, opts) {
28218
28532
  }
28219
28533
  if (sub === "token-saving") {
28220
28534
  const raw = (rest[0] ?? "").toLowerCase();
28221
- const tiers = ["off", "minimal", "light", "medium", "aggressive"];
28535
+ const tiers = ["auto", "off", "minimal", "light", "medium", "aggressive"];
28222
28536
  if (!tiers.includes(raw)) {
28223
28537
  return {
28224
- message: `${color56.amber("Usage:")} /settings token-saving off|minimal|light|medium|aggressive`
28538
+ message: `${color56.amber("Usage:")} /settings token-saving auto|off|minimal|light|medium|aggressive`
28225
28539
  };
28226
28540
  }
28227
28541
  await persistConfigSetting(persistDeps, (cfg) => {
@@ -28230,7 +28544,8 @@ async function executeSettingsSubcommand(sub, rest, opts) {
28230
28544
  cfg.features = feat;
28231
28545
  });
28232
28546
  return {
28233
- message: `${color56.green("\u2713")} token-saving \u2192 ${color56.cyan(raw)} ${color56.dim("token-saving mode")}`
28547
+ message: `${color56.green("\u2713")} token-saving \u2192 ${color56.cyan(raw)} ${color56.dim("token-saving mode")}
28548
+ ` + color56.dim(" Applies to the next session - the tool registry is tiered at startup.")
28234
28549
  };
28235
28550
  }
28236
28551
  if (sub === "max-concurrent") {
@@ -28663,7 +28978,7 @@ var SETTINGS_HELP = [
28663
28978
  " /settings context-mode balanced|frugal|deep Context window policy",
28664
28979
  " /settings context-strategy hybrid|intelligent|selective Compactor strategy",
28665
28980
  " /settings context-auto-compact on|off Auto-compact context when thresholds crossed",
28666
- " /settings token-saving off|minimal|light|medium|aggressive Token-saving mode",
28981
+ " /settings token-saving auto|off|minimal|light|medium|aggressive Token-saving tier (next session)",
28667
28982
  " /settings nextsteps-tool on|off Give the leader a `nextsteps` tool alongside the <nextsteps> block (next session)",
28668
28983
  " /settings autothin on|off|status Stats-driven tool auto-thinning (off by default)",
28669
28984
  " /settings autothin-idle <days> Tools idle for N days are candidates (default 30)",
@@ -30884,6 +31199,11 @@ function formatResultRenderMode(mode) {
30884
31199
  const raw = `result:${mode}`;
30885
31200
  return mode === "simple" ? color65.amber(raw) : color65.cyan(raw);
30886
31201
  }
31202
+ function toolStatus(reg, name) {
31203
+ if (reg.isDisabled(name)) return color65.red("disabled");
31204
+ if (reg.isExposedToProvider?.(name) === false) return color65.cyan("lazy");
31205
+ return color65.green("direct");
31206
+ }
30887
31207
  function buildToolCommand(opts) {
30888
31208
  const help = [
30889
31209
  "Usage:",
@@ -31010,6 +31330,20 @@ function buildToolCommand(opts) {
31010
31330
  ""
31011
31331
  );
31012
31332
  }
31333
+ const lazy = opts.toolRegistry.listWithOwner().filter(({ tool }) => opts.toolRegistry.isExposedToProvider?.(tool.name) === false).map(({ tool }) => tool.name);
31334
+ if (lazy.length > 0) {
31335
+ lines.push(
31336
+ `${color65.bold("Held back by the token-saving tier")} ${color65.dim(`(${lazy.length})`)}`,
31337
+ "",
31338
+ ` ${color65.cyan("lazy")}: ${lazy.map((n) => color65.dim(n)).join(", ")}`,
31339
+ "",
31340
+ color65.dim(
31341
+ " Still callable via tool_search/tool_use; their schemas are not sent each turn."
31342
+ ),
31343
+ color65.dim(" Change the tier with /settings token-saving <tier>."),
31344
+ ""
31345
+ );
31346
+ }
31013
31347
  lines.push(
31014
31348
  color65.dim(
31015
31349
  " /tool <name> desc simple \xB7 /tool <name> result simple \xB7 /tool list \xB7 /tool disable|enable <name>"
@@ -31023,7 +31357,7 @@ function buildToolCommand(opts) {
31023
31357
  const descMode = getToolDescriptionMode(opts.toolRegistry, tool.name);
31024
31358
  const resultMode = getToolResultRenderMode(opts.toolRegistry, tool.name);
31025
31359
  const owner = opts.toolRegistry.ownerOf(tool.name) ?? "core";
31026
- const status = opts.toolRegistry.isDisabled(tool.name) ? color65.red("disabled") : color65.green("active");
31360
+ const status = toolStatus(opts.toolRegistry, tool.name);
31027
31361
  return ` ${fit(tool.name, 28)} ${color65.dim(fit(`[${owner}]`, 28))} ${fit(status, 10)} ${fit(formatDescriptionMode(descMode), 14)} ` + formatResultRenderMode(resultMode);
31028
31362
  });
31029
31363
  return [
@@ -31493,6 +31827,11 @@ function formatDescriptionMode2(mode) {
31493
31827
  const raw = `desc:${mode}`;
31494
31828
  return mode === "simple" ? color66.amber(raw) : color66.dim(raw);
31495
31829
  }
31830
+ function toolStatus2(reg, name) {
31831
+ if (reg.isDisabled(name)) return color66.red("disabled");
31832
+ if (reg.isExposedToProvider?.(name) === false) return color66.cyan("lazy");
31833
+ return color66.green("direct");
31834
+ }
31496
31835
  function buildToolsCommand(opts) {
31497
31836
  return {
31498
31837
  name: "tools",
@@ -31520,13 +31859,30 @@ function buildToolsCommand(opts) {
31520
31859
  const lines = all.map(({ tool, owner }) => {
31521
31860
  const mode = getToolDescriptionMode2(reg, tool.name);
31522
31861
  const rw = tool.mutating ? color66.yellow(fit2("mut", 4)) : color66.cyan(fit2("ro", 4));
31523
- const status = reg.isDisabled(tool.name) ? color66.red("disabled") : color66.green("active");
31862
+ const status = toolStatus2(reg, tool.name);
31524
31863
  return ` ${fit2(tool.name, 28)} ${color66.dim(fit2(`[${owner}]`, 28))} ${rw} ${color66.dim(fit2(tool.permission, 8))} ${fit2(status, 10)} ` + formatDescriptionMode2(mode);
31525
31864
  });
31526
- const extra = disabled.length > 0 ? `
31527
- ${color66.dim(`${disabled.length} tool(s) disabled. Use /tool enable <name> or /tool enable-all to restore.`)}` : "";
31865
+ const lazy = allTools.filter(
31866
+ ({ tool }) => !reg.isDisabled(tool.name) && reg.isExposedToProvider?.(tool.name) === false
31867
+ );
31868
+ const notes = [];
31869
+ if (lazy.length > 0) {
31870
+ notes.push(
31871
+ `${lazy.length} tool(s) held back from the provider by the token-saving tier - still callable via tool_search/tool_use. Change with /settings token-saving <tier>.`
31872
+ );
31873
+ }
31874
+ if (disabled.length > 0) {
31875
+ notes.push(
31876
+ `${disabled.length} tool(s) disabled. Use /tool enable <name> or /tool enable-all to restore.`
31877
+ );
31878
+ }
31879
+ const extra = notes.length > 0 ? `
31880
+ ${color66.dim(notes.join("\n"))}` : "";
31528
31881
  const filterNote = filter ? color66.dim(` matching "${filter}" (${all.length} of ${allTools.length})`) : "";
31529
- const msg = `${color66.bold("Tools")}${filterNote} (${all.length} shown, ${disabled.length} disabled) ${color66.dim("description detail via /tool <name> simple|extend")}:
31882
+ const directCount = allTools.filter(
31883
+ ({ tool }) => !reg.isDisabled(tool.name) && reg.isExposedToProvider?.(tool.name) !== false
31884
+ ).length;
31885
+ const msg = `${color66.bold("Tools")}${filterNote} (${all.length} shown, ${directCount} direct, ${lazy.length} lazy, ${disabled.length} disabled) ${color66.dim("description detail via /tool <name> simple|extend")}:
31530
31886
  ${header}
31531
31887
  ${lines.join("\n")}${extra}
31532
31888
  `;
@@ -32506,7 +32862,72 @@ function buildWorktreeCommand(opts) {
32506
32862
  }
32507
32863
 
32508
32864
  // src/slash-commands/yolo.ts
32865
+ import {
32866
+ ALL_DESTRUCTIVE_KINDS,
32867
+ isDestructiveKind,
32868
+ LOCKED_DESTRUCTIVE_KINDS
32869
+ } from "@wrongstack/core/security";
32509
32870
  import { color as color69 } from "@wrongstack/core/utils";
32871
+ var KIND_BLURBS = {
32872
+ "disk-wipe": "mkfs, dd to a raw device, format C:, fork bomb",
32873
+ "system-halt": "shutdown, reboot",
32874
+ "delete-outside": "recursive force-delete that leaves the project, or hits a system/home root",
32875
+ "git-history": "reset --hard, clean -f, push --force, filter-branch",
32876
+ publish: "npm publish, docker push, kubectl delete namespace",
32877
+ "download-and-run": "curl | sh, and inline payloads that fetch then execute",
32878
+ "bulk-delete": "find -exec rm, inline rmSync across many paths",
32879
+ "agent-state": "writes to WrongStack's own config.json / trust.json / auth.json",
32880
+ "credential-bind": "attaching a well-known API key to a chosen provider endpoint"
32881
+ };
32882
+ function renderConfirmList(opts, map) {
32883
+ const lines = ["Kinds that still prompt while YOLO is on:", ""];
32884
+ for (const kind of ALL_DESTRUCTIVE_KINDS) {
32885
+ const locked = LOCKED_DESTRUCTIVE_KINDS.has(kind);
32886
+ const state = map[kind] ? locked ? color69.dim("ALWAYS ASKS") : color69.yellow("ASKS") : color69.green("runs");
32887
+ lines.push(` ${kind.padEnd(17)} ${state.padEnd(20)} ${color69.dim(KIND_BLURBS[kind])}`);
32888
+ }
32889
+ lines.push("", color69.dim(" /yolo confirm <kind> off lets YOLO run that kind unattended"));
32890
+ lines.push(
32891
+ color69.dim(" ALWAYS ASKS cannot be turned off \u2014 those writes can disable approval itself")
32892
+ );
32893
+ const msg = lines.join("\n");
32894
+ opts.renderer.write(msg);
32895
+ return { message: msg };
32896
+ }
32897
+ function runConfirm(opts, rest) {
32898
+ if (!opts.onYoloConfirm) {
32899
+ const msg2 = "Per-kind YOLO confirmation is not available in this session.";
32900
+ opts.renderer.writeWarning(msg2);
32901
+ return { message: msg2 };
32902
+ }
32903
+ if (!rest) return renderConfirmList(opts, opts.onYoloConfirm());
32904
+ const [rawKind, rawState] = rest.split(/\s+/);
32905
+ if (!isDestructiveKind(rawKind)) {
32906
+ const msg2 = `Unknown kind: ${rawKind}. Known kinds: ${ALL_DESTRUCTIVE_KINDS.join(", ")}.`;
32907
+ opts.renderer.writeWarning(msg2);
32908
+ return { message: msg2 };
32909
+ }
32910
+ if (rawState === void 0) {
32911
+ const msg2 = `Usage: /yolo confirm ${rawKind} on|off`;
32912
+ opts.renderer.writeWarning(msg2);
32913
+ return { message: msg2 };
32914
+ }
32915
+ const confirm = rawState === "on" || rawState === "true" || rawState === "1";
32916
+ if (!confirm && !["off", "false", "0"].includes(rawState)) {
32917
+ const msg2 = `Unknown state: ${rawState}. Use on or off.`;
32918
+ opts.renderer.writeWarning(msg2);
32919
+ return { message: msg2 };
32920
+ }
32921
+ const after = opts.onYoloConfirm({ kind: rawKind, confirm });
32922
+ if (!confirm && after[rawKind]) {
32923
+ const msg2 = `${color69.amber(rawKind)} always asks \u2014 ${KIND_BLURBS[rawKind]} can switch the approval system itself off.`;
32924
+ opts.renderer.writeWarning(msg2);
32925
+ return { message: msg2 };
32926
+ }
32927
+ const msg = confirm ? `${color69.yellow(rawKind)} will keep asking under YOLO.` : `${color69.green(rawKind)} will now run unattended under YOLO.`;
32928
+ opts.renderer.write(msg);
32929
+ return { message: msg };
32930
+ }
32510
32931
  function buildYoloCommand(opts) {
32511
32932
  return {
32512
32933
  name: "yolo",
@@ -32517,9 +32938,14 @@ function buildYoloCommand(opts) {
32517
32938
  " /yolo Show current YOLO status",
32518
32939
  " /yolo on Enable YOLO mode (auto-approve tool calls)",
32519
32940
  " /yolo off Disable YOLO mode (restore permission prompts)",
32520
- " /yolo destructive Show deprecated destructive-gate compatibility status",
32941
+ " /yolo confirm List which kinds of damage still prompt under YOLO",
32942
+ " /yolo confirm <kind> on Keep prompting for that kind",
32943
+ " /yolo confirm <kind> off Let YOLO run that kind unattended",
32521
32944
  "",
32522
- "YOLO mode auto-approves tool calls unless an explicit deny rule blocks them."
32945
+ "YOLO auto-approves tool calls unless an explicit deny rule blocks them, or",
32946
+ "the call would do damage of a kind still listed under `/yolo confirm`.",
32947
+ `Kinds: ${ALL_DESTRUCTIVE_KINDS.join(", ")}.`,
32948
+ `Always gated (they can switch approval itself off): ${[...LOCKED_DESTRUCTIVE_KINDS].join(", ")}.`
32523
32949
  ].join("\n"),
32524
32950
  async run(args) {
32525
32951
  const arg = args.trim().toLowerCase();
@@ -32542,18 +32968,10 @@ function buildYoloCommand(opts) {
32542
32968
  newState = false;
32543
32969
  } else if (arg === "toggle") {
32544
32970
  newState = !opts.onYolo();
32545
- } else if (arg === "destructive") {
32546
- const currentMode = opts.onYolo();
32547
- if (!currentMode) {
32548
- const msg3 = `${color69.amber("YOLO is OFF.")} Destructive-gate flags are deprecated; prompts are active because YOLO is off.`;
32549
- opts.renderer.writeWarning(msg3);
32550
- return { message: msg3 };
32551
- }
32552
- const msg2 = `${color69.amber("Destructive gate:")} ${color69.dim("deprecated \u2014 YOLO auto-approves all non-denied tool calls.")}`;
32553
- opts.renderer.writeWarning(msg2);
32554
- return { message: msg2 };
32971
+ } else if (arg === "confirm" || arg.startsWith("confirm ")) {
32972
+ return runConfirm(opts, arg.slice("confirm".length).trim());
32555
32973
  } else {
32556
- const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, /yolo toggle, or /yolo destructive.`;
32974
+ const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, /yolo toggle, or /yolo confirm.`;
32557
32975
  opts.renderer.writeWarning(msg2);
32558
32976
  return { message: msg2 };
32559
32977
  }
@@ -32742,6 +33160,7 @@ function setupCliSlashCommands(params) {
32742
33160
  eventWiring,
32743
33161
  mcpRegistry,
32744
33162
  setYoloMode,
33163
+ setYoloConfirm,
32745
33164
  getNextPredict,
32746
33165
  setNextPredict,
32747
33166
  getCurrentSuggestions,
@@ -32818,6 +33237,7 @@ function setupCliSlashCommands(params) {
32818
33237
  ...agentMonitor ? { agentMonitor } : {},
32819
33238
  onPanelOpen,
32820
33239
  configStore,
33240
+ onActiveProfileChange: params.onActiveProfileChange,
32821
33241
  reader,
32822
33242
  readSecret: secretInputController.readSecret,
32823
33243
  readText: secretInputController.readText,
@@ -32849,6 +33269,7 @@ function setupCliSlashCommands(params) {
32849
33269
  setEventMaxContext: eventWiring.setEffectiveMaxContext,
32850
33270
  mcpRegistry,
32851
33271
  onYolo: setYoloMode,
33272
+ onYoloConfirm: setYoloConfirm,
32852
33273
  getNextPredict,
32853
33274
  setNextPredict,
32854
33275
  getCurrentSuggestions,
@@ -32921,8 +33342,14 @@ function setupCliSlashCommands(params) {
32921
33342
  import {
32922
33343
  areSubagentsAllowedForSession as areSubagentsAllowedForSession4,
32923
33344
  FLEET_ROSTER as FLEET_ROSTER5,
32924
- mailboxSessionTag as mailboxSessionTag6
33345
+ mailboxSessionTag as mailboxSessionTag6,
33346
+ makeKanbanQueueTool
32925
33347
  } from "@wrongstack/core/coordination";
33348
+ import {
33349
+ ALL_DESTRUCTIVE_KINDS as ALL_DESTRUCTIVE_KINDS2,
33350
+ normalizeYoloConfirmKinds,
33351
+ resolveYoloConfirmKinds
33352
+ } from "@wrongstack/core/security";
32926
33353
  import {
32927
33354
  AgentError as AgentError3,
32928
33355
  resolveFleetChatVerbosity as resolveFleetChatVerbosity2
@@ -33002,6 +33429,44 @@ async function setupCommandHostState(input) {
33002
33429
  input.hqCommandController.killFleet = () => killFleet2(input.getDirector());
33003
33430
  input.hqCommandController.terminateAgent = (subagentId) => terminateAgent(input.getDirector(), subagentId);
33004
33431
  input.hqCommandController.spawnAgent = (role, task, maxIterations) => spawnAgent2(input.getDirector(), liveSessionId(), role, task, maxIterations);
33432
+ input.hqCommandController.kanbanDispatch = async (request) => {
33433
+ const director = input.getDirector();
33434
+ const sessionId = request.sessionId ?? liveSessionId();
33435
+ if (director === null) {
33436
+ throw new AgentError3({
33437
+ message: "No Director is active for Kanban dispatch.",
33438
+ code: "AGENT_RUN_FAILED",
33439
+ context: { phase: "hq-kanban-dispatch", taskId: request.taskId }
33440
+ });
33441
+ }
33442
+ if (!areSubagentsAllowedForSession4(sessionId)) {
33443
+ throw new Error("Subagents are disabled for this session.");
33444
+ }
33445
+ const result = await makeKanbanQueueTool(director).execute(
33446
+ {
33447
+ action: "dispatch_ready",
33448
+ boardId: request.boardId,
33449
+ taskId: request.taskId,
33450
+ maxTasks: 1,
33451
+ awaitCompletion: false
33452
+ },
33453
+ {
33454
+ projectRoot: input.projectRoot,
33455
+ eventSessionId: () => sessionId
33456
+ },
33457
+ { signal: new AbortController().signal }
33458
+ );
33459
+ if (result.ok !== true || result.count !== 1) {
33460
+ throw new AgentError3({
33461
+ message: result.errors?.[0]?.error ?? result.message ?? "Kanban task could not be dispatched.",
33462
+ code: "AGENT_RUN_FAILED",
33463
+ context: { phase: "hq-kanban-dispatch", taskId: request.taskId }
33464
+ });
33465
+ }
33466
+ const subagentId = result.dispatched?.[0]?.subagentId;
33467
+ const summary = subagentId ? `task ${request.taskId} dispatched to ${subagentId}` : `task ${request.taskId} dispatched`;
33468
+ return `${summary} \xB7 ${request.comment}`;
33469
+ };
33005
33470
  const enhanceController = createEnhanceController(input.getConfig());
33006
33471
  const statuslineConfigDeps = createStatuslineConfigDeps();
33007
33472
  const hidden = await loadStatuslineHiddenItems();
@@ -33054,6 +33519,31 @@ async function setupCommandHostState(input) {
33054
33519
  }
33055
33520
  return input.permissionPolicy.getYolo?.() ?? input.getConfig().yolo ?? false;
33056
33521
  };
33522
+ const setYoloConfirm = (update) => {
33523
+ if (update) {
33524
+ const current = resolveYoloConfirmKinds(input.getConfig().autonomy?.yoloConfirm);
33525
+ const next = new Set(current);
33526
+ if (update.confirm) next.add(update.kind);
33527
+ else next.delete(update.kind);
33528
+ const normalized = normalizeYoloConfirmKinds(next);
33529
+ input.permissionPolicy.setYoloConfirmKinds?.(normalized);
33530
+ const config = input.getConfig();
33531
+ input.setConfig(
33532
+ patchConfig(config, {
33533
+ autonomy: {
33534
+ ...config.autonomy,
33535
+ yoloConfirm: Object.fromEntries(
33536
+ ALL_DESTRUCTIVE_KINDS2.map((kind) => [kind, normalized.has(kind)])
33537
+ )
33538
+ }
33539
+ })
33540
+ );
33541
+ }
33542
+ const effective = input.permissionPolicy.getYoloConfirmKinds?.() ?? resolveYoloConfirmKinds(input.getConfig().autonomy?.yoloConfirm);
33543
+ return Object.fromEntries(
33544
+ ALL_DESTRUCTIVE_KINDS2.map((kind) => [kind, effective.has(kind)])
33545
+ );
33546
+ };
33057
33547
  const secretInputController = {
33058
33548
  readSecret: (prompt) => input.reader.readSecret(prompt),
33059
33549
  readText: (prompt) => input.reader.readLine(prompt)
@@ -33088,6 +33578,7 @@ async function setupCommandHostState(input) {
33088
33578
  goalHost,
33089
33579
  coordinatorController,
33090
33580
  setYoloMode,
33581
+ setYoloConfirm,
33091
33582
  secretInputController,
33092
33583
  sddRunRegistry
33093
33584
  };
@@ -33283,9 +33774,12 @@ async function ensureDirectorAndAnnounce(args) {
33283
33774
  const director = await multiAgentHost.ensureDirector();
33284
33775
  if (director) {
33285
33776
  if (priorFleetState) director.setCheckpointState(priorFleetState);
33777
+ const directorToolNames = [];
33286
33778
  for (const tool of director.tools(FLEET_ROSTER6)) {
33287
33779
  toolRegistry.register(tool);
33780
+ directorToolNames.push(tool.name);
33288
33781
  }
33782
+ toolRegistry.exposeToProvider(directorToolNames);
33289
33783
  const browserSurface = flags.webui === true || flags.simpleui === true;
33290
33784
  if (browserSurface) {
33291
33785
  renderer.writeInfo(
@@ -33644,7 +34138,9 @@ function setupHqTelemetry(deps) {
33644
34138
  // writer ref, because an in-process resume / session.new swaps
33645
34139
  // `ctx.session` for a new writer object and this captured one goes stale.
33646
34140
  sessionId: () => session.id,
33647
- allowRunCommand: () => flags["hq-allow-exec"] === true
34141
+ allowRunCommand: () => flags["hq-allow-exec"] === true,
34142
+ kanbanTransition: createProjectKanbanTransitionHandler(projectRoot),
34143
+ kanbanAssign: createProjectKanbanAssignHandler(projectRoot)
33648
34144
  };
33649
34145
  const hqOnCommand = createHqCommandDispatcher(hqCommandController);
33650
34146
  const hqConnection = startCliHqConnection({
@@ -33658,7 +34154,8 @@ function setupHqTelemetry(deps) {
33658
34154
  "mailbox.summary",
33659
34155
  "fleet.summary",
33660
34156
  "session.summary",
33661
- "control.receive"
34157
+ "control.receive",
34158
+ "kanban.dispatch"
33662
34159
  ],
33663
34160
  onConnect: (publisher) => {
33664
34161
  hqPublisherRef.current = publisher;
@@ -33807,9 +34304,9 @@ import { NotifierImpl } from "@wrongstack/core/notifications";
33807
34304
  import {
33808
34305
  SlashCommandRegistry
33809
34306
  } from "@wrongstack/core/registry";
33810
- import { normalizeTokenSavingTier as normalizeTokenSavingTier5 } from "@wrongstack/core/types";
33811
34307
  import {
33812
34308
  CONTEXT_WINDOW_MODE_PINNED_META_KEY as CONTEXT_WINDOW_MODE_PINNED_META_KEY2,
34309
+ normalizeTokenSavingTier as normalizeTokenSavingTier5,
33813
34310
  resolveContextWindowPolicy as resolveContextWindowPolicy3
33814
34311
  } from "@wrongstack/core/types";
33815
34312
  import {
@@ -34418,12 +34915,129 @@ function createAgent(params) {
34418
34915
  executionStrategy: params.config.tools.defaultExecutionStrategy,
34419
34916
  perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
34420
34917
  loopDetection: params.config.tools.loopDetection,
34918
+ // Previously unwired: the Agent fell back to `autoExtendLimit: false`, so
34919
+ // `tools.autoExtendLimit` did nothing on the CLI/TUI host while the WebUI
34920
+ // host honoured it — the same config produced two different budget
34921
+ // behaviours depending on the surface.
34922
+ ...params.config.tools.autoExtendLimit !== void 0 ? { autoExtendLimit: params.config.tools.autoExtendLimit } : {},
34923
+ ...params.config.tools.maxAutoExtensions !== void 0 ? { maxAutoExtensions: params.config.tools.maxAutoExtensions } : {},
34421
34924
  confirmAwaiter: params.confirmAwaiter,
34422
34925
  toolExecutor,
34423
34926
  tracer: params.tracer
34424
34927
  });
34425
34928
  }
34426
34929
 
34930
+ // src/wiring/prompt-journal-recorder.ts
34931
+ import {
34932
+ PROMPT_JOURNAL_RAW_MARKER,
34933
+ recordPromptJournalEntry
34934
+ } from "@wrongstack/core/prompts";
34935
+ function systemPromptText(blocks) {
34936
+ if (!blocks || blocks.length === 0) return "";
34937
+ return blocks.map((block) => block.type === "text" ? block.text : "").filter((text) => text.trim().length > 0).join("\n").trim();
34938
+ }
34939
+ function createPromptJournalRecorder() {
34940
+ const systemPromptRecorded = /* @__PURE__ */ new Set();
34941
+ return {
34942
+ name: "PromptJournalRecorder",
34943
+ async handler(payload, next) {
34944
+ const ctx = payload.ctx;
34945
+ const meta = ctx.meta;
34946
+ const raw = typeof meta?.[PROMPT_JOURNAL_RAW_MARKER] === "string" ? meta[PROMPT_JOURNAL_RAW_MARKER] : void 0;
34947
+ if (meta) delete meta[PROMPT_JOURNAL_RAW_MARKER];
34948
+ const projectRoot = ctx.projectRoot;
34949
+ const text = payload.text.trim();
34950
+ if (projectRoot && text) {
34951
+ const sessionId = ctx.session?.id ?? ctx.activeRunSessionId ?? "general";
34952
+ const common = {
34953
+ projectRoot,
34954
+ sessionId,
34955
+ model: ctx.model,
34956
+ provider: ctx.provider?.id,
34957
+ activeTools: ctx.tools?.map((tool) => tool.name) ?? []
34958
+ };
34959
+ if (raw !== void 0 && raw !== text) {
34960
+ void recordPromptJournalEntry({
34961
+ ...common,
34962
+ category: "refined_user",
34963
+ content: text,
34964
+ rawContent: raw
34965
+ }).catch(() => {
34966
+ });
34967
+ } else {
34968
+ void recordPromptJournalEntry({
34969
+ ...common,
34970
+ category: "raw_user",
34971
+ content: text
34972
+ }).catch(() => {
34973
+ });
34974
+ }
34975
+ if (!systemPromptRecorded.has(sessionId)) {
34976
+ systemPromptRecorded.add(sessionId);
34977
+ const systemText = systemPromptText(ctx.systemPrompt);
34978
+ if (systemText) {
34979
+ void recordPromptJournalEntry({
34980
+ ...common,
34981
+ category: "system_prompt",
34982
+ content: systemText
34983
+ }).catch(() => {
34984
+ });
34985
+ }
34986
+ }
34987
+ }
34988
+ return next(payload);
34989
+ }
34990
+ };
34991
+ }
34992
+ var TOOL_CATEGORY = {
34993
+ clarify: "clarify_interaction",
34994
+ delegate: "subagent_delegation",
34995
+ continue_to_next_iteration: "autonomous_next_step"
34996
+ };
34997
+ function createPromptJournalToolCallRecorder() {
34998
+ return {
34999
+ name: "PromptJournalToolCallRecorder",
35000
+ async handler(payload, next) {
35001
+ const ctx = payload.ctx;
35002
+ const projectRoot = ctx.projectRoot;
35003
+ const toolName2 = payload.toolUse?.name;
35004
+ const category = toolName2 ? TOOL_CATEGORY[toolName2] : void 0;
35005
+ if (projectRoot && category) {
35006
+ const input = payload.toolUse.input;
35007
+ const sessionId = ctx.session?.id ?? ctx.activeRunSessionId ?? "general";
35008
+ const common = {
35009
+ projectRoot,
35010
+ sessionId,
35011
+ model: ctx.model,
35012
+ provider: ctx.provider?.id,
35013
+ activeTools: ctx.tools?.map((tool) => tool.name) ?? []
35014
+ };
35015
+ let content = "";
35016
+ let decisionReason;
35017
+ if (toolName2 === "clarify") {
35018
+ content = typeof input?.question === "string" ? input.question : "";
35019
+ decisionReason = typeof input?.context === "string" ? input.context : void 0;
35020
+ } else if (toolName2 === "delegate") {
35021
+ content = typeof input?.task === "string" ? input.task : "";
35022
+ decisionReason = typeof input?.role === "string" ? `delegated to ${input.role}` : typeof input?.name === "string" ? `delegated to ${input.name}` : void 0;
35023
+ } else {
35024
+ content = "Autonomous continue to the next iteration";
35025
+ }
35026
+ if (content.trim()) {
35027
+ void recordPromptJournalEntry({
35028
+ ...common,
35029
+ category,
35030
+ content,
35031
+ decisionReason
35032
+ }).catch(() => {
35033
+ });
35034
+ }
35035
+ }
35036
+ return next(payload);
35037
+ }
35038
+ };
35039
+ }
35040
+
34427
35041
  // src/wiring/provider-utility-tools.ts
34428
35042
  import {
34429
35043
  createCouncilPersonaRegistry,
@@ -34557,117 +35171,6 @@ function registerOrOverride(registry, name, tool) {
34557
35171
  }
34558
35172
  }
34559
35173
 
34560
- // src/wiring/prompt-journal-recorder.ts
34561
- import {
34562
- PROMPT_JOURNAL_RAW_MARKER,
34563
- recordPromptJournalEntry
34564
- } from "@wrongstack/core/prompts";
34565
- function systemPromptText(blocks) {
34566
- if (!blocks || blocks.length === 0) return "";
34567
- return blocks.map((block) => block.type === "text" ? block.text : "").filter((text) => text.trim().length > 0).join("\n").trim();
34568
- }
34569
- function createPromptJournalRecorder() {
34570
- const systemPromptRecorded = /* @__PURE__ */ new Set();
34571
- return {
34572
- name: "PromptJournalRecorder",
34573
- async handler(payload, next) {
34574
- const ctx = payload.ctx;
34575
- const meta = ctx.meta;
34576
- const raw = typeof meta?.[PROMPT_JOURNAL_RAW_MARKER] === "string" ? meta[PROMPT_JOURNAL_RAW_MARKER] : void 0;
34577
- if (meta) delete meta[PROMPT_JOURNAL_RAW_MARKER];
34578
- const projectRoot = ctx.projectRoot;
34579
- const text = payload.text.trim();
34580
- if (projectRoot && text) {
34581
- const sessionId = ctx.session?.id ?? ctx.activeRunSessionId ?? "general";
34582
- const common = {
34583
- projectRoot,
34584
- sessionId,
34585
- model: ctx.model,
34586
- provider: ctx.provider?.id,
34587
- activeTools: ctx.tools?.map((tool) => tool.name) ?? []
34588
- };
34589
- if (raw !== void 0 && raw !== text) {
34590
- void recordPromptJournalEntry({
34591
- ...common,
34592
- category: "refined_user",
34593
- content: text,
34594
- rawContent: raw
34595
- }).catch(() => {
34596
- });
34597
- } else {
34598
- void recordPromptJournalEntry({
34599
- ...common,
34600
- category: "raw_user",
34601
- content: text
34602
- }).catch(() => {
34603
- });
34604
- }
34605
- if (!systemPromptRecorded.has(sessionId)) {
34606
- systemPromptRecorded.add(sessionId);
34607
- const systemText = systemPromptText(ctx.systemPrompt);
34608
- if (systemText) {
34609
- void recordPromptJournalEntry({
34610
- ...common,
34611
- category: "system_prompt",
34612
- content: systemText
34613
- }).catch(() => {
34614
- });
34615
- }
34616
- }
34617
- }
34618
- return next(payload);
34619
- }
34620
- };
34621
- }
34622
- var TOOL_CATEGORY = {
34623
- clarify: "clarify_interaction",
34624
- delegate: "subagent_delegation",
34625
- continue_to_next_iteration: "autonomous_next_step"
34626
- };
34627
- function createPromptJournalToolCallRecorder() {
34628
- return {
34629
- name: "PromptJournalToolCallRecorder",
34630
- async handler(payload, next) {
34631
- const ctx = payload.ctx;
34632
- const projectRoot = ctx.projectRoot;
34633
- const toolName = payload.toolUse?.name;
34634
- const category = toolName ? TOOL_CATEGORY[toolName] : void 0;
34635
- if (projectRoot && category) {
34636
- const input = payload.toolUse.input;
34637
- const sessionId = ctx.session?.id ?? ctx.activeRunSessionId ?? "general";
34638
- const common = {
34639
- projectRoot,
34640
- sessionId,
34641
- model: ctx.model,
34642
- provider: ctx.provider?.id,
34643
- activeTools: ctx.tools?.map((tool) => tool.name) ?? []
34644
- };
34645
- let content = "";
34646
- let decisionReason;
34647
- if (toolName === "clarify") {
34648
- content = typeof input?.question === "string" ? input.question : "";
34649
- decisionReason = typeof input?.context === "string" ? input.context : void 0;
34650
- } else if (toolName === "delegate") {
34651
- content = typeof input?.task === "string" ? input.task : "";
34652
- decisionReason = typeof input?.role === "string" ? `delegated to ${input.role}` : typeof input?.name === "string" ? `delegated to ${input.name}` : void 0;
34653
- } else {
34654
- content = "Autonomous continue to the next iteration";
34655
- }
34656
- if (content.trim()) {
34657
- void recordPromptJournalEntry({
34658
- ...common,
34659
- category,
34660
- content,
34661
- decisionReason
34662
- }).catch(() => {
34663
- });
34664
- }
34665
- }
34666
- return next(payload);
34667
- }
34668
- };
34669
- }
34670
-
34671
35174
  // src/wiring/lifecycle-plugins.ts
34672
35175
  async function setupLifecycleAndPlugins(deps) {
34673
35176
  const {
@@ -34877,7 +35380,8 @@ async function setupLifecycleAndPlugins(deps) {
34877
35380
  }
34878
35381
  }
34879
35382
  registerMcpObservability(healthRegistry, metricsSink, mcpRegistry);
34880
- const slashRegistry = new SlashCommandRegistry();
35383
+ const onSlashNotice = (message) => logger.warn(message);
35384
+ const slashRegistry = new SlashCommandRegistry({ onNotice: onSlashNotice });
34881
35385
  const hqPublisherRef = { current: void 0 };
34882
35386
  const brainMailbox = getSharedProjectMailbox10(
34883
35387
  wpaths.projectDir,
@@ -35119,6 +35623,7 @@ import {
35119
35623
  SessionMemoryCurator,
35120
35624
  watchProviderConfig
35121
35625
  } from "@wrongstack/core/storage";
35626
+ import { readJsonObjectFile as readJsonObjectFile3 } from "@wrongstack/core/utils";
35122
35627
  import { createProxyInstantApply } from "@wrongstack/core/wiring/proxy-rewrite";
35123
35628
  import { withCatalogCapabilities as withCatalogCapabilities2 } from "@wrongstack/providers";
35124
35629
  import { getSageService as getSageService2 } from "@wrongstack/sage";
@@ -35452,6 +35957,20 @@ function setupProviderRuntime(deps) {
35452
35957
  { warn: (msg) => logger.warn(`Config watcher (${layer.path}): ${msg}`) }
35453
35958
  )
35454
35959
  );
35960
+ watchers.push(
35961
+ watchProviderConfig(
35962
+ wpaths.globalConfig,
35963
+ vault,
35964
+ async () => {
35965
+ const bootstrap = await readJsonObjectFile3(wpaths.globalConfig);
35966
+ const selected = bootstrap["activeProfile"];
35967
+ const current = configStore.get().activeProfile ?? "default";
35968
+ if (typeof selected !== "string" || !selected || selected === current) return;
35969
+ configStore.update({ activeProfile: selected });
35970
+ },
35971
+ { warn: (msg) => logger.warn(`Config watcher (bootstrap): ${msg}`) }
35972
+ )
35973
+ );
35455
35974
  };
35456
35975
  const closeWatchers = () => {
35457
35976
  for (const w of watchers) w.close();
@@ -37698,6 +38217,7 @@ async function runInteractive(cliCtx) {
37698
38217
  goalHost,
37699
38218
  coordinatorController,
37700
38219
  setYoloMode,
38220
+ setYoloConfirm,
37701
38221
  secretInputController,
37702
38222
  sddRunRegistry
37703
38223
  } = await setupCommandHostState({
@@ -37755,6 +38275,12 @@ async function runInteractive(cliCtx) {
37755
38275
  agentMonitor,
37756
38276
  onPanelOpen,
37757
38277
  configStore,
38278
+ // `/profile switch` mutates the store; adopt the resulting config into the
38279
+ // host's own `config` closure exactly the way setupProviderRuntime's
38280
+ // onConfigUpdate does, so the two copies cannot drift apart.
38281
+ onActiveProfileChange: (nextConfig) => {
38282
+ config = nextConfig;
38283
+ },
37758
38284
  secretInputController,
37759
38285
  vault,
37760
38286
  brain,
@@ -37776,6 +38302,7 @@ async function runInteractive(cliCtx) {
37776
38302
  eventWiring,
37777
38303
  mcpRegistry,
37778
38304
  setYoloMode,
38305
+ setYoloConfirm,
37779
38306
  getNextPredict: () => nextPredictEnabled,
37780
38307
  setNextPredict: (enabled) => {
37781
38308
  nextPredictEnabled = enabled;
@@ -37979,4 +38506,4 @@ export {
37979
38506
  CLI_VERSION,
37980
38507
  runInteractive
37981
38508
  };
37982
- //# sourceMappingURL=cli-main-JQXCDZEA.js.map
38509
+ //# sourceMappingURL=cli-main-GU3YZLMO.js.map