@wrongstack/cli 0.260.0 → 0.264.0

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
@@ -2,7 +2,7 @@
2
2
  import * as fsp5 from 'fs/promises';
3
3
  import * as path39 from 'path';
4
4
  import { join } from 'path';
5
- import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, wstackGlobalRoot, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, DefaultLogger, DefaultModelsRegistry, isStdinTTY, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, noOpVault, decryptConfigSecrets, encryptConfigSecrets, atomicWrite, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
5
+ import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, wstackGlobalRoot, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, DefaultLogger, DefaultModelsRegistry, isStdinTTY, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, AutonomousCoordinator, noOpVault, decryptConfigSecrets, encryptConfigSecrets, atomicWrite, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
6
6
  import { DefaultSecretVault, encryptConfigSecrets as encryptConfigSecrets$1, decryptConfigSecrets as decryptConfigSecrets$1, isSecretField } from '@wrongstack/core/security';
7
7
  import * as crypto3 from 'crypto';
8
8
  import { createHash, randomBytes, randomUUID } from 'crypto';
@@ -11,8 +11,10 @@ import * as os from 'os';
11
11
  import os__default from 'os';
12
12
  import { findFreePort, AutoPhaseWebSocketHandler, handleShellOpen, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
13
13
  import { makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry } from '@wrongstack/providers';
14
+ import { toErrorMessage } from '@wrongstack/core/utils';
14
15
  import { getProcessRegistry, builtinToolsPack, TIER1_TOOLS, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, resetIndexCircuitBreaker } from '@wrongstack/tools';
15
16
  import { DefaultSessionStore } from '@wrongstack/core/storage';
17
+ import { probeLocalLlm } from '@wrongstack/runtime/probe';
16
18
  import { WebSocketServer, WebSocket } from 'ws';
17
19
  import { spawn, execFile, execFileSync } from 'child_process';
18
20
  import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
@@ -21,10 +23,10 @@ import { writeFileSync, existsSync, readFileSync } from 'fs';
21
23
  import { fileURLToPath } from 'url';
22
24
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
23
25
  import * as readline from 'readline';
26
+ import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, runEnsemble, renderEnsembleText, EnsembleRegistry, makeACPSubagentRunnerWithStop, findAgentDescriptor } from '@wrongstack/acp';
24
27
  import { parseNextSteps } from '@wrongstack/tui';
25
- import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
26
28
  import { WrongStackACPServer } from '@wrongstack/acp/agent';
27
- import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
29
+ import { SubagentBudget } from '@wrongstack/core/coordination';
28
30
  import { loadBenchConfig, reportHeaderLine, readSummary, renderMarkdownReport, createPolyglotSuite, createSwebenchSuite, runBenchmark, writeJsonArtifacts, collectCellPredictions, writePredictionsJsonl, gradePolyglot, gradeSwebench } from '@wrongstack/bench';
29
31
  import { allServers } from '@wrongstack/core/infrastructure';
30
32
  import { ToolExecutor } from '@wrongstack/core/execution';
@@ -372,26 +374,26 @@ function fmtDuration(ms) {
372
374
  const remMin = m - h * 60;
373
375
  return `${h}h${remMin}m`;
374
376
  }
375
- function fmtTaskResultLine(r, color72) {
377
+ function fmtTaskResultLine(r, color74) {
376
378
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
377
379
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
378
380
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
379
381
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
380
- const errKindChip = errKind ? color72.dim(` [${errKind}]`) : "";
381
- const errSnip = errMsg || errKind ? `${errKindChip}${color72.dim(errTail)}` : "";
382
+ const errKindChip = errKind ? color74.dim(` [${errKind}]`) : "";
383
+ const errSnip = errMsg || errKind ? `${errKindChip}${color74.dim(errTail)}` : "";
382
384
  switch (r.status) {
383
385
  case "success":
384
- return { mark: color72.green("\u2713"), stats, tail: "" };
386
+ return { mark: color74.green("\u2713"), stats, tail: "" };
385
387
  case "timeout":
386
388
  return {
387
- mark: color72.yellow("\u23F1"),
388
- stats: `${color72.yellow("timeout")} ${stats}`,
389
+ mark: color74.yellow("\u23F1"),
390
+ stats: `${color74.yellow("timeout")} ${stats}`,
389
391
  tail: errSnip
390
392
  };
391
393
  case "stopped":
392
- return { mark: color72.dim("\u2298"), stats: `${color72.dim("stopped")} ${stats}`, tail: errSnip };
394
+ return { mark: color74.dim("\u2298"), stats: `${color74.dim("stopped")} ${stats}`, tail: errSnip };
393
395
  case "failed":
394
- return { mark: color72.red("\u2717"), stats: `${color72.red("failed")} ${stats}`, tail: errSnip };
396
+ return { mark: color74.red("\u2717"), stats: `${color74.red("failed")} ${stats}`, tail: errSnip };
395
397
  }
396
398
  }
397
399
  var init_utils = __esm({
@@ -2908,7 +2910,7 @@ async function handleModesList(ctx, ws) {
2908
2910
  payload: {
2909
2911
  modes: [],
2910
2912
  activeId: "default",
2911
- error: err instanceof Error ? err.message : String(err)
2913
+ error: toErrorMessage(err)
2912
2914
  }
2913
2915
  });
2914
2916
  }
@@ -2931,7 +2933,7 @@ async function handleModeSwitch(ctx, ws, id) {
2931
2933
  const payload = await ctx.buildSessionStart({ mode: id });
2932
2934
  ctx.broadcast({ type: "session.start", payload });
2933
2935
  } catch (err) {
2934
- sendResult(ctx, ws, false, err instanceof Error ? err.message : String(err));
2936
+ sendResult(ctx, ws, false, toErrorMessage(err));
2935
2937
  }
2936
2938
  }
2937
2939
  async function handleModelSwitch(ctx, ws, payload) {
@@ -2950,7 +2952,7 @@ async function handleModelSwitch(ctx, ws, payload) {
2950
2952
  ctx,
2951
2953
  ws,
2952
2954
  false,
2953
- `Switch failed: ${err instanceof Error ? err.message : String(err)}`
2955
+ `Switch failed: ${toErrorMessage(err)}`
2954
2956
  );
2955
2957
  }
2956
2958
  }
@@ -2999,7 +3001,7 @@ async function handleModelRefine(ctx, ws, text) {
2999
3001
  payload: {
3000
3002
  refined: text,
3001
3003
  english: text,
3002
- error: err instanceof Error ? err.message : String(err)
3004
+ error: toErrorMessage(err)
3003
3005
  }
3004
3006
  });
3005
3007
  }
@@ -3009,8 +3011,6 @@ var init_agent_config = __esm({
3009
3011
  init_provider_config();
3010
3012
  }
3011
3013
  });
3012
-
3013
- // src/webui-server/ws-handlers/brain.ts
3014
3014
  function sendResult2(ctx, ws, success, message) {
3015
3015
  ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
3016
3016
  }
@@ -3064,7 +3064,7 @@ async function handleBrainAsk(ctx, ws, question) {
3064
3064
  ctx,
3065
3065
  ws,
3066
3066
  false,
3067
- `Brain consultation failed: ${err instanceof Error ? err.message : String(err)}`
3067
+ `Brain consultation failed: ${toErrorMessage(err)}`
3068
3068
  );
3069
3069
  }
3070
3070
  }
@@ -3072,8 +3072,6 @@ var init_brain = __esm({
3072
3072
  "src/webui-server/ws-handlers/brain.ts"() {
3073
3073
  }
3074
3074
  });
3075
-
3076
- // src/webui-server/ws-handlers/connection.ts
3077
3075
  async function handleUserMessage(ctx, ws, content) {
3078
3076
  if (ctx.abortControllers.has(ws)) {
3079
3077
  ctx.send(ws, {
@@ -3106,7 +3104,7 @@ async function handleUserMessage(ctx, ws, content) {
3106
3104
  type: "error",
3107
3105
  payload: {
3108
3106
  phase: "agent.run",
3109
- message: err instanceof Error ? err.message : String(err)
3107
+ message: toErrorMessage(err)
3110
3108
  }
3111
3109
  });
3112
3110
  } finally {
@@ -3355,8 +3353,6 @@ var init_cost_helpers = __esm({
3355
3353
  "src/webui-server/cost-helpers.ts"() {
3356
3354
  }
3357
3355
  });
3358
-
3359
- // src/webui-server/ws-handlers/introspection.ts
3360
3356
  async function handleSkillsList(ctx, ws) {
3361
3357
  if (!ctx.skillLoader) {
3362
3358
  ctx.send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
@@ -3387,7 +3383,7 @@ async function handleSkillsList(ctx, ws) {
3387
3383
  payload: {
3388
3384
  skills: [],
3389
3385
  enabled: true,
3390
- error: err instanceof Error ? err.message : String(err)
3386
+ error: toErrorMessage(err)
3391
3387
  }
3392
3388
  });
3393
3389
  }
@@ -3644,7 +3640,7 @@ async function handleProjectsSelect(ctx, ws, payload) {
3644
3640
  const switchedP = await ctx.buildSessionStart({ reset: true, clearedSessionId: oldSessionId });
3645
3641
  ctx.broadcast({ type: "session.start", payload: switchedP });
3646
3642
  } catch (err) {
3647
- sendResult6(ctx, ws, false, err instanceof Error ? err.message : String(err));
3643
+ sendResult6(ctx, ws, false, toErrorMessage(err));
3648
3644
  }
3649
3645
  }
3650
3646
  async function handleProjectsAdd(ctx, ws, payload) {
@@ -3684,7 +3680,7 @@ async function handleProjectsAdd(ctx, ws, payload) {
3684
3680
  name: path39.basename(addRoot),
3685
3681
  root: addRoot,
3686
3682
  slug: "",
3687
- message: err instanceof Error ? err.message : String(err)
3683
+ message: toErrorMessage(err)
3688
3684
  }
3689
3685
  });
3690
3686
  }
@@ -3706,7 +3702,7 @@ async function handleWorkingDirSet(ctx, ws, newPath) {
3706
3702
  ctx.broadcast({ type: "working_dir.changed", payload: { cwd: resolved, projectRoot: wdRoot } });
3707
3703
  sendResult6(ctx, ws, true, `Working directory set to ${resolved}`);
3708
3704
  } catch (err) {
3709
- sendResult6(ctx, ws, false, err instanceof Error ? err.message : String(err));
3705
+ sendResult6(ctx, ws, false, toErrorMessage(err));
3710
3706
  }
3711
3707
  }
3712
3708
  var init_projects = __esm({
@@ -3714,11 +3710,27 @@ var init_projects = __esm({
3714
3710
  init_project_utils();
3715
3711
  }
3716
3712
  });
3717
-
3718
- // src/webui-server/ws-handlers/providers.ts
3719
3713
  function sendResult7(ctx, ws, success, message) {
3720
3714
  ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
3721
3715
  }
3716
+ function broadcastSaved(ctx, providers) {
3717
+ ctx.broadcast({
3718
+ type: "providers.saved",
3719
+ payload: {
3720
+ providers: Object.entries(providers).map(([id, cfg]) => ({
3721
+ id,
3722
+ family: cfg.family,
3723
+ baseUrl: cfg.baseUrl,
3724
+ apiKeys: normalizeKeys(cfg).map((k) => ({
3725
+ label: k.label,
3726
+ maskedKey: maskedKey(k.apiKey),
3727
+ isActive: k.label === cfg.activeKey,
3728
+ createdAt: k.createdAt
3729
+ }))
3730
+ }))
3731
+ }
3732
+ });
3733
+ }
3722
3734
  async function handleProvidersList(ctx, ws) {
3723
3735
  if (!ctx.modelsRegistry) {
3724
3736
  sendResult7(ctx, ws, false, "Models registry not available");
@@ -3743,7 +3755,7 @@ async function handleProvidersList(ctx, ws) {
3743
3755
  }
3744
3756
  });
3745
3757
  } catch (err) {
3746
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3758
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3747
3759
  }
