@wrongstack/cli 0.295.0 → 0.295.1

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 (134) hide show
  1. package/dist/auth-menu/auth-menu-audit.d.ts +1 -1
  2. package/dist/auth-menu/auth-menu-audit.d.ts.map +1 -1
  3. package/dist/auth-menu/local.d.ts +1 -1
  4. package/dist/auth-menu/local.d.ts.map +1 -1
  5. package/dist/auth-menu/shared.d.ts +1 -1
  6. package/dist/auth-menu/shared.d.ts.map +1 -1
  7. package/dist/boot/launch-menu.d.ts +1 -1
  8. package/dist/boot/launch-menu.d.ts.map +1 -1
  9. package/dist/boot/system-prompt-builder.d.ts +1 -1
  10. package/dist/boot/system-prompt-builder.d.ts.map +1 -1
  11. package/dist/boot/tui-coordinator-setup.d.ts +2 -2
  12. package/dist/boot/tui-coordinator-setup.d.ts.map +1 -1
  13. package/dist/boot/tui-project-picker-callback.d.ts +1 -1
  14. package/dist/boot/tui-project-picker-callback.d.ts.map +1 -1
  15. package/dist/boot/tui-project-switch.d.ts +2 -2
  16. package/dist/boot/tui-project-switch.d.ts.map +1 -1
  17. package/dist/boot/tui-settings-adapter.d.ts +2 -2
  18. package/dist/boot/tui-settings-adapter.d.ts.map +1 -1
  19. package/dist/boot-config.d.ts +2 -2
  20. package/dist/boot-config.d.ts.map +1 -1
  21. package/dist/boot.d.ts +1 -1
  22. package/dist/boot.d.ts.map +1 -1
  23. package/dist/cli-eternal-flag.d.ts +2 -2
  24. package/dist/cli-eternal-flag.d.ts.map +1 -1
  25. package/dist/cli-main.d.ts.map +1 -1
  26. package/dist/fleet/routing.d.ts +1 -1
  27. package/dist/fleet/routing.d.ts.map +1 -1
  28. package/dist/fleet/status-broadcast.d.ts.map +1 -1
  29. package/dist/goal-host.d.ts +2 -2
  30. package/dist/goal-host.d.ts.map +1 -1
  31. package/dist/hq-publisher.d.ts +2 -2
  32. package/dist/hq-publisher.d.ts.map +1 -1
  33. package/dist/hq-server/auth-state.d.ts +1 -1
  34. package/dist/hq-server/auth-state.d.ts.map +1 -1
  35. package/dist/hq-server/routes/system-handlers.d.ts +16 -0
  36. package/dist/hq-server/routes/system-handlers.d.ts.map +1 -0
  37. package/dist/hq-server/routes.d.ts +5 -5
  38. package/dist/hq-server/routes.d.ts.map +1 -1
  39. package/dist/hq-server/ws.d.ts +1 -1
  40. package/dist/hq-server/ws.d.ts.map +1 -1
  41. package/dist/index.js +558 -160
  42. package/dist/index.js.map +4 -4
  43. package/dist/input-reader.d.ts +1 -1
  44. package/dist/input-reader.d.ts.map +1 -1
  45. package/dist/kanban-hq-sync.d.ts.map +1 -1
  46. package/dist/mcp-serve.d.ts +1 -1
  47. package/dist/mcp-serve.d.ts.map +1 -1
  48. package/dist/next-task-predictor.d.ts +1 -1
  49. package/dist/next-task-predictor.d.ts.map +1 -1
  50. package/dist/plugin-management.d.ts +1 -1
  51. package/dist/plugin-management.d.ts.map +1 -1
  52. package/dist/repl.d.ts.map +1 -1
  53. package/dist/services/dispatch-classifier.d.ts +1 -1
  54. package/dist/services/dispatch-classifier.d.ts.map +1 -1
  55. package/dist/services/project-facts.d.ts.map +1 -1
  56. package/dist/services/sdd/task-manager.d.ts +1 -1
  57. package/dist/services/sdd/task-manager.d.ts.map +1 -1
  58. package/dist/session-event-wiring.d.ts +3 -0
  59. package/dist/session-event-wiring.d.ts.map +1 -1
  60. package/dist/settings-menu.d.ts +1 -1
  61. package/dist/settings-menu.d.ts.map +1 -1
  62. package/dist/slash-commands/agent-improve.d.ts +6 -5
  63. package/dist/slash-commands/agent-improve.d.ts.map +1 -1
  64. package/dist/slash-commands/brain.d.ts.map +1 -1
  65. package/dist/slash-commands/btw.d.ts +1 -1
  66. package/dist/slash-commands/btw.d.ts.map +1 -1
  67. package/dist/slash-commands/clear.d.ts +1 -1
  68. package/dist/slash-commands/clear.d.ts.map +1 -1
  69. package/dist/slash-commands/goal-refiner.d.ts +2 -2
  70. package/dist/slash-commands/goal-refiner.d.ts.map +1 -1
  71. package/dist/slash-commands/interrupt.d.ts +1 -1
  72. package/dist/slash-commands/interrupt.d.ts.map +1 -1
  73. package/dist/slash-commands/kanban.d.ts +1 -1
  74. package/dist/slash-commands/kanban.d.ts.map +1 -1
  75. package/dist/slash-commands/mailbox.d.ts +1 -1
  76. package/dist/slash-commands/mailbox.d.ts.map +1 -1
  77. package/dist/slash-commands/refiner.d.ts +1 -1
  78. package/dist/slash-commands/refiner.d.ts.map +1 -1
  79. package/dist/slash-commands/sdd.d.ts +1 -1
  80. package/dist/slash-commands/sdd.d.ts.map +1 -1
  81. package/dist/slash-commands/tool.d.ts +1 -1
  82. package/dist/slash-commands/tool.d.ts.map +1 -1
  83. package/dist/slash-commands/working-dir.d.ts +1 -1
  84. package/dist/slash-commands/working-dir.d.ts.map +1 -1
  85. package/dist/subcommands/handlers/chronicle.d.ts.map +1 -1
  86. package/dist/webui-server/connection-handler.d.ts +2 -1
  87. package/dist/webui-server/connection-handler.d.ts.map +1 -1
  88. package/dist/webui-server/kanban-host-adapter.d.ts +4 -4
  89. package/dist/webui-server/kanban-host-adapter.d.ts.map +1 -1
  90. package/dist/webui-server/prefs-seeding.d.ts.map +1 -1
  91. package/dist/webui-server/terminal-log-view.d.ts +30 -0
  92. package/dist/webui-server/terminal-log-view.d.ts.map +1 -0
  93. package/dist/webui-server.d.ts.map +1 -1
  94. package/dist/wiring/brain-and-orchestration.d.ts +2 -2
  95. package/dist/wiring/brain-and-orchestration.d.ts.map +1 -1
  96. package/dist/wiring/codebase-index.d.ts.map +1 -1
  97. package/dist/wiring/command-host-adapters.d.ts +2 -2
  98. package/dist/wiring/command-host-adapters.d.ts.map +1 -1
  99. package/dist/wiring/command-host-state.d.ts +3 -3
  100. package/dist/wiring/command-host-state.d.ts.map +1 -1
  101. package/dist/wiring/dep-watcher.d.ts +1 -1
  102. package/dist/wiring/dep-watcher.d.ts.map +1 -1
  103. package/dist/wiring/director-setup.d.ts +4 -4
  104. package/dist/wiring/director-setup.d.ts.map +1 -1
  105. package/dist/wiring/eternal-command-handlers.d.ts +5 -5
  106. package/dist/wiring/eternal-command-handlers.d.ts.map +1 -1
  107. package/dist/wiring/fleet-command-handlers.d.ts +2 -2
  108. package/dist/wiring/fleet-command-handlers.d.ts.map +1 -1
  109. package/dist/wiring/hq-telemetry.d.ts +4 -4
  110. package/dist/wiring/hq-telemetry.d.ts.map +1 -1
  111. package/dist/wiring/lifecycle-plugins.d.ts +5 -5
  112. package/dist/wiring/lifecycle-plugins.d.ts.map +1 -1
  113. package/dist/wiring/pipeline.d.ts +2 -2
  114. package/dist/wiring/pipeline.d.ts.map +1 -1
  115. package/dist/wiring/provider-runtime-setup.d.ts +5 -5
  116. package/dist/wiring/provider-runtime-setup.d.ts.map +1 -1
  117. package/dist/wiring/provider-status.d.ts +3 -3
  118. package/dist/wiring/provider-status.d.ts.map +1 -1
  119. package/dist/wiring/provider-utility-tools.d.ts +4 -4
  120. package/dist/wiring/provider-utility-tools.d.ts.map +1 -1
  121. package/dist/wiring/replay.d.ts +2 -2
  122. package/dist/wiring/replay.d.ts.map +1 -1
  123. package/dist/wiring/runtime-controller-deps.d.ts +3 -3
  124. package/dist/wiring/runtime-controller-deps.d.ts.map +1 -1
  125. package/dist/wiring/runtime-dispatch-state.d.ts +5 -5
  126. package/dist/wiring/runtime-dispatch-state.d.ts.map +1 -1
  127. package/dist/wiring/runtime-picker-deps.d.ts +3 -3
  128. package/dist/wiring/runtime-picker-deps.d.ts.map +1 -1
  129. package/dist/wiring/sdd-handlers.d.ts.map +1 -1
  130. package/dist/wiring/session-command-handlers.d.ts +5 -5
  131. package/dist/wiring/session-command-handlers.d.ts.map +1 -1
  132. package/dist/wiring/tools.d.ts +2 -2
  133. package/dist/wiring/tools.d.ts.map +1 -1
  134. package/package.json +21 -21
package/dist/index.js CHANGED
@@ -1698,7 +1698,7 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
1698
1698
  if (ws.readyState === WebSocket2.OPEN) {
1699
1699
  ws.send(JSON.stringify({ type: "hq.kanban_snapshot", payload: payload2 }));
1700
1700
  }
1701
- });
1701
+ }).catch(() => void 0);
1702
1702
  }
