@wrongstack/cli 0.280.1 → 0.281.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { CODEX_MODELS, color, ToolCapabilities, readBundledInstructionText, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, ConfigError, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, createHqPersistence, HqCommandAuditLog, HqAlertEngine, toAlertMessage, validateHqCommand, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, FetchError, wstackGlobalRoot, DefaultSecretScrubber, resolveWstackPaths, watchProviderConfig, PromptUsageStore, TOKENS, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, shellHooksEqual, countShellHooks, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DefaultLogger, DefaultModelsRegistry, isStdinTTY, FsError, atomicWrite, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, sessionScopedPath as sessionScopedPath$1, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, applyToolDescriptionModes, applyToolResultRenderModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, applyModelRuntime, installDesignStudioMiddleware, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveModelMatrix, resolveModelTargetFromEntry, DEFAULT_SUBAGENT_BASELINE, DefaultTokenCounter, AutoApprovePermissionPolicy, mergeModelRuntime, ToolValidationError, WIDE_SUBAGENT_CAPABILITIES, AgentError, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, assignNickname, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, resolveProjectDir, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, ProviderRegistry, makeAgentSubagentRunner, NULL_FLEET_BUS, ERROR_CODES, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, loadTasks, formatTaskList, formatTaskProgress, mutateTasks, emptyPlan, addPlanItem, savePlan, formatPlan, recordCompletedWorkEvidence, saveCompletedWorkCheckpoint, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, getDesignKitLoader, getDesignState, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, applyTokenOverrides, materializeTokens, setActiveKit, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, fallbackProfileChain, parseModelRef, normalizeModelRef, onResize, hasOpenTodos, InputBuilder, DefaultSecretVault as DefaultSecretVault$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DEFAULT_CONTEXT_WINDOW_MODE_ID, HQ_AUTH_FILE_VERSION, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, normalizeToolResultRenderMode, getToolResultRenderMode, setToolResultRenderMode, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, isSecretField as isSecretField$1, renderInstructionTemplate, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, mutatePlan, setPlanItemStatus, getPlanTemplate, resolveProviderModelList, ParseError, startSessionTelemetryBridge, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge } from '@wrongstack/core';
2
+ import { CODEX_MODELS, color, ToolCapabilities, readBundledInstructionText, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, ConfigError, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, createHqPersistence, HqCommandAuditLog, HqAlertEngine, toAlertMessage, validateHqCommand, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, FetchError, wstackGlobalRoot, DefaultSecretScrubber, resolveWstackPaths, watchProviderConfig, PromptUsageStore, TOKENS, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, shellHooksEqual, countShellHooks, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DefaultLogger, DefaultModelsRegistry, isStdinTTY, FsError, atomicWrite, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, sessionScopedPath as sessionScopedPath$1, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, makeFleetStatusTool, applyToolDescriptionModes, applyToolResultRenderModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, applyModelRuntime, installDesignStudioMiddleware, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveModelMatrix, resolveModelTargetFromEntry, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, mergeModelRuntime, ToolValidationError, WIDE_SUBAGENT_CAPABILITIES, resolveProjectDir, FleetSupervisor, AgentError, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, assignNickname, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, ProviderRegistry, makeAgentSubagentRunner, NULL_FLEET_BUS, ERROR_CODES, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, loadTasks, formatTaskList, formatTaskProgress, mutateTasks, emptyPlan, addPlanItem, savePlan, formatPlan, recordCompletedWorkEvidence, saveCompletedWorkCheckpoint, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, getDesignKitLoader, getDesignState, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, applyTokenOverrides, materializeTokens, setActiveKit, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, fallbackProfileChain, parseModelRef, normalizeModelRef, onResize, hasOpenTodos, InputBuilder, DefaultSecretVault as DefaultSecretVault$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DEFAULT_CONTEXT_WINDOW_MODE_ID, HQ_AUTH_FILE_VERSION, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, normalizeToolResultRenderMode, getToolResultRenderMode, setToolResultRenderMode, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, renderInstructionTemplate, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, mutatePlan, setPlanItemStatus, getPlanTemplate, resolveProviderModelList, ParseError, startSessionTelemetryBridge, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge } from '@wrongstack/core';
3
3
  import * as fs2 from 'fs/promises';
4
4
  import { access, readFile } from 'fs/promises';
5
5
  import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
@@ -32,10 +32,10 @@ import * as readline from 'readline';
32
32
  import { ACP_AGENT_COMMANDS, findAgentDescriptor, makeACPSubagentRunner, runEnsemble, renderEnsembleText, EnsembleRegistry, resolveAcpAgentCommand, runOneAcpTask, probeAcpAgents, runAcpBench, renderAcpBenchText, fetchAcpRegistry } from '@wrongstack/acp';
33
33
  import { parseNextSteps } from '@wrongstack/tui';
34
34
  import { WrongStackACPServer, makeACPServerAgentTurn, ACPSessionStore, WsBridgeTransport, ACPProtocolHandler } from '@wrongstack/acp/agent';
35
+ import { allServers, DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
35
36
  import { ToolExecutor } from '@wrongstack/core/execution';
36
37
  import { loadBenchConfig, reportHeaderLine, readSummary, renderMarkdownReport, createPolyglotSuite, createSwebenchSuite, runBenchmark, writeJsonArtifacts, collectCellPredictions, writePredictionsJsonl, gradePolyglot, gradeSwebench } from '@wrongstack/bench';
37
38
  import { createAgentMonitorService, acquireOrJoin, release, finalize, readLiveLock } from '@wrongstack/core/coordination';
38
- import { allServers } from '@wrongstack/core/infrastructure';
39
39
  import * as net from 'net';
40
40
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
41
41
 
@@ -76,16 +76,16 @@ function writeKeysBack(cfg, keys) {
76
76
  return;
77
77
  }
78
78
  cfg.apiKeys = keys;
79
- const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
79
+ const active2 = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
80
80
  delete cfg.apiKey;
81
81
  if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
82
- cfg.activeKey = active.label;
82
+ cfg.activeKey = active2.label;
83
83
  }
84
84
  }
85
85
  function resolveActiveApiKey(cfg) {
86
86
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
87
- const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
88
- return (active ?? cfg.apiKeys[0])?.apiKey;
87
+ const active2 = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
88
+ return (active2 ?? cfg.apiKeys[0])?.apiKey;
89
89
  }
90
90
  return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
91
91
  }
@@ -667,26 +667,26 @@ function fmtDuration(ms) {
667
667
  const remMin = m - h * 60;
668
668
  return `${h}h${remMin}m`;
669
669
  }