3748
3760
  }
3749
3761
  async function handleProviderModels(ctx, ws, providerId) {
@@ -3778,7 +3790,7 @@ async function handleProviderModels(ctx, ws, providerId) {
3778
3790
  }
3779
3791
  });
3780
3792
  } catch (err) {
3781
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3793
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3782
3794
  }
3783
3795
  }
3784
3796
  async function handleProvidersSaved(ctx, ws) {
@@ -3801,7 +3813,7 @@ async function handleProvidersSaved(ctx, ws) {
3801
3813
  }
3802
3814
  });
3803
3815
  } catch (err) {
3804
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3816
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3805
3817
  }
3806
3818
  }
3807
3819
  async function handleKeyUpsert(ctx, ws, providerId, label, apiKey) {
@@ -3821,7 +3833,7 @@ async function handleKeyUpsert(ctx, ws, providerId, label, apiKey) {
3821
3833
  await ctx.providerStore.save(providers);
3822
3834
  sendResult7(ctx, ws, true, `Key "${label}" saved for ${providerId}`);
3823
3835
  } catch (err) {
3824
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3836
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3825
3837
  }
3826
3838
  }
3827
3839
  async function handleKeyDelete(ctx, ws, providerId, label) {
@@ -3845,7 +3857,7 @@ async function handleKeyDelete(ctx, ws, providerId, label) {
3845
3857
  await ctx.providerStore.save(providers);
3846
3858
  sendResult7(ctx, ws, true, `Key "${label}" deleted from ${providerId}`);
3847
3859
  } catch (err) {
3848
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3860
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3849
3861
  }
3850
3862
  }
3851
3863
  async function handleKeySetActive(ctx, ws, providerId, label) {
@@ -3862,7 +3874,7 @@ async function handleKeySetActive(ctx, ws, providerId, label) {
3862
3874
  await ctx.providerStore.save(providers);
3863
3875
  sendResult7(ctx, ws, true, `Active key for ${providerId} set to "${label}"`);
3864
3876
  } catch (err) {
3865
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3877
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3866
3878
  }
3867
3879
  }
3868
3880
  async function handleProviderAdd(ctx, ws, payload) {
@@ -3907,7 +3919,7 @@ async function handleProviderAdd(ctx, ws, payload) {
3907
3919
  }
3908
3920
  });
3909
3921
  } catch (err) {
3910
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3922
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3911
3923
  }
3912
3924
  }
3913
3925
  async function handleProviderRemove(ctx, ws, providerId) {
@@ -3921,12 +3933,92 @@ async function handleProviderRemove(ctx, ws, providerId) {
3921
3933
  await ctx.providerStore.save(providers);
3922
3934
  sendResult7(ctx, ws, true, `Provider "${providerId}" removed`);
3923
3935
  } catch (err) {
3924
- sendResult7(ctx, ws, false, err instanceof Error ? err.message : String(err));
3936
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3937
+ }
3938
+ }
3939
+ async function handleProviderClearModels(ctx, ws, providerId) {
3940
+ try {
3941
+ const providers = await ctx.providerStore.load();
3942
+ const cfg = providers[providerId];
3943
+ if (!cfg) {
3944
+ sendResult7(ctx, ws, false, `Unknown provider "${providerId}"`);
3945
+ return;
3946
+ }
3947
+ delete cfg.models;
3948
+ await ctx.providerStore.save(providers);
3949
+ sendResult7(ctx, ws, true, `Cleared model allowlist for ${providerId}`);
3950
+ broadcastSaved(ctx, providers);
3951
+ } catch (err) {
3952
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3953
+ }
3954
+ }
3955
+ async function handleProviderUndoClear(ctx, ws, providerId, previousModels) {
3956
+ try {
3957
+ const providers = await ctx.providerStore.load();
3958
+ const cfg = providers[providerId];
3959
+ if (!cfg) {
3960
+ sendResult7(ctx, ws, false, `Unknown provider "${providerId}"`);
3961
+ return;
3962
+ }
3963
+ cfg.models = [...previousModels];
3964
+ await ctx.providerStore.save(providers);
3965
+ sendResult7(ctx, ws, true, `Restored ${previousModels.length} model(s) for ${providerId}`);
3966
+ broadcastSaved(ctx, providers);
3967
+ } catch (err) {
3968
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3969
+ }
3970
+ }
3971
+ async function handleProviderUpdate(ctx, ws, payload) {
3972
+ try {
3973
+ const providers = await ctx.providerStore.load();
3974
+ const cfg = providers[payload.id];
3975
+ if (!cfg) {
3976
+ sendResult7(ctx, ws, false, `Unknown provider "${payload.id}"`);
3977
+ return;
3978
+ }
3979
+ if (payload.family !== void 0) cfg.family = payload.family;
3980
+ if (payload.baseUrl !== void 0) cfg.baseUrl = payload.baseUrl;
3981
+ if (payload.envVars !== void 0) cfg.envVars = payload.envVars;
3982
+ if (payload.models !== void 0) cfg.models = payload.models;
3983
+ await ctx.providerStore.save(providers);
3984
+ sendResult7(ctx, ws, true, `Updated ${payload.id}`);
3985
+ broadcastSaved(ctx, providers);
3986
+ } catch (err) {
3987
+ sendResult7(ctx, ws, false, toErrorMessage(err));
3988
+ }
3989
+ }
3990
+ async function handleProviderProbe(ctx, ws, providerId, timeoutMs) {
3991
+ const reply = (payload) => ctx.send(ws, { type: "provider.probe", payload: { providerId, ...payload } });
3992
+ try {
3993
+ const providers = await ctx.providerStore.load();
3994
+ const cfg = providers[providerId];
3995
+ if (!cfg) {
3996
+ reply({ ok: false, status: "no_provider" });
3997
+ return;
3998
+ }
3999
+ if (!cfg.baseUrl) {
4000
+ reply({ ok: false, status: "no_base_url" });
4001
+ return;
4002
+ }
4003
+ const keys = normalizeKeys(cfg);
4004
+ const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
4005
+ const result = await probeLocalLlm({
4006
+ baseUrl: cfg.baseUrl,
4007
+ apiKey: active?.apiKey,
4008
+ noAuth: false,
4009
+ scrubber: probeScrubber,
4010
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
4011
+ });
4012
+ reply(result);
4013
+ } catch (err) {
4014
+ reply({ ok: false, status: "unreachable", detail: toErrorMessage(err) });
3925
4015
  }
3926
4016
  }
4017
+ var probeScrubber;
3927
4018
  var init_providers = __esm({
3928
4019
  "src/webui-server/ws-handlers/providers.ts"() {
3929
4020
  init_provider_config();
4021
+ probeScrubber = new DefaultSecretScrubber();
3930
4022
  }
3931
4023
  });
3932
4024
  function sendResult8(ctx, ws, success, message) {
@@ -3968,7 +4060,7 @@ async function handleSessionsList(ctx, ws, limit) {
3968
4060
  } catch (err) {
3969
4061
  ctx.send(ws, {
3970
4062
  type: "sessions.list",
3971
- payload: { sessions: [], error: err instanceof Error ? err.message : String(err) }
4063
+ payload: { sessions: [], error: toErrorMessage(err) }
3972
4064
  });
3973
4065
  }
3974
4066
  }
@@ -4000,7 +4092,7 @@ async function handleSessionNew(ctx, _ws) {
4000
4092
  JSON.stringify({
4001
4093
  level: "warn",
4002
4094
  event: "webui.session_new_store_failed",
4003
- message: err instanceof Error ? err.message : String(err),
4095
+ message: toErrorMessage(err),
4004
4096
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4005
4097
  })
4006
4098
  );
@@ -4040,7 +4132,7 @@ async function handleSessionRewind(ctx, ws, checkpointIndex) {
4040
4132
  const payload = await ctx.buildSessionStart({ reset: true });
4041
4133
  ctx.broadcast({ type: "session.start", payload });
4042
4134
  } catch (err) {
4043
- sendResult8(ctx, ws, false, err instanceof Error ? err.message : String(err));
4135
+ sendResult8(ctx, ws, false, toErrorMessage(err));
4044
4136
  }
4045
4137
  }
4046
4138
  async function handleSessionDelete(ctx, ws, id) {
@@ -4052,7 +4144,7 @@ async function handleSessionDelete(ctx, ws, id) {
4052
4144
  await storeFor(ctx.opts).delete(id);
4053
4145
  sendResult8(ctx, ws, true, `Session ${id} deleted`);
4054
4146
  } catch (err) {
4055
- sendResult8(ctx, ws, false, err instanceof Error ? err.message : String(err));
4147
+ sendResult8(ctx, ws, false, toErrorMessage(err));
4056
4148
  }
4057
4149
  }
4058
4150
  function handleSessionSave(ctx, ws) {
@@ -4095,7 +4187,7 @@ async function handleSessionResume(ctx, ws, id) {
4095
4187
  ctx.broadcast({ type: "session.start", payload });
4096
4188
  sendResult8(ctx, ws, true, `Resumed session ${id}`);
4097
4189
  } catch (err) {
4098
- sendResult8(ctx, ws, false, err instanceof Error ? err.message : String(err));
4190
+ sendResult8(ctx, ws, false, toErrorMessage(err));
4099
4191
  }
4100
4192
  }