1703
1703
  const event = {
1704
1704
  id: randomUUID2(),
@@ -1814,7 +1814,16 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
1814
1814
  const payload = event.payload;
1815
1815
  if (payload.projectId !== client.projectId || persistence === void 0) return;
1816
1816
  void persistence.kanban.merge(payload).then((merged) => {
1817
- const message = JSON.stringify({ type: "hq.kanban_snapshot", payload: merged });
1817
+ const touched = /* @__PURE__ */ new Set();
1818
+ for (const record of payload.boards) touched.add(record.boardId);
1819
+ for (const record of payload.tombstones) touched.add(record.boardId);
1820
+ const delta = {
1821
+ projectId: merged.projectId,
1822
+ generatedAt: merged.generatedAt,
1823
+ boards: merged.boards.filter((record) => touched.has(record.boardId)),
1824
+ tombstones: merged.tombstones.filter((record) => touched.has(record.boardId))
1825
+ };
1826
+ const message = JSON.stringify({ type: "hq.kanban_snapshot", payload: delta });
1818
1827
  for (const peer of clients.values()) {
1819
1828
  if (peer.projectId === client.projectId && peer.ws.readyState === WebSocket2.OPEN) {
1820
1829
  peer.ws.send(message);
@@ -2123,6 +2132,98 @@ var init_trust_boundary = __esm({
2123
2132
  }
2124
2133
  });
2125
2134
 
2135
+ // src/hq-server/routes/system-handlers.ts
2136
+ async function handleApiSystemUpdate(res) {
2137
+ const [{ checkForUpdate: checkForUpdate2 }, { detectUpdatePackageName: detectUpdatePackageName2 }] = await Promise.all([
2138
+ Promise.resolve().then(() => (init_update_check(), update_check_exports)),
2139
+ Promise.resolve().then(() => (init_update(), update_exports))
2140
+ ]);
2141
+ const packageName = detectUpdatePackageName2();
2142
+ const info = await checkForUpdate2({ packageName });
2143
+ res.writeHead(200, {
2144
+ "Content-Type": "application/json",
2145
+ "Cache-Control": "no-store"
2146
+ });
2147
+ res.end(
2148
+ JSON.stringify({
2149
+ ...info,
2150
+ packageName,
2151
+ command: "wstack update"
2152
+ })
2153
+ );
2154
+ }
2155
+ async function handleApiSystemHealth(res, clients, persistence, eventLog) {
2156
+ const now = Date.now();
2157
+ const eventCount = eventLog.length;
2158
+ let timeseriesStoreStatus = "unknown";
2159
+ let kanbanStoreStatus = "unknown";
2160
+ try {
2161
+ const tsOk = await persistence.timeseries.read(now - 3e5);
2162
+ timeseriesStoreStatus = Array.isArray(tsOk) ? "ok" : "degraded";
2163
+ } catch {
2164
+ timeseriesStoreStatus = "degraded";
2165
+ }
2166
+ try {
2167
+ await persistence.kanban.load("__hq_health_probe__");
2168
+ kanbanStoreStatus = "ok";
2169
+ } catch {
2170
+ kanbanStoreStatus = "degraded";
2171
+ }
2172
+ const inMemoryEventLogActive = true;
2173
+ const allOk = inMemoryEventLogActive && timeseriesStoreStatus === "ok" && kanbanStoreStatus === "ok";
2174
+ const clientCount = clients.size;
2175
+ const connectedClients = [...clients.values()].filter(
2176
+ (c) => Date.now() - new Date(c.lastSeenAt).getTime() < 6e4
2177
+ ).length;
2178
+ res.writeHead(200, {
2179
+ "Content-Type": "application/json",
2180
+ "Cache-Control": "no-store"
2181
+ });
2182
+ res.end(
2183
+ JSON.stringify({
2184
+ status: allOk ? "healthy" : "degraded",
2185
+ uptime: {
2186
+ serverTime: (/* @__PURE__ */ new Date()).toISOString(),
2187
+ eventLogSize: eventCount
2188
+ },
2189
+ stores: {
2190
+ events: "ok",
2191
+ timeseries: timeseriesStoreStatus,
2192
+ kanban: kanbanStoreStatus
2193
+ },
2194
+ connections: {
2195
+ total: clientCount,
2196
+ active: connectedClients,
2197
+ stale: clientCount - connectedClients
2198
+ }
2199
+ })
2200
+ );
2201
+ }
2202
+ async function handleApiCommandsAudit(req2, res, auditLog) {
2203
+ const url = new URL(req2.url ?? "/", "http://localhost");
2204
+ const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
2205
+ const limit = Math.min(1e3, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 200));
2206
+ res.writeHead(200, { "Content-Type": "application/json" });
2207
+ res.end(JSON.stringify({ commands: auditLog.recent(limit) }));
2208
+ }
2209
+ async function handleApiAlerts(req2, res, alertEngine) {
2210
+ const url = new URL(req2.url ?? "/", "http://localhost");
2211
+ const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "100", 10);
2212
+ const limit = Math.min(500, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 100));
2213
+ res.writeHead(200, { "Content-Type": "application/json" });
2214
+ res.end(
2215
+ JSON.stringify({
2216
+ active: alertEngine.activeAlerts(),
2217
+ history: alertEngine.recentAlerts(limit)
2218
+ })
2219
+ );
2220
+ }
2221
+ var init_system_handlers = __esm({
2222
+ "src/hq-server/routes/system-handlers.ts"() {
2223
+ "use strict";
2224
+ }
2225
+ });
2226
+
2126
2227
  // src/hq-server/routes.ts
2127
2228
  import { randomUUID as randomUUID3 } from "node:crypto";
2128
2229
  import * as path18 from "node:path";
@@ -2142,6 +2243,7 @@ function createHqRouter(deps) {
2142
2243
  loginAttempts,
2143
2244
  clients,
2144
2245
  browsers,
2246
+ eventLog,
2145
2247
  transcripts,
2146
2248
  agentMessages,
2147
2249
  alertEngine,
@@ -2217,6 +2319,10 @@ function createHqRouter(deps) {
2217
2319
  await handleApiSystemUpdate(res);
2218
2320
  return;
2219
2321
  }
2322
+ if (url.pathname === "/api/system/health" && req2.method === "GET") {
2323
+ await handleApiSystemHealth(res, clients, persistence, eventLog);
2324
+ return;
2325
+ }
2220
2326
  const mailboxGatewayMatch = url.pathname.match(
2221
2327
  /^\/api\/projects\/([^/]+)\/mailbox(?:\/(.*))?$/
2222
2328
  );
@@ -2536,25 +2642,6 @@ async function handleApiPassword(req2, res, mutableAuth, sessions, dataDir, secu
2536
2642
  })
2537
2643
  );
2538
2644
  }
2539
- async function handleApiSystemUpdate(res) {
2540
- const [{ checkForUpdate: checkForUpdate2 }, { detectUpdatePackageName: detectUpdatePackageName2 }] = await Promise.all([
2541
- Promise.resolve().then(() => (init_update_check(), update_check_exports)),
2542
- Promise.resolve().then(() => (init_update(), update_exports))
2543
- ]);
2544
- const packageName = detectUpdatePackageName2();
2545
- const info = await checkForUpdate2({ packageName });
2546
- res.writeHead(200, {
2547
- "Content-Type": "application/json",
2548
- "Cache-Control": "no-store"
2549
- });
2550
- res.end(
2551
- JSON.stringify({
2552
- ...info,
2553
- packageName,
2554
- command: "wstack update"
2555
- })
2556
- );
2557
- }
2558
2645
  async function handleMailboxGateway(_req, res, match, authorizeMailboxGateway, getMailboxGateway, dataDir) {
2559
2646
  const projectId = decodePathSegment2(match[1]);
2560
2647
  if (projectId === null || projectId.length === 0) {
@@ -2906,25 +2993,6 @@ async function handleMailboxAction(_req, res, match, _mutableAuth, _sessions, da
2906
2993
  );
2907
2994
  }
2908
2995
  }