670
- function fmtTaskResultLine(r, color89) {
670
+ function fmtTaskResultLine(r, color90) {
671
671
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
672
672
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
673
673
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
674
674
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
675
- const errKindChip = errKind ? color89.dim(` [${errKind}]`) : "";
676
- const errSnip = errMsg || errKind ? `${errKindChip}${color89.dim(errTail)}` : "";
675
+ const errKindChip = errKind ? color90.dim(` [${errKind}]`) : "";
676
+ const errSnip = errMsg || errKind ? `${errKindChip}${color90.dim(errTail)}` : "";
677
677
  switch (r.status) {
678
678
  case "success":
679
- return { mark: color89.green("\u2713"), stats, tail: "" };
679
+ return { mark: color90.green("\u2713"), stats, tail: "" };
680
680
  case "timeout":
681
681
  return {
682
- mark: color89.yellow("\u23F1"),
683
- stats: `${color89.yellow("timeout")} ${stats}`,
682
+ mark: color90.yellow("\u23F1"),
683
+ stats: `${color90.yellow("timeout")} ${stats}`,
684
684
  tail: errSnip
685
685
  };
686
686
  case "stopped":
687
- return { mark: color89.dim("\u2298"), stats: `${color89.dim("stopped")} ${stats}`, tail: errSnip };
687
+ return { mark: color90.dim("\u2298"), stats: `${color90.dim("stopped")} ${stats}`, tail: errSnip };
688
688
  case "failed":
689
- return { mark: color89.red("\u2717"), stats: `${color89.red("failed")} ${stats}`, tail: errSnip };
689
+ return { mark: color90.red("\u2717"), stats: `${color90.red("failed")} ${stats}`, tail: errSnip };
690
690
  }
691
691
  }
692
692
  var init_utils = __esm({
@@ -3112,6 +3112,8 @@ var init_hq_dashboard_html = __esm({
3112
3112
  #root { height: 100vh; display: flex; flex-direction: column; }
3113
3113
  .hq-top { display: flex; align-items: center; gap: 14px; padding: 12px 20px; border-bottom: 1px solid var(--border); background: rgba(13,17,23,0.7); backdrop-filter: blur(8px); }
3114
3114
  .hq-brand { font-size: 17px; font-weight: 800; letter-spacing: 0.2px; background: linear-gradient(90deg, var(--accent), var(--purple)); -webkit-background-clip: text; background-clip: text; color: transparent; white-space: nowrap; }
3115
+ .hq-ui-badge { color: var(--amber); border: 1px solid var(--border); background: var(--inset); border-radius: 7px; padding: 3px 8px; font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; }
3116
+ .hq-ui-badge::before { content: 'UI:'; color: var(--dim); margin-right: 4px; }
3115
3117
  .hq-led { width: 9px; height: 9px; border-radius: 50%; display: inline-block; margin-right: 6px; }
3116
3118
  .hq-led.live { background: var(--green); box-shadow: 0 0 8px var(--green); }
3117
3119
  .hq-led.dead { background: var(--dim); }
@@ -3681,6 +3683,7 @@ async function boot(){
3681
3683
  });
3682
3684
  return h('div', { className: 'hq-top' },
3683
3685
  h('div', { className: 'hq-brand' }, '\u{1F4CB} WrongStack HQ'),
3686
+ h('div', { className: 'hq-ui-badge', title: 'Served from packages/cli/src/hq-dashboard-html.ts' }, 'inline fallback'),
3684
3687
  h('div', { className: 'hq-conn' }, h('span', { className: 'hq-led ' + (p.connected?'live':'dead') }), p.connected ? 'Live' : 'Reconnecting\u2026'),
3685
3688
  h('button', { className: 'theme-btn', title: 'Toggle light / dark', onClick: toggleTheme }, Store.theme==='light' ? '\u{1F319}' : '\u2600\uFE0F'),
3686
3689
  h('div', { className: 'statbar' },
@@ -4127,7 +4130,7 @@ function renderFallback(){
4127
4130
  var tree = buildTree(snap);
4128
4131
  var sel = Store.selected;
4129
4132
  var html = '';
4130
- html += '<div class="hq-top"><div class="hq-brand">\u{1F4CB} WrongStack HQ</div>';
4133
+ html += '<div class="hq-top"><div class="hq-brand">\u{1F4CB} WrongStack HQ</div><div class="hq-ui-badge" title="Served from packages/cli/src/hq-dashboard-html.ts">inline fallback</div>';
4131
4134
  html += '<div class="hq-conn"><span class="hq-led '+(Store.connected?'live':'dead')+'"></span>'+(Store.connected?'Live':'Reconnecting\u2026')+'</div>';
4132
4135
  html += '<button class="theme-btn" id="fb-theme">'+(Store.theme==='light'?'\u{1F319}':'\u2600\uFE0F')+'</button>';
4133
4136
  html += '<div class="statbar">';
@@ -7123,7 +7126,7 @@ async function handleModesList(ctx, ws) {
7123
7126
  }
7124
7127
  try {
7125
7128
  const modes = await ctx.modeStore.listModes();
7126
- const active = await ctx.modeStore.getActiveMode();
7129
+ const active2 = await ctx.modeStore.getActiveMode();
7127
7130
  ctx.send(ws, {
7128
7131
  type: "modes.list",
7129
7132
  payload: {
@@ -7131,9 +7134,9 @@ async function handleModesList(ctx, ws) {
7131
7134
  id: m.id,
7132
7135
  name: m.name,
7133
7136
  description: m.description,
7134
- isActive: m.id === (active?.id ?? "default")
7137
+ isActive: m.id === (active2?.id ?? "default")
7135
7138
  })),
7136
- activeId: active?.id ?? "default"
7139
+ activeId: active2?.id ?? "default"
7137
7140
  }
7138
7141
  });
7139
7142
  } catch (err) {
@@ -7567,17 +7570,17 @@ function handleContextRepair(ctx, ws) {
7567
7570
  );
7568
7571
  }
7569
7572
  async function handleContextModesList(ctx, ws) {
7570
- const active = String(ctx.agent.ctx.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
7573
+ const active2 = String(ctx.agent.ctx.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
7571
7574
  const modeStore = await ctx.getCustomModeStore();
7572
7575
  ctx.send(ws, {
7573
7576
  type: "context.modes.list",
7574
7577
  payload: sessionPayload2(ctx, {
7575
- activeId: active,
7578
+ activeId: active2,
7576
7579
  modes: modeStore.list().map((m) => ({
7577
7580
  id: m.id,
7578
7581
  name: m.name,
7579
7582
  description: m.description,
7580
- isActive: m.id === active,
7583
+ isActive: m.id === active2,
7581
7584
  thresholds: m.thresholds,
7582
7585
  preserveK: m.preserveK,
7583
7586
  eliseThreshold: m.eliseThreshold,
@@ -8063,7 +8066,7 @@ async function handleProviderModels(ctx, ws, providerId) {
8063
8066
  type: "provider.models",
8064
8067
  payload: {
8065
8068
  provider: providerId,
8066
- models: resolveProviderModelList(cfg?.models, provider)
8069
+ models: resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId)
8067
8070
  }
8068
8071
  });
8069
8072
  } catch (err) {
@@ -8255,10 +8258,10 @@ async function handleProviderProbe(ctx, ws, providerId, timeoutMs) {
8255
8258
  return;
8256
8259
  }
8257
8260
  const keys = normalizeKeys(cfg);
8258
- const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
8261
+ const active2 = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
8259
8262
  const result = await probeLocalLlm({
8260
8263
  baseUrl: cfg.baseUrl,
8261
- apiKey: active?.apiKey,
8264
+ apiKey: active2?.apiKey,
8262
8265
  noAuth: false,
8263
8266
  scrubber: probeScrubber,
8264
8267
  ...timeoutMs !== void 0 ? { timeoutMs } : {}
@@ -8508,6 +8511,36 @@ async function handleSessionDelete(ctx, ws, id) {
8508
8511
  sendResult8(ctx, ws, false, toErrorMessage$1(err));
8509
8512
  }
8510
8513
  }
8514
+ async function handleSessionRename(ctx, ws, id, name) {
8515
+ if (!id) {
8516
+ sendResult8(ctx, ws, false, "Session id is required");
8517
+ return;
8518
+ }
8519
+ try {
8520
+ const store = storeFor(ctx.opts);
8521
+ await store.rename(id, name);
8522
+ sendResult8(ctx, ws, true, name ? `Renamed session to "${name}"` : `Cleared session name`);
8523
+ const list = await store.list(50);
8524
+ const currentId = currentSessionId3(ctx);
8525
+ ctx.broadcast({
8526
+ type: "sessions.list",
8527
+ payload: {
8528
+ sessions: list.map((s) => ({
8529
+ id: s.id,
8530
+ name: s.name,
8531
+ title: s.title,
8532
+ startedAt: s.startedAt,
8533
+ model: s.model,
8534
+ provider: s.provider,
8535
+ tokenTotal: s.tokenTotal,
8536
+ isCurrent: s.id === currentId
8537
+ }))
8538
+ }
8539
+ });
8540
+ } catch (err) {
8541
+ sendResult8(ctx, ws, false, toErrorMessage$1(err));
8542
+ }
8543
+ }
8511
8544
  function handleSessionSave(ctx, ws) {
8512
8545
  sendResult8(ctx, ws, true, `Session ${ctx.opts.session.id} is auto-saved`);
8513
8546
  }
@@ -9186,6 +9219,12 @@ function createMessageRouter(deps) {
9186
9219
  msg.payload?.limit ?? 50
9187
9220
  ),
9188
9221
  "session.new": (_msg, ws) => handleSessionNew(sessionsCtx),
9222
+ "session.rename": (msg, ws) => handleSessionRename(
9223
+ sessionsCtx,
9224
+ ws,
9225
+ msg.payload.id,
9226
+ msg.payload.name ?? ""
9227
+ ),
9189
9228
  "session.delete": (msg, ws) => handleSessionDelete(sessionsCtx, ws, msg.payload.id),
9190
9229
  "session.save": (_msg, ws) => handleSessionSave(sessionsCtx, ws),
9191
9230
  "session.resume": (msg, ws) => handleSessionResume(sessionsCtx, ws, msg.payload.id),
@@ -12110,7 +12149,7 @@ async function resolveModelSelection(answer, models, provider, _registry, render
12110
12149
  init_provider_config_utils();
12111
12150
  function renderProviderLine(renderer, id, cfg, idx) {
12112
12151
  const keys = normalizeKeys(cfg);
12113
- const active = activeLabel(cfg, keys);
12152
+ const active2 = activeLabel(cfg, keys);
12114
12153
  const firstKey = keys[0];
12115
12154
  let summary;
12116
12155
  if (keys.length === 0) {
@@ -12118,8 +12157,8 @@ function renderProviderLine(renderer, id, cfg, idx) {
12118
12157
  } else if (keys.length === 1) {
12119
12158
  summary = maskedKey(firstKey?.apiKey ?? "");
12120
12159
  } else {
12121
- const activeKeyObj = active != null ? keys.find((k) => k.label === active) : void 0;
12122
- summary = `${color.dim(`${keys.length} keys`)} ${color.dim("active:")} ${color.bold(active ?? "?")} ` + maskedKey(activeKeyObj?.apiKey ?? firstKey?.apiKey ?? "");
12160
+ const activeKeyObj = active2 != null ? keys.find((k) => k.label === active2) : void 0;
12161
+ summary = `${color.dim(`${keys.length} keys`)} ${color.dim("active:")} ${color.bold(active2 ?? "?")} ` + maskedKey(activeKeyObj?.apiKey ?? firstKey?.apiKey ?? "");
12123
12162
  }
12124
12163
  const fam = cfg.family ? color.dim(`[${cfg.family}]`) : "";
12125
12164
  const aliasHint = cfg.type && cfg.type !== id ? color.dim(`\u2192 ${cfg.type}`) : "";
@@ -12130,7 +12169,7 @@ function renderProviderLine(renderer, id, cfg, idx) {
12130
12169
  }
12131
12170
  function renderProviderHeader(renderer, providerId, cfg) {
12132
12171
  const keys = normalizeKeys(cfg);
12133
- const active = activeLabel(cfg, keys);
12172
+ const active2 = activeLabel(cfg, keys);
12134
12173
  renderer.write(
12135
12174
  `
12136
12175
  ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.amber("[no family]")}
@@ -12153,12 +12192,12 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
12153
12192
  } else {
12154
12193
  for (let i = 0; i < keys.length; i++) {
12155
12194
  const key = keys[i];
12156
- if (key) renderKeyLine(renderer, key, i + 1, active);
12195
+ if (key) renderKeyLine(renderer, key, i + 1, active2);
12157
12196
  }
12158
12197
  }
12159
12198
  }
12160
- function renderKeyLine(renderer, key, idx, active) {
12161
- const marker = key.label === active ? color.green("\u25CF") : color.dim("\u25CB");
12199
+ function renderKeyLine(renderer, key, idx, active2) {
12200
+ const marker = key.label === active2 ? color.green("\u25CF") : color.dim("\u25CB");
12162
12201
  renderer.write(
12163
12202
  ` ${color.dim(`${idx}.`.padStart(4))} ${marker} ${key.label.padEnd(20)} ${maskedKey(key.apiKey)} ${color.dim(key.createdAt)}
12164
12203
  `
@@ -12877,12 +12916,12 @@ function createAuthPanelHost(deps) {
12877
12916
  const cfg = providers[id];
12878
12917
  if (!cfg) continue;
12879
12918
  const keys = normalizeKeys(cfg);
12880
- const active = activeLabel(cfg, keys);
12919
+ const active2 = activeLabel(cfg, keys);
12881
12920
  const keyRows = keys.map((k) => ({
12882
12921
  label: k.label,
12883
12922
  masked: plainMaskedKey(k.apiKey),
12884
12923
  createdAt: k.createdAt,
12885
- active: k.label === active,
12924
+ active: k.label === active2,
12886
12925
  authMethod: k.authMethod,
12887
12926
  expiresAt: k.expiresAt
12888
12927
  }));
@@ -13250,7 +13289,7 @@ async function readScripts(cwd) {
13250
13289
  }
13251
13290
  function createAutoPhaseHost(deps) {
13252
13291
  const store = new PhaseStore({ baseDir: deps.storeDir });
13253
- let active = null;
13292
+ let active2 = null;
13254
13293
  const log = deps.log ?? (() => {
13255
13294
  });
13256
13295
  async function runOnce(prompt, label, signal, cwd) {
@@ -13349,7 +13388,7 @@ ${res.out}` };
13349
13388
  }
13350
13389
  return {
13351
13390
  async onAutoPhaseStart({ goal, projectContext }) {
13352
- if (active?.orchestrator.isRunning()) {
13391
+ if (active2?.orchestrator.isRunning()) {
13353
13392
  return {
13354
13393
  ok: false,
13355
13394
  error: "An AutoPhase run is already in progress. Use /autophase stop first."
@@ -13415,7 +13454,7 @@ ${res.out}` };
13415
13454
  const nick = assignNickname("executor", usedNicknames);
13416
13455
  usedNicknames.add(nick.key);
13417
13456
  agentName = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
13418
- active?.orchestrator.setTaskAssignee(task.id, void 0, agentName);
13457
+ active2?.orchestrator.setTaskAssignee(task.id, void 0, agentName);
13419
13458
  }
13420
13459
  return runOnce(
13421
13460
  buildTaskPrompt(task, phaseName, goal),
@@ -13474,9 +13513,9 @@ ${res.out}` };
13474
13513
  const onUntyped = (event, handler) => busOn.on(event, handler);
13475
13514
  const offUntyped = (event, handler) => busOn.off(event, handler);
13476
13515
  const finalizeActiveRun = () => {
13477
- if (active?.graph.id !== graph.id) return;
13478
- active.unsubscribe();
13479
- active = null;
13516
+ if (active2?.graph.id !== graph.id) return;
13517
+ active2.unsubscribe();
13518
+ active2 = null;
13480
13519
  };
13481
13520
  const onDone = () => {
13482
13521
  log(`\u{1F389} AutoPhase complete: ${graph.title}`);
@@ -13493,7 +13532,7 @@ ${res.out}` };
13493
13532
  offUntyped("graph.completed", onDone);
13494
13533
  offUntyped("graph.failed", onFailed);
13495
13534
  };
13496
- active = { graph, orchestrator, abort, unsubscribe };
13535
+ active2 = { graph, orchestrator, abort, unsubscribe };
13497
13536
  void orchestrator.start().catch((err) => {
13498
13537
  log(`\u{1F4A5} AutoPhase aborted: ${err instanceof Error ? err.message : String(err)}`);
13499
13538
  void persist(graph);
@@ -13502,22 +13541,22 @@ ${res.out}` };
13502
13541
  return { ok: true, graph };
13503
13542
  },
13504
13543
  onAutoPhasePause() {
13505
- active?.orchestrator.pause();
13544
+ active2?.orchestrator.pause();
13506
13545
  },
13507
13546
  onAutoPhaseResume() {
13508
- active?.orchestrator.resume();
13547
+ active2?.orchestrator.resume();
13509
13548
  },
13510
13549
  onAutoPhaseStop() {
13511
- if (!active) return;
13512
- active.abort.abort();
13513
- active.orchestrator.stop();
13514
- active.unsubscribe();
13515
- void persist(active.graph);
13516
- active = null;
13550
+ if (!active2) return;
13551
+ active2.abort.abort();
13552
+ active2.orchestrator.stop();
13553
+ active2.unsubscribe();
13554
+ void persist(active2.graph);
13555
+ active2 = null;
13517
13556
  },
13518
13557
  getAutoPhaseRunner() {
13519
- if (!active) return null;
13520
- const a = active;
13558
+ if (!active2) return null;
13559
+ const a = active2;
13521
13560
  return {
13522
13561
  graph: a.graph,
13523
13562
  getProgress: () => a.orchestrator.getProgress(),
@@ -13525,27 +13564,27 @@ ${res.out}` };
13525
13564
  };
13526
13565
  },
13527
13566
  onAutoPhaseMoveTask(taskId, toPhaseId) {
13528
- if (!active) return false;
13529
- const ok = active.orchestrator.moveTask(taskId, toPhaseId);
13530
- if (ok) void persist(active.graph);
13567
+ if (!active2) return false;
13568
+ const ok = active2.orchestrator.moveTask(taskId, toPhaseId);
13569
+ if (ok) void persist(active2.graph);
13531
13570
  return ok;
13532
13571
  },
13533
13572
  onAutoPhaseAssignTask(taskId, agentId, agentName) {
13534
- if (!active) return false;
13535
- const ok = active.orchestrator.setTaskAssignee(taskId, agentId, agentName);
13536
- if (ok) void persist(active.graph);
13573
+ if (!active2) return false;
13574
+ const ok = active2.orchestrator.setTaskAssignee(taskId, agentId, agentName);
13575
+ if (ok) void persist(active2.graph);
13537
13576
  return ok;
13538
13577
  },
13539
13578
  onAutoPhaseAddTask(phaseId, spec) {
13540
- if (!active) return null;
13541
- const id = active.orchestrator.addTask(phaseId, spec);
13542
- if (id) void persist(active.graph);
13579
+ if (!active2) return null;
13580
+ const id = active2.orchestrator.addTask(phaseId, spec);
13581
+ if (id) void persist(active2.graph);
13543
13582
  return id;
13544
13583
  },
13545
13584
  onAutoPhaseRetryTask(taskId) {
13546
- if (!active) return false;
13547
- const ok = active.orchestrator.requeueTask(taskId);
13548
- if (ok) void persist(active.graph);
13585
+ if (!active2) return false;
13586
+ const ok = active2.orchestrator.requeueTask(taskId);
13587
+ if (ok) void persist(active2.graph);
13549
13588
  return ok;
13550
13589
  },
13551
13590
  async onWorktree(action, target) {
@@ -13750,8 +13789,8 @@ function parseSpawnFlags(input) {
13750
13789
  }
13751
13790
  function resolveKey(cfg) {
13752
13791
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
13753
- const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
13754
- return (active ?? cfg.apiKeys[0])?.apiKey;
13792
+ const active2 = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
13793
+ return (active2 ?? cfg.apiKeys[0])?.apiKey;
13755
13794
  }
13756
13795
  return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
13757
13796
  }
@@ -14334,7 +14373,7 @@ function buildAuthCommand(opts) {
14334
14373
  };
14335
14374
  }
14336
14375
  const keys = cfg.apiKeys ?? [];
14337
- const active = keys.find((k) => cfg && cfg.activeKey === k.label) ?? keys[0];
14376
+ const active2 = keys.find((k) => cfg && cfg.activeKey === k.label) ?? keys[0];
14338
14377
  const lines2 = [
14339
14378
  `${color.bold(pid)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.amber("[no family]")}`,
14340
14379
  "",
@@ -14354,8 +14393,8 @@ function buildAuthCommand(opts) {
14354
14393
  } else {
14355
14394
  lines2.push(` ${color.dim("Keys:")}`);
14356
14395
  for (const k of keys) {
14357
- const marker = k.label === active?.label ? color.green("\u25CF") : color.dim("\u25CB");
14358
- const masked = k.label === active?.label ? color.dim("(active \u2014 masked)") : color.dim("(masked)");
14396
+ const marker = k.label === active2?.label ? color.green("\u25CF") : color.dim("\u25CB");
14397
+ const masked = k.label === active2?.label ? color.dim("(active \u2014 masked)") : color.dim("(masked)");
14359
14398
  lines2.push(
14360
14399
  ` ${marker} ${color.bold(k.label.padEnd(18))} ${masked} ${color.dim(k.createdAt)}`
14361
14400
  );
@@ -15280,9 +15319,9 @@ function buildContextCommand(opts) {
15280
15319
  }
15281
15320
  const trimmed = args.trim();
15282
15321
  if (trimmed === "mode" || trimmed === "modes") {
15283
- const active = readPolicy(ctx)?.id ?? "balanced";
15322
+ const active2 = readPolicy(ctx)?.id ?? "balanced";
15284
15323
  const msg2 = `${color.bold("Context Window Modes")}
15285
- ${formatContextWindowModeList(active)}`;
15324
+ ${formatContextWindowModeList(active2)}`;
15286
15325
  opts.renderer.write(`${msg2}
15287
15326
  `);
15288
15327
  return { message: msg2 };
@@ -16145,7 +16184,7 @@ function scanPlaintextSecrets(node, prefix, findings) {
16145
16184
  for (const [key, value] of Object.entries(node)) {
16146
16185
  const path62 = prefix ? `${prefix}.${key}` : key;
16147
16186
  if (typeof value === "string") {
16148
- if (value.length > 0 && isSecretField$1(key) && !value.startsWith(ENC_PREFIX)) {
16187
+ if (value.length > 0 && isSecretField(key) && !value.startsWith(ENC_PREFIX)) {
16149
16188
  findings.push({
16150
16189
  path: path62,
16151
16190
  problem: "looks like a plaintext secret (not vault-encrypted) \u2014 it will be encrypted on next boot",
@@ -20587,20 +20626,20 @@ ${color.dim("Pin one with /design <kit-id> [stack].")}`
20587
20626
  return { message: color.green(`Overrides set: ${list || "(none)"}`) };
20588
20627
  }
20589
20628
  if (sub === "materialize") {
20590
- const active = await loadActiveKit(opts.projectRoot);
20591
- if (!active) {
20629
+ const active2 = await loadActiveKit(opts.projectRoot);
20630
+ if (!active2) {
20592
20631
  return { message: "No active kit. Pin one first: /design <kit-id>." };
20593
20632
  }
20594
20633
  const stackArg2 = tokens[1]?.toLowerCase();
20595
- const matStack = stackArg2 && isDesignStack(stackArg2) ? stackArg2 : active.stack && isDesignStack(active.stack) ? active.stack : "web";
20634
+ const matStack = stackArg2 && isDesignStack(stackArg2) ? stackArg2 : active2.stack && isDesignStack(active2.stack) ? active2.stack : "web";
20596
20635
  const outPath = stackArg2 && !isDesignStack(stackArg2) ? tokens[1] : tokens[2];
20597
- const raw = await loader.readTokens(active.kit);
20598
- if (!raw) return { message: `Kit "${active.kit}" has no tokens.json.` };
20599
- const merged = applyTokenOverrides(raw, active.overrides);
20636
+ const raw = await loader.readTokens(active2.kit);
20637
+ if (!raw) return { message: `Kit "${active2.kit}" has no tokens.json.` };
20638
+ const merged = applyTokenOverrides(raw, active2.overrides);
20600
20639
  const result = materializeTokens({
20601
20640
  tokens: merged,
20602
20641
  stack: matStack,
20603
- kitId: active.kit,
20642
+ kitId: active2.kit,
20604
20643
  outPath
20605
20644
  });
20606
20645
  const abs = path5.join(opts.projectRoot, result.path);
@@ -20635,8 +20674,8 @@ ${menu}` };
20635
20674
  }
20636
20675
  async function runModePicker(modeStore, reader) {
20637
20676
  const modes = await modeStore.listModes();
20638
- const active = await modeStore.getActiveMode();
20639
- const activeIdx = modes.findIndex((m) => m.id === active?.id);
20677
+ const active2 = await modeStore.getActiveMode();
20678
+ const activeIdx = modes.findIndex((m) => m.id === active2?.id);
20640
20679
  let cursor = activeIdx >= 0 ? activeIdx : 0;
20641
20680
  const render = (currentCursor) => {
20642
20681
  const lines = [];
@@ -20646,7 +20685,7 @@ ${color.bold(color.amber("WrongStack") + color.dim(" \u2014 Mode Selection"))}
20646
20685
  lines.push(color.dim(" \u2191\u2193 navigate Enter select q quit\n"));
20647
20686
  lines.push("");
20648
20687
  for (const [i, m] of modes.entries()) {
20649
- const mark = m.id === active?.id ? color.green(" [active]") : "";
20688
+ const mark = m.id === active2?.id ? color.green(" [active]") : "";
20650
20689
  const prefix = i === currentCursor ? color.bold("\u276F ") : " ";
20651
20690
  const name = i === currentCursor ? color.bold(m.name) : m.name;
20652
20691
  lines.push(` ${prefix}${name} ${color.dim(m.description)}${mark}`);
@@ -20700,7 +20739,7 @@ function buildModeCommand(opts) {
20700
20739
  return { message: "Mode store not available in this context." };
20701
20740
  }
20702
20741
  const modes = await modeStore.listModes();
20703
- const active = await modeStore.getActiveMode();
20742
+ const active2 = await modeStore.getActiveMode();
20704
20743
  if (!args.trim()) {
20705
20744
  if (opts.inputReader) {
20706
20745
  const selected = await runModePicker(modeStore, opts.inputReader);
@@ -20714,9 +20753,9 @@ function buildModeCommand(opts) {
20714
20753
  ${selected.description}`
20715
20754
  };
20716
20755
  }
20717
- const lines = [`Current mode: ${active?.name ?? "none"}`, "", "Available modes:"];
20756
+ const lines = [`Current mode: ${active2?.name ?? "none"}`, "", "Available modes:"];
20718
20757
  for (const m of modes) {
20719
- const mark = m.id === active?.id ? " [active]" : "";
20758
+ const mark = m.id === active2?.id ? " [active]" : "";
20720
20759
  lines.push(` ${m.id} \u2014 ${m.description}${mark}`);
20721
20760
  }
20722
20761
  return { message: lines.join("\n") };
@@ -21508,6 +21547,51 @@ function buildLoadCommand(opts) {
21508
21547
  }
21509
21548
  return killSession(targetId, force ? void 0 : opts.confirm);
21510
21549
  }
21550
+ if (first === "rename") {
21551
+ const targetId = parts[1];
21552
+ if (!targetId) {
21553
+ return { message: color.yellow("Usage: /sessions rename <sessionId> [name...] (empty name clears)") };
21554
+ }
21555
+ if (!opts.sessionStore) {
21556
+ return { message: color.yellow("No session store configured.") };
21557
+ }
21558
+ const nameParts = parts.slice(2);
21559
+ const name = nameParts.join(" ").trim();
21560
+ try {
21561
+ const summary = await opts.sessionStore.rename(targetId, name);
21562
+ return {
21563
+ message: name ? color.green(`Renamed ${targetId} \u2192 "${name}"`) : color.green(`Cleared name on ${targetId} (title: "${summary.title}")`)
21564
+ };
21565
+ } catch (err) {
21566
+ return { message: color.red(`Rename failed: ${toErrorMessage$1(err)}`) };
21567
+ }
21568
+ }
21569
+ if (first === "delete") {
21570
+ const targetId = parts[1];
21571
+ if (!targetId) {
21572
+ return { message: color.yellow("Usage: /sessions delete <sessionId> [--force]") };
21573
+ }
21574
+ if (!opts.sessionStore) {
21575
+ return { message: color.yellow("No session store configured.") };
21576
+ }
21577
+ const currentId2 = opts.context?.session?.id;
21578
+ if (targetId === currentId2) {
21579
+ return {
21580
+ message: color.yellow("Cannot delete the active session. Resume or start another first.")
21581
+ };
21582
+ }
21583
+ const force = parts.includes("--force") || parts.includes("-y");
21584
+ if (!force && opts.confirm) {
21585
+ const ok = await opts.confirm(`Delete session ${targetId}? This cannot be undone.`, false);
21586
+ if (ok !== true) return { message: color.dim("Delete cancelled.") };
21587
+ }
21588
+ try {
21589
+ await opts.sessionStore.delete(targetId);
21590
+ return { message: color.green(`Deleted session ${targetId}`) };
21591
+ } catch (err) {
21592
+ return { message: color.red(`Delete failed: ${toErrorMessage$1(err)}`) };
21593
+ }
21594
+ }
21511
21595
  const showIncomplete = parts.includes("--incomplete") || parts.includes("-i");
21512
21596
  const recoverIdx = parts.indexOf("--recover");
21513
21597
  const recoverTarget = recoverIdx >= 0 ? parts[recoverIdx + 1] : void 0;
@@ -21604,7 +21688,8 @@ function buildLoadCommand(opts) {
21604
21688
  const date = color.dim(s.startedAt.slice(0, 16).replace("T", " "));
21605
21689
  const isCurrent = s.id === currentId;
21606
21690
  const marker = isCurrent ? color.cyan(" (current)") : "";
21607
- return ` ${color.bold(s.id)}${marker}
21691
+ const label = s.name ? `${color.bold(s.id)} ${color.cyan(`(${s.name})`)}` : color.bold(s.id);
21692
+ return ` ${label}${marker}
21608
21693
  ${date} ${stat8}
21609
21694
  ${color.dim(s.title)}`;
21610
21695
  });
@@ -23445,10 +23530,13 @@ function scanCommand(opts) {
23445
23530
  ""
23446
23531
  ];
23447
23532
  lines.push(color.dim(" The TUI is a synchronous surface and cannot host a subagent itself."));
23448
- lines.push(color.dim(" Dispatch from any of these surfaces instead:"));
23533
+ lines.push(color.dim(" Start or use a director-capable session, then dispatch with:"));
23449
23534
  lines.push("");
23450
- lines.push(color.cyan(" pnpm --filter @wrongstack/cli security:scan"));
23451
- lines.push(color.cyan(" \u2192 /collab with bug-hunter role from a subagent session"));
23535
+ lines.push(color.cyan(" /director"));
23536
+ lines.push(
23537
+ color.cyan(' /delegate --role=bug-hunter "Run a security scan of the current project"')
23538
+ );
23539
+ lines.push(color.cyan(" /fleet dispatch Run a security scan of the current project"));
23452
23540
  lines.push(color.cyan(" \u2192 HQ \u2192 Security \u2192 Run scan"));
23453
23541
  lines.push("");
23454
23542
  lines.push(color.dim(" The subagent role is `bug-hunter`. Findings stream on the"));
@@ -26487,6 +26575,97 @@ function parseFlags2(args) {
26487
26575
  );
26488
26576
  }
26489
26577
 
26578
+ // src/fleet/supervisor-registry.ts
26579
+ var active = null;
26580
+ function setActiveFleetSupervisor(supervisor) {
26581
+ active = supervisor;
26582
+ }
26583
+ function getActiveFleetSupervisor() {
26584
+ return active;
26585
+ }
26586
+
26587
+ // src/slash-commands/supervisor.ts
26588
+ function fmtAge3(at) {
26589
+ const s = Math.max(0, Math.round((Date.now() - at) / 1e3));
26590
+ if (s < 60) return `${s}s ago`;
26591
+ if (s < 3600) return `${Math.round(s / 60)}m ago`;
26592
+ return `${Math.round(s / 3600)}h ago`;
26593
+ }
26594
+ var NO_SUPERVISOR_MSG = "No fleet supervisor is active. It starts automatically with the Director fleet (--director / first delegate) when a Brain is available and config fleet.supervisor.enabled is not false.";
26595
+ function buildSupervisorCommand(opts) {
26596
+ return {
26597
+ name: "supervisor",
26598
+ category: "Agent",
26599
+ argsHint: "[status|on|off|log [n]]",
26600
+ description: "Inspect or toggle the fleet supervisor (brain-gated rebalance/steer watcher).",
26601
+ help: [
26602
+ "Usage:",
26603
+ " /supervisor Show supervisor status (running state, config, last activity)",
26604
+ " /supervisor status Same as /supervisor",
26605
+ " /supervisor on Arm the supervision loop for this session",
26606
+ " /supervisor off Disarm it (no further automatic interventions)",
26607
+ " /supervisor log [n] Show the last n supervision entries (default 10)",
26608
+ "",
26609
+ "The supervisor watches the Director fleet and, gated by the Brain",
26610
+ "(/brain risk), rebalances pending tasks off busy workers, spawns",
26611
+ "helpers on deep backlogs, and steers stuck or failing workers.",
26612
+ "Configure via ~/.wrongstack/config.json \u2192 fleet.supervisor."
26613
+ ].join("\n"),
26614
+ async run(args) {
26615
+ const supervisor = getActiveFleetSupervisor();
26616
+ const sub = (args.trim().split(/\s+/)[0] ?? "").toLowerCase();
26617
+ if (!supervisor) {
26618
+ opts.renderer.writeWarning(NO_SUPERVISOR_MSG);
26619
+ return { message: NO_SUPERVISOR_MSG };
26620
+ }
26621
+ if (sub === "on") {
26622
+ supervisor.start();
26623
+ const msg2 = `Fleet supervisor ${color.green("armed")} \u2014 evaluating every ${Math.round(supervisor.configSnapshot().intervalMs / 1e3)}s.`;
26624
+ opts.renderer.write(msg2);
26625
+ return { message: msg2 };
26626
+ }
26627
+ if (sub === "off") {
26628
+ supervisor.stop();
26629
+ const msg2 = `Fleet supervisor ${color.yellow("disarmed")} \u2014 no further automatic interventions this session.`;
26630
+ opts.renderer.write(msg2);
26631
+ return { message: msg2 };
26632
+ }
26633
+ if (sub === "log") {
26634
+ const n = Math.max(1, Number.parseInt(args.trim().split(/\s+/)[1] ?? "10", 10) || 10);
26635
+ const entries = supervisor.history().slice(-n);
26636
+ if (entries.length === 0) {
26637
+ const msg3 = "No supervision activity yet.";
26638
+ opts.renderer.write(msg3);
26639
+ return { message: msg3 };
26640
+ }
26641
+ const lines2 = entries.map((e) => {
26642
+ const who = e.subagentId ? ` ${color.cyan(e.subagentId)}` : "";
26643
+ const task = e.taskId ? ` task=${e.taskId.slice(0, 8)}` : "";
26644
+ const outcome = e.outcome === "approved" ? color.green(e.outcome) : e.outcome === "denied" || e.outcome === "error" ? color.red(e.outcome) : color.yellow(e.outcome);
26645
+ return `${color.dim(fmtAge3(e.at))} ${e.kind}${who}${task} \u2192 ${e.proposedAction} [${outcome}] ${color.dim(e.detail)}`;
26646
+ });
26647
+ const msg2 = lines2.join("\n");
26648
+ opts.renderer.write(msg2);
26649
+ return { message: msg2 };
26650
+ }
26651
+ const cfg = supervisor.configSnapshot();
26652
+ const history = supervisor.history();
26653
+ const last = history[history.length - 1];
26654
+ const lines = [
26655
+ `Fleet supervisor: ${supervisor.isRunning() ? color.green("armed") : color.yellow("disarmed")}`,
26656
+ ` interval ${Math.round(cfg.intervalMs / 1e3)}s \xB7 cooldown ${Math.round(cfg.cooldownMs / 1e3)}s \xB7 max ${cfg.maxInterventionsPerSubagent} interventions/agent`,
26657
+ ` signals: starvation>${Math.round(cfg.pinnedWaitMs / 1e3)}s \xB7 overload\u2265${cfg.overloadPinnedThreshold} pinned \xB7 backlog>${cfg.backlogFactor}\xD7workers \xB7 stuck>${Math.round(cfg.stuckMs / 1e3)}s \xB7 failstreak\u2265${cfg.failureStreak}`,
26658
+ ` actions: retarget \u2713 \xB7 spawn ${cfg.allowSpawn ? "\u2713" : "\u2717"} \xB7 steer \u2713 \xB7 terminate ${cfg.allowTerminate ? "\u2713" : "\u2717 (config fleet.supervisor.allowTerminate)"}`,
26659
+ ` activity: ${history.length} engagement(s)${last ? ` \u2014 last: ${last.kind} \u2192 ${last.proposedAction} [${last.outcome}] ${fmtAge3(last.at)}` : ""}`,
26660
+ color.dim(" decisions are gated by the Brain \u2014 see /brain (risk ceiling applies)")
26661
+ ];
26662
+ const msg = lines.join("\n");
26663
+ opts.renderer.write(msg);
26664
+ return { message: msg };
26665
+ }
26666
+ };
26667
+ }
26668
+
26490
26669
  // src/slash-commands/audit.ts
26491
26670
  function buildAuditCommand(opts) {
26492
26671
  return {
@@ -26624,6 +26803,7 @@ function buildBuiltinSlashCommands(opts) {
26624
26803
  onPanelOpen: opts.onPanelOpen
26625
26804
  }),
26626
26805
  buildShadowCommand(opts),
26806
+ buildSupervisorCommand(opts),
26627
26807
  buildAuditCommand(opts)
26628
26808
  ];
26629
26809
  }
@@ -28778,7 +28958,7 @@ ${color.bold("Saved providers")} ${color.dim(`(${ids.length})`)}
28778
28958
  const cfg = providers[id];
28779
28959
  if (!cfg) continue;
28780
28960
  const keys = normalizeKeys(cfg);
28781
- const active = cfg.activeKey ?? keys[0]?.label;
28961
+ const active2 = cfg.activeKey ?? keys[0]?.label;
28782
28962
  const famTag = cfg.family ? `${cfg.family}` : color.amber("no-family");
28783
28963
  const aliasHint = cfg.type && cfg.type !== id ? color.dim(` (\u2192 ${cfg.type})`) : "";
28784
28964
  const modelHint = cfg.models && cfg.models.length > 0 ? color.dim(` [${cfg.models.length} models]`) : "";
@@ -28797,7 +28977,7 @@ ${color.bold("Saved providers")} ${color.dim(`(${ids.length})`)}
28797
28977
  `
28798
28978
  );
28799
28979
  for (const k of keys) {
28800
- const marker = k.label === active ? color.green("\u25CF") : color.dim("\u25CB");
28980
+ const marker = k.label === active2 ? color.green("\u25CF") : color.dim("\u25CB");
28801
28981
  deps.renderer.write(
28802
28982
  ` ${marker} ${k.label.padEnd(18)} ${maskedKey(k.apiKey)} ${color.dim(k.createdAt)}
28803
28983
  `
@@ -28828,7 +29008,7 @@ async function runAuthStatus(deps, providerId) {
28828
29008
  return 1;
28829
29009
  }
28830
29010
  const keys = normalizeKeys(cfg);
28831
- const active = cfg.activeKey ?? keys[0]?.label;
29011
+ const active2 = cfg.activeKey ?? keys[0]?.label;
28832
29012
  const lines = [
28833
29013
  `
28834
29014
  ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.amber("[no family]")}`,
@@ -28849,7 +29029,7 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
28849
29029
  } else {
28850
29030
  lines.push(` ${color.dim("Keys:")}`);
28851
29031
  for (const k of keys) {
28852
- const marker = k.label === active ? color.green("\u25CF") : color.dim("\u25CB");
29032
+ const marker = k.label === active2 ? color.green("\u25CF") : color.dim("\u25CB");
28853
29033
  lines.push(
28854
29034
  ` ${marker} ${color.bold(k.label.padEnd(18))} ${maskedKey(k.apiKey)} ${color.dim(k.createdAt)}`
28855
29035
  );
@@ -34003,6 +34183,9 @@ function registerBuiltinTools(deps) {
34003
34183
  deps.toolRegistry.register(
34004
34184
  makeMailInboxTool({ projectDir: deps.wpaths.projectDir, events: deps.events })
34005
34185
  );
34186
+ deps.toolRegistry.register(
34187
+ makeFleetStatusTool({ projectDir: deps.wpaths.projectDir, events: deps.events })
34188
+ );
34006
34189
  applyToolDescriptionModes(deps.toolRegistry, deps.config.tools?.descriptionMode);
34007
34190
  applyToolResultRenderModes(deps.toolRegistry, deps.config.tools?.resultRenderMode);
34008
34191
  if (deps.config.tools?.disabledTools) {
@@ -35478,6 +35661,10 @@ async function resumeSession(ctx, sessionId) {
35478
35661
  return null;
35479
35662
  }
35480
35663
  }
35664
+ var ANIMATION_STYLES = ["rainbow", "wave", "pulse", "dots", "breathe", "cycle"];
35665
+ function normalizeAnimationStyle(raw) {
35666
+ return typeof raw === "string" && ANIMATION_STYLES.includes(raw) ? raw : "rainbow";
35667
+ }
35481
35668
  function createSettingsAdapter(ctx) {
35482
35669
  const { configStore, wpaths, fleetStreamController, applyLiveSettings } = ctx;
35483
35670
  const deriveFsAccess = deriveFsAccessPair;
@@ -35524,6 +35711,7 @@ function createSettingsAdapter(ctx) {
35524
35711
  debugStream: cfg.debugStream ?? false,
35525
35712
  statuslineMode: autonomy?.statuslineMode === "minimum" ? "minimum" : "detailed",
35526
35713
  thinkingWord: normalizeTuiThinkingWord(autonomy?.thinkingWord),
35714
+ animationStyle: normalizeAnimationStyle(autonomy?.animationStyle),
35527
35715
  configScope: cfg.configScope ?? "global",
35528
35716
  enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4,
35529
35717
  enhanceEnabled: cfg.autonomy?.enhance ?? true,
@@ -35541,7 +35729,7 @@ function createSettingsAdapter(ctx) {
35541
35729
  }
35542
35730
  async function saveSettings(s) {
35543
35731
  try {
35544
- if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.streamFleet !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0) {
35732
+ if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.streamFleet !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0) {
35545
35733
  const configScope = s.configScope ?? configStore.get().configScope ?? "global";
35546
35734
  const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
35547
35735
  let raw;
@@ -35574,6 +35762,7 @@ function createSettingsAdapter(ctx) {
35574
35762
  if (s.statuslineMode !== void 0) autonomy.statuslineMode = s.statuslineMode;
35575
35763
  if (s.thinkingWord !== void 0)
35576
35764
  autonomy.thinkingWord = normalizeTuiThinkingWord(s.thinkingWord);
35765
+ if (s.animationStyle !== void 0) autonomy.animationStyle = s.animationStyle;
35577
35766
  if (s.autonomyNextPrompt !== void 0) autonomy.autonomyNextPrompt = s.autonomyNextPrompt;
35578
35767
  if (s.autoProceedMaxIterations !== void 0)
35579
35768
  autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
@@ -37482,7 +37671,7 @@ async function execute(deps) {
37482
37671
  onProjectSelect: (slug, kind) => onProjectSelect(pickerCtx, slug, kind),
37483
37672
  // `wrongstack quick` sets flags.quick — open the F3 agents monitor by default.
37484
37673
  initialAgentsMonitorOpen: !!flags.quick,
37485
- tokenSavingMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off",
37674
+ tokenSavingMode: normalizeTokenSavingTier(config.features.tokenSavingMode),
37486
37675
  toolCount: agent.tools.list().length,
37487
37676
  onPanelOpen
37488
37677
  });
@@ -37785,6 +37974,137 @@ function buildRoutingRunner(config, host) {
37785
37974
  };
37786
37975
  }
37787
37976
 
37977
+ // src/fleet/status-broadcast.ts
37978
+ var DEFAULT_MIN_INTERVAL_MS = 15e3;
37979
+ var DEFAULT_GLOBAL_PER_MINUTE = 20;
37980
+ var BODY_MAX = 200;
37981
+ function createFleetStatusBroadcaster(opts) {
37982
+ const cfg = opts.config;
37983
+ const minIntervalMs = cfg?.minIntervalMsPerAgent ?? DEFAULT_MIN_INTERVAL_MS;
37984
+ const perMinuteCap = cfg?.globalPerMinuteCap ?? DEFAULT_GLOBAL_PER_MINUTE;
37985
+ const disabled = cfg?.enabled === false;
37986
+ let mailbox = null;
37987
+ const mb = () => {
37988
+ if (!mailbox) mailbox = opts.mailboxFactory();
37989
+ return mailbox;
37990
+ };
37991
+ const nameOf = (id) => opts.subagentName?.(id) ?? id;
37992
+ const from = () => `fleet@${opts.sessionTag()}`;
37993
+ const lastSentAt = /* @__PURE__ */ new Map();
37994
+ const coalesced = /* @__PURE__ */ new Map();
37995
+ const sentTimestamps = [];
37996
+ let droppedSinceLastSend = 0;
37997
+ const announcedSpawn = /* @__PURE__ */ new Set();
37998
+ const announcedBudget = /* @__PURE__ */ new Set();
37999
+ const disposers = [];
38000
+ let stopped = false;
38001
+ function underGlobalCap(now) {
38002
+ while (sentTimestamps.length > 0 && now - (sentTimestamps[0] ?? 0) > 6e4) {
38003
+ sentTimestamps.shift();
38004
+ }
38005
+ return sentTimestamps.length < perMinuteCap;
38006
+ }
38007
+ function sendNow(subagentId, p) {
38008
+ const now = Date.now();
38009
+ if (!underGlobalCap(now)) {
38010
+ droppedSinceLastSend++;
38011
+ return;
38012
+ }
38013
+ sentTimestamps.push(now);
38014
+ lastSentAt.set(subagentId, now);
38015
+ let body = p.body;
38016
+ if (droppedSinceLastSend > 0) {
38017
+ body = `${body} [${droppedSinceLastSend} earlier status broadcast(s) dropped by rate cap]`;
38018
+ droppedSinceLastSend = 0;
38019
+ }
38020
+ void mb().send({
38021
+ from: from(),
38022
+ to: "*",
38023
+ type: "status",
38024
+ subject: p.subject,
38025
+ body: body.length > BODY_MAX ? `${body.slice(0, BODY_MAX)}\u2026` : body,
38026
+ priority: p.priority
38027
+ }).catch(() => {
38028
+ });
38029
+ }
38030
+ function broadcast(subagentId, p) {
38031
+ if (stopped) return;
38032
+ const now = Date.now();
38033
+ const last = lastSentAt.get(subagentId) ?? 0;
38034
+ const waitMs = last + minIntervalMs - now;
38035
+ if (waitMs <= 0) {
38036
+ sendNow(subagentId, p);
38037
+ return;
38038
+ }
38039
+ const existing = coalesced.get(subagentId);
38040
+ if (existing) {
38041
+ existing.pending = p;
38042
+ return;
38043
+ }
38044
+ const timer = setTimeout(() => {
38045
+ const entry = coalesced.get(subagentId);
38046
+ coalesced.delete(subagentId);
38047
+ if (!stopped && entry) sendNow(subagentId, entry.pending);
38048
+ }, waitMs);
38049
+ timer.unref?.();
38050
+ coalesced.set(subagentId, { pending: p, timer });
38051
+ }
38052
+ function heartbeat(input) {
38053
+ void mb().heartbeat(input).catch(() => {
38054
+ });
38055
+ }
38056
+ return {
38057
+ start() {
38058
+ if (disabled || stopped) return;
38059
+ disposers.push(
38060
+ opts.events.on("subagent.spawned", (e) => {
38061
+ if (announcedSpawn.has(e.subagentId)) return;
38062
+ announcedSpawn.add(e.subagentId);
38063
+ broadcast(e.subagentId, {
38064
+ subject: `[${e.name ?? nameOf(e.subagentId)}] joined the fleet`,
38065
+ body: `${e.name ?? nameOf(e.subagentId)} spawned${e.model ? ` on ${e.model}` : ""}${e.description ? ` \u2014 ${e.description.slice(0, 80)}` : ""}`,
38066
+ priority: "low"
38067
+ });
38068
+ }),
38069
+ opts.events.on("subagent.task_started", (e) => {
38070
+ heartbeat({
38071
+ agentId: e.subagentId,
38072
+ status: "running",
38073
+ currentTask: e.description?.slice(0, 80)
38074
+ });
38075
+ }),
38076
+ opts.events.on("subagent.task_completed", (e) => {
38077
+ heartbeat({ agentId: e.subagentId, status: "idle" });
38078
+ const name = nameOf(e.subagentId);
38079
+ const ok = e.status === "success";
38080
+ const secs = Math.round(e.durationMs / 1e3);
38081
+ broadcast(e.subagentId, {
38082
+ subject: `[${name}] task ${e.status}`,
38083
+ body: `${name} finished a task: ${e.status} \u2014 ${e.iterations} iter, ${e.toolCalls} tools, ${secs}s${!ok && e.error ? ` (${e.error.kind})` : ""}`,
38084
+ priority: ok ? "low" : "normal"
38085
+ });
38086
+ }),
38087
+ opts.events.on("subagent.budget_warning", (e) => {
38088
+ const key = `${e.subagentId}|${e.kind}`;
38089
+ if (announcedBudget.has(key)) return;
38090
+ announcedBudget.add(key);
38091
+ broadcast(e.subagentId, {
38092
+ subject: `[${nameOf(e.subagentId)}] budget pressure`,
38093
+ body: `${nameOf(e.subagentId)} hit its ${e.kind} limit (${e.used}/${e.limit}) \u2014 coordinator negotiating`,
38094
+ priority: "low"
38095
+ });
38096
+ })
38097
+ );
38098
+ },
38099
+ stop() {
38100
+ stopped = true;
38101
+ for (const d of disposers.splice(0)) d();
38102
+ for (const { timer } of coalesced.values()) clearTimeout(timer);
38103
+ coalesced.clear();
38104
+ }
38105
+ };
38106
+ }
38107
+
37788
38108
  // src/fleet/host.ts
37789
38109
  function buildShadowAgentTaskDescription(reason) {
37790
38110
  return `Shadow Agent one-shot fleet monitor. Reason: ${reason}.
@@ -37861,6 +38181,14 @@ var MultiAgentHost = class _MultiAgentHost {
37861
38181
  shadowPassInFlight = false;
37862
38182
  shadowQueuedProblem = null;
37863
38183
  shadowActivityOffHandles = [];
38184
+ /** Peer-awareness status broadcaster (mailbox `status` mails on subagent
38185
+ * transitions + rich registry heartbeats). Started in buildDirector(),
38186
+ * stopped in dispose(). */
38187
+ statusBroadcaster = null;
38188
+ /** Brain-gated FleetSupervisor over this director's fleet. Built in
38189
+ * buildDirector() (when a BrainArbiter is available), stopped in
38190
+ * dispose(). Also published to the supervisor registry for /supervisor. */
38191
+ fleetSupervisor = null;
37864
38192
  /**
37865
38193
  * Force the lazy build path to run *now* and return the live Director,
37866
38194
  * or null when director mode is off. Used by the CLI to register the
@@ -37939,8 +38267,12 @@ var MultiAgentHost = class _MultiAgentHost {
37939
38267
  fleetManager,
37940
38268
  // pass so director.fleetManager is never undefined
37941
38269
  brain: this.opts.brain,
37942
- roster: FLEET_ROSTER
38270
+ roster: FLEET_ROSTER,
37943
38271
  // pass so spawn_subagent recognizes shadow-agent role
38272
+ // Fire-and-forget report-back: when an assign_task completes with no
38273
+ // pending await, post the result to this session's leader via the
38274
+ // project mailbox (injected inline before the leader's next step).
38275
+ taskResultNotifier: (n) => this.reportTaskResultToLeader(n)
37944
38276
  });
37945
38277
  this.director.on("task.completed", ({ task, result }) => {
37946
38278
  this.fleetManager?.removePendingTask(task.id);
@@ -37966,6 +38298,15 @@ var MultiAgentHost = class _MultiAgentHost {
37966
38298
  agentMonitor.setFleetBus(this.director.fleet);
37967
38299
  agentMonitor.start();
37968
38300
  }
38301
+ this.statusBroadcaster = createFleetStatusBroadcaster({
38302
+ events: this.deps.events,
38303
+ mailboxFactory: () => new GlobalMailbox(this.mailboxProjectDir(), this.deps.events),
38304
+ sessionTag: () => mailboxSessionTag(this.deps.session.id),
38305
+ subagentName: (id) => this.director?.status().subagents.find((s) => s.id === id)?.name,
38306
+ config: config.fleet?.statusBroadcasts
38307
+ });
38308
+ this.statusBroadcaster.start();
38309
+ this.buildFleetSupervisor(config);
37969
38310
  this.directorOffHandles.push(
37970
38311
  this.director.fleet.filter("budget.threshold_reached", (e) => {
37971
38312
  const payload = e.payload;
@@ -38251,7 +38592,7 @@ var MultiAgentHost = class _MultiAgentHost {
38251
38592
  const subCwd = subCfg.cwd ?? this.deps.cwd;
38252
38593
  let onlineAgents = [];
38253
38594
  try {
38254
- const subagentMailbox = new GlobalMailbox(this.deps.projectRoot);
38595
+ const subagentMailbox = new GlobalMailbox(this.mailboxProjectDir());
38255
38596
  onlineAgents = await subagentMailbox.getAgentStatuses();
38256
38597
  } catch {
38257
38598
  }
@@ -38607,6 +38948,107 @@ var MultiAgentHost = class _MultiAgentHost {
38607
38948
  }
38608
38949
  return [...caps];
38609
38950
  }
38951
+ /** Resolved shared-mailbox project dir — same as the leader's checker. */
38952
+ mailboxProjectDir() {
38953
+ return this.opts.mailboxProjectDir ?? resolveProjectDir(this.deps.projectRoot, wstackGlobalRoot());
38954
+ }
38955
+ /**
38956
+ * Construct + start the brain-gated FleetSupervisor over this director's
38957
+ * fleet. Requires a BrainArbiter (production always wires one); without
38958
+ * it there is no safe decision path, so supervision stays off. All
38959
+ * actions flow through Director APIs — the supervisor never touches
38960
+ * coordinator internals or budget negotiation.
38961
+ */
38962
+ buildFleetSupervisor(config) {
38963
+ const director = this.director;
38964
+ const brain = this.opts.brain;
38965
+ const supervisorCfg = config.fleet?.supervisor;
38966
+ if (!director || !brain || supervisorCfg?.enabled === false) return;
38967
+ const supTag = () => mailboxSessionTag(this.deps.session.id);
38968
+ const mailbox = () => new GlobalMailbox(this.mailboxProjectDir(), this.deps.events);
38969
+ this.fleetSupervisor = new FleetSupervisor({
38970
+ events: this.deps.events,
38971
+ fleet: director.fleet,
38972
+ brain,
38973
+ sessionId: () => this.deps.session.id,
38974
+ config: supervisorCfg,
38975
+ source: {
38976
+ subagents: () => director.status().subagents,
38977
+ listPendingTasks: () => director.listPendingTasks(),
38978
+ isWorkComplete: () => director.isWorkComplete()
38979
+ },
38980
+ actions: {
38981
+ retargetPendingTask: (taskId, subagentId) => director.retargetPendingTask(taskId, subagentId),
38982
+ spawnHelper: async ({ reason }) => {
38983
+ try {
38984
+ const subagentId = await director.spawn({
38985
+ id: `helper-${randomUUID().slice(0, 8)}`,
38986
+ name: "fleet helper",
38987
+ systemPromptOverride: `You are a helper worker spawned by the fleet supervisor to drain a task backlog (${reason}). Complete assigned tasks efficiently and report results.`
38988
+ });
38989
+ return { subagentId };
38990
+ } catch (err) {
38991
+ return { error: toErrorMessage(err) };
38992
+ }
38993
+ },
38994
+ steerAgent: async (subagentId, subject, body) => {
38995
+ await mailbox().send({
38996
+ from: `supervisor@${supTag()}`,
38997
+ to: `${subagentId}@${supTag()}`,
38998
+ type: "steer",
38999
+ subject,
39000
+ body,
39001
+ priority: "high"
39002
+ });
39003
+ },
39004
+ notifyLeader: async (subject, body) => {
39005
+ const leaderId2 = this.opts.getLeaderMailboxId?.() ?? `leader@${supTag()}`;
39006
+ await mailbox().send({
39007
+ from: `supervisor@${supTag()}`,
39008
+ to: leaderId2,
39009
+ type: "status",
39010
+ subject,
39011
+ body,
39012
+ priority: "normal"
39013
+ });
39014
+ },
39015
+ terminate: (subagentId) => director.terminate(subagentId)
39016
+ }
39017
+ });
39018
+ this.fleetSupervisor.start();
39019
+ setActiveFleetSupervisor(this.fleetSupervisor);
39020
+ }
39021
+ /**
39022
+ * Fire-and-forget report-back (wired as the Director's
39023
+ * `taskResultNotifier`): posts a non-awaited task's outcome to THIS
39024
+ * session's leader via the project mailbox. The leader's mailbox checker
39025
+ * injects it inline before the next step, so `assign_task`-without-await
39026
+ * results reach the conversation without polling. The body carries a
39027
+ * short excerpt only — the full result stays in the director's completed
39028
+ * cache, retrievable via `await_tasks`/`roll_up`.
39029
+ */
39030
+ async reportTaskResultToLeader(n) {
39031
+ try {
39032
+ const leaderId2 = this.opts.getLeaderMailboxId?.() ?? `leader@${mailboxSessionTag(this.deps.session.id)}`;
39033
+ const mailbox = new GlobalMailbox(this.mailboxProjectDir(), this.deps.events);
39034
+ const ok = n.status === "success";
39035
+ const MAX_BODY = 700;
39036
+ const raw = (ok ? n.resultText : n.errorText ?? n.status) ?? "(no textual result)";
39037
+ const excerpt = raw.length > MAX_BODY ? `${raw.slice(0, MAX_BODY)}\u2026` : raw;
39038
+ const title = n.title.length > 80 ? `${n.title.slice(0, 80)}\u2026` : n.title;
39039
+ await mailbox.send({
39040
+ from: n.subagentName ?? n.subagentId,
39041
+ to: leaderId2,
39042
+ type: "result",
39043
+ subject: `[task ${n.status}] ${title}`,
39044
+ body: `${excerpt}
39045
+
39046
+ (taskId: ${n.taskId} \u2014 ${n.iterations} iterations, ${n.toolCalls} tool calls, ${Math.round(n.durationMs / 1e3)}s. Full result: await_tasks(["${n.taskId}"]) or roll_up.)`,
39047
+ priority: ok ? "normal" : "high"
39048
+ });
39049
+ } catch {
39050
+ }
39051
+ }
38610
39052
  subagentToolRegistry(allow) {
38611
39053
  if (!allow || allow.length === 0) return this.deps.toolRegistry;
38612
39054
  const sub = new ToolRegistry();
@@ -38945,6 +39387,11 @@ var MultiAgentHost = class _MultiAgentHost {
38945
39387
  this.directorOffHandles.length = 0;
38946
39388
  this.coordinatorOffHandle?.();
38947
39389
  this.coordinatorOffHandle = null;
39390
+ this.statusBroadcaster?.stop();
39391
+ this.statusBroadcaster = null;
39392
+ this.fleetSupervisor?.stop();
39393
+ if (this.fleetSupervisor) setActiveFleetSupervisor(null);
39394
+ this.fleetSupervisor = null;
38948
39395
  this.adaptiveConcurrencyController?.dispose();
38949
39396
  this.adaptiveConcurrencyController = void 0;
38950
39397
  if (this.director) {
@@ -40150,12 +40597,12 @@ async function main(argv) {
40150
40597
  const p = all[providerId];
40151
40598
  if (!p) return;
40152
40599
  const keys = normalizeKeys(p);
40153
- const active = p.activeKey ? keys.find((k) => k.label === p.activeKey) : keys[0];
40154
- if (!active) return;
40155
- active.apiKey = creds.accessToken;
40156
- active.refreshToken = creds.refreshToken;
40157
- active.expiresAt = new Date(creds.expiresAt).toISOString();
40158
- if (creds.accountId) active.accountId = creds.accountId;
40600
+ const active2 = p.activeKey ? keys.find((k) => k.label === p.activeKey) : keys[0];
40601
+ if (!active2) return;
40602
+ active2.apiKey = creds.accessToken;
40603
+ active2.refreshToken = creds.refreshToken;
40604
+ active2.expiresAt = new Date(creds.expiresAt).toISOString();
40605
+ if (creds.accountId) active2.accountId = creds.accountId;
40159
40606
  writeKeysBack(p, keys);
40160
40607
  }).catch(() => {
40161
40608
  });
@@ -42104,24 +42551,24 @@ Restart WrongStack to load or unload plugin code in this session.`;
42104
42551
  },
42105
42552
  onSddRetryAllFailed: () => sddRunRegistry.getActive()?.retryAllFailed() ?? 0,
42106
42553
  onSddSplitTask: (taskId, subtasks) => {
42107
- const active = sddRunRegistry.getActive();
42108
- if (!active) return null;
42109
- const snap = active.snapshot();
42554
+ const active2 = sddRunRegistry.getActive();
42555
+ if (!active2) return null;
42556
+ const snap = active2.snapshot();
42110
42557
  const match = snap.tasks.find((t) => t.id === taskId || t.shortId === taskId);
42111
- const ids = active.splitTask(match?.id ?? taskId, subtasks);
42558
+ const ids = active2.splitTask(match?.id ?? taskId, subtasks);
42112
42559
  return ids.length ? ids : null;
42113
42560
  },
42114
42561
  // ── SDD lifecycle: clean worktrees · rollback commits · destroy project ──
42115
42562
  onSddCleanWorktrees: async () => {
42116
- const active = sddRunRegistry.getActive();
42117
- if (active) return active.cleanupWorktrees();
42563
+ const active2 = sddRunRegistry.getActive();
42564
+ if (active2) return active2.cleanupWorktrees();
42118
42565
  const core = await import('@wrongstack/core');
42119
42566
  const { removed } = await core.cleanupSddWorktrees(projectRoot);
42120
42567
  return removed;
42121
42568
  },
42122
42569
  onSddRollback: async () => {
42123
- const active = sddRunRegistry.getActive();
42124
- if (active) return active.rollback();
42570
+ const active2 = sddRunRegistry.getActive();
42571
+ if (active2) return active2.rollback();
42125
42572
  const core = await import('@wrongstack/core');
42126
42573
  return core.rollbackSddRunFromDisk({ projectRoot, boardsDir: wpaths.projectSddBoards });
42127
42574
  },
@@ -42408,9 +42855,9 @@ Reply YES to auto-proceed, NO to wait for human input.`
42408
42855
  // Stops a live run for destroy; refuses clean/rollback while one is still
42409
42856
  // running. Reuses the same disk-backed core helper as the WebUI + /sdd.
42410
42857
  onSddLifecycle: async (op, lcOpts) => {
42411
- const active = sddRunRegistry.getActive();
42412
- if (op === "destroy") active?.stop();
42413
- else if (active?.isRunning()) {
42858
+ const active2 = sddRunRegistry.getActive();
42859
+ if (op === "destroy") active2?.stop();
42860
+ else if (active2?.isRunning()) {
42414
42861
  return { op, ok: false, reason: "Stop the run first (Ctrl+C), then retry." };
42415
42862
  }
42416
42863
  const core = await import('@wrongstack/core');