4101
4193
  var init_sessions = __esm({
@@ -5227,6 +5319,31 @@ async function runWebUI(opts) {
5227
5319
  await handleProviderRemove(wsHandlerCtx, ws, m.payload.providerId);
5228
5320
  break;
5229
5321
  }
5322
+ case "provider.clear_models": {
5323
+ const m = msg;
5324
+ await handleProviderClearModels(wsHandlerCtx, ws, m.payload.providerId);
5325
+ break;
5326
+ }
5327
+ case "provider.undo_clear": {
5328
+ const m = msg;
5329
+ await handleProviderUndoClear(
5330
+ wsHandlerCtx,
5331
+ ws,
5332
+ m.payload.providerId,
5333
+ m.payload.previousModels
5334
+ );
5335
+ break;
5336
+ }
5337
+ case "provider.update": {
5338
+ const m = msg;
5339
+ await handleProviderUpdate(wsHandlerCtx, ws, m.payload);
5340
+ break;
5341
+ }
5342
+ case "provider.probe": {
5343
+ const m = msg;
5344
+ await handleProviderProbe(wsHandlerCtx, ws, m.payload.providerId, m.payload.timeoutMs);
5345
+ break;
5346
+ }
5230
5347
  case "todos.get": {
5231
5348
  handleTodosGet(worklistCtx, ws);
5232
5349
  break;
@@ -5501,11 +5618,16 @@ async function runWebUI(opts) {
5501
5618
  break;
5502
5619
  }
5503
5620
  // Collaboration messages — the CLI webui-server doesn't run a
5504
- // full collab hub; silently acknowledge and ignore.
5621
+ // full collab hub; silently acknowledge and ignore. request_pause /
5622
+ // resume are included so the CollabPanel's pause/resume buttons don't
5623
+ // trip the "Unhandled message type" warning (the standalone webui
5624
+ // server is the one that wires the real CollaborationWebSocketHandler).
5505
5625
  case "collab.join":
5506
5626
  case "collab.leave":
5507
5627
  case "collab.annotate":
5508
5628
  case "collab.resolve":
5629
+ case "collab.request_pause":
5630
+ case "collab.resume":
5509
5631
  break;
5510
5632
  case "projects.list": {
5511
5633
  await handleProjectsList(projectsCtx, ws);
@@ -5664,6 +5786,57 @@ async function runWebUI(opts) {
5664
5786
  }
5665
5787
  break;
5666
5788
  }
5789
+ case "mailbox.purge": {
5790
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5791
+ const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : "";
5792
+ if (!projectRoot || !globalRoot) {
5793
+ send(ws, { type: "mailbox.purged", payload: { error: "No project root available" } });
5794
+ break;
5795
+ }
5796
+ try {
5797
+ const mbDir = resolveProjectDir(projectRoot, globalRoot);
5798
+ const mb = new GlobalMailbox(mbDir);
5799
+ const payload = msg;
5800
+ const result = await mb.purgeStale(payload.payload);
5801
+ send(ws, { type: "mailbox.purged", payload: result });
5802
+ } catch (err) {
5803
+ send(ws, {
5804
+ type: "mailbox.purged",
5805
+ payload: { error: err instanceof Error ? err.message : String(err) }
5806
+ });
5807
+ }
5808
+ break;
5809
+ }
5810
+ case "git.info": {
5811
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5812
+ const cwd = projectRoot || void 0;
5813
+ const { execFile: ef } = await import('child_process');
5814
+ const git = (args) => new Promise((resolve11) => {
5815
+ ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
5816
+ resolve11(err ? "" : stdout.trim());
5817
+ });
5818
+ });
5819
+ const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
5820
+ git(["branch", "--show-current"]),
5821
+ git(["diff", "--stat"]),
5822
+ git(["status", "--porcelain"]),
5823
+ git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
5824
+ ]);
5825
+ const branch = branchRaw || "(detached)";
5826
+ const addMatch = /(\d+)\s+insertion/i.exec(diffRaw);
5827
+ const delMatch = /(\d+)\s+deletion/i.exec(diffRaw);
5828
+ const added = addMatch ? Number(addMatch[1]) : 0;
5829
+ const deleted = delMatch ? Number(delMatch[1]) : 0;
5830
+ const untracked = statusRaw.split("\n").filter((l) => l.startsWith("??")).length;
5831
+ const [behindRaw, aheadRaw] = (upstreamRaw || "0 0").split(" ");
5832
+ const behind = Number(behindRaw) || 0;
5833
+ const ahead = Number(aheadRaw) || 0;
5834
+ send(ws, {
5835
+ type: "git.info",
5836
+ payload: { branch, added, deleted, untracked, ahead, behind }
5837
+ });
5838
+ break;
5839
+ }
5667
5840
  default: {
5668
5841
  const msgType = msg.type;
5669
5842
  if (msgType.startsWith("autophase.")) {
@@ -6769,7 +6942,7 @@ async function ensureHistoryDir(homeFn = defaultHomeDir) {
6769
6942
  await fsp5.mkdir(historyDir(homeFn), { recursive: true });
6770
6943
  } catch (err) {
6771
6944
  throw new FsError({
6772
- message: err instanceof Error ? err.message : String(err),
6945
+ message: toErrorMessage(err),
6773
6946
  code: ERROR_CODES.FS_MKDIR_FAILED,
6774
6947
  path: historyDir(homeFn),
6775
6948
  cause: err
@@ -6790,7 +6963,7 @@ async function writeIndex(idx, homeFn = defaultHomeDir) {
6790
6963
  await atomicWrite(historyIndexPath(homeFn), JSON.stringify(idx, null, 2));
6791
6964
  } catch (err) {
6792
6965
  throw new FsError({
6793
- message: err instanceof Error ? err.message : String(err),
6966
+ message: toErrorMessage(err),
6794
6967
  code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
6795
6968
  path: historyIndexPath(homeFn),
6796
6969
  cause: err
@@ -6811,7 +6984,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6811
6984
  await atomicWrite(last, content);
6812
6985
  } catch (err) {
6813
6986
  writeErr(
6814
- `[config-history] .last backup failed: ${err instanceof Error ? err.message : String(err)}`
6987
+ `[config-history] .last backup failed: ${toErrorMessage(err)}`
6815
6988
  );
6816
6989
  }
6817
6990
  }
@@ -6821,7 +6994,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6821
6994
  await atomicWrite(bakPath, content);
6822
6995
  } catch (err) {
6823
6996
  writeErr(
6824
- `[config-history] timestamped backup failed: ${err instanceof Error ? err.message : String(err)}`
6997
+ `[config-history] timestamped backup failed: ${toErrorMessage(err)}`
6825
6998
  );
6826
6999
  }
6827
7000
  }
@@ -6834,7 +7007,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6834
7007
  }
6835
7008
  } catch (err) {
6836
7009
  writeErr(
6837
- `[config-history] backup prune failed: ${err instanceof Error ? err.message : String(err)}`
7010
+ `[config-history] backup prune failed: ${toErrorMessage(err)}`
6838
7011
  );
6839
7012
  }
6840
7013
  }
@@ -6857,7 +7030,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6857
7030
  );
6858
7031
  } catch (err) {
6859
7032
  throw new FsError({
6860
- message: err instanceof Error ? err.message : String(err),
7033
+ message: toErrorMessage(err),
6861
7034
  code: ERROR_CODES.FS_WRITE_FAILED,
6862
7035
  path: path39.join(historyDir(homeFn), `${id}.json`),
6863
7036
  cause: err
@@ -6985,8 +7158,6 @@ async function buildPickableProviders(modelsRegistry, config) {
6985
7158
  }
6986
7159
  return out;
6987
7160
  }
6988
-
6989
- // src/picker.ts
6990
7161
  var theme = { primary: color.amber };
6991
7162
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os__default.homedir()) {
6992
7163
  try {
@@ -7008,7 +7179,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
7008
7179
  JSON.stringify({
7009
7180
  level: "warn",
7010
7181
  event: "picker.backup_failed",
7011
- message: err instanceof Error ? err.message : String(err),
7182
+ message: toErrorMessage(err),
7012
7183
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7013
7184
  })
7014
7185
  );
@@ -7029,7 +7200,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
7029
7200
  JSON.stringify({
7030
7201
  level: "warn",
7031
7202
  event: "picker.save_failed",
7032
- message: err instanceof Error ? err.message : String(err),
7203
+ message: toErrorMessage(err),
7033
7204
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7034
7205
  })
7035
7206
  );
@@ -8027,7 +8198,7 @@ function buildCodebaseReindexCommand(opts) {
8027
8198
  ${color.yellow(` ${r.errors.length} file(s) had errors`)}` : "");
8028
8199
  return { message: summary };
8029
8200
  } catch (err) {
8030
- const msg = `${color.red("Codebase reindex failed:")} ${err instanceof Error ? err.message : String(err)}`;
8201
+ const msg = `${color.red("Codebase reindex failed:")} ${toErrorMessage(err)}`;
8031
8202
  return { message: msg };
8032
8203
  }
8033
8204
  }
@@ -8114,7 +8285,7 @@ async function historyCommand(opts, sessionId, args) {
8114
8285
  } catch (err) {
8115
8286
  return {
8116
8287
  message: color.yellow(
8117
- `Failed to read session: ${err instanceof Error ? err.message : String(err)}`
8288
+ `Failed to read session: ${toErrorMessage(err)}`
8118
8289
  )
8119
8290
  };
8120
8291
  }
@@ -8616,7 +8787,7 @@ async function spawnAgent(opts, role, _task, _name, header) {
8616
8787
  opts.renderer.write(msg);
8617
8788
  return { message: msg };
8618
8789
  } catch (err) {
8619
- const msg = `${color.red("\u2717 Spawn failed")}: ${err instanceof Error ? err.message : String(err)}`;
8790
+ const msg = `${color.red("\u2717 Spawn failed")}: ${toErrorMessage(err)}`;
8620
8791
  opts.renderer.writeWarning(msg);
8621
8792
  return { message: msg };
8622
8793
  }
@@ -9415,7 +9586,7 @@ function buildDoctorCommand(opts) {
9415
9586
  parsed = JSON.parse(raw);
9416
9587
  } catch (err) {
9417
9588
  errorCount++;
9418
- const msg = err instanceof Error ? err.message : String(err);
9589
+ const msg = toErrorMessage(err);
9419
9590
  lines.push(` ${color.red("\u2717")} invalid JSON \u2014 ${msg}`);
9420
9591
  if (!applyFixes) {
9421
9592
  fixableCount++;
@@ -9554,6 +9725,65 @@ function buildEnhanceCommand(opts) {
9554
9725
  }
9555
9726
  };
9556
9727
  }
9728
+ function buildEnsembleCommand(_opts) {
9729
+ return {
9730
+ name: "ensemble",
9731
+ category: "Agent",
9732
+ description: "Fan a task out to multiple ACP agents in parallel (claude-code, gemini-cli, codex-cli, etc.).",
9733
+ argsHint: "<agent-ids-csv> <task description>",
9734
+ help: [
9735
+ "Fan a single task out to multiple ACP-supporting agents in parallel.",
9736
+ "",
9737
+ "Usage:",
9738
+ " /ensemble <agent-ids-csv> <task description>",
9739
+ "",
9740
+ "Examples:",
9741
+ ' /ensemble claude-code,gemini-cli "review this diff"',
9742
+ ' /ensemble claude-code,codex-cli "refactor auth/session.ts"',
9743
+ ' /ensemble claude-code,gemini-cli,codex-cli "explain the v1 protocol"',
9744
+ "",
9745
+ "Each agent runs in its own process. Agents not installed on the host",
9746
+ "are skipped with a warning. The command waits for all agents to finish",
9747
+ "and reports per-agent outcomes (success / failed / skipped / cancelled).",
9748
+ "",
9749
+ "Use /acp list (or wstack acp list) to see which agents are detected."
9750
+ ].join("\n"),
9751
+ async run(args) {
9752
+ const trimmed = args.trim();
9753
+ if (!trimmed) {
9754
+ return {
9755
+ message: 'Usage: /ensemble <agent-ids-csv> <task description>\n\nExamples:\n /ensemble claude-code,gemini-cli "review this diff"\n /ensemble claude-code,codex-cli "refactor auth/session.ts"\n\nRun `wstack acp list` to see which agents are detected on this host.'
9756
+ };
9757
+ }
9758
+ const spaceIdx = trimmed.search(/\s/);
9759
+ if (spaceIdx === -1) {
9760
+ return {
9761
+ message: 'Task description is required.\n\nUsage: /ensemble <agent-ids-csv> <task description>\nExample: /ensemble claude-code,gemini-cli "explain this code"'
9762
+ };
9763
+ }
9764
+ const agentIds = trimmed.slice(0, spaceIdx);
9765
+ const task = stripSurroundingQuotes(trimmed.slice(spaceIdx + 1).trim());
9766
+ if (!task) {
9767
+ return { message: "Task description is required." };
9768
+ }
9769
+ try {
9770
+ const result = await runEnsemble({ agentIds, task });
9771
+ return { message: renderEnsembleText(result) };
9772
+ } catch (err) {
9773
+ return { message: `Ensemble failed: ${toErrorMessage(err)}` };
9774
+ }
9775
+ }
9776
+ };
9777
+ }
9778
+ function stripSurroundingQuotes(s) {
9779
+ if (s.length < 2) return s;
9780
+ const first = s[0];
9781
+ const last = s[s.length - 1];
9782
+ if ((first === '"' || first === "'") && first === last) {
9783
+ return s.slice(1, -1);
9784
+ }
9785
+ return s;
9786
+ }
9557
9787
  function parseModelRef(ref) {
9558
9788
  const trimmed = ref.trim();
9559
9789
  const slash = trimmed.indexOf("/");
@@ -9854,7 +10084,7 @@ function buildFallbackCommand(opts) {
9854
10084
  };
9855
10085
  } catch (err) {
9856
10086
  return {
9857
- message: `${color.red("fallback error")}: ${err instanceof Error ? err.message : String(err)}`
10087
+ message: `${color.red("fallback error")}: ${toErrorMessage(err)}`
9858
10088
  };
9859
10089
  }
9860
10090
  }
@@ -11071,7 +11301,7 @@ async function handleSpawn(opts, subargs) {
11071
11301
  const id = await opts.onFleetSpawn(role);
11072
11302
  spawned.push(id);
11073
11303
  } catch (err) {
11074
- const warnMsg = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${err instanceof Error ? err.message : String(err)}`;
11304
+ const warnMsg = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${toErrorMessage(err)}`;
11075
11305
  opts.renderer.writeWarning(warnMsg);
11076
11306
  }
11077
11307
  }
@@ -11125,7 +11355,7 @@ async function handleDispatch(opts, subargs) {
11125
11355
  lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
11126
11356
  } catch (err) {
11127
11357
  lines.push(
11128
- ` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
11358
+ ` ${color.amber("\u26A0 spawn failed:")} ${toErrorMessage(err)}`
11129
11359
  );
11130
11360
  }