2909
- async function handleApiCommandsAudit(req2, res, auditLog) {
2910
- const url = new URL(req2.url ?? "/", "http://localhost");
2911
- const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
2912
- const limit = Math.min(1e3, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 200));
2913
- res.writeHead(200, { "Content-Type": "application/json" });
2914
- res.end(JSON.stringify({ commands: auditLog.recent(limit) }));
2915
- }
2916
- async function handleApiAlerts(req2, res, alertEngine) {
2917
- const url = new URL(req2.url ?? "/", "http://localhost");
2918
- const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "100", 10);
2919
- const limit = Math.min(500, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 100));
2920
- res.writeHead(200, { "Content-Type": "application/json" });
2921
- res.end(
2922
- JSON.stringify({
2923
- active: alertEngine.activeAlerts(),
2924
- history: alertEngine.recentAlerts(limit)
2925
- })
2926
- );
2927
- }
2928
2996
  async function handleApiSessions(res) {
2929
2997
  const { SessionRegistry } = await import("@wrongstack/core/storage");
2930
2998
  const globalRoot = path18.dirname(resolveHqDataDir2());
@@ -3125,6 +3193,7 @@ var init_routes = __esm({
3125
3193
  init_snapshot();
3126
3194
  init_utils2();
3127
3195
  init_trust_boundary();
3196
+ init_system_handlers();
3128
3197
  setHqSecurityHeaders2 = setHqSecurityHeaders;
3129
3198
  hasTrustedBrowserOrigin2 = hasTrustedBrowserOrigin;
3130
3199
  authenticateBrowserRequest2 = authenticateBrowserRequest;
@@ -3401,10 +3470,10 @@ import * as http from "node:http";
3401
3470
  import * as path20 from "node:path";
3402
3471
  import { WebSocket as WebSocket3, WebSocketServer as WebSocketServer2 } from "ws";
3403
3472
  import { createCompatibilityTrustBoundary } from "@wrongstack/core/security";
3404
- import { createHqPersistence as createHqPersistence2, toAlertMessage, watchHqAuthFile } from "@wrongstack/core/hq";
3473
+ import { createHqPersistence, toAlertMessage, watchHqAuthFile } from "@wrongstack/core/hq";
3405
3474
  import { createMailboxHttpRouter, MAILBOX_HTTP_DEFAULT_MAX_AGE_MS } from "@wrongstack/core/coordination";
3406
3475
  import { HqAlertEngine, HqCommandAuditLog } from "@wrongstack/core/hq";
3407
- import { GlobalMailbox as GlobalMailbox2, MailboxEventEmitter, MailboxHttpRateLimiter as MailboxHttpRateLimiter2 } from "@wrongstack/core/coordination";
3476
+ import { GlobalMailbox, MailboxEventEmitter, MailboxHttpRateLimiter } from "@wrongstack/core/coordination";
3408
3477
  import { HqInsecureExposureError as HqInsecureExposureError2 } from "@wrongstack/core/hq";
3409
3478
  async function startHqServer(options = {}) {
3410
3479
  const { host, port, dataDir, firstRunAuth } = await prepareHqServerStart(options, {
@@ -3443,7 +3512,7 @@ function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
3443
3512
  const transcripts = /* @__PURE__ */ new Map();
3444
3513
  const agentMessages = /* @__PURE__ */ new Map();
3445
3514
  const mailboxGateways = /* @__PURE__ */ new Map();
3446
- const mailboxGatewayRateLimiter = new MailboxHttpRateLimiter2();
3515
+ const mailboxGatewayRateLimiter = new MailboxHttpRateLimiter();
3447
3516
  const mailboxGatewayRateLimitCleanup = setInterval(
3448
3517
  () => mailboxGatewayRateLimiter.cleanup(),
3449
3518
  12e4
@@ -3464,15 +3533,15 @@ function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
3464
3533
  }
3465
3534
  }, SESSION_CLEANUP_INTERVAL_MS);
3466
3535
  sessionCleanupTimer.unref?.();
3467
- const persistence = createHqPersistence2(dataDir);
3536
+ const persistence = createHqPersistence(dataDir);
3468
3537
  const auditLog = new HqCommandAuditLog(1e3, (entry) => persistence.commandLog.append(entry));
3469
3538
  void (async () => {
3470
3539
  const projectsDir = path20.join(path20.dirname(dataDir), "projects");
3471
3540
  const entries = await fs20.readdir(projectsDir, { withFileTypes: true }).catch(() => []);
3472
- const { GlobalMailbox: GlobalMailbox11 } = await import("@wrongstack/core/coordination");
3541
+ const { GlobalMailbox: GlobalMailbox10 } = await import("@wrongstack/core/coordination");
3473
3542
  await Promise.all(
3474
3543
  entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
3475
- const mailbox = new GlobalMailbox11(path20.join(projectsDir, entry.name));
3544
+ const mailbox = new GlobalMailbox10(path20.join(projectsDir, entry.name));
3476
3545
  await Promise.all([mailbox.getAgentStatuses(), mailbox.purgeClients()]);
3477
3546
  })
3478
3547
  );
@@ -3499,7 +3568,7 @@ function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
3499
3568
  const existing = mailboxGateways.get(projectDir);
3500
3569
  if (existing) return existing;
3501
3570
  const eventEmitter = new MailboxEventEmitter();
3502
- const mailbox = new GlobalMailbox2(projectDir, void 0, void 0, eventEmitter);
3571
+ const mailbox = new GlobalMailbox(projectDir, void 0, void 0, eventEmitter);
3503
3572
  const router = createMailboxHttpRouter({
3504
3573
  mailbox,
3505
3574
  eventEmitter,
@@ -3526,8 +3595,8 @@ function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
3526
3595
  },
3527
3596
  onPersist: (alert) => persistence.alertLog.append(alert)
3528
3597
  });
3529
- void persistence.eventLog.hydrate();
3530
- void persistence.timeseries.load();
3598
+ void persistence.eventLog.hydrate().catch(() => void 0);
3599
+ void persistence.timeseries.load().catch(() => void 0);
3531
3600
  persistence.eventLog.recent(MAX_EVENT_LOG2).then((prior) => {
3532
3601
  for (let i = prior.length - 1; i >= 0; i--) eventLog.push(prior[i]);
3533
3602
  }).catch(() => {
@@ -3850,6 +3919,12 @@ import {
3850
3919
  readBoard,
3851
3920
  writeBoard
3852
3921
  } from "@wrongstack/kanban";
3922
+ function warnSyncFailure(error) {
3923
+ process.emitWarning(
3924
+ `WrongStack kanban HQ sync failed (best-effort, will retry on next change): ${error instanceof Error ? error.message : String(error)}`,
3925
+ { code: "WRONGSTACK_HQ_KANBAN_SYNC_FAILED" }
3926
+ );
3927
+ }
3853
3928
  function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId2(projectRoot)) {
3854
3929
  const dir = getKanbanDir(projectRoot);
3855
3930
  const statePath = path21.join(dir, SYNC_STATE_FILE);
@@ -3861,6 +3936,8 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId2(projectR
3861
3936
  let applyingRemote = false;
3862
3937
  let stopped = false;
3863
3938
  let operationChain = Promise.resolve();
3939
+ let pendingRemote = null;
3940
+ let remoteApplyQueued = false;
3864
3941
  const runExclusive = (operation) => {
3865
3942
  let resolveResult;
3866
3943
  let rejectResult;
@@ -3877,21 +3954,28 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId2(projectR
3877
3954
  });
3878
3955
  return result;
3879
3956
  };
3880
- const schedulePublish = (boardId) => {
3881
- if (stopped || applyingRemote || publisher === void 0) return;
3882
- if (boardId === null) fullRescanPending = true;
3883
- else pendingBoardIds.add(boardId);
3884
- if (timer !== void 0) clearTimeout(timer);
3957
+ const scheduleNext = () => {
3958
+ if (stopped || timer !== void 0 || applyingRemote) return;
3885
3959
  timer = setTimeout(() => {
3886
3960
  timer = void 0;
3887
3961
  const rescan = fullRescanPending;
3888
3962
  const dirtyBoardIds = [...pendingBoardIds];
3889
3963
  fullRescanPending = false;
3890
3964
  pendingBoardIds.clear();
3891
- void runExclusive(() => publishLocal(false, rescan ? void 0 : dirtyBoardIds));
3965
+ runExclusive(() => publishLocal(false, rescan ? void 0 : dirtyBoardIds)).catch(
3966
+ warnSyncFailure
3967
+ );
3892
3968
  }, 100);
3893
3969
  timer.unref?.();
3894
3970
  };
3971
+ const schedulePublish = (boardId) => {
3972
+ if (stopped || applyingRemote || publisher === void 0) return;
3973
+ if (boardId === null) fullRescanPending = true;
3974
+ else pendingBoardIds.add(boardId);
3975
+ if (timer !== void 0) clearTimeout(timer);
3976
+ timer = void 0;
3977
+ scheduleNext();
3978
+ };
3895
3979
  const publishLocal = async (fullSnapshot, dirtyBoardIds) => {
3896
3980
  const target = publisher;
3897
3981
  if (target === void 0 || stopped) return;
@@ -3901,7 +3985,15 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId2(projectR
3901
3985
  const boards = [];
3902
3986
  const tombstones = [];
3903
3987
  const fullScan = fullSnapshot || dirtyBoardIds === void 0;
3904
- const boardIds = fullScan ? await listBoardIds(projectRoot) : dirtyBoardIds;
3988
+ const allBoardIds = fullScan ? await listBoardIds(projectRoot) : dirtyBoardIds;
3989
+ let boardIds = allBoardIds;
3990
+ if (!fullScan && boardIds.length > MAX_BOARDS_PER_PUBLISH) {
3991
+ for (const id of boardIds.slice(MAX_BOARDS_PER_PUBLISH)) {
3992
+ pendingBoardIds.add(id);
3993
+ }
3994
+ scheduleNext();
3995
+ boardIds = boardIds.slice(0, MAX_BOARDS_PER_PUBLISH);
3996
+ }
3905
3997
  const present = /* @__PURE__ */ new Set();
3906
3998
  const now = (/* @__PURE__ */ new Date()).toISOString();
3907
3999
  for (const boardId of boardIds) {
@@ -3978,6 +4070,7 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId2(projectR
3978
4070
  schedulePublish(null);
3979
4071
  return;
3980
4072
  }
4073
+ if (filename.length > MAX_BOARD_ID_LENGTH + 5) return;
3981
4074
  if (filename === SYNC_STATE_FILE || filename.endsWith(".events.jsonl")) return;
3982
4075
  if (!filename.endsWith(".json")) return;
3983
4076
  const boardId = filename.slice(0, -".json".length);
@@ -4035,14 +4128,27 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId2(projectR
4035
4128
  applyingRemote = false;
4036
4129
  }
4037
4130
  };
4131
+ const handleRemote = (snapshot) => {
4132
+ if (snapshot.projectId !== projectId || stopped) return Promise.resolve();
4133
+ pendingRemote = pendingRemote === null ? snapshot : coalesceRemoteSnapshots(pendingRemote, snapshot);
4134
+ if (remoteApplyQueued) return Promise.resolve();
4135
+ remoteApplyQueued = true;
4136
+ return runExclusive(async () => {
4137
+ remoteApplyQueued = false;
4138
+ const batch = pendingRemote;
4139
+ pendingRemote = null;
4140
+ if (batch !== null) await applyRemote(batch);
4141
+ }).catch(warnSyncFailure);
4142
+ };
4038
4143
  return {
4039
4144
  attachPublisher,
4040
- handleRemote: (snapshot) => runExclusive(() => applyRemote(snapshot)),
4145
+ handleRemote,
4041
4146
  stop: () => {
4042
4147
  stopped = true;
4043
4148
  if (timer !== void 0) clearTimeout(timer);
4044
4149
  pendingBoardIds.clear();
4045
4150
  fullRescanPending = false;
4151
+ pendingRemote = null;
4046
4152
  watcher?.close();
4047
4153
  watcher = void 0;
4048
4154
  publisher = void 0;
@@ -4106,6 +4212,37 @@ function chunkSnapshotPayload(projectId, generatedAt, boards, tombstones) {
4106
4212
  pushCurrent();
4107
4213
  return chunks.length > 0 ? chunks : [emptyPayload()];
4108
4214
  }
4215
+ function coalesceRemoteSnapshots(older, newer) {
4216
+ const byBoardId = /* @__PURE__ */ new Map();
4217
+ const put = (candidate, boardId) => {
4218
+ const existing = byBoardId.get(boardId);
4219
+ if (existing === void 0 || compareVersions(existing.revision, existing.time, candidate.revision, candidate.time) <= 0) {
4220
+ byBoardId.set(boardId, candidate);
4221
+ }
4222
+ };
4223
+ for (const snapshot of [older, newer]) {
4224
+ for (const record of snapshot.boards) {
4225
+ put({ kind: "board", revision: record.revision, time: record.updatedAt, record }, record.boardId);
4226
+ }
4227
+ for (const record of snapshot.tombstones) {
4228
+ put(
4229
+ { kind: "tombstone", revision: record.revision, time: record.deletedAt, record },
4230
+ record.boardId
4231
+ );
4232
+ }
4233
+ }
4234
+ const merged = {
4235
+ projectId: newer.projectId,
4236
+ generatedAt: newer.generatedAt,
4237
+ boards: [],
4238
+ tombstones: []
4239
+ };
4240
+ for (const entry of byBoardId.values()) {
4241
+ if (entry.kind === "board") merged.boards.push(entry.record);
4242
+ else merged.tombstones.push(entry.record);
4243
+ }
4244
+ return merged;
4245
+ }
4109
4246
  function compareVersions(aRevision, aTime, bRevision, bTime) {
4110
4247
  if (aRevision !== bRevision) return aRevision - bRevision;
4111
4248
  return aTime.localeCompare(bTime);
@@ -4139,13 +4276,15 @@ async function writeState(filePath, state) {
4139
4276
  mode: 384
4140
4277
  });
4141
4278
  }
4142
- var SYNC_STATE_FILE, MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES, KANBAN_TOMBSTONE_RETENTION_MS;
4279
+ var SYNC_STATE_FILE, MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES, KANBAN_TOMBSTONE_RETENTION_MS, MAX_BOARDS_PER_PUBLISH, MAX_BOARD_ID_LENGTH;
4143
4280
  var init_kanban_hq_sync = __esm({
4144
4281
  "src/kanban-hq-sync.ts"() {
4145
4282
  "use strict";
4146
4283
  SYNC_STATE_FILE = ".hq-sync.json";
4147
4284
  MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES = 512 * 1024;
4148
4285
  KANBAN_TOMBSTONE_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
4286
+ MAX_BOARDS_PER_PUBLISH = 50;
4287
+ MAX_BOARD_ID_LENGTH = 128;
4149
4288
  }
4150
4289
  });
4151
4290
 
@@ -4202,7 +4341,14 @@ function startCliHqConnection(options) {
4202
4341
  kanbanSync = createKanbanHqSync(options.projectRoot, next.project.projectId);
4203
4342
  }
4204
4343
  next.connect();
4205
- if (kanbanSync !== void 0) void kanbanSync.attachPublisher(next);
4344
+ if (kanbanSync !== void 0) {
4345
+ kanbanSync.attachPublisher(next).catch((error) => {
4346
+ process.emitWarning(
4347
+ `WrongStack kanban HQ sync attach failed (best-effort, retried on reconnect): ${error instanceof Error ? error.message : String(error)}`,
4348
+ { code: "WRONGSTACK_HQ_KANBAN_SYNC_FAILED" }
4349
+ );
4350
+ });
4351
+ }
4206
4352
  options.onConnect?.(next);
4207
4353
  };
4208
4354
  tryConnect();
@@ -5089,7 +5235,7 @@ import {
5089
5235
  createConnectionLifecycle,
5090
5236
  verifyClient as verifyWsClient
5091
5237
  } from "@wrongstack/webui-server";
5092
- import { decodeProtocolFrame } from "@wrongstack/webui-server/protocol";
5238
+ import { buildReplayPayload, decodeProtocolFrame } from "@wrongstack/webui-server/protocol";
5093
5239
  function createConnectionHandler(deps) {
5094
5240
  const clientHandlers = [
5095
5241
  deps.goalHandler,
@@ -5157,13 +5303,7 @@ function createConnectionHandler(deps) {
5157
5303
  const payload = { ...await deps.buildSessionStartPayload({}, deps.needsSetup) };
5158
5304
  try {
5159
5305
  const replay = await deps.loadReplay?.();
5160
- if (replay?.messages.length) {
5161
- payload["replayMessages"] = replay.messages.length > REPLAY_MESSAGE_CAP ? replay.messages.slice(-REPLAY_MESSAGE_CAP) : replay.messages;
5162
- }
5163
- const usage3 = replay?.usage;
5164
- if (usage3 && usage3.input + usage3.output + (usage3.cacheRead ?? 0) + (usage3.cacheWrite ?? 0) > 0) {
5165
- payload["replayUsage"] = usage3;
5166
- }
5306
+ if (replay) Object.assign(payload, buildReplayPayload(replay));
5167
5307
  } catch {
5168
5308
  console.debug("[WebUI] Failed to load replay");
5169
5309
  }
@@ -5177,11 +5317,9 @@ function createConnectionHandler(deps) {
5177
5317
  }
5178
5318
  });
5179
5319
  }
5180
- var REPLAY_MESSAGE_CAP;
5181
5320
  var init_connection_handler = __esm({
5182
5321
  "src/webui-server/connection-handler.ts"() {
5183
5322
  "use strict";
5184
- REPLAY_MESSAGE_CAP = 2e3;
5185
5323
  }
5186
5324
  });
5187
5325
 
@@ -5421,8 +5559,7 @@ async function seedConfigToMeta(opts) {
5421
5559
  return;
5422
5560
  }
5423
5561
  }
5424
- const meta = opts.agent.ctx.meta ??= {};
5425
- seedContextMeta(config, { meta });
5562
+ seedContextMeta(config, { meta: opts.agent.ctx.meta });
5426
5563
  }
5427
5564
  function createPrefsSeeding(opts) {
5428
5565
  const writeLock = { lock: Promise.resolve() };
@@ -5709,6 +5846,76 @@ var init_stream_coalescer = __esm({
5709
5846
  }
5710
5847
  });
5711
5848
 
5849
+ // src/webui-server/terminal-log-view.ts
5850
+ import { format } from "node:util";
5851
+ function startBoundedTerminalLogView(options = {}) {
5852
+ const isTTY = options.isTTY ?? process.stdout.isTTY === true;
5853
+ if (!isTTY) {
5854
+ return { enabled: false, redraw: () => {
5855
+ }, stop: () => {
5856
+ } };
5857
+ }
5858
+ const maxLines = Math.max(1, Math.trunc(options.maxLines ?? DEFAULT_MAX_LINES));
5859
+ const refreshMs = Math.max(100, Math.trunc(options.refreshMs ?? DEFAULT_REFRESH_MS));
5860
+ const target = options.consoleTarget ?? console;
5861
+ const write = options.write ?? ((chunk) => process.stdout.write(chunk));
5862
+ const setIntervalFn = options.setIntervalFn ?? setInterval;
5863
+ const clearIntervalFn = options.clearIntervalFn ?? clearInterval;
5864
+ const lines = [];
5865
+ let stopped = false;
5866
+ const originals = {
5867
+ log: target.log,
5868
+ info: target.info,
5869
+ warn: target.warn,
5870
+ error: target.error,
5871
+ debug: target.debug
5872
+ };
5873
+ const wrappers = {};
5874
+ const remember = (args) => {
5875
+ for (const line of format(...args).split(/\r?\n/u)) {
5876
+ lines.push(line);
5877
+ if (lines.length > maxLines) lines.shift();
5878
+ }
5879
+ };
5880
+ for (const level of Object.keys(originals)) {
5881
+ const original = originals[level];
5882
+ const wrapper = (...args) => {
5883
+ remember(args);
5884
+ original.apply(target, args);
5885
+ };
5886
+ wrappers[level] = wrapper;
5887
+ target[level] = wrapper;
5888
+ }
5889
+ const redraw = () => {
5890
+ if (stopped || lines.length === 0) return;
5891
+ write(`${CLEAR_VISIBLE_TERMINAL}${lines.join("\n")}
5892
+ `);
5893
+ };
5894
+ const timer = setIntervalFn(redraw, refreshMs);
5895
+ timer.unref?.();
5896
+ return {
5897
+ enabled: true,
5898
+ redraw,
5899
+ stop: () => {
5900
+ if (stopped) return;
5901
+ stopped = true;
5902
+ clearIntervalFn(timer);
5903
+ for (const level of Object.keys(originals)) {
5904
+ if (target[level] === wrappers[level]) target[level] = originals[level];
5905
+ }
5906
+ }
5907
+ };
5908
+ }
5909
+ var DEFAULT_MAX_LINES, DEFAULT_REFRESH_MS, CLEAR_VISIBLE_TERMINAL;
5910
+ var init_terminal_log_view = __esm({
5911
+ "src/webui-server/terminal-log-view.ts"() {
5912
+ "use strict";
5913
+ DEFAULT_MAX_LINES = 5;
5914
+ DEFAULT_REFRESH_MS = 5e3;
5915
+ CLEAR_VISIBLE_TERMINAL = "\x1B[2J\x1B[H";
5916
+ }
5917
+ });
5918
+
5712
5919
  // src/webui-server.ts
5713
5920
  var webui_server_exports = {};
5714
5921
  __export(webui_server_exports, {
@@ -6316,6 +6523,7 @@ async function runWebUI(opts) {
6316
6523
  terminalHandler,
6317
6524
  kanbanHostRoutes
6318
6525
  });
6526
+ const terminalLogView = startBoundedTerminalLogView();
6319
6527
  const stopped = new Promise((resolve14) => {
6320
6528
  let listeningAnnounced = false;
6321
6529
  const announceListening = () => {
@@ -6359,7 +6567,7 @@ async function runWebUI(opts) {
6359
6567
  await activeSession.flush().catch(() => void 0);
6360
6568
  if (opts.sessionStore) {
6361
6569
  const data = await opts.sessionStore.load(activeSession.id);
6362
- return { messages: data.messages, usage: data.usage };
6570
+ return { messages: data.messages, events: data.events, usage: data.usage };
6363
6571
  }
6364
6572
  const usage3 = opts.agent.ctx.tokenCounter.total();
6365
6573
  return { messages: opts.agent.ctx.messages, usage: usage3 };
@@ -6405,7 +6613,11 @@ async function runWebUI(opts) {
6405
6613
  wss,
6406
6614
  pid: process.pid,
6407
6615
  registryBaseDir,
6408
- onStopped: resolve14
6616
+ onStopped: () => {
6617
+ terminalLogView.redraw();
6618
+ terminalLogView.stop();
6619
+ resolve14();
6620
+ }
6409
6621
  });
6410
6622
  registerWebuiSignalHandlers(signalShutdown);
6411
6623
  });
@@ -6416,6 +6628,8 @@ async function runWebUI(opts) {
6416
6628
  }
6417
6629
  function shutdown() {
6418
6630
  console.log("[WebUI] Shutting down...");
6631
+ terminalLogView.redraw();
6632
+ terminalLogView.stop();
6419
6633
  credentialWatcherClose?.();
6420
6634
  flushAllStreamBuffers();
6421
6635
  worktreeHandler.dispose();
@@ -6441,7 +6655,7 @@ async function runWebUI(opts) {
6441
6655
  function sendResult(ws, success, message) {
6442
6656
  send(ws, { type: "key.operation_result", payload: { success, message } });
6443
6657
  }
6444
- return stopped;
6658
+ return stopped.finally(() => terminalLogView.stop());
6445
6659
  }
6446
6660
  var init_webui_server = __esm({
6447
6661
  "src/webui-server.ts"() {
@@ -6459,6 +6673,7 @@ var init_webui_server = __esm({
6459
6673
  init_setup_events();
6460
6674
  init_static_serve();
6461
6675
  init_stream_coalescer();
6676
+ init_terminal_log_view();
6462
6677
  }
6463
6678
  });
6464
6679
 
@@ -7842,9 +8057,9 @@ var init_plugins = __esm({
7842
8057
 
7843
8058
  // src/cli-main.ts
7844
8059
  import { execFile as execFile3 } from "node:child_process";
7845
- import * as fs56 from "node:fs/promises";
8060
+ import * as fs55 from "node:fs/promises";
7846
8061
  import * as path71 from "node:path";
7847
- import { attachDepWatcherBridge, GlobalMailbox as GlobalMailbox10, mailboxSessionTag as mailboxSessionTag3 } from "@wrongstack/core/coordination";
8062
+ import { attachDepWatcherBridge, GlobalMailbox as GlobalMailbox9, mailboxSessionTag as mailboxSessionTag3 } from "@wrongstack/core/coordination";
7848
8063
  import { createFallbackManageTools, createPluginManagerTool } from "@wrongstack/core/tools";
7849
8064
  import { FLEET_ROSTER as FLEET_ROSTER4 } from "@wrongstack/core/coordination";
7850
8065
  import { gatedEnhancerReasoning } from "@wrongstack/core/execution";
@@ -9559,10 +9774,10 @@ ${color6.dim(` ${CODEX_LEGACY_NOTE}`)}
9559
9774
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2.homedir()) {
9560
9775
  try {
9561
9776
  const { atomicWrite: atomicWrite22 } = await import("@wrongstack/core/utils");
9562
- const fs57 = await import("node:fs/promises");
9777
+ const fs56 = await import("node:fs/promises");
9563
9778
  let existing = {};
9564
9779
  try {
9565
- const raw = await fs57.readFile(configPath2, "utf8");
9780
+ const raw = await fs56.readFile(configPath2, "utf8");
9566
9781
  existing = JSON.parse(raw);
9567
9782
  } catch {
9568
9783
  }
@@ -13190,6 +13405,7 @@ import { color as color14, toErrorMessage as toErrorMessage4 } from "@wrongstack
13190
13405
  // src/services/project-facts.ts
13191
13406
  import * as fs8 from "node:fs/promises";
13192
13407
  import * as path6 from "node:path";
13408
+ import { DEFAULT_WALK_IGNORE_DIRS } from "@wrongstack/core/utils";
13193
13409
  import {
13194
13410
  detectLanguageWorkspaces,
13195
13411
  languageProfileRegistry,
@@ -13307,26 +13523,14 @@ var EXT_LANG = {
13307
13523
  ".svelte": "Svelte"
13308
13524
  };
13309
13525
  var SCAN_IGNORE_DIRS = /* @__PURE__ */ new Set([
13310
- ".git",
13311
- "node_modules",
13312
- "dist",
13313
- "build",
13526
+ ...DEFAULT_WALK_IGNORE_DIRS,
13314
13527
  "out",
13315
- "target",
13316
13528
  "vendor",
13317
- ".venv",
13318
- "venv",
13319
13529
  "env",
13320
- "__pycache__",
13321
- ".next",
13322
- ".nuxt",
13323
- ".cache",
13324
- "coverage",
13325
13530
  ".wrongstack",
13326
13531
  ".idea",
13327
13532
  ".vscode",
13328
13533
  "obj",
13329
- ".gradle",
13330
13534
  ".dart_tool",
13331
13535
  "Pods"
13332
13536
  ]);
@@ -15331,14 +15535,14 @@ var auditCmd = async (args, deps) => {
15331
15535
  return verify.ok ? 0 : 1;
15332
15536
  };
15333
15537
  async function listAudits(log, dir, deps) {
15334
- const fs57 = await import("node:fs/promises");
15538
+ const fs56 = await import("node:fs/promises");
15335
15539
  const path72 = await import("node:path");
15336
15540
  const out = [];
15337
15541
  let foundRoot = true;
15338
15542
  const scan = async (scanDir, prefix, depth) => {
15339
15543
  let entries;
15340
15544
  try {
15341
- entries = await fs57.readdir(scanDir, { withFileTypes: true });
15545
+ entries = await fs56.readdir(scanDir, { withFileTypes: true });
15342
15546
  } catch {
15343
15547
  if (depth === 0) foundRoot = false;
15344
15548
  return;
@@ -16140,7 +16344,7 @@ async function benchList(_args, deps) {
16140
16344
  }
16141
16345
 
16142
16346
  // src/subcommands/handlers/chronicle.ts
16143
- import { ChronicleJournal, ChronicleQueryEngine, resolveChronicleRuntimeLocation } from "@wrongstack/core/chronicle";
16347
+ import { ChronicleJournal, ChronicleMetricsStore, ChronicleQueryEngine, resolveChronicleRuntimeLocation } from "@wrongstack/core/chronicle";
16144
16348
  import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
16145
16349
  import { statSync } from "node:fs";
16146
16350
  import * as path13 from "node:path";
@@ -16214,6 +16418,40 @@ var chronicleCmd = async (args, deps) => {
16214
16418
  }
16215
16419
  return result2.errors.length > 0 ? 1 : 0;
16216
16420
  }
16421
+ if (operation === "metrics") {
16422
+ const hasView = Boolean(args[1] && !args[1].includes("="));
16423
+ const view = hasView ? args[1] : "summary";
16424
+ const filters = parseQuery(args.slice(hasView ? 2 : 1));
16425
+ let store;
16426
+ try {
16427
+ store = ChronicleMetricsStore.open(path13.join(paths.projectDir, "chronicle"));
16428
+ } catch (error) {
16429
+ deps.renderer.writeError(`${error instanceof Error ? error.message : String(error)}
16430
+ `);
16431
+ return 1;
16432
+ }
16433
+ try {
16434
+ const refreshed = await store.refresh();
16435
+ const payload = view === "providers" ? store.providerDaily({ ...filters.from ? { from: filters.from } : {}, ...filters.to ? { to: filters.to } : {} }) : view === "tasks" ? store.taskOutcomes({
16436
+ ...filters.sessionId ? { sessionId: filters.sessionId } : {},
16437
+ ...filters.limit ? { limit: filters.limit } : {}
16438
+ }) : view === "files" ? store.fileLineage({
16439
+ ...filters.path ? { path: filters.path } : {},
16440
+ ...filters.taskId ? { taskId: filters.taskId } : {},
16441
+ ...filters.sessionId ? { sessionId: filters.sessionId } : {},
16442
+ ...filters.limit ? { limit: filters.limit } : {}
16443
+ }) : view === "summary" ? store.summary() : void 0;
16444
+ if (payload === void 0) {
16445
+ deps.renderer.writeError(`Unknown metrics view: ${view}. Use providers|tasks|files|summary.
16446
+ `);
16447
+ return 1;
16448
+ }
16449
+ deps.renderer.write(JSON.stringify({ refreshed, [view]: payload }, null, 2) + "\n");
16450
+ return 0;
16451
+ } finally {
16452
+ store.close();
16453
+ }
16454
+ }
16217
16455
  if (operation === "facet") {
16218
16456
  const engine2 = await ChronicleQueryEngine.fromDirectory(path13.join(paths.projectDir, "chronicle"));
16219
16457
  const field = args[1];
@@ -16247,9 +16485,15 @@ function parseQuery(tokens) {
16247
16485
  else if (key === "resourceKind") query.resourceKind = value;
16248
16486
  else if (key === "line" || key === "limit") query[key] = Number(value);
16249
16487
  else if (key === "order" && (value === "asc" || value === "desc")) query.order = value;
16250
- else if (key.startsWith("tag.")) (query.tags ??= {})[key.slice(4)] = value;
16251
- else if (key.startsWith("attr.")) (query.attributes ??= {})[key.slice(5)] = parseValue(value);
16252
- else if (isStringQueryKey(key)) query[key] = value;
16488
+ else if (key.startsWith("tag.")) {
16489
+ const tags = query.tags ?? {};
16490
+ tags[key.slice(4)] = value;
16491
+ query.tags = tags;
16492
+ } else if (key.startsWith("attr.")) {
16493
+ const attributes = query.attributes ?? {};
16494
+ attributes[key.slice(5)] = parseValue(value);
16495
+ query.attributes = attributes;
16496
+ } else if (isStringQueryKey(key)) query[key] = value;
16253
16497
  }
16254
16498
  return query;
16255
16499
  }
@@ -16281,7 +16525,7 @@ function parseValue(value) {
16281
16525
  }
16282
16526
  }
16283
16527
  function usage() {
16284
- return "Usage:\n wstack chronicle query [field=value ...]\n wstack chronicle facet <field> [field=value ...]\n wstack chronicle prune [--days N] [--dry-run]\n\nExamples:\n wstack chronicle query path=src/app.ts line=42\n wstack chronicle query providerId=openai outcome=failure\n wstack chronicle query eventType=tool.executed sessionId=<id> limit=50\n wstack chronicle query attr.tool.name=read\n wstack chronicle facet modelId from=2026-07-01T00:00:00Z\n wstack chronicle prune --days 7 --dry-run\n wstack chronicle prune --days 30\n";
16528
+ return "Usage:\n wstack chronicle query [field=value ...]\n wstack chronicle facet <field> [field=value ...]\n wstack chronicle metrics [providers|tasks|files|summary] [field=value ...]\n wstack chronicle prune [--days N] [--dry-run]\n\nExamples:\n wstack chronicle query path=src/app.ts line=42\n wstack chronicle query providerId=openai outcome=failure\n wstack chronicle query eventType=tool.executed sessionId=<id> limit=50\n wstack chronicle query attr.tool.name=read\n wstack chronicle facet modelId from=2026-07-01T00:00:00Z\n wstack chronicle metrics providers from=2026-07-01\n wstack chronicle metrics files path=src/app.ts\n wstack chronicle metrics tasks limit=20\n wstack chronicle prune --days 7 --dry-run\n wstack chronicle prune --days 30\n";
16285
16529
  }
16286
16530
  function formatBytes(bytes) {
16287
16531
  if (bytes < 1024) return `${bytes} B`;
@@ -16469,7 +16713,7 @@ var exportCmd = async (args, deps) => {
16469
16713
  deps.renderer.writeError("No session store configured.");
16470
16714
  return 1;
16471
16715
  }
16472
- let format = "markdown";
16716
+ let format2 = "markdown";
16473
16717
  let output;
16474
16718
  let includeTools = true;
16475
16719
  let includeDiagnostics = true;
@@ -16482,7 +16726,7 @@ var exportCmd = async (args, deps) => {
16482
16726
  deps.renderer.writeError(`Unknown --format ${v}. Use markdown, json, or text.`);
16483
16727
  return 1;
16484
16728
  }
16485
- format = v;
16729
+ format2 = v;
16486
16730
  } else if (a === "--out" || a === "-o") output = args[++i];
16487
16731
  else if (a === "--no-tools") includeTools = false;
16488
16732
  else if (a === "--no-diagnostics") includeDiagnostics = false;
@@ -16500,7 +16744,7 @@ var exportCmd = async (args, deps) => {
16500
16744
  const reader = new DefaultSessionReader({ store: deps.sessionStore });
16501
16745
  let rendered;
16502
16746
  try {
16503
- rendered = await reader.export(sessionId, { format, includeTools, includeDiagnostics });
16747
+ rendered = await reader.export(sessionId, { format: format2, includeTools, includeDiagnostics });
16504
16748
  } catch (err) {
16505
16749
  deps.renderer.writeError(`Export failed: ${toErrorMessage7(err)}`);
16506
16750
  return 1;
@@ -16890,7 +17134,7 @@ function resolveTokenTtl(args, deps) {
16890
17134
  if (inline) {
16891
17135
  return parseTokenTtlValue(inline.slice("--ttl=".length));
16892
17136
  }
16893
- const idx = args.findIndex((a) => a === "--ttl");
17137
+ const idx = args.indexOf("--ttl");
16894
17138
  if (idx >= 0) {
16895
17139
  const raw = args[idx + 1];
16896
17140
  if (raw === void 0 || raw.startsWith("-")) {
@@ -17081,7 +17325,7 @@ var initCmd = async (_args, deps) => {
17081
17325
  init_hq_publisher();
17082
17326
  import { createServer as createServer3 } from "node:http";
17083
17327
  import * as path22 from "node:path";
17084
- import { authorizeMailboxBearerToken, GlobalMailbox as GlobalMailbox3, MailboxEventEmitter as MailboxEventEmitter2, MailboxHttpRateLimiter as MailboxHttpRateLimiter3, resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core/coordination";
17328
+ import { authorizeMailboxBearerToken, GlobalMailbox as GlobalMailbox2, MailboxEventEmitter as MailboxEventEmitter2, MailboxHttpRateLimiter as MailboxHttpRateLimiter2, resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core/coordination";
17085
17329
  import { createMailboxHttpRouter as createMailboxHttpRouter2, MAILBOX_HTTP_DEFAULT_MAX_AGE_MS as MAILBOX_HTTP_DEFAULT_MAX_AGE_MS2 } from "@wrongstack/core/coordination";
17086
17330
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
17087
17331
  import { acquireOrJoin, finalize, release as release2 } from "@wrongstack/core/coordination";
@@ -17148,13 +17392,13 @@ or stop the conflicting process and retry.
17148
17392
  const tentative = acquireResult.lock;
17149
17393
  const eventEmitter = new MailboxEventEmitter2();
17150
17394
  let hqConnection;
17151
- const mailbox = new GlobalMailbox3(
17395
+ const mailbox = new GlobalMailbox2(
17152
17396
  projectDir,
17153
17397
  void 0,
17154
17398
  () => hqConnection?.getPublisher(),
17155
17399
  eventEmitter
17156
17400
  );
17157
- const rateLimiter = new MailboxHttpRateLimiter3();
17401
+ const rateLimiter = new MailboxHttpRateLimiter2();
17158
17402
  const rateLimitCleanup = setInterval(() => rateLimiter.cleanup(), 12e4);
17159
17403
  rateLimitCleanup.unref?.();
17160
17404
  const router = createMailboxHttpRouter2({
@@ -23779,7 +24023,7 @@ import * as path46 from "node:path";
23779
24023
  import { toErrorMessage as toErrorMessage13 } from "@wrongstack/core/utils";
23780
24024
  import { color as color51, estimateRequestTokensCalibrated, expectDefined as expectDefined11, hasOpenTodos, wstackGlobalRoot as wstackGlobalRoot4 } from "@wrongstack/core/utils";
23781
24025
  import { detectContinueIntent, InputBuilder, resolveContinuation } from "@wrongstack/core/agent";
23782
- import { GlobalMailbox as GlobalMailbox4, resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
24026
+ import { GlobalMailbox as GlobalMailbox3, resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
23783
24027
  import { goalFilePath, loadGoal, summarizeUsage } from "@wrongstack/core/goal";
23784
24028
  import { readClipboardImage, routeImagesForModel } from "@wrongstack/runtime";
23785
24029
  import { parseNextSteps } from "@wrongstack/tools/next-steps";
@@ -23812,7 +24056,8 @@ function clearSuggestions() {
23812
24056
 
23813
24057
  // src/repl.ts
23814
24058
  import {
23815
- createAutoProceedLoopGuard
24059
+ createAutoProceedLoopGuard,
24060
+ GROUNDED_NO_PROGRESS_STEER
23816
24061
  } from "@wrongstack/tools/auto-proceed-loop-guard";
23817
24062
  var DEFAULT_MAX_CONSECUTIVE_AUTO_PROCEED = 50;
23818
24063
  function parseSuggestionsFromOutput(finalText, todos) {
@@ -23893,7 +24138,7 @@ async function runRepl(opts) {
23893
24138
  appConfig: opts.appConfig,
23894
24139
  capabilities: ["telemetry.publish", "mailbox.summary"]
23895
24140
  });
23896
- const clientMailbox = new GlobalMailbox4(projectDir, void 0, () => hqConnection.getPublisher());
24141
+ const clientMailbox = new GlobalMailbox3(projectDir, void 0, () => hqConnection.getPublisher());
23897
24142
  let clientHeartbeat;
23898
24143
  clientMailbox.registerClient({
23899
24144
  clientId,
@@ -24061,6 +24306,7 @@ ${lines.join("\n")}
24061
24306
  }
24062
24307
  if (mode === "auto") {
24063
24308
  let top;
24309
+ let groundedTodo = false;
24064
24310
  if (suggestions.length > 0) {
24065
24311
  const isYolo = opts.getYolo?.() ?? false;
24066
24312
  const autoSuggestions = opts.getAutoSuggestions?.() ?? [];
@@ -24070,6 +24316,7 @@ ${lines.join("\n")}
24070
24316
  const todos = opts.agent.ctx.todos ?? [];
24071
24317
  const resolved = resolveContinuation({ todos, suggestions: [] });
24072
24318
  top = resolved.source === "todo" ? resolved.text : "";
24319
+ groundedTodo = top !== "";
24073
24320
  }
24074
24321
  if (!top) {
24075
24322
  } else {
@@ -24095,7 +24342,8 @@ ${lines.join("\n")}
24095
24342
  promptToFeed,
24096
24343
  delay,
24097
24344
  ctrl,
24098
- autoProceedLoopGuard
24345
+ autoProceedLoopGuard,
24346
+ groundedTodo
24099
24347
  );
24100
24348
  if (submitted) {
24101
24349
  autoIterCount++;
@@ -24579,7 +24827,7 @@ async function renderGoalBanner(opts) {
24579
24827
  }
24580
24828
  opts.renderer.write("\n");
24581
24829
  }
24582
- async function runAutoProceed(opts, suggestion, delayMs, ctrl, loopGuard) {
24830
+ async function runAutoProceed(opts, suggestion, delayMs, ctrl, loopGuard, grounded = false) {
24583
24831
  const truncated = suggestion.length > 80 ? `${suggestion.slice(0, 77)}\u2026` : suggestion;
24584
24832
  console.log(
24585
24833
  JSON.stringify({
@@ -24612,7 +24860,7 @@ async function runAutoProceed(opts, suggestion, delayMs, ctrl, loopGuard) {
24612
24860
  );
24613
24861
  return false;
24614
24862
  }
24615
- const repetition = loopGuard.record(suggestion);
24863
+ const repetition = grounded ? loopGuard.recordGrounded(suggestion) : loopGuard.record(suggestion);
24616
24864
  if (repetition.shouldHalt) {
24617
24865
  setSuggestions([]);
24618
24866
  setAutoSuggestions([]);
@@ -24622,7 +24870,20 @@ async function runAutoProceed(opts, suggestion, delayMs, ctrl, loopGuard) {
24622
24870
  );
24623
24871
  return false;
24624
24872
  }
24625
- const runBlocks = [{ type: "text", text: suggestion }];
24873
+ const steered = "action" in repetition && repetition.action === "steer";
24874
+ if (steered) {
24875
+ opts.renderer.writeWarning(
24876
+ "Todo board unchanged since the last automatic turn \u2014 steering the agent to update the board before continuing."
24877
+ );
24878
+ }
24879
+ const runBlocks = [
24880
+ {
24881
+ type: "text",
24882
+ text: steered ? `${suggestion}
24883
+
24884
+ ${GROUNDED_NO_PROGRESS_STEER}` : suggestion
24885
+ }
24886
+ ];
24626
24887
  const runResult = await opts.agent.run(runBlocks, { signal: ctrl.signal });
24627
24888
  console.log(
24628
24889
  JSON.stringify({
@@ -26359,6 +26620,14 @@ import { DefaultSecretScrubber as DefaultSecretScrubber5 } from "@wrongstack/cor
26359
26620
  import { createSessionEventBridge, resolveSessionLoggingConfig } from "@wrongstack/core/storage";
26360
26621
  import { recordFileAction } from "@wrongstack/core/coordination";
26361
26622
  import { startNetworkTelemetryMonitor } from "@wrongstack/core/observability";
26623
+ var DEFAULT_CHRONICLE_RETENTION_DAYS = 30;
26624
+ var MIN_CHRONICLE_RETENTION_DAYS = 7;
26625
+ function resolveChronicleRetentionDays(config) {
26626
+ const raw = config.chronicle?.retentionDays;
26627
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return DEFAULT_CHRONICLE_RETENTION_DAYS;
26628
+ if (raw <= 0) return 0;
26629
+ return Math.max(MIN_CHRONICLE_RETENTION_DAYS, raw);
26630
+ }
26362
26631
  function wireSessionEvents(deps) {
26363
26632
  const { evOn, config, context, session, sessionRef, wpaths, projectRoot } = deps;
26364
26633
  const sessionConfig = resolveSessionLoggingConfig(
@@ -26392,7 +26661,10 @@ function wireSessionEvents(deps) {
26392
26661
  projectId: wpaths.projectHash ?? wpaths.projectSlug,
26393
26662
  projectDir: wpaths.projectDir
26394
26663
  });
26395
- chronicleJournal = new ChronicleJournal2({ filePath: location.journalPath });
26664
+ chronicleJournal = new ChronicleJournal2({
26665
+ filePath: location.journalPath,
26666
+ retentionDays: resolveChronicleRetentionDays(config)
26667
+ });
26396
26668
  const chronicleContext = createChronicleContext({
26397
26669
  installationId: location.installationId,
26398
26670
  machineId: location.machineId,
@@ -27214,7 +27486,20 @@ function stripQuotes(s) {
27214
27486
 
27215
27487
  // src/slash-commands/agent-improve.ts
27216
27488
  import { color as color54 } from "@wrongstack/core/utils";
27217
- import { captureLearnedFromAgentOutput, loadProjectAgentConfig, loadProjectAgentIdentity, loadProjectAgentLearned, loadRoleKnowledgeManifest, resetProjectAgentIdentity, refreshProjectAgentIdentity, updateProjectAgentIdentity } from "@wrongstack/core/agent-catalog";
27489
+ import {
27490
+ buildConsolidationInstruction,
27491
+ captureLearnedFromAgentOutput,
27492
+ getProjectAgentLearnStats,
27493
+ isConsolidated,
27494
+ loadConsolidationMetadata,
27495
+ loadProjectAgentConfig,
27496
+ loadProjectAgentIdentity,
27497
+ loadProjectAgentLearned,
27498
+ loadRoleKnowledgeManifest,
27499
+ resetProjectAgentIdentity,
27500
+ refreshProjectAgentIdentity,
27501
+ updateProjectAgentIdentity
27502
+ } from "@wrongstack/core/agent-catalog";
27218
27503
  function buildAgentImproveCommand(opts) {
27219
27504
  const projectRoot = opts.projectRoot;
27220
27505
  function listRoles2() {
@@ -27238,6 +27523,7 @@ function buildAgentImproveCommand(opts) {
27238
27523
  " /agent-improve [role] update Update identity + learned content",
27239
27524
  " /agent-improve [role] refresh Reset identity + learned to empty templates",
27240
27525
  " /agent-improve [role] capture Scan last output for ## LEARNED blocks and persist them",
27526
+ " /agent-improve [role] consolidate Optimize raw learned entries into a consolidated document",
27241
27527
  " /agent-improve [role] reset Delete ALL custom files for this role",
27242
27528
  " /agent-improve * reset Delete ALL custom files for every role",
27243
27529
  "",
@@ -27247,6 +27533,8 @@ function buildAgentImproveCommand(opts) {
27247
27533
  " Agents output a ## LEARNED section in their response to persist",
27248
27534
  " project-specific patterns. The runtime captures these automatically.",
27249
27535
  ' Use "capture" to manually re-scan any text for LEARNED blocks.',
27536
+ ' Use "consolidate" to synthesize all raw entries into a single',
27537
+ " narrowly-scoped document that replaces raw entries in the agent prompt.",
27250
27538
  "",
27251
27539
  "Files live under: .wrongstack/agents/<role>/"
27252
27540
  ].join("\n"),
@@ -27329,6 +27617,24 @@ Or edit files directly under .wrongstack/agents/${role}/`
27329
27617
  message: `No ## LEARNED blocks found in recent output for role "${role}".`
27330
27618
  };
27331
27619
  }
27620
+ case "consolidate": {
27621
+ const stats = getProjectAgentLearnStats(role, projectRoot);
27622
+ if (stats.entryCount === 0) {
27623
+ const msg = `No raw learned entries for role "${role}" to consolidate.`;
27624
+ opts.renderer.write(msg);
27625
+ return { message: msg };
27626
+ }
27627
+ const { instruction } = buildConsolidationInstruction(role, projectRoot);
27628
+ const prompt = `Optimize what the "${role}" agent has learned for its skills. ${instruction}`;
27629
+ const consolidatedAlready = isConsolidated(role, projectRoot);
27630
+ const meta = loadConsolidationMetadata(role, projectRoot);
27631
+ const summary = `${color54.cyan(role)}: ${stats.entryCount} raw entries (${stats.totalBytes}B)` + (consolidatedAlready && meta ? ` \xB7 last consolidated ${meta.consolidatedAt.slice(0, 10)} (${meta.sourceBytes}B \u2192 ${meta.consolidatedBytes}B)` : " \xB7 no prior consolidation") + "\n\nSending consolidation instruction to the agent...";
27632
+ opts.renderer.write(summary);
27633
+ return {
27634
+ message: summary,
27635
+ runText: prompt
27636
+ };
27637
+ }
27332
27638
  case "reset": {
27333
27639
  const removed = resetProjectAgentIdentity(
27334
27640
  role === "*" ? void 0 : role,
@@ -27346,7 +27652,7 @@ Or edit files directly under .wrongstack/agents/${role}/`
27346
27652
  }
27347
27653
  default:
27348
27654
  return {
27349
- message: `Unknown action "${action}". Use: show, update, refresh, capture, reset, or reset-all.`
27655
+ message: `Unknown action "${action}". Use: show, update, refresh, capture, consolidate, reset, or reset-all.`
27350
27656
  };
27351
27657
  }
27352
27658
  }
@@ -27786,6 +28092,12 @@ function parseRefEntry(ref) {
27786
28092
  function compactEntry2(entry) {
27787
28093
  return entry.provider ? `${entry.provider}/${entry.model}` : entry.model;
27788
28094
  }
28095
+ function judgeSummary(s) {
28096
+ if (!s.judgeLabel) return "";
28097
+ const seated = s.councilLabels.some((label) => label.startsWith(`${s.judgeLabel} (`));
28098
+ const origin = s.council.judge ? "" : " [derived]";
28099
+ return `, judge: ${s.judgeLabel}${origin}${seated ? " \u26A0 also a voter" : ""}`;
28100
+ }
27789
28101
  function parseSeat(token) {
27790
28102
  const parts = token.split(":");
27791
28103
  const mods = [];
@@ -28035,7 +28347,7 @@ function buildBrainCommand(opts) {
28035
28347
  }
28036
28348
  if (subcommand === "council") {
28037
28349
  const op = (rest[0] ?? "").toLowerCase();
28038
- const councilSummary = (s) => s.council.enabled ? `council ${color57.cyan("convened")}: ${s.councilLabels.join(", ")} ${color57.dim(`(minRisk: ${s.council.minRisk}${s.council.judge ? `, judge: ${compactEntry2(s.council.judge)}` : ""})`)}` : `council ${color57.dim("disabled")}`;
28350
+ const councilSummary = (s) => s.council.enabled ? `council ${color57.cyan("convened")}: ${s.councilLabels.join(", ")} ${color57.dim(`(minRisk: ${s.council.minRisk}${judgeSummary(s)})`)}` : `council ${color57.dim("disabled")}`;
28039
28351
  if (!op) {
28040
28352
  const snapshot = opts.brainRuntime?.getSnapshot();
28041
28353
  const msg3 = snapshot ? `Brain ${councilSummary(snapshot)}` : "The Brain runtime is not available in this session.";
@@ -28070,7 +28382,7 @@ function buildBrainCommand(opts) {
28070
28382
  opts.renderer.writeWarning(msg3);
28071
28383
  return { message: msg3 };
28072
28384
  }
28073
- return applyPatch({ council: { judge: ref.toLowerCase() === "auto" ? null : ref } }, (s) => `Brain council judge: ${color57.cyan(s.council.judge ? compactEntry2(s.council.judge) : "auto (first pool/voter model)")}`);
28385
+ return applyPatch({ council: { judge: ref.toLowerCase() === "auto" ? null : ref } }, (s) => `Brain council judge: ${color57.cyan(s.council.judge ? compactEntry2(s.council.judge) : "auto")}${s.council.judge ? "" : color57.dim(judgeSummary(s).replace(/^, judge: /, " \u2192 "))}`);
28074
28386
  }
28075
28387
  if (op === "quorum" || op === "approval") {
28076
28388
  const value = Number(rest[1]);
@@ -28205,7 +28517,7 @@ ${color57.dim(decision.rationale)}` : ""}`;
28205
28517
  const councilSeats = opts.brainSettings?.councilLabels ?? [];
28206
28518
  if (councilSeats.length > 0) {
28207
28519
  lines.push(
28208
- ` council: ${color57.cyan(councilSeats.join(", "))}${snapshot ? color57.dim(` (minRisk: ${snapshot.council.minRisk}${snapshot.council.judge ? `, judge: ${compactEntry2(snapshot.council.judge)}` : ""})`) : ""}`
28520
+ ` council: ${color57.cyan(councilSeats.join(", "))}${snapshot ? color57.dim(` (minRisk: ${snapshot.council.minRisk}${judgeSummary(snapshot)})`) : ""}`
28209
28521
  );
28210
28522
  } else if (snapshot) {
28211
28523
  lines.push(` council: ${color57.dim("disabled (/brain council on + voters)")}`);
@@ -33689,6 +34001,7 @@ function buildKanbanCommand(opts) {
33689
34001
  " /kanban duplicate <boardId> [title] Duplicate a board",
33690
34002
  " /kanban show <boardId> Show board details (columns + tasks)",
33691
34003
  " /kanban delete <boardId> Delete a board",
34004
+ " /kanban prune [days] [--all] [--yes] Delete stale boards (dry-run without --yes)",
33692
34005
  " /kanban rename <boardId> <t> Rename a board",
33693
34006
  " /kanban snapshot [boardId] Show agent queue snapshot",
33694
34007
  "",
@@ -33778,6 +34091,60 @@ function buildKanbanCommand(opts) {
33778
34091
  message: removed ? color74.green(`\u2705 Board deleted: ${rest[0]}`) : color74.red(`Board not found: ${rest[0]}`)
33779
34092
  };
33780
34093
  }
34094
+ if (cmd === "prune") {
34095
+ const flags = new Set(rest.filter((t) => t.startsWith("--") || t === "-y"));
34096
+ const positional = rest.filter((t) => !t.startsWith("--") && t !== "-y");
34097
+ const days = positional[0] ? Number(positional[0]) : 7;
34098
+ if (!Number.isFinite(days) || days < 0) {
34099
+ return { message: color74.red("Usage: /kanban prune [days>=0] [--all] [--yes]") };
34100
+ }
34101
+ const includeUnfinished = flags.has("--all");
34102
+ const apply = flags.has("--yes") || flags.has("-y");
34103
+ const cutoff = Date.now() - days * 864e5;
34104
+ const boards = await listBoards(projectRoot);
34105
+ const stale = boards.filter((b) => {
34106
+ const updatedAt = Date.parse(b.updatedAt);
34107
+ if (!Number.isFinite(updatedAt) || updatedAt > cutoff) return false;
34108
+ if (includeUnfinished) return true;
34109
+ return b.taskCount === 0 || b.completedTaskCount === b.taskCount;
34110
+ });
34111
+ if (stale.length === 0) {
34112
+ return {
34113
+ message: color74.green(
34114
+ `Nothing to prune: no ${includeUnfinished ? "" : "finished/empty "}boards idle \u2265 ${days}d (of ${boards.length} total).`
34115
+ )
34116
+ };
34117
+ }
34118
+ if (!apply) {
34119
+ const preview = stale.slice(0, 20).map(
34120
+ (b) => ` ${LABEL(b.id.slice(0, 8))} ${b.title.slice(0, 48)} ${DIM(`${b.taskCount} tasks \xB7 updated ${fmtAge2(b.updatedAt)}`)}`
34121
+ );
34122
+ if (stale.length > 20) preview.push(DIM(` \u2026 and ${stale.length - 20} more`));
34123
+ return {
34124
+ message: [
34125
+ HEADING(
34126
+ `Would prune ${stale.length} of ${boards.length} boards (idle \u2265 ${days}d${includeUnfinished ? ", including unfinished" : ", finished/empty only"})`
34127
+ ),
34128
+ ...preview,
34129
+ "",
34130
+ DIM(`Run /kanban prune ${positional[0] ?? days}${includeUnfinished ? " --all" : ""} --yes (or -y) to delete.`)
34131
+ ].join("\n")
34132
+ };
34133
+ }
34134
+ let removed = 0;
34135
+ let failed = 0;
34136
+ for (const b of stale) {
34137
+ if (await removeBoard(projectRoot, b.id)) removed++;
34138
+ else failed++;
34139
+ }
34140
+ const parts = [
34141
+ `\u2705 Pruned ${removed} board${removed === 1 ? "" : "s"} (${boards.length - removed} remain).`
34142
+ ];
34143
+ if (failed > 0) {
34144
+ parts.push(color74.yellow(`${failed} board${failed === 1 ? "" : "s"} could not be deleted \u2014 check file permissions or HQ sync locks.`));
34145
+ }
34146
+ return { message: color74.green(parts.join(" ")) };
34147
+ }
33781
34148
  if (cmd === "rename") {
33782
34149
  if (rest.length < 2)
33783
34150
  return { message: color74.red("Usage: /kanban rename <boardId> <title>") };
@@ -33838,6 +34205,7 @@ function buildKanbanCommand(opts) {
33838
34205
  "duplicate",
33839
34206
  "show",
33840
34207
  "delete",
34208
+ "prune",
33841
34209
  "rename",
33842
34210
  "generate",
33843
34211
  "snapshot",
@@ -34449,11 +34817,11 @@ function stripQuotes2(value) {
34449
34817
 
34450
34818
  // src/slash-commands/mailbox.ts
34451
34819
  import { color as color75, wstackGlobalRoot as wstackGlobalRoot5 } from "@wrongstack/core/utils";
34452
- import { GlobalMailbox as GlobalMailbox5, mailboxSessionTag, resolveMailboxIdentity, resolveProjectDir as resolveProjectDir4 } from "@wrongstack/core/coordination";
34820
+ import { GlobalMailbox as GlobalMailbox4, mailboxSessionTag, resolveMailboxIdentity, resolveProjectDir as resolveProjectDir4 } from "@wrongstack/core/coordination";
34453
34821
  function buildMailbox(opts) {
34454
34822
  const projectDir = opts.paths?.projectDir ?? resolveProjectDir4(opts.projectRoot, wstackGlobalRoot5());
34455
34823
  try {
34456
- return new GlobalMailbox5(projectDir);
34824
+ return new GlobalMailbox4(projectDir);
34457
34825
  } catch {
34458
34826
  return null;
34459
34827
  }
@@ -34772,13 +35140,13 @@ function buildMailboxCommand(opts) {
34772
35140
  }
34773
35141
 
34774
35142
  // src/slash-commands/mailbox-demo.ts
34775
- import { GlobalMailbox as GlobalMailbox6, resolveProjectDir as resolveProjectDir5 } from "@wrongstack/core/coordination";
35143
+ import { GlobalMailbox as GlobalMailbox5, resolveProjectDir as resolveProjectDir5 } from "@wrongstack/core/coordination";
34776
35144
  import { wstackGlobalRoot as wstackGlobalRoot6 } from "@wrongstack/core/utils";
34777
35145
  var DEMO_AGENT_ID = "mailbox-demo";
34778
35146
  function buildMailbox2(opts) {
34779
35147
  const projectDir = opts.paths?.projectDir ?? resolveProjectDir5(opts.projectRoot, wstackGlobalRoot6());
34780
35148
  try {
34781
- return new GlobalMailbox6(projectDir);
35149
+ return new GlobalMailbox5(projectDir);
34782
35150
  } catch {
34783
35151
  return null;
34784
35152
  }
@@ -46429,7 +46797,7 @@ import {
46429
46797
  FleetManager,
46430
46798
  FleetSupervisor,
46431
46799
  formatSubagentStructuredReport,
46432
- GlobalMailbox as GlobalMailbox7,
46800
+ GlobalMailbox as GlobalMailbox6,
46433
46801
  HARD_MAX_SPAWN_DEPTH,
46434
46802
  mailboxSessionTag as mailboxSessionTag2,
46435
46803
  makeDirectorSessionFactory,
@@ -46609,7 +46977,19 @@ function createFleetStatusBroadcaster(opts) {
46609
46977
  // A retired subagent's id will never be seen again, so its one-shot dedup
46610
46978
  // guards and throttle timestamp are dead weight. Prune them so a long-lived
46611
46979
  // fleet that spawns thousands of subagents does not leak one entry each.
46980
+ //
46981
+ // Deregister from the mailbox agent registry so the retirement is visible
46982
+ // cross-process immediately. Without this, the registry entry lingers with
46983
+ // status 'idle' (the last heartbeat from the task_completed handler) until
46984
+ // the 60s lazy prune fires — leaking dead agents in fleet_status and the
46985
+ // fleet pulse for up to a minute. deregisterAgent is used instead of
46986
+ // heartbeat({ status: 'offline' }) because (a) RegisteredAgent.status
46987
+ // does not include 'offline' (only the read-side MailboxAgentStatus does),
46988
+ // and (b) the heartbeat path is throttled to 1/5s so it silently drops
46989
+ // when fired right after the task_completed idle heartbeat.
46612
46990
  opts.events.on("subagent.removed", (e) => {
46991
+ void mb().deregisterAgent(e.subagentId).catch(() => {
46992
+ });
46613
46993
  announcedSpawn.delete(e.subagentId);
46614
46994
  lastSentAt.delete(e.subagentId);
46615
46995
  for (const key of announcedBudget) {
@@ -46922,7 +47302,7 @@ var MultiAgentHost = class _MultiAgentHost {
46922
47302
  }
46923
47303
  this.statusBroadcaster = createFleetStatusBroadcaster({
46924
47304
  events: this.deps.events,
46925
- mailboxFactory: () => new GlobalMailbox7(this.mailboxProjectDir(), this.deps.events),
47305
+ mailboxFactory: () => new GlobalMailbox6(this.mailboxProjectDir(), this.deps.events),
46926
47306
  sessionTag: () => mailboxSessionTag2(this.deps.session.id),
46927
47307
  subagentName: (id) => this.director?.status().subagents.find((s) => s.id === id)?.name,
46928
47308
  config: config.fleet?.statusBroadcasts
@@ -47309,7 +47689,7 @@ ${message} Falling back to the assigned checkout.` : `${message} Falling back to
47309
47689
  }
47310
47690
  let onlineAgents = [];
47311
47691
  try {
47312
- const subagentMailbox = new GlobalMailbox7(this.mailboxProjectDir());
47692
+ const subagentMailbox = new GlobalMailbox6(this.mailboxProjectDir());
47313
47693
  onlineAgents = await subagentMailbox.getAgentStatuses();
47314
47694
  } catch {
47315
47695
  }
@@ -47833,7 +48213,7 @@ ${resolved.join("\n\n---\n\n")}` : void 0
47833
48213
  const supervisorCfg = config.fleet?.supervisor;
47834
48214
  if (!director || !brain || supervisorCfg?.enabled === false) return;
47835
48215
  const supTag = () => mailboxSessionTag2(this.deps.session.id);
47836
- const mailbox = () => new GlobalMailbox7(this.mailboxProjectDir(), this.deps.events);
48216
+ const mailbox = () => new GlobalMailbox6(this.mailboxProjectDir(), this.deps.events);
47837
48217
  this.fleetSupervisor = new FleetSupervisor({
47838
48218
  events: this.deps.events,
47839
48219
  fleet: director.fleet,
@@ -47908,7 +48288,7 @@ ${resolved.join("\n\n---\n\n")}` : void 0
47908
48288
  async reportTaskResultToLeader(n) {
47909
48289
  try {
47910
48290
  const leaderId2 = this.opts.getLeaderMailboxId?.() ?? `leader@${mailboxSessionTag2(this.deps.session.id)}`;
47911
- const mailbox = new GlobalMailbox7(this.mailboxProjectDir(), this.deps.events);
48291
+ const mailbox = new GlobalMailbox6(this.mailboxProjectDir(), this.deps.events);
47912
48292
  const ok = n.status === "success";
47913
48293
  const MAX_BODY = 700;
47914
48294
  const failureText = [
@@ -48848,14 +49228,14 @@ async function spawnAgent2(director, role, task, maxIterations) {
48848
49228
  // src/wiring/dep-watcher.ts
48849
49229
  import * as path62 from "node:path";
48850
49230
  import { startTechStackConsumer } from "@wrongstack/core/coordination";
48851
- import { GlobalMailbox as GlobalMailbox8, startPackageOutdatedWatcher } from "@wrongstack/core/coordination";
49231
+ import { GlobalMailbox as GlobalMailbox7, startPackageOutdatedWatcher } from "@wrongstack/core/coordination";
48852
49232
  function setupDepWatcherConsumers(deps) {
48853
49233
  const { dwCfg, globalRoot, projectSlug, events, multiAgentHost, sessionId, logger, teardownHandlers, projectRoot } = deps;
48854
49234
  let techStackConsumerDispose;
48855
49235
  if (dwCfg?.["enabled"] === true) {
48856
49236
  try {
48857
49237
  const projectDir = path62.join(globalRoot, "projects", projectSlug);
48858
- const tsMailbox = new GlobalMailbox8(projectDir, events);
49238
+ const tsMailbox = new GlobalMailbox7(projectDir, events);
48859
49239
  const fileAuthorOpts = {
48860
49240
  storageDir: projectDir,
48861
49241
  projectRoot
@@ -48888,7 +49268,7 @@ function setupDepWatcherConsumers(deps) {
48888
49268
  if (dwCfg?.["enabled"] === true) {
48889
49269
  try {
48890
49270
  const projectDir = path62.join(globalRoot, "projects", projectSlug);
48891
- const pkgMailbox = new GlobalMailbox8(projectDir, events);
49271
+ const pkgMailbox = new GlobalMailbox7(projectDir, events);
48892
49272
  const pkgTrackerOpts = {
48893
49273
  storageDir: projectDir,
48894
49274
  projectRoot
@@ -49631,7 +50011,7 @@ function setupHqTelemetry(deps) {
49631
50011
  import * as path68 from "node:path";
49632
50012
  import { allServers as allServers2 } from "@wrongstack/core/infrastructure";
49633
50013
  import { countShellHooks, HookRegistry, HookRunner, shellHooksEqual } from "@wrongstack/core/hooks";
49634
- import { GlobalMailbox as GlobalMailbox9 } from "@wrongstack/core/coordination";
50014
+ import { GlobalMailbox as GlobalMailbox8 } from "@wrongstack/core/coordination";
49635
50015
  import { NotifierImpl } from "@wrongstack/core/notifications";
49636
50016
  import { normalizeTokenSavingTier as normalizeTokenSavingTier6 } from "@wrongstack/core/types";
49637
50017
  import { SlashCommandRegistry } from "@wrongstack/core/registry";
@@ -50377,7 +50757,7 @@ async function setupLifecycleAndPlugins(deps) {
50377
50757
  registerMcpObservability(healthRegistry, metricsSink, mcpRegistry);
50378
50758
  const slashRegistry = new SlashCommandRegistry();
50379
50759
  const hqPublisherRef = { current: void 0 };
50380
- const brainMailbox = new GlobalMailbox9(wpaths.projectDir, events, () => hqPublisherRef.current);
50760
+ const brainMailbox = new GlobalMailbox8(wpaths.projectDir, events, () => hqPublisherRef.current);
50381
50761
  const notifier = new NotifierImpl();
50382
50762
  const pluginHost = await setupPlugins({
50383
50763
  config,
@@ -51108,8 +51488,11 @@ init_utils();
51108
51488
  import { getToolDescriptionMode as getToolDescriptionMode3 } from "@wrongstack/core/utils";
51109
51489
 
51110
51490
  // src/wiring/codebase-index.ts
51111
- import * as fs55 from "node:fs";
51112
51491
  import * as path69 from "node:path";
51492
+ import {
51493
+ DEFAULT_WALK_IGNORE_SET,
51494
+ watchProjectTree
51495
+ } from "@wrongstack/core/utils";
51113
51496
  import {
51114
51497
  cancelPendingReindexes,
51115
51498
  enqueueReindex,
@@ -51118,19 +51501,8 @@ import {
51118
51501
  shutdownCodebaseIndexHost
51119
51502
  } from "@wrongstack/tools";
51120
51503
  var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["write", "edit"]);
51121
- var IGNORE_DIRS = /* @__PURE__ */ new Set([
51122
- "node_modules",
51123
- ".git",
51124
- "dist",
51125
- "build",
51126
- ".next",
51127
- "coverage",
51128
- ".turbo",
51129
- "__snapshots__",
51130
- ".nyc_output"
51131
- ]);
51132
51504
  function isIgnored(rel) {
51133
- return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
51505
+ return rel.split(/[/\\]/).some((seg) => DEFAULT_WALK_IGNORE_SET.has(seg));
51134
51506
  }
51135
51507
  async function setupCodebaseIndexing(deps) {
51136
51508
  const { config, context, pipelines, projectRoot, logger } = deps;
@@ -51180,16 +51552,18 @@ async function setupCodebaseIndexing(deps) {
51180
51552
  let watcher;
51181
51553
  if (idx.watchExternal) {
51182
51554
  try {
51183
- watcher = fs55.watch(projectRoot, { recursive: true }, (_event, filename) => {
51184
- if (!filename) return;
51185
- const rel = filename.toString();
51186
- if (isIgnored(rel)) return;
51187
- const abs = path69.resolve(projectRoot, rel);
51188
- if (!isIndexableFile(abs)) return;
51189
- enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
51190
- });
51191
- watcher.on("error", (err) => logger.debug(`codebase index watcher error: ${err}`));
51192
- watcher.unref?.();
51555
+ watcher = watchProjectTree(
51556
+ projectRoot,
51557
+ (event) => {
51558
+ if (!event.filename) return;
51559
+ const rel = event.filename;
51560
+ if (isIgnored(rel)) return;
51561
+ const abs = path69.resolve(projectRoot, rel);
51562
+ if (!isIndexableFile(abs)) return;
51563
+ enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
51564
+ },
51565
+ { onError: (err) => logger.debug(`codebase index watcher error: ${err}`) }
51566
+ );
51193
51567
  } catch (err) {
51194
51568
  logger.debug(
51195
51569
  `codebase index watcher unavailable: ${err instanceof Error ? err.message : String(err)}`
@@ -51688,12 +52062,32 @@ function createSddHandlers(deps) {
51688
52062
  }
51689
52063
  }
51690
52064
  const boardStore = new sddApi.SddBoardStore({ baseDir: wpaths.projectSddBoards });
51691
- const verifyTask = sddApi.makeCommandVerifier();
51692
52065
  const sddSupervisor = new sddApi.SddSupervisor({
51693
52066
  brain,
51694
52067
  reassignModels: agentCore.effectiveFallbackChain(config)
51695
52068
  });
51696
52069
  const sddSubagentFactory = multiAgentHost.makeSubagentFactory(config);
52070
+ let verifySeq = 0;
52071
+ const verifyTask = sddApi.makeCompositeVerifier([
52072
+ sddApi.makeCommandVerifier(),
52073
+ sddApi.makeAcceptanceCriteriaVerifier({
52074
+ run: async (prompt) => {
52075
+ const r = await sddSubagentFactory({
52076
+ id: `sdd-acceptance-${verifySeq++}`,
52077
+ role: "executor",
52078
+ name: "Acceptance Reviewer",
52079
+ disabledTools: ["delegate", "write", "edit", "patch", "bash", "exec"],
52080
+ allowedCapabilities: ["fs.read"]
52081
+ });
52082
+ try {
52083
+ const res = await r.agent.run([{ type: "text", text: prompt }]);
52084
+ return res.finalText ?? "";
52085
+ } finally {
52086
+ await r.dispose?.();
52087
+ }
52088
+ }
52089
+ })
52090
+ ]);
51697
52091
  const conflictMode = process.env["WRONGSTACK_SDD_CONFLICT_RESOLVER"];
51698
52092
  const conflictResolver = conflictMode === "prefer-incoming" ? sddApi.makePreferSideConflictResolver("incoming") : conflictMode === "prefer-base" ? sddApi.makePreferSideConflictResolver("base") : conflictMode === "llm" ? sddApi.makeLlmConflictResolver({
51699
52093
  run: async (prompt) => {
@@ -52093,10 +52487,10 @@ async function main(argv) {
52093
52487
  const fallbackManageTools = createFallbackManageTools({
52094
52488
  getConfig: () => configStore.get(),
52095
52489
  updateConfig: async (mutate) => {
52096
- const raw = await fs56.readFile(profileConfigPath, "utf8").catch(() => "{}");
52490
+ const raw = await fs55.readFile(profileConfigPath, "utf8").catch(() => "{}");
52097
52491
  const parsed = JSON.parse(raw);
52098
52492
  mutate(parsed);
52099
- await fs56.writeFile(profileConfigPath, JSON.stringify(parsed, null, 2), { mode: 384 });
52493
+ await fs55.writeFile(profileConfigPath, JSON.stringify(parsed, null, 2), { mode: 384 });
52100
52494
  configStore.update(parsed);
52101
52495
  },
52102
52496
  // Interactive key entry for REPL mode — reads a line from stdin without echo.
@@ -52194,7 +52588,7 @@ ${prompt}
52194
52588
  let onlineAgents = [];
52195
52589
  if (flags["simpleui"] !== true) {
52196
52590
  try {
52197
- const systemMailbox = new GlobalMailbox10(wpaths.projectDir);
52591
+ const systemMailbox = new GlobalMailbox9(wpaths.projectDir);
52198
52592
  onlineAgents = await systemMailbox.getAgentStatuses();
52199
52593
  } catch {
52200
52594
  }
@@ -52311,7 +52705,9 @@ ${prompt}
52311
52705
  });
52312
52706
  const stats = new SessionStats(events, tokenCounter);
52313
52707
  let activeReasoningConfig;
52708
+ const warnedRuntimeMessages = /* @__PURE__ */ new Set();
52314
52709
  const refreshActiveReasoningConfig = async (providerId, modelId) => {
52710
+ warnedRuntimeMessages.clear();
52315
52711
  try {
52316
52712
  const resolved = await modelsRegistry.getModel(providerId, modelId);
52317
52713
  activeReasoningConfig = resolved?.capabilities.reasoningConfig;
@@ -52328,6 +52724,8 @@ ${prompt}
52328
52724
  getReasoningConfig: () => activeReasoningConfig,
52329
52725
  getCapabilities: () => provider.capabilities,
52330
52726
  onWarning: (message) => {
52727
+ if (warnedRuntimeMessages.has(message)) return;
52728
+ warnedRuntimeMessages.add(message);
52331
52729
  logger.warn(`model-runtime: ${message}`);
52332
52730
  }
52333
52731
  }
@@ -52377,7 +52775,7 @@ ${prompt}
52377
52775
  if (dwCfg?.["enabled"] === true) {
52378
52776
  try {
52379
52777
  const projectDir = path71.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
52380
- const dwMailbox = new GlobalMailbox10(projectDir, events);
52778
+ const dwMailbox = new GlobalMailbox9(projectDir, events);
52381
52779
  depWatcherDispose = attachDepWatcherBridge({
52382
52780
  events,
52383
52781
  mailbox: dwMailbox,