11131
11361
  } else {
@@ -11758,6 +11988,7 @@ function buildMailboxCommand(opts) {
11758
11988
  " /mailbox broadcast <message> Message every agent on the project.",
11759
11989
  " /mailbox history [n] Last n messages on the project (default 20).",
11760
11990
  " /mailbox clear Delete all messages from the mailbox.",
11991
+ " /mailbox purge Remove stale/orphaned messages (completed >1d, incomplete >7d).",
11761
11992
  "",
11762
11993
  "Examples:",
11763
11994
  " /mailbox broadcast pausing deploys, hold off on main",
@@ -11866,8 +12097,17 @@ function buildMailboxCommand(opts) {
11866
12097
  await mb.clearAll();
11867
12098
  return { message: color.green("\u2713 All messages deleted from the mailbox.") };
11868
12099
  }
12100
+ if (sub === "purge") {
12101
+ const result = await mb.purgeStale();
12102
+ if (result.totalPurged === 0) {
12103
+ return { message: color.green("\u2713 No stale messages found. Mailbox is clean.") };
12104
+ }
12105
+ return {
12106
+ message: `\u2713 Purged ${result.totalPurged} message(s): ${result.completedPurged} completed, ${result.incompletePurged} incomplete. ${result.remaining} message(s) remain.`
12107
+ };
12108
+ }
11869
12109
  return {
11870
- message: `Unknown subcommand "${sub}". Use: /mailbox [agents|online|send|broadcast|history|clear]`
12110
+ message: `Unknown subcommand "${sub}". Use: /mailbox [agents|online|send|broadcast|history|clear purge]`
11871
12111
  };
11872
12112
  }
11873
12113
  };
@@ -12484,7 +12724,7 @@ async function runCompact(opts) {
12484
12724
  responseText = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
12485
12725
  } catch (err) {
12486
12726
  return {
12487
- message: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`
12727
+ message: `LLM call failed: ${toErrorMessage(err)}`
12488
12728
  };
12489
12729
  }
12490
12730
  if (!responseText) {
@@ -12500,7 +12740,7 @@ ${responseText.slice(0, 500)}` };
12500
12740
  parsed = JSON.parse(jsonMatch[0]);
12501
12741
  } catch (err) {
12502
12742
  return {
12503
- message: `Failed to parse LLM response: ${err instanceof Error ? err.message : String(err)}
12743
+ message: `Failed to parse LLM response: ${toErrorMessage(err)}
12504
12744
 
12505
12745
  Raw response:
12506
12746
  ${responseText.slice(0, 500)}`
@@ -12558,7 +12798,7 @@ ${responseText.slice(0, 500)}`
12558
12798
  }
12559
12799
  } catch (err) {
12560
12800
  errors.push(
12561
- `${op.action} failed for ${op.targets.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
12801
+ `${op.action} failed for ${op.targets.join(", ")}: ${toErrorMessage(err)}`
12562
12802
  );
12563
12803
  }
12564
12804
  }
@@ -13195,7 +13435,7 @@ function buildModelsCommand(opts) {
13195
13435
  };
13196
13436
  } catch (err) {
13197
13437
  return {
13198
- message: `${color.red("models error")}: ${err instanceof Error ? err.message : String(err)}`
13438
+ message: `${color.red("models error")}: ${toErrorMessage(err)}`
13199
13439
  };
13200
13440
  }
13201
13441
  }
@@ -13879,7 +14119,7 @@ async function killSession(sessionId) {
13879
14119
  } catch (err) {
13880
14120
  return {
13881
14121
  message: color.red(
13882
- `Failed to kill session: ${err instanceof Error ? err.message : String(err)}`
14122
+ `Failed to kill session: ${toErrorMessage(err)}`
13883
14123
  )
13884
14124
  };
13885
14125
  }
@@ -14250,7 +14490,7 @@ function buildSetModelCommand(opts) {
14250
14490
  };
14251
14491
  } catch (err) {
14252
14492
  return {
14253
- message: `${color.red("setmodel error")}: ${err instanceof Error ? err.message : String(err)}`
14493
+ message: `${color.red("setmodel error")}: ${toErrorMessage(err)}`
14254
14494
  };
14255
14495
  }
14256
14496
  }
@@ -14353,7 +14593,7 @@ function buildSuggestCommand(opts) {
14353
14593
  opts.onSuggestions?.(suggestions);
14354
14594
  return { message: formatSuggestions(suggestions) };
14355
14595
  } catch (err) {
14356
- const msg = `Suggestion generation failed: ${err instanceof Error ? err.message : String(err)}`;
14596
+ const msg = `Suggestion generation failed: ${toErrorMessage(err)}`;
14357
14597
  opts.renderer.writeWarning(msg);
14358
14598
  return { message: msg };
14359
14599
  }
@@ -15232,14 +15472,12 @@ function buildSettingsCommand(opts) {
15232
15472
  };
15233
15473
  } catch (err) {
15234
15474
  return {
15235
- message: `${color.red("Settings error")}: ${err instanceof Error ? err.message : String(err)}`
15475
+ message: `${color.red("Settings error")}: ${toErrorMessage(err)}`
15236
15476
  };
15237
15477
  }
15238
15478
  }
15239
15479
  };
15240
15480
  }
15241
-
15242
- // src/slash-commands/spawn-agents.ts
15243
15481
  function buildSpawnCommand(opts) {
15244
15482
  return {
15245
15483
  name: "spawn",
@@ -15276,7 +15514,7 @@ function buildSpawnCommand(opts) {
15276
15514
  const summary = Object.keys(parsed).length > 0 ? await opts.onSpawn(description, parsed) : await opts.onSpawn(description);
15277
15515
  return { message: summary };
15278
15516
  } catch (err) {
15279
- return { message: `Spawn failed: ${err instanceof Error ? err.message : String(err)}` };
15517
+ return { message: `Spawn failed: ${toErrorMessage(err)}` };
15280
15518
  }
15281
15519
  }
15282
15520
  };
@@ -15381,7 +15619,7 @@ async function saveStatuslineConfig(cfg) {
15381
15619
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
15382
15620
  } catch (err) {
15383
15621
  throw new FsError({
15384
- message: err instanceof Error ? err.message : String(err),
15622
+ message: toErrorMessage(err),
15385
15623
  code: err instanceof Error && err.message.includes("mkdir") ? ERROR_CODES.FS_MKDIR_FAILED : ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
15386
15624
  path: p,
15387
15625
  cause: err
@@ -15616,7 +15854,7 @@ ${formatPlan(updated)}`;
15616
15854
  const priority = validatePriority(parts[2] ?? "") ?? "medium";
15617
15855
  const now = (/* @__PURE__ */ new Date()).toISOString();
15618
15856
  file.tasks.push({
15619
- id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
15857
+ id: `task_${randomUUID()}`,
15620
15858
  title,
15621
15859
  type,
15622
15860
  priority,
@@ -15739,7 +15977,7 @@ ${formatTaskProgress(file.tasks)}`;
15739
15977
  ];
15740
15978
  if (found.item.description) {
15741
15979
  todos.push({
15742
- id: `todo_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
15980
+ id: `todo_${randomUUID()}`,
15743
15981
  content: found.item.description.slice(0, 200),
15744
15982
  status: "pending",
15745
15983
  promotedFromTask: found.item.id
@@ -15947,7 +16185,7 @@ function buildTechStackCommand(opts) {
15947
16185
  }
15948
16186
  } catch (err) {
15949
16187
  discoveryNote = color.red(
15950
- `Could not scan for package files: ${err instanceof Error ? err.message : String(err)}`
16188
+ `Could not scan for package files: ${toErrorMessage(err)}`
15951
16189
  );
15952
16190
  }
15953
16191
  const task = buildTechStackTask({
@@ -15975,7 +16213,7 @@ function buildTechStackCommand(opts) {
15975
16213
  const summary = await opts.onSpawnAndWait(task, { name });
15976
16214
  return { message: summary };
15977
16215
  } catch (err) {
15978
- const msg = `Tech stack scan failed: ${err instanceof Error ? err.message : String(err)}`;
16216
+ const msg = `Tech stack scan failed: ${toErrorMessage(err)}`;
15979
16217
  opts.renderer.writeWarning(msg);
15980
16218
  return { message: msg };
15981
16219
  }
@@ -16126,6 +16364,163 @@ ${color.dim("No default chat set. You can add it later: /telegram-setup <token>
16126
16364
  }
16127
16365
  };
16128
16366
  }
16367
+ init_helpers();
16368
+ var HELP2 = [
16369
+ "Usage:",
16370
+ " /telegram-settings Show current Telegram notification settings",
16371
+ " /telegram-settings session-end on|off Notify on session end",
16372
+ " /telegram-settings delegate on|off Notify when a delegated subagent finishes",
16373
+ " /telegram-settings long-tool <ms|off> Notify for tools slower than <ms> (0/off = disabled)",
16374
+ " /telegram-settings poll <seconds> Bot polling interval (1\u201360)",
16375
+ " /telegram-settings chat <chatId> Default chat for notifications",
16376
+ "",
16377
+ "Aliases: /tg-settings",
16378
+ "",
16379
+ "Settings apply immediately \u2014 no restart needed."
16380
+ ].join("\n");
16381
+ function buildTelegramSettingsCommand(opts) {
16382
+ function currentView() {
16383
+ const config = opts.configStore.get();
16384
+ const tg = config.extensions?.telegram ?? {};
16385
+ const sessionEnd = tg.notifyOnSessionEnd === true;
16386
+ const delegate = tg.notifyOnDelegate !== false;
16387
+ const longToolMs = typeof tg.longToolThresholdMs === "number" ? tg.longToolThresholdMs : 3e4;
16388
+ const longTool = longToolMs > 0 ? `${longToolMs}ms` : "off";
16389
+ const poll = typeof tg.pollIntervalSec === "number" ? `${tg.pollIntervalSec}s` : "2s";
16390
+ const chat = tg.notifyChatId !== void 0 && tg.notifyChatId !== null ? String(tg.notifyChatId) : "not set";
16391
+ const hasToken = typeof tg.botToken === "string" && tg.botToken.length > 0;
16392
+ return [
16393
+ `${color.bold("Telegram")} ${color.dim("\u2014 Notification Settings")}`,
16394
+ "",
16395
+ ` session end: ${sessionEnd ? color.cyan("on") : color.dim("off")} ${color.dim("change: /telegram-settings session-end on|off")}`,
16396
+ ` delegate done: ${delegate ? color.cyan("on") : color.dim("off")} ${color.dim("change: /telegram-settings delegate on|off")}`,
16397
+ ` long tool: ${color.cyan(longTool)} ${color.dim("change: /telegram-settings long-tool <ms|off>")}`,
16398
+ ` poll interval: ${color.cyan(poll)} ${color.dim("change: /telegram-settings poll <seconds>")}`,
16399
+ ` notify chat: ${color.cyan(chat)} ${color.dim("change: /telegram-settings chat <chatId>")}`,
16400
+ "",
16401
+ hasToken ? color.dim(" Bot token configured. Changes apply immediately.") : `${color.amber("\u26A0")} No bot token configured. Run: /telegram-setup <botToken> [chatId]`
16402
+ ].join("\n");
16403
+ }
16404
+ return {
16405
+ name: "telegram-settings",
16406
+ category: "Config",
16407
+ aliases: ["tg-settings"],
16408
+ description: "Toggle which agent events are reported to Telegram.",
16409
+ argsHint: "[session-end|delegate|long-tool|poll|chat <value>]",
16410
+ help: HELP2,
16411
+ async run(args) {
16412
+ const { cmd: sub, rest } = parseSubcommand(args);
16413
+ if (sub === "help" || sub === "--help" || sub === "-h") {
16414
+ return { message: HELP2 };
16415
+ }
16416
+ if (!opts.configStore || !opts.paths?.globalConfig) {
16417
+ return { message: `${color.red("Error")} config store not available.` };
16418
+ }
16419
+ if (!sub) {
16420
+ return { message: currentView() };
16421
+ }
16422
+ const persistDeps = {
16423
+ configStore: opts.configStore,
16424
+ globalConfigPath: opts.paths.globalConfig,
16425
+ vault: noOpVault
16426
+ };
16427
+ try {
16428
+ if (sub === "session-end") {
16429
+ const raw = (rest[0] ?? "").toLowerCase();
16430
+ if (!["on", "off"].includes(raw)) {
16431
+ return { message: `${color.amber("Usage:")} /telegram-settings session-end on|off` };
16432
+ }
16433
+ const on = raw === "on";
16434
+ await persistTelegramConfig(persistDeps, (tg) => {
16435
+ tg.notifyOnSessionEnd = on;
16436
+ });
16437
+ return {
16438
+ message: `${color.green("\u2713")} session-end \u2192 ${on ? color.cyan("on") : color.dim("off")}`
16439
+ };
16440
+ }
16441
+ if (sub === "delegate") {
16442
+ const raw = (rest[0] ?? "").toLowerCase();
16443
+ if (!["on", "off"].includes(raw)) {
16444
+ return { message: `${color.amber("Usage:")} /telegram-settings delegate on|off` };
16445
+ }
16446
+ const on = raw === "on";
16447
+ await persistTelegramConfig(persistDeps, (tg) => {
16448
+ tg.notifyOnDelegate = on;
16449
+ });
16450
+ return {
16451
+ message: `${color.green("\u2713")} delegate \u2192 ${on ? color.cyan("on") : color.dim("off")}`
16452
+ };
16453
+ }
16454
+ if (sub === "long-tool") {
16455
+ const raw = rest[0];
16456
+ if (raw === void 0) {
16457
+ return {
16458
+ message: `${color.amber("Usage:")} /telegram-settings long-tool <ms|off> ${color.dim("(0 or off disables)")}`
16459
+ };
16460
+ }
16461
+ if (raw === "off") {
16462
+ await persistTelegramConfig(persistDeps, (tg) => {
16463
+ tg.longToolThresholdMs = 0;
16464
+ });
16465
+ return {
16466
+ message: `${color.green("\u2713")} long-tool \u2192 ${color.dim("off")}`
16467
+ };
16468
+ }
16469
+ const ms = Number.parseInt(raw, 10);
16470
+ if (Number.isNaN(ms) || ms < 0) {
16471
+ return {
16472
+ message: `${color.red("Invalid number")}: "${raw}". Enter milliseconds, e.g. /telegram-settings long-tool 15000`
16473
+ };
16474
+ }
16475
+ await persistTelegramConfig(persistDeps, (tg) => {
16476
+ tg.longToolThresholdMs = ms;
16477
+ });
16478
+ return {
16479
+ message: `${color.green("\u2713")} long-tool \u2192 ${color.cyan(`${ms}ms`)}`
16480
+ };
16481
+ }
16482
+ if (sub === "poll") {
16483
+ const raw = rest[0];
16484
+ if (raw === void 0) {
16485
+ return { message: `${color.amber("Usage:")} /telegram-settings poll <seconds> ${color.dim("(1\u201360)")}` };
16486
+ }
16487
+ const sec = Number.parseInt(raw, 10);
16488
+ if (Number.isNaN(sec) || sec < 1 || sec > 60) {
16489
+ return {
16490
+ message: `${color.red("Invalid value")}: "${raw}". Enter seconds between 1 and 60.`
16491
+ };
16492
+ }
16493
+ await persistTelegramConfig(persistDeps, (tg) => {
16494
+ tg.pollIntervalSec = sec;
16495
+ });
16496
+ return {
16497
+ message: `${color.green("\u2713")} poll \u2192 ${color.cyan(`${sec}s`)}`
16498
+ };
16499
+ }
16500
+ if (sub === "chat") {
16501
+ const raw = rest[0];
16502
+ if (!raw) {
16503
+ return { message: `${color.amber("Usage:")} /telegram-settings chat <chatId>` };
16504
+ }
16505
+ const chatId = /^\d+$/.test(raw) ? Number(raw) : raw;
16506
+ await persistTelegramConfig(persistDeps, (tg) => {
16507
+ tg.notifyChatId = chatId;
16508
+ });
16509
+ return {
16510
+ message: `${color.green("\u2713")} notify chat \u2192 ${color.cyan(raw)}`
16511
+ };
16512
+ }
16513
+ return {
16514
+ message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["session-end", "delegate", "long-tool", "poll", "chat"], "telegram-settings")}`
16515
+ };
16516
+ } catch (err) {
16517
+ return {
16518
+ message: `${color.red("Settings error")}: ${toErrorMessage(err)}`
16519
+ };
16520
+ }
16521
+ }
16522
+ };
16523
+ }
16129
16524
 
16130
16525
  // src/slash-commands/todos.ts
16131
16526
  init_helpers();
@@ -16287,7 +16682,7 @@ function buildWorkingDirCommand(_opts) {
16287
16682
  ctx.setWorkingDir(resolved);
16288
16683
  } catch (err) {
16289
16684
  return {
16290
- message: color.red(err instanceof Error ? err.message : String(err))
16685
+ message: color.red(toErrorMessage(err))
16291
16686
  };
16292
16687
  }
16293
16688
  const prevRel = path39.relative(ctx.projectRoot, previous) || ".";
@@ -16434,6 +16829,7 @@ function buildBuiltinSlashCommands(opts) {
16434
16829
  buildDirectorCommand(opts),
16435
16830
  buildFleetCommand(opts),
16436
16831
  buildEnhanceCommand(opts),
16832
+ buildEnsembleCommand(),
16437
16833
  buildMemoryCommand(opts),
16438
16834
  buildTodosCommand(opts),
16439
16835
  buildTasksCommand(),
@@ -16456,6 +16852,7 @@ function buildBuiltinSlashCommands(opts) {
16456
16852
  buildWorktreeCommand(opts),
16457
16853
  buildSettingsCommand(opts),
16458
16854
  buildTelegramSetupCommand(opts),
16855
+ buildTelegramSettingsCommand(opts),
16459
16856
  buildSetModelCommand(opts),
16460
16857
  buildFallbackCommand(opts),
16461
16858
  buildModelCapsCommand(opts),
@@ -16475,8 +16872,6 @@ function buildBuiltinSlashCommands(opts) {
16475
16872
  })
16476
16873
  ];
16477
16874
  }
16478
-
16479
- // src/pre-launch.ts
16480
16875
  var MANIFESTS = [
16481
16876
  "package.json",
16482
16877
  "pyproject.toml",
@@ -16544,7 +16939,7 @@ async function runProjectCheck(opts) {
16544
16939
  `);
16545
16940
  } catch (err) {
16546
16941
  renderer.writeError(
16547
- `Failed to scaffold AGENTS.md: ${err instanceof Error ? err.message : String(err)}`
16942
+ `Failed to scaffold AGENTS.md: ${toErrorMessage(err)}`
16548
16943
  );
16549
16944
  }
16550
16945
  }
@@ -16589,7 +16984,7 @@ async function runProjectCheck(opts) {
16589
16984
  `);
16590
16985
  } catch (err) {
16591
16986
  renderer.writeError(
16592
- `git init failed: ${err instanceof Error ? err.message : String(err)}
16987
+ `git init failed: ${toErrorMessage(err)}
16593
16988
  `
16594
16989
  );
16595
16990
  }
@@ -17148,6 +17543,17 @@ function summarize(value, name) {
17148
17543
 
17149
17544
  // src/boot.ts
17150
17545
  init_project_utils();
17546
+ function resolveCmdFromCatalog(subagentId) {
17547
+ const desc = findAgentDescriptor(subagentId);
17548
+ if (!desc) return null;
17549
+ const out = {
17550
+ command: desc.acp.command,
17551
+ args: [...desc.acp.args ?? []],
17552
+ role: subagentId
17553
+ };
17554
+ if (desc.acp.env) out.env = desc.acp.env;
17555
+ return out;
17556
+ }
17151
17557
  var acpCmd = async (args, deps) => {
17152
17558
  const sub = args[0];
17153
17559
  if (!sub || sub === "server" || sub === "serve") {
@@ -17162,6 +17568,9 @@ Usage:
17162
17568
  wstack acp list List available ACP agents
17163
17569
  wstack acp spawn <id> <task>
17164
17570
  Spawn an ACP agent as a subagent and wait for result
17571
+ wstack acp parallel <agent-id-csv> <task>
17572
+ Fan a task out to multiple ACP agents in parallel
17573
+ and aggregate the results
17165
17574
  wstack acp help Show this help
17166
17575
 
17167
17576
  ACP Mode:
@@ -17171,9 +17580,17 @@ ACP Mode:
17171
17580
  Press Ctrl+C to stop.
17172
17581
 
17173
17582
  spawn:
17174
- Spawns a named ACP agent (cline, gemini-cli, copilot, openhands, goose)
17175
- with the given task and waits for its result.
17583
+ Spawns a named ACP agent (claude-code, gemini-cli, codex-cli, copilot,
17584
+ cline, goose, openhands, qwen-code, kiro-cli, opencode, mistral-vibe,
17585
+ cursor) with the given task and waits for its result.
17176
17586
  Example: wstack acp spawn cline "fix the login bug"
17587
+
17588
+ parallel:
17589
+ Runs the same task on a comma-separated list of ACP agents concurrently.
17590
+ Example: wstack acp parallel claude-code,gemini-cli,codex-cli "review this diff"
17591
+ Each agent's result is rendered under a clearly-marked header. Returns 0
17592
+ if at least one agent succeeds, 1 if all fail. Agents that aren't
17593
+ installed are skipped with a warning.
17177
17594
  `);
17178
17595
  return 0;
17179
17596
  }
@@ -17183,20 +17600,20 @@ spawn:
17183
17600
  if (sub === "spawn") {
17184
17601
  return spawnACPAgent(args.slice(1), deps);
17185
17602
  }
17603
+ if (sub === "parallel") {
17604
+ return parallelACPAgents(args.slice(1), deps);
17605
+ }
17186
17606
  deps.renderer.writeError(`Unknown acp subcommand: ${sub}
17187
17607
  `);
17188
17608
  deps.renderer.write("Run `wstack acp help` for usage.\n");
17189
17609
  return 1;
17190
17610
  };
17191
17611
  async function runACPServer(deps) {
17192
- const toolRegistry = deps.toolRegistry;
17193
- const tools = toolRegistry?.list() ?? [];
17194
17612
  deps.renderer.writeInfo("Starting WrongStack ACP server...\n");
17195
- deps.renderer.writeInfo(`Exposing ${tools.length} tool(s) via ACP protocol.
17196
- `);
17197
17613
  deps.renderer.writeInfo("Waiting for ACP client connection on stdin/stdout...\n");
17614
+ deps.renderer.writeInfo("(default runTurn is a no-op echo \u2014 wire makeACPServerAgentTurn for a real agent)\n");
17198
17615
  deps.renderer.writeInfo("Press Ctrl+C to stop.\n");
17199
- const server = new WrongStackACPServer({ tools });
17616
+ const server = new WrongStackACPServer({});
17200
17617
  const shutdown = () => {
17201
17618
  deps.renderer.writeWarning("\nShutting down ACP server...");
17202
17619
  server.stop();
@@ -17215,16 +17632,25 @@ async function runACPServer(deps) {
17215
17632
  }
17216
17633
  return 0;
17217
17634
  }
17218
- function listACPAgents(deps) {
17219
- deps.renderer.write("Available ACP agents:\n\n");
17220
- for (const a of ACP_AGENTS) {
17221
- const id = a.id ?? a.role;
17222
- const name = a.name ?? a.role ?? "";
17223
- const desc = a.prompt?.slice(0, 50) ?? "";
17224
- deps.renderer.write(` ${id.padEnd(16)} ${name.padEnd(20)} ${desc}\u2026
17635
+ async function listACPAgents(deps) {
17636
+ const registry = new EnsembleRegistry();
17637
+ const detected = await registry.list();
17638
+ deps.renderer.write("Detected ACP agents:\n\n");
17639
+ const installed = detected.filter((a) => a.installed);
17640
+ const missing = detected.filter((a) => !a.installed);
17641
+ for (const a of installed) {
17642
+ const ver = a.version ? ` (${a.version.split("\n")[0]})` : "";
17643
+ deps.renderer.write(` \u2713 ${a.id.padEnd(16)} ${a.displayName}${ver}
17644
+ `);
17645
+ }
17646
+ for (const a of missing) {
17647
+ deps.renderer.write(` \u2717 ${a.id.padEnd(16)} ${a.displayName} (${a.reason ?? "not installed"})
17225
17648
  `);
17226
17649
  }
17227
- deps.renderer.write("\nUse `wstack acp spawn <agent> <task>` to delegate a task.\n");
17650
+ deps.renderer.write(`
17651
+ ${installed.length} of ${detected.length} agents available.
17652
+ `);
17653
+ deps.renderer.write("Use `wstack acp spawn <agent-id> <task>` to delegate a task.\n");
17228
17654
  return 0;
17229
17655
  }
17230
17656
  async function spawnACPAgent(args, deps) {
@@ -17240,7 +17666,7 @@ async function spawnACPAgent(args, deps) {
17240
17666
  deps.renderer.write("Task description is required.\n");
17241
17667
  return 1;
17242
17668
  }
17243
- const cmd = ACP_AGENT_COMMANDS[subagentId];
17669
+ const cmd = ACP_AGENT_COMMANDS[subagentId] ?? resolveCmdFromCatalog(subagentId);
17244
17670
  if (!cmd) {
17245
17671
  deps.renderer.writeError(`Unknown ACP agent: ${subagentId}
17246
17672
  `);
@@ -17294,10 +17720,11 @@ async function spawnACPAgent(args, deps) {
17294
17720
  );
17295
17721
  return 0;
17296
17722
  } catch (err) {
17297
- deps.renderer.writeError(
17298
- `ACP agent error: ${err instanceof Error ? err.message : String(err)}
17299
- `
17300
- );
17723
+ const e = err;
17724
+ const detail = e.kind ? `[${e.kind}] ` : "";
17725
+ const message = e.message ?? (err instanceof Error ? err.message : String(err));
17726
+ deps.renderer.writeError(`ACP agent error: ${detail}${message}
17727
+ `);
17301
17728
  return 1;
17302
17729
  } finally {
17303
17730
  cleanup();
@@ -17305,6 +17732,94 @@ async function spawnACPAgent(args, deps) {
17305
17732
  process.off("SIGTERM", cleanup);
17306
17733
  }
17307
17734
  }
17735
+ async function parallelACPAgents(args, deps) {
17736
+ const [csv, ...taskParts] = args;
17737
+ if (!csv) {
17738
+ deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
17739
+ deps.renderer.write('Example: wstack acp parallel claude-code,gemini-cli "review this diff"\n');
17740
+ return 1;
17741
+ }
17742
+ const task = taskParts.join(" ");
17743
+ if (!task) {
17744
+ deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
17745
+ deps.renderer.writeError("Task description is required.\n");
17746
+ return 1;
17747
+ }
17748
+ const ac = new AbortController();
17749
+ const onSignal = () => ac.abort();
17750
+ process.on("SIGINT", onSignal);
17751
+ process.on("SIGTERM", onSignal);
17752
+ try {
17753
+ const result = await runEnsemble({
17754
+ agentIds: csv,
17755
+ task,
17756
+ resolveCmd: resolveCmdFromCatalog,
17757
+ signal: ac.signal
17758
+ });
17759
+ const skipped = result.results.filter((r) => r.status === "skipped");
17760
+ if (skipped.length > 0) {
17761
+ deps.renderer.writeWarning(
17762
+ `Skipping ${skipped.length} agent(s) not installed: ${skipped.map((s) => `${s.agentId} (${s.reason ?? "not installed"})`).join(", ")}
17763
+ `
17764
+ );
17765
+ }
17766
+ if (result.summary.succeeded + result.summary.failed + result.summary.cancelled === 0) {
17767
+ deps.renderer.writeError("No installed agents to run.\n");
17768
+ deps.renderer.write("Run `wstack acp list` to see what is available.\n");
17769
+ return 1;
17770
+ }
17771
+ const fannedOut = result.results.filter((r) => r.status !== "skipped").map((r) => r.agentId).join(", ");
17772
+ deps.renderer.writeInfo(
17773
+ `Fanning out to ${result.summary.succeeded + result.summary.failed + result.summary.cancelled} agent(s): ${fannedOut}
17774
+ `
17775
+ );
17776
+ deps.renderer.writeInfo(`Task: ${result.task}
17777
+
17778
+ `);
17779
+ for (const r of result.results) {
17780
+ if (r.status === "skipped") continue;
17781
+ deps.renderer.write(`
17782
+ === ${r.agentId} ===
17783
+ `);
17784
+ if (r.status === "success") {
17785
+ deps.renderer.write(r.result && r.result.length > 0 ? r.result : "(no result)");
17786
+ deps.renderer.write(
17787
+ `
17788
+ [${r.agentId}] success ${r.durationMs}ms iterations=${r.iterations} toolCalls=${r.toolCalls}
17789
+ `
17790
+ );
17791
+ } else if (r.status === "failed") {
17792
+ deps.renderer.writeError(
17793
+ `[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}
17794
+ `
17795
+ );
17796
+ deps.renderer.write(
17797
+ `[${r.agentId}] failed ${r.durationMs}ms
17798
+ `
17799
+ );
17800
+ } else {
17801
+ deps.renderer.writeError(
17802
+ `[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}
17803
+ `
17804
+ );
17805
+ deps.renderer.write(
17806
+ `[${r.agentId}] cancelled ${r.durationMs}ms
17807
+ `
17808
+ );
17809
+ }
17810
+ }
17811
+ const { succeeded, failed, cancelled, skipped: skip } = result.summary;
17812
+ deps.renderer.write(
17813
+ `
17814
+ Parallel summary: ${succeeded} succeeded, ${failed} failed, ${cancelled} cancelled, ${skip} skipped.
17815
+ `
17816
+ );
17817
+ return succeeded > 0 ? 0 : 1;
17818
+ } finally {
17819
+ process.off("SIGINT", onSignal);
17820
+ process.off("SIGTERM", onSignal);
17821
+ }
17822
+ }
17308
17823
  var auditCmd = async (args, deps) => {
17309
17824
  const wpaths = resolveWstackPaths({
17310
17825
  projectRoot: deps.projectRoot,
@@ -18092,6 +18607,35 @@ ${color.amber("?")} Pick: `)).trim().toLowerCase();
18092
18607
  }
18093
18608
  }
18094
18609
 
18610
+ // src/auth-menu/local.ts
18611
+ init_provider_config_utils();
18612
+
18613
+ // src/auth-menu/local.ts
18614
+ var LOCAL_LLM_PRESETS = [
18615
+ {
18616
+ id: "ollama",
18617
+ label: "Ollama",
18618
+ defaultBaseUrl: "http://localhost:11434/v1",
18619
+ noAuth: true,
18620
+ hint: "https://ollama.com \u2014 port 11434, no auth"
18621
+ },
18622
+ {
18623
+ id: "vllm",
18624
+ label: "vLLM",
18625
+ defaultBaseUrl: "http://localhost:8000/v1",
18626
+ noAuth: false,
18627
+ hint: "https://docs.vllm.ai \u2014 port 8000, optional Bearer"
18628
+ },
18629
+ {
18630
+ id: "lmstudio",
18631
+ label: "LM Studio",
18632
+ defaultBaseUrl: "http://localhost:1234/v1",
18633
+ noAuth: false,
18634
+ hint: "https://lmstudio.ai \u2014 port 1234, optional Bearer"
18635
+ }
18636
+ ];
18637
+ new Map(LOCAL_LLM_PRESETS.map((p) => [p.id, p]));
18638
+
18095
18639
  // src/subcommands/handlers/auth.ts
18096
18640
  init_provider_config_utils();
18097
18641
  var authCmd = async (args, deps) => {
@@ -18351,7 +18895,7 @@ async function benchRun(_args, deps) {
18351
18895
  try {
18352
18896
  config = await loadBenchConfig(path39.resolve(deps.cwd, modelsPath));
18353
18897
  } catch (err) {
18354
- deps.renderer.writeError(err instanceof Error ? err.message : String(err));
18898
+ deps.renderer.writeError(toErrorMessage(err));
18355
18899
  return 1;
18356
18900
  }
18357
18901
  const concurrencyRaw = flagStr(deps, "concurrency");
@@ -18405,7 +18949,7 @@ async function benchRun(_args, deps) {
18405
18949
  onProgress: (msg) => deps.renderer.write(color.dim(msg) + "\n")
18406
18950
  });
18407
18951
  } catch (err) {
18408
- deps.renderer.writeError(err instanceof Error ? err.message : String(err));
18952
+ deps.renderer.writeError(toErrorMessage(err));
18409
18953
  return 1;
18410
18954
  }
18411
18955
  await writeJsonArtifacts(outDir, report);
@@ -18438,7 +18982,7 @@ async function benchReport(args, deps) {
18438
18982
  summary = await readSummary(outDir);
18439
18983
  } catch (err) {
18440
18984
  deps.renderer.writeError(
18441
- `cannot read summary.json in ${outDir}: ${err instanceof Error ? err.message : String(err)}`
18985
+ `cannot read summary.json in ${outDir}: ${toErrorMessage(err)}`
18442
18986
  );
18443
18987
  return 1;
18444
18988
  }
@@ -18476,7 +19020,7 @@ async function benchList(_args, deps) {
18476
19020
  });
18477
19021
  deps.renderer.write("\n" + color.dim(`Harness: ${fp}`) + "\n");
18478
19022
  } catch (err) {
18479
- deps.renderer.writeError(err instanceof Error ? err.message : String(err));
19023
+ deps.renderer.writeError(toErrorMessage(err));
18480
19024
  return 1;
18481
19025
  }
18482
19026
  }
@@ -18560,7 +19104,7 @@ var doctorCmd = async (_args, deps) => {
18560
19104
  checks.push({
18561
19105
  name: "models cache",
18562
19106
  status: "warn",
18563
- detail: `read failed: ${err instanceof Error ? err.message : String(err)}`
19107
+ detail: `read failed: ${toErrorMessage(err)}`
18564
19108
  });
18565
19109
  }
18566
19110
  try {
@@ -18583,7 +19127,7 @@ var doctorCmd = async (_args, deps) => {
18583
19127
  checks.push({
18584
19128
  name: "sessions writable",
18585
19129
  status: "fail",
18586
- detail: `cannot write to ${deps.paths.projectSessions}: ${err instanceof Error ? err.message : String(err)}`
19130
+ detail: `cannot write to ${deps.paths.projectSessions}: ${toErrorMessage(err)}`
18587
19131
  });
18588
19132
  }
18589
19133
  const mcpEntries = Object.entries(cfg.mcpServers ?? {});
@@ -18674,7 +19218,7 @@ var exportCmd = async (args, deps) => {
18674
19218
  try {
18675
19219
  rendered = await reader.export(sessionId, { format, includeTools, includeDiagnostics });
18676
19220
  } catch (err) {
18677
- deps.renderer.writeError(`Export failed: ${err instanceof Error ? err.message : String(err)}`);
19221
+ deps.renderer.writeError(`Export failed: ${toErrorMessage(err)}`);
18678
19222
  return 1;
18679
19223
  }
18680
19224
  if (output) {
@@ -19647,7 +20191,7 @@ var modeldiagCmd = async (args, deps) => {
19647
20191
  ` ${label} ${provColor(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.amber(fmtMs(latency).padEnd(8))} ${color.dim(`in${usage?.input ?? "?"}/out${usage?.output ?? "?"}`.padEnd(12))} ${firstLineClean}`
19648
20192
  );
19649
20193
  } catch (err) {
19650
- const errMsg = err instanceof Error ? err.message : String(err);
20194
+ const errMsg = toErrorMessage(err);
19651
20195
  writeLine(
19652
20196
  ` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("FAILED")} ${color.dim(errMsg.slice(0, 40))}`
19653
20197
  );
@@ -20691,7 +21235,7 @@ ${result.errors.length} error(s):
20691
21235
  }
20692
21236
  return 0;
20693
21237
  } catch (err) {
20694
- deps.renderer.writeError(err instanceof Error ? err.message : String(err));
21238
+ deps.renderer.writeError(toErrorMessage(err));
20695
21239
  return 1;
20696
21240
  }
20697
21241
  };
@@ -21230,7 +21774,7 @@ async function boot(argv) {
21230
21774
  try {
21231
21775
  bootResult = await bootConfig(flags);
21232
21776
  } catch (err) {
21233
- writeErr(`Config error: ${err instanceof Error ? err.message : String(err)}
21777
+ writeErr(`Config error: ${toErrorMessage(err)}
21234
21778
  `);
21235
21779
  return 2;
21236
21780
  }
@@ -21283,7 +21827,7 @@ async function boot(argv) {
21283
21827
  await modelsRegistry.refresh();
21284
21828
  logger.info("models.dev catalog refreshed");
21285
21829
  } catch (err) {
21286
- const msg = err instanceof Error ? err.message : String(err);
21830
+ const msg = toErrorMessage(err);
21287
21831
  logger.warn(`models.dev refresh failed (${msg}); using cached catalog`);
21288
21832
  }
21289
21833
  }
@@ -21584,7 +22128,7 @@ async function checkGitInCwd(opts) {
21584
22128
  hasCwdGit = true;
21585
22129
  } catch (err) {
21586
22130
  renderer.writeError(
21587
- `git init failed: ${err instanceof Error ? err.message : String(err)}
22131
+ `git init failed: ${toErrorMessage(err)}
21588
22132
  `
21589
22133
  );
21590
22134
  }
@@ -22359,7 +22903,7 @@ async function runRepl(opts) {
22359
22903
  }
22360
22904
  } catch (err) {
22361
22905
  opts.renderer.writeError(
22362
- `[eternal] ${err instanceof Error ? err.message : String(err)}`
22906
+ `[eternal] ${toErrorMessage(err)}`
22363
22907
  );
22364
22908
  }
22365
22909
  await new Promise((resolve11) => setTimeout(resolve11, 250));
@@ -22435,7 +22979,7 @@ async function runRepl(opts) {
22435
22979
  }
22436
22980
  } catch (err) {
22437
22981
  opts.renderer.writeError(
22438
- `[parallel] ${err instanceof Error ? err.message : String(err)}`
22982
+ `[parallel] ${toErrorMessage(err)}`
22439
22983
  );
22440
22984
  }
22441
22985
  await new Promise((resolve11) => setTimeout(resolve11, 250));
@@ -22508,7 +23052,7 @@ ${lines.join("\n")}
22508
23052
  if (res?.message) opts.renderer.write(`${res.message}
22509
23053
  `);
22510
23054
  } catch (err) {
22511
- opts.renderer.writeError(err instanceof Error ? err.message : String(err));
23055
+ opts.renderer.writeError(toErrorMessage(err));
22512
23056
  }
22513
23057
  continue;
22514
23058
  }
@@ -22597,7 +23141,7 @@ ${color.dim(taskList2)}
22597
23141
  }
22598
23142
  }
22599
23143
  } catch (err) {
22600
- opts.renderer.writeError(err instanceof Error ? err.message : String(err));
23144
+ opts.renderer.writeError(toErrorMessage(err));
22601
23145
  }
22602
23146
  continue;
22603
23147
  }
@@ -22786,7 +23330,7 @@ ${color.dim(
22786
23330
  }
22787
23331
  } catch (err) {
22788
23332
  opts.renderer.writeError(
22789
- `[autonomy] ${err instanceof Error ? err.message : String(err)}`
23333
+ `[autonomy] ${toErrorMessage(err)}`
22790
23334
  );
22791
23335
  } finally {
22792
23336
  activeCtrl = void 0;
@@ -22857,7 +23401,7 @@ ${color.dim(lines)}
22857
23401
  }
22858
23402
  }
22859
23403
  } catch (err) {
22860
- opts.renderer.writeError(err instanceof Error ? err.message : String(err));
23404
+ opts.renderer.writeError(toErrorMessage(err));
22861
23405
  } finally {
22862
23406
  activeCtrl = void 0;
22863
23407
  }
@@ -22884,7 +23428,7 @@ async function pasteClipboardImage(builder, opts) {
22884
23428
  `));
22885
23429
  } catch (err) {
22886
23430
  opts.renderer.writeError(
22887
- `Clipboard image error: ${err instanceof Error ? err.message : String(err)}`
23431
+ `Clipboard image error: ${toErrorMessage(err)}`
22888
23432
  );
22889
23433
  }
22890
23434
  }
@@ -22922,7 +23466,7 @@ async function renderGoalBanner(opts) {
22922
23466
  await opts.slashRegistry.dispatch("/autonomy eternal", opts.agent.ctx);
22923
23467
  } catch (err) {
22924
23468
  opts.renderer.writeError(
22925
- `Auto-resume failed: ${err instanceof Error ? err.message : String(err)}`
23469
+ `Auto-resume failed: ${toErrorMessage(err)}`
22926
23470
  );
22927
23471
  }
22928
23472
  } else {
@@ -23134,6 +23678,7 @@ async function execute(deps) {
23134
23678
  effectiveMaxContext,
23135
23679
  queueStore,
23136
23680
  context,
23681
+ mailbox,
23137
23682
  stats,
23138
23683
  detachTodosCheckpoint,
23139
23684
  savedProviderCfg,
@@ -23223,8 +23768,8 @@ async function execute(deps) {
23223
23768
  timeoutMs: 3e5
23224
23769
  };
23225
23770
  const subagentId = await dir.spawn(cfg);
23226
- const { randomUUID: randomUUID6 } = await import('crypto');
23227
- const taskId = randomUUID6();
23771
+ const { randomUUID: randomUUID7 } = await import('crypto');
23772
+ const taskId = randomUUID7();
23228
23773
  await dir.assign({
23229
23774
  id: taskId,
23230
23775
  description: taskDesc,
@@ -23437,6 +23982,76 @@ async function execute(deps) {
23437
23982
  };
23438
23983
  const PROJECT_SWITCH_EXIT_CODE = 42;
23439
23984
  let pendingProjectSwitch = null;
23985
+ const coordinatorEvents = /* @__PURE__ */ new Set();
23986
+ let autonomousCoordinator = null;
23987
+ const onDirectorReady = (dir) => {
23988
+ if (autonomousCoordinator) return;
23989
+ const transcript = context.session.transcriptPath;
23990
+ const sessionDir = transcript ? path39.dirname(transcript) : wpaths.projectDir;
23991
+ const llmProvider = {
23992
+ decide: async (prompt) => {
23993
+ const sysPrompt = [
23994
+ {
23995
+ type: "text",
23996
+ text: 'You are the autonomous brain of a multi-agent coordination system. Pick the best option for the decision described and reply with JSON: {"optionId":"<id>","rationale":"<short why>"}.'
23997
+ }
23998
+ ];
23999
+ const userPrompt = {
24000
+ type: "text",
24001
+ text: `Decision: ${prompt.question}
24002
+
24003
+ Context: ${prompt.context}
24004
+
24005
+ Options:
24006
+ ${prompt.options.map((o, i) => ` ${i + 1}. [${o.id}] ${o.label}${o.consequence ? ` \u2014 ${o.consequence}` : ""}`).join("\n")}
24007
+
24008
+ Risk: ${prompt.risk}
24009
+
24010
+ Reply with ONLY the JSON object.`
24011
+ };
24012
+ const resp = await context.provider.complete(
24013
+ {
24014
+ model: context.model,
24015
+ system: sysPrompt,
24016
+ messages: [
24017
+ {
24018
+ role: "user",
24019
+ content: [userPrompt]
24020
+ }
24021
+ ],
24022
+ maxTokens: 1024,
24023
+ temperature: 0
24024
+ },
24025
+ { signal: context.signal }
24026
+ );
24027
+ const text = resp.content.filter((b) => b.type === "text").map((b) => b.text).join("\n").trim();
24028
+ const cleaned = text.replace(/^```(?:json)?\s*/i, "").replace(/```$/, "").trim();
24029
+ try {
24030
+ const parsed = JSON.parse(cleaned);
24031
+ const optId = parsed.optionId ?? prompt.options[0]?.id ?? "";
24032
+ return { optionId: optId, rationale: parsed.rationale ?? "" };
24033
+ } catch {
24034
+ return { optionId: prompt.options[0]?.id ?? "", rationale: text };
24035
+ }
24036
+ }
24037
+ };
24038
+ autonomousCoordinator = new AutonomousCoordinator({
24039
+ sessionDir,
24040
+ fleet: dir.fleet,
24041
+ mailbox,
24042
+ selfAgentId: `leader@${context.session.id ?? "unknown"}`,
24043
+ selfAgentName: "Leader",
24044
+ llmProvider
24045
+ });
24046
+ };
24047
+ if (director) onDirectorReady(director);
24048
+ const offDirectorSpawned = events.onPattern("subagent.spawned", () => {
24049
+ const dir = director;
24050
+ if (dir) {
24051
+ offDirectorSpawned();
24052
+ onDirectorReady(dir);
24053
+ }
24054
+ });
23440
24055
  try {
23441
24056
  code = await runTui({
23442
24057
  agent,
@@ -23521,6 +24136,7 @@ async function execute(deps) {
23521
24136
  featureSkills: cfg.features?.skills !== false,
23522
24137
  featureModelsRegistry: cfg.features?.modelsRegistry !== false,
23523
24138
  featureTokenSaving: cfg.features?.tokenSavingMode ?? false,
24139
+ allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
23524
24140
  contextAutoCompact: cfg.context?.autoCompact !== false,
23525
24141
  contextStrategy: cfg.context?.strategy ?? "hybrid",
23526
24142
  logLevel: cfg.log?.level ?? "info",
@@ -23559,9 +24175,11 @@ async function execute(deps) {
23559
24175
  if (s.enhanceEnabled !== void 0) a["enhance"] = s.enhanceEnabled;
23560
24176
  if (s.enhanceLanguage !== void 0) a["enhanceLanguage"] = s.enhanceLanguage;
23561
24177
  if (s.autonomyNextPrompt !== void 0) a["autonomyNextPrompt"] = s.autonomyNextPrompt;
24178
+ if (s.autoProceedMaxIterations !== void 0)
24179
+ a["autoProceedMaxIterations"] = s.autoProceedMaxIterations;
23562
24180
  }
23563
24181
  );
23564
- if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
24182
+ if (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.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
23565
24183
  const configScope = s.configScope ?? configStore.get().configScope ?? "global";
23566
24184
  const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
23567
24185
  let raw;
@@ -23578,7 +24196,7 @@ async function execute(deps) {
23578
24196
  if (s.nextPrediction !== void 0) {
23579
24197
  decrypted.nextPrediction = s.nextPrediction;
23580
24198
  }
23581
- if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0) {
24199
+ if (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) {
23582
24200
  const feats = decrypted.features ?? {};
23583
24201
  if (s.featureMcp !== void 0) feats.mcp = s.featureMcp;
23584
24202
  if (s.featurePlugins !== void 0) feats.plugins = s.featurePlugins;
@@ -23588,6 +24206,8 @@ async function execute(deps) {
23588
24206
  feats.modelsRegistry = s.featureModelsRegistry;
23589
24207
  if (s.featureTokenSaving !== void 0)
23590
24208
  feats.tokenSavingMode = s.featureTokenSaving;
24209
+ if (s.allowOutsideProjectRoot !== void 0)
24210
+ feats.allowOutsideProjectRoot = s.allowOutsideProjectRoot;
23591
24211
  decrypted.features = feats;
23592
24212
  }
23593
24213
  if (s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0) {
@@ -23673,6 +24293,7 @@ async function execute(deps) {
23673
24293
  if (s.streamFleet !== void 0) {
23674
24294
  fleetStreamController?.setEnabled(s.streamFleet);
23675
24295
  }
24296
+ deps.applyLiveSettings?.(s);
23676
24297
  return null;
23677
24298
  } catch (err) {
23678
24299
  const message = err instanceof Error ? err.message : String(err);
@@ -23697,6 +24318,17 @@ async function execute(deps) {
23697
24318
  confirmExit: config.autonomy?.["confirmExit"] ?? true,
23698
24319
  director,
23699
24320
  fleetRoster,
24321
+ // ── AutonomousCoordinator: project-level multi-session coordination ─────────
24322
+ // The coordinator tracks goals, tasks, knowledge, and consensus across all
24323
+ // active sessions in the same project. It runs independently of the leader
24324
+ // agent and is accessible to any session in the project via the GlobalMailbox.
24325
+ getAutonomousCoordinator: () => autonomousCoordinator,
24326
+ subscribeCoordinatorEvents: (fn) => {
24327
+ coordinatorEvents.add(fn);
24328
+ return () => {
24329
+ coordinatorEvents.delete(fn);
24330
+ };
24331
+ },
23700
24332
  // /clear: signal the TUI to wipe entries and reset fleet/leader stats
23701
24333
  // AND bump the context chip version — so the display reflects a
23702
24334
  // completely fresh session after the backend has been cleared.
@@ -24080,6 +24712,7 @@ ${parts.join("\n")}
24080
24712
  }
24081
24713
  } finally {
24082
24714
  renderer.setSilent(false);
24715
+ offDirectorSpawned();
24083
24716
  }
24084
24717
  } else if (flags.webui) {
24085
24718
  agent.disableInteractiveConfirmation();
@@ -25486,7 +26119,7 @@ function setupMetrics(params) {
25486
26119
  });
25487
26120
  } catch (err) {
25488
26121
  logger.warn(
25489
- `metrics endpoint failed to start: ${err instanceof Error ? err.message : String(err)}`
26122
+ `metrics endpoint failed to start: ${toErrorMessage(err)}`
25490
26123
  );
25491
26124
  }
25492
26125
  }
@@ -25542,9 +26175,11 @@ async function setupCompaction(params) {
25542
26175
  context.meta["contextWindowMode"] = initialPolicy.id;
25543
26176
  context.meta["contextWindowPolicy"] = initialPolicy;
25544
26177
  let autoCompactor;
25545
- if (config.context.autoCompact !== false && effectiveMaxContext > 0) {
26178
+ let resolvedBridge;
26179
+ if (effectiveMaxContext > 0) {
25546
26180
  const auditLevel = resolveAuditLevel(fullConfig ?? config);
25547
26181
  const sessionBridge = providedBridge ?? createSessionEventBridge(sessionWriter, auditLevel);
26182
+ resolvedBridge = sessionBridge;
25548
26183
  autoCompactor = new AutoCompactionMiddleware(
25549
26184
  compactor,
25550
26185
  effectiveMaxContext,
@@ -25568,9 +26203,10 @@ async function setupCompaction(params) {
25568
26203
  sessionBridge
25569
26204
  }
25570
26205
  );
26206
+ autoCompactor.setEnabled(config.context.autoCompact !== false);
25571
26207
  pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
25572
26208
  }
25573
- return { effectiveMaxContext, autoCompactor };
26209
+ return { effectiveMaxContext, autoCompactor, sessionBridge: resolvedBridge };
25574
26210
  }
25575
26211
  function createAgent(params) {
25576
26212
  const secretScrubber = params.container.resolve(TOKENS.SecretScrubber);
@@ -25676,7 +26312,7 @@ async function setupSession(params) {
25676
26312
  `Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${restoredToolCalls.length} tool executions, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`
25677
26313
  );
25678
26314
  } catch (err) {
25679
- renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
26315
+ renderer.writeError(`Resume failed: ${toErrorMessage(err)}`);
25680
26316
  throw Object.assign(new Error("RESUME_FAILED"), { exitCode: 2 });
25681
26317
  }
25682
26318
  } else {
@@ -25714,7 +26350,8 @@ async function setupSession(params) {
25714
26350
  model: config.model,
25715
26351
  agentId: "leader",
25716
26352
  agentName: "Leader Agent",
25717
- traceId
26353
+ traceId,
26354
+ allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? true
25718
26355
  });
25719
26356
  context.meta["packageTrackerOpts"] = {
25720
26357
  storageDir: wpaths.projectDir,
@@ -26034,7 +26671,10 @@ async function main(argv) {
26034
26671
  const evOn = (event, handler) => {
26035
26672
  events.on(event, handler);
26036
26673
  teardownHandlers.push(
26037
- () => events.off(event, handler)
26674
+ () => (
26675
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic event dispatcher signature
26676
+ events.off(event, handler)
26677
+ )
26038
26678
  );
26039
26679
  };
26040
26680
  evOn("provider.response", (e) => {
@@ -27553,6 +28193,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
27553
28193
  tokenCounter,
27554
28194
  config,
27555
28195
  configStore,
28196
+ // Project-scoped mailbox — the AutonomousCoordinator in execution.ts
28197
+ // subscribes to it so goals/tasks/knowledge are shared with other
28198
+ // terminals working on the same project.
28199
+ mailbox: brainMailbox,
27556
28200
  renderer,
27557
28201
  reader,
27558
28202
  session,
@@ -27592,6 +28236,34 @@ Restart WrongStack to load or unload plugin code in this session.`;
27592
28236
  return autonomyMode;
27593
28237
  },
27594
28238
  getNextPredict: () => nextPredictEnabled,
28239
+ applyLiveSettings: (s) => {
28240
+ try {
28241
+ if (s.yolo !== void 0) {
28242
+ container.resolve(TOKENS.PermissionPolicy).setYolo?.(s.yolo);
28243
+ config = patchConfig(config, { yolo: s.yolo });
28244
+ }
28245
+ if (s.nextPrediction !== void 0) {
28246
+ nextPredictEnabled = s.nextPrediction;
28247
+ config = patchConfig(config, { nextPrediction: s.nextPrediction });
28248
+ }
28249
+ if (s.enhanceEnabled !== void 0) {
28250
+ enhanceController?.setEnabled(s.enhanceEnabled);
28251
+ }
28252
+ if (s.maxIterations !== void 0) {
28253
+ agent.maxIterations = s.maxIterations;
28254
+ }
28255
+ if (s.logLevel !== void 0) {
28256
+ container.resolve(TOKENS.Logger).level = s.logLevel;
28257
+ }
28258
+ if (s.auditLevel !== void 0) {
28259
+ sessionBridge.setAuditLevel(s.auditLevel);
28260
+ }
28261
+ if (s.contextAutoCompact !== void 0) {
28262
+ autoCompactor?.setEnabled(s.contextAutoCompact);
28263
+ }
28264
+ } catch {
28265
+ }
28266
+ },
27595
28267
  onSuggestionsParsed: (suggestions) => {
27596
28268
  currentSuggestions = suggestions ?? [];
27597
28269
  setSuggestions(suggestions ?? []);
@@ -27674,7 +28346,9 @@ Reply YES to auto-proceed, NO to wait for human input.`
27674
28346
  getBrainLog: () => brainLog,
27675
28347
  // Clean up SessionStats event listeners and all EventBus handlers when the REPL exits.
27676
28348
  onDestroy: () => {
27677
- teardownHandlers.forEach((fn) => fn());
28349
+ teardownHandlers.forEach((fn) => {
28350
+ fn();
28351
+ });
27678
28352
  stats.destroy(events);
27679
28353
  }
27680
28354
  });