@wrongstack/cli 0.273.0 → 0.273.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 { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, DefaultSecretScrubber, resolveWstackPaths, projectHash, wstackGlobalRoot, TOKENS, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DEFAULT_CONTEXT_WINDOW_MODE_ID, resolveProjectDir, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, resolveProviderModelList, DefaultLogger, DefaultModelsRegistry, isStdinTTY, atomicWrite, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, applyToolDescriptionModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, applyModelRuntime, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, WIDE_SUBAGENT_CAPABILITIES, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, assignNickname, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, setToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, InputBuilder, DefaultSecretVault, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, HQ_AUTH_FILE_VERSION, FsError, ERROR_CODES, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, ConfigError, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, DefaultTokenCounter, isSecretField as isSecretField$1, startSessionTelemetryBridge } from '@wrongstack/core';
2
+ import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, DefaultSecretScrubber, resolveWstackPaths, projectHash, wstackGlobalRoot, TOKENS, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DEFAULT_CONTEXT_WINDOW_MODE_ID, resolveProjectDir, DefaultLogger, DefaultModelsRegistry, isStdinTTY, atomicWrite, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, applyToolDescriptionModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, applyModelRuntime, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, DefaultTokenCounter, AutoApprovePermissionPolicy, WIDE_SUBAGENT_CAPABILITIES, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, assignNickname, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, projectSlug, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, repairToolUseAdjacency, mutateTasks, loadTasks, mutatePlan, setPlanItemStatus, getPlanTemplate, emptyPlan, addPlanItem, savePlan, resolveProviderModelList, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, makeAgentSubagentRunner, NULL_FLEET_BUS, FsError, ERROR_CODES, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, setToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, InputBuilder, DefaultSecretVault, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, HQ_AUTH_FILE_VERSION, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, ConfigError, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, isSecretField as isSecretField$1, startSessionTelemetryBridge } from '@wrongstack/core';
3
3
  import * as fs4 from 'fs/promises';
4
4
  import { decryptConfigSecrets, encryptConfigSecrets, DefaultSecretVault as DefaultSecretVault$1, isSecretField } from '@wrongstack/core/security';
5
5
  import * as path4 from 'path';
@@ -12,10 +12,10 @@ import * as os7 from 'os';
12
12
  import os7__default from 'os';
13
13
  import { WebSocketServer, WebSocket } from 'ws';
14
14
  import { createRequire } from 'module';
15
- import { findFreePort, AutoPhaseWebSocketHandler, WorktreeWebSocketHandler, SpecsWebSocketHandler, SddBoardWebSocketHandler, SddWizardWebSocketHandler, buildSddWizardDeps, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleGitDiff, handleGitChanges, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMcpRestart, handleMcpDisable, handleMcpEnable, handleMcpDiscover, handleMcpSleep, handleMcpWake, handleMcpUpdate, handleMcpRemove, handleMcpAdd, handleMcpList, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleCompletionRequest, createToolLspCompletionSource, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser as openBrowser$1, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
15
+ import { findFreePort, AutoPhaseWebSocketHandler, WorktreeWebSocketHandler, SpecsWebSocketHandler, SddBoardWebSocketHandler, SddWizardWebSocketHandler, buildSddWizardDeps, generateAuthToken, verifyClient, createEternalSubscription, handleShellOpen, handleGitInfo, handleGitDiff, handleGitChanges, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMcpRestart, handleMcpDisable, handleMcpEnable, handleMcpDiscover, handleMcpSleep, handleMcpWake, handleMcpUpdate, handleMcpRemove, handleMcpAdd, handleMcpList, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleCompletionRequest, createToolLspCompletionSource, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createHttpServer, registerInstance, openBrowser as openBrowser$1, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
16
16
  import { toErrorMessage, readJsonObjectFile, updateJsonObjectFile, setJsonPath, jsonObjectFileExists, removeJsonPath } from '@wrongstack/core/utils';
17
- import { setOAuthTokenPersister, makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry, refreshCopilotToken, copilotBaseUrlFromToken } from '@wrongstack/providers';
18
- import { getProcessRegistry, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
17
+ import { setOAuthTokenPersister, capabilitiesFor, makeProviderFromConfig, buildProviderFactoriesFromRegistry, refreshCopilotToken, copilotBaseUrlFromToken } from '@wrongstack/providers';
18
+ import { ensureSessionShell, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, configureExecPolicy, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, getProcessRegistry, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
19
19
  import { DefaultSessionStore } from '@wrongstack/core/storage';
20
20
  import { probeLocalLlm } from '@wrongstack/runtime/probe';
21
21
  import * as fs3 from 'fs';
@@ -7435,7 +7435,11 @@ async function runWebUI(opts) {
7435
7435
  });
7436
7436
  }
7437
7437
  });
7438
- const mailbox = new GlobalMailbox(projectDir, opts.events, () => webuiHqConnection?.getPublisher());
7438
+ const mailbox = new GlobalMailbox(
7439
+ projectDir,
7440
+ opts.events,
7441
+ () => webuiHqConnection?.getPublisher()
7442
+ );
7439
7443
  webuiClientId = `webui@${crypto2.randomUUID().slice(0, 8)}`;
7440
7444
  const projectName = opts.projectRoot ? path4.basename(opts.projectRoot) : "unknown";
7441
7445
  await mailbox.registerClient({
@@ -8270,7 +8274,7 @@ ${text}` : text;
8270
8274
  broadcast,
8271
8275
  log: (m) => console.log(m)
8272
8276
  };
8273
- return new Promise((resolve13) => {
8277
+ const stopped = new Promise((resolve13) => {
8274
8278
  wss.on("listening", () => {
8275
8279
  console.log(`[WebUI] WebSocket server running on ws://${host}:${port}`);
8276
8280
  setupEvents();
@@ -8437,572 +8441,312 @@ ${text}` : text;
8437
8441
  });
8438
8442
  registerWebuiSignalHandlers(signalShutdown);
8439
8443
  });
8440
- async function handleMessage(ws, _client, msg) {
8441
- switch (msg.type) {
8442
- case "user_message":
8443
- await handleUserMessage(
8444
- connectionCtx,
8445
- ws,
8446
- msg.payload.content
8447
- );
8448
- break;
8449
- case "abort":
8450
- handleAbort(connectionCtx, ws);
8451
- break;
8452
- case "ping":
8453
- handlePing(connectionCtx, ws);
8454
- break;
8455
- case "tool.confirm_result": {
8456
- const { id, decision } = msg.payload;
8457
- handleToolConfirmResult(connectionCtx, id, decision);
8458
- break;
8459
- }
8460
- case "providers.list":
8461
- await handleProvidersList(wsHandlerCtx, ws);
8462
- break;
8463
- case "provider.models":
8464
- await handleProviderModels(
8465
- wsHandlerCtx,
8466
- ws,
8467
- msg.payload.providerId
8468
- );
8469
- break;
8470
- case "providers.saved":
8471
- await handleProvidersSaved(wsHandlerCtx, ws);
8472
- break;
8473
- case "key.add":
8474
- case "key.update": {
8475
- const m = msg;
8476
- await handleKeyUpsert(
8477
- wsHandlerCtx,
8478
- ws,
8479
- m.payload.providerId,
8480
- m.payload.label,
8481
- m.payload.apiKey
8482
- );
8483
- break;
8484
- }
8485
- case "key.delete": {
8486
- const m = msg;
8487
- await handleKeyDelete(wsHandlerCtx, ws, m.payload.providerId, m.payload.label);
8488
- break;
8489
- }
8490
- case "key.set_active": {
8491
- const m = msg;
8492
- await handleKeySetActive(wsHandlerCtx, ws, m.payload.providerId, m.payload.label);
8493
- break;
8494
- }
8495
- case "provider.add": {
8496
- const m = msg;
8497
- await handleProviderAdd(wsHandlerCtx, ws, m.payload);
8498
- break;
8499
- }
8500
- case "provider.remove": {
8501
- const m = msg;
8502
- await handleProviderRemove(wsHandlerCtx, ws, m.payload.providerId);
8503
- break;
8504
- }
8505
- case "provider.clear_models": {
8506
- const m = msg;
8507
- await handleProviderClearModels(wsHandlerCtx, ws, m.payload.providerId);
8508
- break;
8509
- }
8510
- case "provider.undo_clear": {
8511
- const m = msg;
8512
- await handleProviderUndoClear(
8513
- wsHandlerCtx,
8514
- ws,
8515
- m.payload.providerId,
8516
- m.payload.previousModels
8517
- );
8518
- break;
8519
- }
8520
- case "provider.update": {
8521
- const m = msg;
8522
- await handleProviderUpdate(wsHandlerCtx, ws, m.payload);
8523
- break;
8524
- }
8525
- case "provider.probe": {
8526
- const m = msg;
8527
- await handleProviderProbe(wsHandlerCtx, ws, m.payload.providerId, m.payload.timeoutMs);
8528
- break;
8529
- }
8530
- case "todos.get": {
8531
- handleTodosGet(worklistCtx, ws);
8532
- break;
8533
- }
8534
- case "goal.get": {
8535
- await handleGoalGet(sessionsCtx);
8536
- break;
8537
- }
8538
- case "sessions.list": {
8539
- await handleSessionsList(
8540
- sessionsCtx,
8541
- ws,
8542
- msg.payload?.limit ?? 50
8543
- );
8544
- break;
8545
- }
8546
- case "session.new": {
8547
- await handleSessionNew(sessionsCtx);
8548
- break;
8549
- }
8550
- case "todos.clear": {
8551
- handleTodosClear(worklistCtx, ws);
8552
- break;
8553
- }
8554
- case "todos.remove": {
8555
- handleTodosRemove(
8556
- worklistCtx,
8557
- ws,
8558
- msg.payload
8559
- );
8560
- break;
8561
- }
8562
- case "todo.update": {
8563
- handleTodoUpdate(
8564
- worklistCtx,
8565
- ws,
8566
- msg.payload
8567
- );
8568
- break;
8569
- }
8570
- case "context.clear": {
8571
- await handleContextClear(contextHandlerCtx, ws);
8572
- break;
8573
- }
8574
- case "process.list": {
8575
- handleProcessList(wsCommon, ws);
8576
- break;
8577
- }
8578
- case "process.kill": {
8579
- handleProcessKill(wsCommon, ws, msg.payload.pid);
8580
- break;
8581
- }
8582
- case "process.killAll": {
8583
- handleProcessKillAll(wsCommon, ws);
8584
- break;
8585
- }
8586
- case "diag.get": {
8587
- handleDiagGet(introspectionCtx, ws);
8588
- break;
8589
- }
8590
- case "stats.get": {
8591
- await handleStatsGet(introspectionCtx, ws);
8592
- break;
8593
- }
8594
- case "autonomy.switch": {
8595
- handleAutonomySwitch(prefsCtx, ws, msg.payload.mode);
8596
- break;
8597
- }
8598
- case "tools.list": {
8599
- handleToolsList(introspectionCtx, ws);
8600
- break;
8601
- }
8602
- case "session.checkpoints": {
8603
- await handleSessionCheckpoints(sessionsCtx, ws);
8604
- break;
8605
- }
8606
- case "session.rewind": {
8607
- await handleSessionRewind(
8608
- sessionsCtx,
8609
- ws,
8610
- msg.payload.checkpointIndex
8611
- );
8612
- break;
8613
- }
8614
- // ── File operations delegated to shared handlers (file-handlers.ts) ──
8615
- // These handlers are also used by the standalone WebUI server. When
8616
- // adding or modifying file-operation WebSocket messages, update
8617
- // file-handlers.ts NOT these case blocks individually.
8618
- case "files.list": {
8619
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
8620
- return handleFilesList(ws, msg, projectRoot);
8621
- }
8622
- case "files.tree": {
8623
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
8624
- return handleFilesTree(ws, msg, projectRoot);
8625
- }
8626
- case "files.read": {
8627
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
8628
- return handleFilesRead(ws, msg, projectRoot);
8629
- }
8630
- case "files.write": {
8631
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
8632
- return handleFilesWrite(ws, msg, projectRoot);
8633
- }
8634
- case "completion.request": {
8635
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
8636
- return handleCompletionRequest(ws, msg, {
8637
- projectRoot,
8638
- provider: opts.agent.ctx.provider,
8639
- model: opts.agent.ctx.model,
8640
- indexDir: typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
8641
- lspCompletion: createToolLspCompletionSource(
8642
- opts.agent.ctx.tools.find((tool) => tool.name === "lsp_completion"),
8643
- opts.agent.ctx
8644
- )
8444
+ const noop = () => {
8445
+ };
8446
+ const projectRootFor = () => opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
8447
+ const wsRoutes = {
8448
+ // ── Core connection ──
8449
+ user_message: (msg, ws) => handleUserMessage(
8450
+ connectionCtx,
8451
+ ws,
8452
+ msg.payload.content
8453
+ ),
8454
+ abort: (_msg, ws) => handleAbort(connectionCtx, ws),
8455
+ ping: (_msg, ws) => handlePing(connectionCtx, ws),
8456
+ "tool.confirm_result": (msg, _ws) => {
8457
+ const { id, decision } = msg.payload;
8458
+ handleToolConfirmResult(connectionCtx, id, decision);
8459
+ },
8460
+ "webui.shutdown": () => {
8461
+ console.log("[WebUI] Shutdown requested from client");
8462
+ shutdown();
8463
+ },
8464
+ // ── Providers / keys ──
8465
+ "providers.list": (_msg, ws) => handleProvidersList(wsHandlerCtx, ws),
8466
+ "provider.models": (msg, ws) => handleProviderModels(
8467
+ wsHandlerCtx,
8468
+ ws,
8469
+ msg.payload.providerId
8470
+ ),
8471
+ "providers.saved": (_msg, ws) => handleProvidersSaved(wsHandlerCtx, ws),
8472
+ "key.add": (msg, ws) => {
8473
+ const m = msg;
8474
+ handleKeyUpsert(wsHandlerCtx, ws, m.payload.providerId, m.payload.label, m.payload.apiKey);
8475
+ },
8476
+ "key.update": (msg, ws) => {
8477
+ const m = msg;
8478
+ handleKeyUpsert(wsHandlerCtx, ws, m.payload.providerId, m.payload.label, m.payload.apiKey);
8479
+ },
8480
+ "key.delete": (msg, ws) => {
8481
+ const m = msg;
8482
+ handleKeyDelete(wsHandlerCtx, ws, m.payload.providerId, m.payload.label);
8483
+ },
8484
+ "key.set_active": (msg, ws) => {
8485
+ const m = msg;
8486
+ handleKeySetActive(wsHandlerCtx, ws, m.payload.providerId, m.payload.label);
8487
+ },
8488
+ "provider.add": (msg, ws) => handleProviderAdd(
8489
+ wsHandlerCtx,
8490
+ ws,
8491
+ msg.payload
8492
+ ),
8493
+ "provider.remove": (msg, ws) => handleProviderRemove(
8494
+ wsHandlerCtx,
8495
+ ws,
8496
+ msg.payload.providerId
8497
+ ),
8498
+ "provider.clear_models": (msg, ws) => handleProviderClearModels(
8499
+ wsHandlerCtx,
8500
+ ws,
8501
+ msg.payload.providerId
8502
+ ),
8503
+ "provider.undo_clear": (msg, ws) => {
8504
+ const m = msg;
8505
+ handleProviderUndoClear(wsHandlerCtx, ws, m.payload.providerId, m.payload.previousModels);
8506
+ },
8507
+ "provider.update": (msg, ws) => handleProviderUpdate(
8508
+ wsHandlerCtx,
8509
+ ws,
8510
+ msg.payload
8511
+ ),
8512
+ "provider.probe": (msg, ws) => {
8513
+ const m = msg;
8514
+ handleProviderProbe(wsHandlerCtx, ws, m.payload.providerId, m.payload.timeoutMs);
8515
+ },
8516
+ // ── Todos / goals / plans / tasks ──
8517
+ "todos.get": (_msg, ws) => handleTodosGet(worklistCtx, ws),
8518
+ "todos.clear": (_msg, ws) => handleTodosClear(worklistCtx, ws),
8519
+ "todos.remove": (msg, ws) => handleTodosRemove(
8520
+ worklistCtx,
8521
+ ws,
8522
+ msg.payload
8523
+ ),
8524
+ "todo.update": (msg, ws) => handleTodoUpdate(
8525
+ worklistCtx,
8526
+ ws,
8527
+ msg.payload
8528
+ ),
8529
+ "goal.get": (_msg, ws) => handleGoalGet(sessionsCtx),
8530
+ "plan.get": (_msg, ws) => handlePlanGet(worklistCtx, ws),
8531
+ "plan.template_use": (msg, ws) => handlePlanTemplateUse(
8532
+ worklistCtx,
8533
+ ws,
8534
+ msg.payload.template
8535
+ ),
8536
+ "plan.item.update": (msg, ws) => handlePlanItemUpdate(
8537
+ worklistCtx,
8538
+ ws,
8539
+ msg.payload
8540
+ ),
8541
+ "tasks.get": (_msg, ws) => handleTasksGet(worklistCtx, ws),
8542
+ "task.update": (msg, ws) => handleTaskUpdate(
8543
+ worklistCtx,
8544
+ ws,
8545
+ msg.payload
8546
+ ),
8547
+ // ── Sessions ──
8548
+ "sessions.list": (msg, ws) => handleSessionsList(
8549
+ sessionsCtx,
8550
+ ws,
8551
+ msg.payload?.limit ?? 50
8552
+ ),
8553
+ "session.new": (_msg, ws) => handleSessionNew(sessionsCtx),
8554
+ "session.delete": (msg, ws) => handleSessionDelete(sessionsCtx, ws, msg.payload.id),
8555
+ "session.save": (_msg, ws) => handleSessionSave(sessionsCtx, ws),
8556
+ "session.resume": (msg, ws) => handleSessionResume(sessionsCtx, ws, msg.payload.id),
8557
+ "session.checkpoints": (_msg, ws) => handleSessionCheckpoints(sessionsCtx, ws),
8558
+ "session.rewind": (msg, ws) => handleSessionRewind(
8559
+ sessionsCtx,
8560
+ ws,
8561
+ msg.payload.checkpointIndex
8562
+ ),
8563
+ // ── Context ──
8564
+ "context.clear": (_msg, ws) => handleContextClear(contextHandlerCtx, ws),
8565
+ "context.debug": (_msg, ws) => handleContextDebug(contextHandlerCtx, ws),
8566
+ "context.compact": (msg, ws) => handleContextCompact(
8567
+ contextHandlerCtx,
8568
+ ws,
8569
+ !!msg.payload?.aggressive
8570
+ ),
8571
+ "context.repair": (_msg, ws) => handleContextRepair(contextHandlerCtx, ws),
8572
+ "context.modes.list": (_msg, ws) => handleContextModesList(contextHandlerCtx, ws),
8573
+ "context.mode.switch": (msg, ws) => handleContextModeSwitch(
8574
+ contextHandlerCtx,
8575
+ ws,
8576
+ msg.payload.id
8577
+ ),
8578
+ "context.mode.create": (msg, ws) => handleContextModeCreate(
8579
+ contextHandlerCtx,
8580
+ ws,
8581
+ msg.payload
8582
+ ),
8583
+ "context.mode.update": (msg, ws) => handleContextModeUpdate(
8584
+ contextHandlerCtx,
8585
+ ws,
8586
+ msg.payload
8587
+ ),
8588
+ "context.mode.delete": (msg, ws) => handleContextModeDelete(
8589
+ contextHandlerCtx,
8590
+ ws,
8591
+ msg.payload.id
8592
+ ),
8593
+ // ── Agent config: modes / models ──
8594
+ "modes.list": (_msg, ws) => handleModesList(agentConfigCtx, ws),
8595
+ "mode.switch": (msg, ws) => handleModeSwitch(agentConfigCtx, ws, msg.payload.id),
8596
+ "model.switch": (msg, ws) => handleModelSwitch(
8597
+ agentConfigCtx,
8598
+ ws,
8599
+ msg.payload
8600
+ ),
8601
+ "model.refine": (msg, ws) => handleModelRefine(agentConfigCtx, ws, msg.payload.text),
8602
+ // ── Process management ──
8603
+ "process.list": (_msg, ws) => handleProcessList(wsCommon, ws),
8604
+ "process.kill": (msg, ws) => handleProcessKill(wsCommon, ws, msg.payload.pid),
8605
+ "process.killAll": (_msg, ws) => handleProcessKillAll(wsCommon, ws),
8606
+ // ── Diagnostics / introspection ──
8607
+ "diag.get": (_msg, ws) => handleDiagGet(introspectionCtx, ws),
8608
+ "stats.get": (_msg, ws) => handleStatsGet(introspectionCtx, ws),
8609
+ "tools.list": (_msg, ws) => handleToolsList(introspectionCtx, ws),
8610
+ // ── Autonomy ──
8611
+ "autonomy.switch": (msg, ws) => handleAutonomySwitch(prefsCtx, ws, msg.payload.mode),
8612
+ // ── Brain ──
8613
+ "brain.status": (_msg, ws) => handleBrainStatus(brainCtx, ws),
8614
+ "brain.risk": (msg, ws) => handleBrainRisk(brainCtx, ws, msg.payload?.level ?? ""),
8615
+ "brain.ask": (msg, ws) => handleBrainAsk(brainCtx, ws, msg.payload?.question),
8616
+ // ── Preferences ──
8617
+ "prefs.get": (_msg, ws) => handlePrefsGet(prefsCtx, ws),
8618
+ "prefs.update": (msg, ws) => handlePrefsUpdate(prefsCtx, ws, msg.payload),
8619
+ // ── File operations (delegated to shared file-handlers.ts) ──
8620
+ "files.list": (msg, ws) => handleFilesList(ws, msg, projectRootFor()),
8621
+ "files.tree": (msg, ws) => handleFilesTree(ws, msg, projectRootFor()),
8622
+ "files.read": (msg, ws) => handleFilesRead(ws, msg, projectRootFor()),
8623
+ "files.write": (msg, ws) => handleFilesWrite(ws, msg, projectRootFor()),
8624
+ "completion.request": (msg, ws) => handleCompletionRequest(ws, msg, {
8625
+ projectRoot: projectRootFor(),
8626
+ provider: opts.agent.ctx.provider,
8627
+ model: opts.agent.ctx.model,
8628
+ indexDir: typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
8629
+ lspCompletion: createToolLspCompletionSource(
8630
+ opts.agent.ctx.tools.find((tool) => tool.name === "lsp_completion"),
8631
+ opts.agent.ctx
8632
+ )
8633
+ }),
8634
+ // ── Memory (guarded — opts.memoryStore may be undefined) ──
8635
+ "memory.list": (_msg, ws) => {
8636
+ if (!opts.memoryStore) {
8637
+ send(ws, {
8638
+ type: "memory.list",
8639
+ payload: { text: "", error: "Memory store not available" }
8645
8640
  });
8641
+ return;
8646
8642
  }
8647
- case "session.delete": {
8648
- await handleSessionDelete(sessionsCtx, ws, msg.payload.id);
8649
- break;
8650
- }
8651
- case "session.save":
8652
- handleSessionSave(sessionsCtx, ws);
8653
- break;
8654
- case "plan.get": {
8655
- await handlePlanGet(worklistCtx, ws);
8656
- break;
8657
- }
8658
- case "plan.template_use": {
8659
- await handlePlanTemplateUse(
8660
- worklistCtx,
8661
- ws,
8662
- msg.payload.template
8663
- );
8664
- break;
8665
- }
8666
- case "plan.item.update": {
8667
- await handlePlanItemUpdate(
8668
- worklistCtx,
8669
- ws,
8670
- msg.payload
8671
- );
8672
- break;
8673
- }
8674
- // ── Memory operations — delegated to shared handlers (memory-handlers.ts) ──
8675
- case "memory.list": {
8676
- if (!opts.memoryStore) {
8677
- send(ws, {
8678
- type: "memory.list",
8679
- payload: { text: "", error: "Memory store not available" }
8680
- });
8681
- break;
8682
- }
8683
- return handleMemoryList(ws, opts.memoryStore);
8684
- }
8685
- case "memory.remember": {
8686
- if (!opts.memoryStore) {
8687
- sendResult10(ws, false, "Memory store not available");
8688
- break;
8689
- }
8690
- return handleMemoryRemember(ws, msg, opts.memoryStore);
8691
- }
8692
- case "memory.forget": {
8693
- if (!opts.memoryStore) {
8694
- sendResult10(ws, false, "Memory store not available");
8695
- break;
8696
- }
8697
- return handleMemoryForget(ws, msg, opts.memoryStore);
8698
- }
8699
- // ── MCP operations — delegated to the shared handlers in
8700
- // @wrongstack/webui/server, which run against the same on-disk config
8701
- // and the live MCPRegistry the agent loop + `/mcp` use. ──
8702
- case "mcp.list":
8703
- await handleMcpList(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8704
- break;
8705
- case "mcp.add":
8706
- await handleMcpAdd(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8707
- break;
8708
- case "mcp.remove":
8709
- await handleMcpRemove(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8710
- break;
8711
- case "mcp.update":
8712
- await handleMcpUpdate(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8713
- break;
8714
- case "mcp.wake":
8715
- await handleMcpWake(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8716
- break;
8717
- case "mcp.sleep":
8718
- await handleMcpSleep(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8719
- break;
8720
- case "mcp.discover":
8721
- await handleMcpDiscover(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8722
- break;
8723
- case "mcp.enable":
8724
- await handleMcpEnable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8725
- break;
8726
- case "mcp.disable":
8727
- await handleMcpDisable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8728
- break;
8729
- case "mcp.restart":
8730
- await handleMcpRestart(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
8731
- break;
8732
- case "skills.list": {
8733
- await handleSkillsList(introspectionCtx, ws);
8734
- break;
8735
- }
8736
- case "skills.content": {
8737
- await handleSkillsContent(ws, skillsCtx, msg);
8738
- break;
8739
- }
8740
- case "skills.install": {
8741
- await handleSkillsInstall(ws, skillsCtx, msg);
8742
- break;
8743
- }
8744
- case "skills.uninstall": {
8745
- await handleSkillsUninstall(ws, skillsCtx, msg);
8746
- break;
8747
- }
8748
- case "skills.update": {
8749
- await handleSkillsUpdate(ws, skillsCtx, msg);
8750
- break;
8751
- }
8752
- case "skills.create": {
8753
- await handleSkillsCreate(ws, skillsCtx, msg);
8754
- break;
8755
- }
8756
- case "skills.edit": {
8757
- await handleSkillsEdit(ws, skillsCtx, msg);
8758
- break;
8759
- }
8760
- case "skills.export": {
8761
- await handleSkillsExport(ws, skillsCtx);
8762
- break;
8763
- }
8764
- case "modes.list": {
8765
- await handleModesList(agentConfigCtx, ws);
8766
- break;
8767
- }
8768
- case "mode.switch": {
8769
- await handleModeSwitch(agentConfigCtx, ws, msg.payload.id);
8770
- break;
8771
- }
8772
- case "model.switch": {
8773
- await handleModelSwitch(
8774
- agentConfigCtx,
8775
- ws,
8776
- msg.payload
8777
- );
8778
- break;
8779
- }
8780
- case "session.resume": {
8781
- await handleSessionResume(sessionsCtx, ws, msg.payload.id);
8782
- break;
8783
- }
8784
- case "context.debug": {
8785
- handleContextDebug(contextHandlerCtx, ws);
8786
- break;
8787
- }
8788
- case "context.compact": {
8789
- await handleContextCompact(
8790
- contextHandlerCtx,
8791
- ws,
8792
- !!msg.payload?.aggressive
8793
- );
8794
- break;
8795
- }
8796
- case "context.repair": {
8797
- handleContextRepair(contextHandlerCtx, ws);
8798
- break;
8799
- }
8800
- case "context.modes.list": {
8801
- await handleContextModesList(contextHandlerCtx, ws);
8802
- break;
8803
- }
8804
- case "context.mode.switch": {
8805
- await handleContextModeSwitch(
8806
- contextHandlerCtx,
8807
- ws,
8808
- msg.payload.id
8809
- );
8810
- break;
8811
- }
8812
- case "context.mode.create": {
8813
- await handleContextModeCreate(
8814
- contextHandlerCtx,
8815
- ws,
8816
- msg.payload
8817
- );
8818
- break;
8819
- }
8820
- case "context.mode.update": {
8821
- await handleContextModeUpdate(
8822
- contextHandlerCtx,
8823
- ws,
8824
- msg.payload
8825
- );
8826
- break;
8827
- }
8828
- case "context.mode.delete": {
8829
- await handleContextModeDelete(
8830
- contextHandlerCtx,
8831
- ws,
8832
- msg.payload.id
8833
- );
8834
- break;
8835
- }
8836
- // ── Brain — status, autonomy ceiling, direct decision support ───
8837
- // Shares the HOST's brain (TOKENS.BrainArbiter) and the same settings
8838
- // object the /brain slash command mutates, so the ceiling shown in the
8839
- // terminal and the WebUI never diverge. These used to be unknown
8840
- // message types on the embedded server.
8841
- case "brain.status": {
8842
- handleBrainStatus(brainCtx, ws);
8843
- break;
8844
- }
8845
- case "brain.risk": {
8846
- const level = msg.payload?.level ?? "";
8847
- handleBrainRisk(brainCtx, ws, level);
8848
- break;
8849
- }
8850
- case "brain.ask": {
8851
- const question = msg.payload?.question;
8852
- await handleBrainAsk(brainCtx, ws, question);
8853
- break;
8854
- }
8855
- // ── Preferences ──────────────────────────────────────────
8856
- case "prefs.get": {
8857
- handlePrefsGet(prefsCtx, ws);
8858
- break;
8859
- }
8860
- case "prefs.update": {
8861
- handlePrefsUpdate(prefsCtx, ws, msg.payload);
8862
- break;
8863
- }
8864
- // ── Tasks ───────────────────────────────────────────────
8865
- case "tasks.get": {
8866
- await handleTasksGet(worklistCtx, ws);
8867
- break;
8868
- }
8869
- case "task.update": {
8870
- await handleTaskUpdate(
8871
- worklistCtx,
8872
- ws,
8873
- msg.payload
8874
- );
8875
- break;
8876
- }
8877
- // Collaboration messages — the CLI webui-server doesn't run a
8878
- // full collab hub; silently acknowledge and ignore. request_pause /
8879
- // resume / grant_control / inject_tool are included so the CollabPanel's
8880
- // controller actions don't trip the "Unhandled message type" warning (the
8881
- // standalone webui server is the one that wires the real
8882
- // CollaborationWebSocketHandler).
8883
- case "collab.join":
8884
- case "collab.leave":
8885
- case "collab.annotate":
8886
- case "collab.resolve":
8887
- case "collab.request_pause":
8888
- case "collab.resume":
8889
- case "collab.grant_control":
8890
- case "collab.inject_tool":
8891
- break;
8892
- // Integrated terminal — the CLI embedded server doesn't run a pty
8893
- // transport (it already has its own terminal); silently acknowledge
8894
- // and ignore so the browser client's terminal panel doesn't trip the
8895
- // "Unhandled message type" warning. The standalone webui server wires
8896
- // the real TerminalWebSocketHandler.
8897
- case "terminal.create":
8898
- case "terminal.input":
8899
- case "terminal.resize":
8900
- case "terminal.close":
8901
- break;
8902
- case "projects.list": {
8903
- await handleProjectsList(projectsCtx, ws);
8904
- break;
8905
- }
8906
- case "projects.select": {
8907
- await handleProjectsSelect(
8908
- projectsCtx,
8909
- ws,
8910
- msg.payload
8911
- );
8912
- break;
8913
- }
8914
- case "projects.add": {
8915
- await handleProjectsAdd(
8916
- projectsCtx,
8917
- ws,
8918
- msg.payload
8919
- );
8920
- break;
8921
- }
8922
- case "working_dir.set": {
8923
- await handleWorkingDirSet(
8924
- projectsCtx,
8925
- ws,
8926
- msg.payload.path
8927
- );
8928
- break;
8929
- }
8930
- case "git.changes": {
8931
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
8932
- await handleGitChanges(ws, projectRoot);
8933
- break;
8934
- }
8935
- case "git.diff": {
8936
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
8937
- const filePath = msg.payload?.path ?? "";
8938
- await handleGitDiff(ws, projectRoot, filePath);
8939
- break;
8940
- }
8941
- case "shell.open": {
8942
- const result = await handleShellOpen(
8943
- msg.payload,
8944
- consoleLogger
8945
- );
8946
- sendResult10(ws, result.success, result.message);
8947
- break;
8948
- }
8949
- case "model.refine": {
8950
- await handleModelRefine(
8951
- agentConfigCtx,
8952
- ws,
8953
- msg.payload.text
8954
- );
8955
- break;
8956
- }
8957
- case "webui.shutdown":
8958
- console.log("[WebUI] Shutdown requested from client");
8959
- shutdown();
8960
- break;
8961
- // ── Mailbox operations — project-level inter-agent messaging (PR 8 of #30) ────
8962
- case "mailbox.messages":
8963
- await handleMailboxMessages(
8964
- mailboxCtx,
8965
- msg,
8966
- ws
8967
- );
8968
- break;
8969
- case "mailbox.agents":
8970
- await handleMailboxAgents(mailboxCtx, msg, ws);
8971
- break;
8972
- case "mailbox.clear":
8973
- await handleMailboxClear(mailboxCtx, ws);
8974
- break;
8975
- case "mailbox.purge":
8976
- await handleMailboxPurge(mailboxCtx, msg, ws);
8977
- break;
8978
- case "git.info": {
8979
- const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
8980
- await handleGitInfo(ws, projectRoot);
8981
- break;
8643
+ return handleMemoryList(ws, opts.memoryStore);
8644
+ },
8645
+ "memory.remember": (msg, ws) => {
8646
+ if (!opts.memoryStore) {
8647
+ sendResult10(ws, false, "Memory store not available");
8648
+ return;
8982
8649
  }
8983
- default: {
8984
- const msgType = msg.type;
8985
- if (msgType.startsWith("autophase.")) {
8986
- await autoPhaseHandler.handleMessage(
8987
- msg
8988
- );
8989
- } else if (msgType.startsWith("specs.")) {
8990
- await specsHandler.handleMessage(
8991
- msg
8992
- );
8993
- } else if (msgType.startsWith("sdd.board.")) {
8994
- await sddBoardHandler.handleMessage(
8995
- msg
8996
- );
8997
- } else if (msgType.startsWith("sdd.spec.") || msgType.startsWith("sdd.run.")) {
8998
- await sddWizardHandler?.handleMessage(
8999
- msg
9000
- );
9001
- } else {
9002
- console.debug(`[WebUI] Unhandled message type: ${msgType}`);
9003
- }
9004
- break;
8650
+ return handleMemoryRemember(ws, msg, opts.memoryStore);
8651
+ },
8652
+ "memory.forget": (msg, ws) => {
8653
+ if (!opts.memoryStore) {
8654
+ sendResult10(ws, false, "Memory store not available");
8655
+ return;
9005
8656
  }
8657
+ return handleMemoryForget(ws, msg, opts.memoryStore);
8658
+ },
8659
+ // ── MCP operations (shared handlers from @wrongstack/webui/server) ──
8660
+ "mcp.list": (msg, ws) => handleMcpList(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8661
+ "mcp.add": (msg, ws) => handleMcpAdd(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8662
+ "mcp.remove": (msg, ws) => handleMcpRemove(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8663
+ "mcp.update": (msg, ws) => handleMcpUpdate(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8664
+ "mcp.wake": (msg, ws) => handleMcpWake(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8665
+ "mcp.sleep": (msg, ws) => handleMcpSleep(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8666
+ "mcp.discover": (msg, ws) => handleMcpDiscover(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8667
+ "mcp.enable": (msg, ws) => handleMcpEnable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8668
+ "mcp.disable": (msg, ws) => handleMcpDisable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8669
+ "mcp.restart": (msg, ws) => handleMcpRestart(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8670
+ // ── Skills ──
8671
+ "skills.list": (_msg, ws) => handleSkillsList(introspectionCtx, ws),
8672
+ "skills.content": (msg, ws) => handleSkillsContent(ws, skillsCtx, msg),
8673
+ "skills.install": (msg, ws) => handleSkillsInstall(ws, skillsCtx, msg),
8674
+ "skills.uninstall": (msg, ws) => handleSkillsUninstall(ws, skillsCtx, msg),
8675
+ "skills.update": (msg, ws) => handleSkillsUpdate(ws, skillsCtx, msg),
8676
+ "skills.create": (msg, ws) => handleSkillsCreate(ws, skillsCtx, msg),
8677
+ "skills.edit": (msg, ws) => handleSkillsEdit(ws, skillsCtx, msg),
8678
+ "skills.export": (_msg, ws) => handleSkillsExport(ws, skillsCtx),
8679
+ // ── Projects / working dir ──
8680
+ "projects.list": (_msg, ws) => handleProjectsList(projectsCtx, ws),
8681
+ "projects.select": (msg, ws) => handleProjectsSelect(
8682
+ projectsCtx,
8683
+ ws,
8684
+ msg.payload
8685
+ ),
8686
+ "projects.add": (msg, ws) => handleProjectsAdd(
8687
+ projectsCtx,
8688
+ ws,
8689
+ msg.payload
8690
+ ),
8691
+ "working_dir.set": (msg, ws) => handleWorkingDirSet(projectsCtx, ws, msg.payload.path),
8692
+ // ── Git ──
8693
+ "git.changes": (_msg, ws) => handleGitChanges(ws, projectRootFor()),
8694
+ "git.diff": (msg, ws) => handleGitDiff(
8695
+ ws,
8696
+ projectRootFor(),
8697
+ msg.payload?.path ?? ""
8698
+ ),
8699
+ "git.info": (_msg, ws) => handleGitInfo(ws, projectRootFor()),
8700
+ // ── Shell ──
8701
+ "shell.open": async (msg, ws) => {
8702
+ const result = await handleShellOpen(
8703
+ msg.payload,
8704
+ consoleLogger
8705
+ );
8706
+ sendResult10(ws, result.success, result.message);
8707
+ },
8708
+ // ── Mailbox ──
8709
+ "mailbox.messages": (msg, ws) => handleMailboxMessages(mailboxCtx, msg, ws),
8710
+ "mailbox.agents": (msg, ws) => handleMailboxAgents(mailboxCtx, msg, ws),
8711
+ "mailbox.clear": (_msg, ws) => handleMailboxClear(mailboxCtx, ws),
8712
+ "mailbox.purge": (msg, ws) => handleMailboxPurge(mailboxCtx, msg, ws),
8713
+ // ── Silent no-ops (standalone server wires real handlers) ──
8714
+ "collab.join": noop,
8715
+ "collab.leave": noop,
8716
+ "collab.annotate": noop,
8717
+ "collab.resolve": noop,
8718
+ "collab.request_pause": noop,
8719
+ "collab.resume": noop,
8720
+ "collab.grant_control": noop,
8721
+ "collab.inject_tool": noop,
8722
+ "terminal.create": noop,
8723
+ "terminal.input": noop,
8724
+ "terminal.resize": noop,
8725
+ "terminal.close": noop
8726
+ };
8727
+ async function handleMessage(ws, _client, msg) {
8728
+ const handler = wsRoutes[msg.type];
8729
+ if (handler) {
8730
+ await handler(msg, ws);
8731
+ return;
8732
+ }
8733
+ const msgType = msg.type;
8734
+ if (msgType.startsWith("autophase.")) {
8735
+ await autoPhaseHandler.handleMessage(
8736
+ msg
8737
+ );
8738
+ } else if (msgType.startsWith("specs.")) {
8739
+ await specsHandler.handleMessage(msg);
8740
+ } else if (msgType.startsWith("sdd.board.")) {
8741
+ await sddBoardHandler.handleMessage(
8742
+ msg
8743
+ );
8744
+ } else if (msgType.startsWith("sdd.spec.") || msgType.startsWith("sdd.run.")) {
8745
+ await sddWizardHandler?.handleMessage(
8746
+ msg
8747
+ );
8748
+ } else {
8749
+ console.debug(`[WebUI] Unhandled message type: ${msgType}`);
9006
8750
  }
9007
8751
  }
9008
8752
  function send(ws, msg) {
@@ -9032,6 +8776,7 @@ ${text}` : text;
9032
8776
  function sendResult10(ws, success, message) {
9033
8777
  send(ws, { type: "key.operation_result", payload: { success, message } });
9034
8778
  }
8779
+ return stopped;
9035
8780
  }
9036
8781
  var init_webui_server = __esm({
9037
8782
  "src/webui-server.ts"() {
@@ -9073,8 +8818,13 @@ function gitText(args, cwd) {
9073
8818
  return;
9074
8819
  }
9075
8820
  const chunks = [];
8821
+ let total = 0;
9076
8822
  const emit = (c) => {
9077
- if (chunks.join("").length < MAX_CMD_OUTPUT) chunks.push(c.toString());
8823
+ if (total < MAX_CMD_OUTPUT) {
8824
+ const s = c.toString();
8825
+ chunks.push(s);
8826
+ total += s.length;
8827
+ }
9078
8828
  };
9079
8829
  child.stdout?.on("data", emit);
9080
8830
  child.stderr?.on("data", emit);
@@ -9086,7 +8836,20 @@ async function isGitRepo(cwd) {
9086
8836
  const { code, out } = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
9087
8837
  return code === 0 && out.trim() === "true";
9088
8838
  }
9089
- var AUTOPHASE_SAFE_CMDS = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun"]);
8839
+ var AUTOPHASE_BASE_SAFE_CMDS = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun"]);
8840
+ var autophaseAllowed = new Set(AUTOPHASE_BASE_SAFE_CMDS);
8841
+ function configureAutophasePolicy(opts = {}) {
8842
+ const next = new Set(AUTOPHASE_BASE_SAFE_CMDS);
8843
+ for (const c of opts.allow ?? []) {
8844
+ const n = c.trim();
8845
+ if (n) next.add(n);
8846
+ }
8847
+ for (const c of opts.deny ?? []) next.delete(c.trim());
8848
+ autophaseAllowed = next;
8849
+ }
8850
+ function isAutophaseCommandAllowed(cmd) {
8851
+ return autophaseAllowed.has(cmd.trim());
8852
+ }
9090
8853
  var DESTRUCTIVE_PATTERNS = [
9091
8854
  /\brm\s+-rf\s+\//,
9092
8855
  /\bdangerously\s+(?:force|reset|--hard)\b/,
@@ -9100,10 +8863,10 @@ var DESTRUCTIVE_PATTERNS = [
9100
8863
  // $(...) subshell
9101
8864
  ];
9102
8865
  function runCmd(cmd, args, cwd, shell = false) {
9103
- if (!AUTOPHASE_SAFE_CMDS.has(cmd)) {
8866
+ if (!isAutophaseCommandAllowed(cmd)) {
9104
8867
  return Promise.resolve({
9105
8868
  code: 1,
9106
- out: `autophase: command "${cmd}" not in autonomous safe-commands allowlist. Allowed: ${[...AUTOPHASE_SAFE_CMDS].join(", ")}. Set WRONGSTACK_AUTOPHASE_VERIFY_CMD to one of these.`
8869
+ out: `autophase: command "${cmd}" not in autonomous safe-commands allowlist. Allowed: ${[...autophaseAllowed].join(", ")}. Add it to tools.exec.allow in ~/.wrongstack/config.json (trusted config only), or set WRONGSTACK_AUTOPHASE_VERIFY_CMD to an allowed command.`
9107
8870
  });
9108
8871
  }
9109
8872
  const fullCmd = [cmd, ...args].join(" ");
@@ -9378,8 +9141,9 @@ ${res.out}` };
9378
9141
  // WRONGSTACK_AUTOPHASE_TASK_CONCURRENCY (1 = strictly sequential).
9379
9142
  maxConcurrentTasks: resolveTaskConcurrency()
9380
9143
  });
9381
- const onUntyped = deps.events.on;
9382
- const offUntyped = deps.events.off;
9144
+ const busOn = deps.events;
9145
+ const onUntyped = (event, handler) => busOn.on(event, handler);
9146
+ const offUntyped = (event, handler) => busOn.off(event, handler);
9383
9147
  const finalizeActiveRun = () => {
9384
9148
  if (active?.graph.id !== graph.id) return;
9385
9149
  active.unsubscribe();
@@ -12232,6 +11996,7 @@ var KNOWN_TOP_LEVEL_KEYS = [
12232
11996
  "tools",
12233
11997
  "mcpServers",
12234
11998
  "fallbackModels",
11999
+ "fallbackAuto",
12235
12000
  "hooks",
12236
12001
  "plugins",
12237
12002
  "log",
@@ -12244,8 +12009,12 @@ var KNOWN_TOP_LEVEL_KEYS = [
12244
12009
  "debugStream",
12245
12010
  "configScope",
12246
12011
  "indexing",
12012
+ "circuitBreaker",
12013
+ "adaptiveConcurrency",
12247
12014
  "launch",
12248
12015
  "session",
12016
+ "modelRuntime",
12017
+ "hq",
12249
12018
  "sync",
12250
12019
  "extensions"
12251
12020
  ];
@@ -12345,17 +12114,17 @@ function diagnoseConfig(cfg, plugins = []) {
12345
12114
  delete fixed["maxConcurrent"];
12346
12115
  findings.push({
12347
12116
  path: "maxConcurrent",
12348
- problem: `expected a positive integer, got ${JSON.stringify(v)}`,
12117
+ problem: `expected a non-negative integer, got ${JSON.stringify(v)}`,
12349
12118
  severity: "error",
12350
12119
  fix: "removed (built-in default applies)"
12351
12120
  });
12352
12121
  } else {
12353
- const clamped = Math.max(1, Math.floor(n));
12122
+ const clamped = Math.max(0, Math.floor(n));
12354
12123
  if (clamped !== v) {
12355
12124
  fixed["maxConcurrent"] = clamped;
12356
12125
  findings.push({
12357
12126
  path: "maxConcurrent",
12358
- problem: `expected a positive integer, got ${JSON.stringify(v)}`,
12127
+ problem: `expected a non-negative integer, got ${JSON.stringify(v)}`,
12359
12128
  severity: "error",
12360
12129
  fix: `set to ${clamped}`
12361
12130
  });
@@ -12604,7 +12373,8 @@ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
12604
12373
  "indexing",
12605
12374
  "tools",
12606
12375
  "launch",
12607
- "circuitBreaker"
12376
+ "circuitBreaker",
12377
+ "modelRuntime"
12608
12378
  ]);
12609
12379
  function filterSafeForProject(cfg) {
12610
12380
  const out = {};
@@ -12746,6 +12516,17 @@ async function persistTelegramConfig(deps, mutator) {
12746
12516
  extensions
12747
12517
  });
12748
12518
  }
12519
+ function deriveFsAccessPair(input) {
12520
+ if (input.allowOutsideProjectRoot !== void 0) {
12521
+ const allow = input.allowOutsideProjectRoot;
12522
+ return { allowOutsideProjectRoot: allow, restrictToProjectRoot: !allow };
12523
+ }
12524
+ if (input.restrictFsToRoot !== void 0) {
12525
+ const restrict = input.restrictFsToRoot;
12526
+ return { allowOutsideProjectRoot: !restrict, restrictToProjectRoot: restrict };
12527
+ }
12528
+ return void 0;
12529
+ }
12749
12530
 
12750
12531
  // src/slash-commands/doctor.ts
12751
12532
  init_helpers();
@@ -18635,7 +18416,7 @@ function buildSettingsCommand(opts) {
18635
18416
  " /settings context-strategy hybrid|intelligent|selective Compactor strategy",
18636
18417
  " /settings context-auto-compact on|off Auto-compact context when thresholds crossed",
18637
18418
  " /settings token-saving off|minimal|light|medium|aggressive Token-saving mode",
18638
- " /settings max-concurrent <n> Max concurrent subagents (0 = unlimited)",
18419
+ " /settings max-concurrent <n> Max concurrent subagents (0 = default)",
18639
18420
  " /settings title-animation on|off Terminal title animation",
18640
18421
  " /settings reasoning auto|on|off Reasoning mode (auto = provider default)",
18641
18422
  " /settings reasoning-effort none|minimal|low|medium|high|xhigh|max Reasoning effort",
@@ -18647,7 +18428,7 @@ function buildSettingsCommand(opts) {
18647
18428
  " /settings hq-raw on|off Send raw content previews to HQ",
18648
18429
  " /settings defaults Show built-in default values",
18649
18430
  "",
18650
- "Settings are persisted to ~/.wrongstack/config.json."
18431
+ "Settings are persisted to the active config scope: global (~/.wrongstack/config.json) or project (<project>/.wrongstack/config.json)."
18651
18432
  ].join("\n");
18652
18433
  function currentView() {
18653
18434
  const autonomy = opts.configStore.get().autonomy;
@@ -18670,7 +18451,7 @@ function buildSettingsCommand(opts) {
18670
18451
  const contextAutoCompact = context?.autoCompact !== false;
18671
18452
  const features = opts.configStore.get().features;
18672
18453
  const tokenSavingTier = features?.tokenSavingMode ?? "off";
18673
- const maxConcurrent = opts.configStore.get().maxConcurrent ?? 0;
18454
+ const maxConcurrent = opts.configStore.get().maxConcurrent ?? 4;
18674
18455
  const modelRuntime = opts.configStore.get().modelRuntime;
18675
18456
  const reasoningMode = modelRuntime?.reasoning?.mode ?? "auto";
18676
18457
  const reasoningEffort = modelRuntime?.reasoning?.effort ?? "(unset)";
@@ -18680,6 +18461,7 @@ function buildSettingsCommand(opts) {
18680
18461
  const hqEnabled = hq?.enabled === true;
18681
18462
  const hqUrl = hq?.url ?? "(auto/local)";
18682
18463
  const hqToken = hq?.token ? `${hq.token.slice(0, 6)}\u2026${hq.token.slice(-4)} (${hq.token.length} chars)` : "(auto/local)";
18464
+ const persistedTo = configScope === "project" ? "<project>/.wrongstack/config.json" : "~/.wrongstack/config.json";
18683
18465
  return [
18684
18466
  `${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
18685
18467
  "",
@@ -18698,7 +18480,7 @@ function buildSettingsCommand(opts) {
18698
18480
  ` context strategy: ${color.cyan(contextStrategy)} ${color.dim("change: /settings context-strategy hybrid|intelligent|selective")}`,
18699
18481
  ` context auto-compact: ${contextAutoCompact ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings context-auto-compact on|off")}`,
18700
18482
  ` token-saving: ${color.cyan(tokenSavingTier)} ${color.dim("change: /settings token-saving off|minimal|light|medium|aggressive")}`,
18701
- ` max-concurrent: ${color.cyan(maxConcurrent === 0 ? "unlimited" : String(maxConcurrent))} ${color.dim("change: /settings max-concurrent <n>")}`,
18483
+ ` max-concurrent: ${color.cyan(maxConcurrent === 0 ? "default" : String(maxConcurrent))} ${color.dim("change: /settings max-concurrent <n>")}`,
18702
18484
  ` reasoning mode: ${color.cyan(reasoningMode)} ${color.dim("change: /settings reasoning auto|on|off")}`,
18703
18485
  ` reasoning effort: ${color.cyan(reasoningEffort)} ${color.dim("change: /settings reasoning-effort <level>")}`,
18704
18486
  ` reasoning preserve: ${reasoningPreserve ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings reasoning-preserve on|off")}`,
@@ -18708,7 +18490,7 @@ function buildSettingsCommand(opts) {
18708
18490
  ` HQ token: ${color.cyan(hqToken)} ${color.dim("change: /settings hq-token <token>")}`,
18709
18491
  ` HQ raw content: ${hq?.rawContent === true ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hq-raw on|off")}`,
18710
18492
  "",
18711
- color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
18493
+ color.dim(` Persisted to ${persistedTo} \xB7 /settings help for more`)
18712
18494
  ].join("\n");
18713
18495
  }
18714
18496
  return {
@@ -18739,6 +18521,7 @@ function buildSettingsCommand(opts) {
18739
18521
  ` iteration timeout: ${color.cyan("5 min")}`,
18740
18522
  ` session timeout: ${color.cyan("30 min")}`,
18741
18523
  ` max iterations: ${color.cyan("100")}`,
18524
+ ` max concurrent: ${color.cyan("4")}`,
18742
18525
  ` semver default part: ${color.cyan("patch")}`
18743
18526
  ].join("\n")
18744
18527
  };
@@ -18879,12 +18662,13 @@ function buildSettingsCommand(opts) {
18879
18662
  return { message: `${color.amber("Usage:")} /settings fs-access unrestricted|project` };
18880
18663
  }
18881
18664
  const restrict = raw === "project";
18665
+ const fsAccess = deriveFsAccessPair({ restrictFsToRoot: restrict });
18882
18666
  await persistConfigSetting(persistDeps, (cfg) => {
18883
18667
  const tools = cfg.tools ?? {};
18884
- tools.restrictToProjectRoot = restrict;
18668
+ tools.restrictToProjectRoot = fsAccess.restrictToProjectRoot;
18885
18669
  cfg.tools = tools;
18886
18670
  const features = cfg.features ?? {};
18887
- features.allowOutsideProjectRoot = !restrict;
18671
+ features.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
18888
18672
  cfg.features = features;
18889
18673
  });
18890
18674
  const label = restrict ? `${color.cyan("project")} \u2014 file tools confined to the project root` : `${color.cyan("unrestricted")} \u2014 file tools may access paths outside the project root`;
@@ -19054,20 +18838,20 @@ function buildSettingsCommand(opts) {
19054
18838
  const raw = rest[0];
19055
18839
  if (raw === void 0) {
19056
18840
  return {
19057
- message: `${color.amber("Usage:")} /settings max-concurrent <n> ${color.dim("(0 = unlimited)")}`
18841
+ message: `${color.amber("Usage:")} /settings max-concurrent <n> ${color.dim("(0 = default)")}`
19058
18842
  };
19059
18843
  }
19060
18844
  const n = Number.parseInt(raw, 10);
19061
18845
  if (Number.isNaN(n) || n < 0) {
19062
18846
  return {
19063
- message: `${color.red("Invalid number")}: "${raw}". Enter a non-negative integer (0 = unlimited)`
18847
+ message: `${color.red("Invalid number")}: "${raw}". Enter a non-negative integer (0 = default)`
19064
18848
  };
19065
18849
  }
19066
18850
  await persistConfigSetting(persistDeps, (cfg) => {
19067
18851
  cfg.maxConcurrent = n;
19068
18852
  });
19069
18853
  return {
19070
- message: `${color.green("\u2713")} max-concurrent \u2192 ${color.cyan(n === 0 ? "unlimited" : String(n))} ${color.dim("max concurrent subagents")}`
18854
+ message: `${color.green("\u2713")} max-concurrent \u2192 ${color.cyan(n === 0 ? "default" : String(n))} ${color.dim("max concurrent subagents")}`
19071
18855
  };
19072
18856
  }
19073
18857
  if (sub === "title-animation") {
@@ -19491,28 +19275,129 @@ function buildDirectorCommand(opts) {
19491
19275
  }
19492
19276
  var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
19493
19277
  var DEFAULTS = {
19278
+ version: true,
19279
+ state: true,
19280
+ model: true,
19494
19281
  todos: true,
19495
19282
  plan: true,
19496
19283
  tasks: true,
19497
19284
  fleet: true,
19285
+ fleet_agents: true,
19498
19286
  git: true,
19499
19287
  elapsed: true,
19500
19288
  context: true,
19289
+ tokens: true,
19290
+ cache: true,
19501
19291
  cost: true,
19502
- working_dir: true
19292
+ queue: true,
19293
+ processes: true,
19294
+ hint: true,
19295
+ index: true,
19296
+ breaker: true,
19297
+ working_dir: true,
19298
+ project: true,
19299
+ yolo: true,
19300
+ autonomy: true,
19301
+ eternal_stage: true,
19302
+ goal: true,
19303
+ mode: true,
19304
+ auto_proceed: true,
19305
+ sessions: true,
19306
+ tools: true,
19307
+ token_saving: true,
19308
+ brain: true,
19309
+ mailbox: true,
19310
+ enhance: true,
19311
+ debug_stream: true,
19312
+ next_steps: true
19503
19313
  };
19314
+ var STATUSLINE_CONFIG_KEYS = [
19315
+ "version",
19316
+ "state",
19317
+ "model",
19318
+ "context",
19319
+ "tokens",
19320
+ "cache",
19321
+ "cost",
19322
+ "queue",
19323
+ "processes",
19324
+ "hint",
19325
+ "index",
19326
+ "breaker",
19327
+ "yolo",
19328
+ "autonomy",
19329
+ "eternal_stage",
19330
+ "elapsed",
19331
+ "project",
19332
+ "working_dir",
19333
+ "goal",
19334
+ "mode",
19335
+ "auto_proceed",
19336
+ "git",
19337
+ "sessions",
19338
+ "tools",
19339
+ "token_saving",
19340
+ "todos",
19341
+ "plan",
19342
+ "tasks",
19343
+ "fleet",
19344
+ "brain",
19345
+ "debug_stream",
19346
+ "enhance",
19347
+ "next_steps",
19348
+ "mailbox",
19349
+ "fleet_agents"
19350
+ ];
19504
19351
  function resolveConfigPath() {
19505
19352
  return process.env[CONFIG_ENV] ?? path4.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
19506
19353
  }
19354
+ function isRecord(value) {
19355
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19356
+ }
19357
+ function normalizeStatuslineConfig(value) {
19358
+ const cfg = { ...DEFAULTS };
19359
+ if (!isRecord(value)) return cfg;
19360
+ for (const key of STATUSLINE_CONFIG_KEYS) {
19361
+ const raw = value[key];
19362
+ if (typeof raw === "boolean") cfg[key] = raw;
19363
+ }
19364
+ return cfg;
19365
+ }
19366
+ function isMissingKnownStatuslineKeys(value) {
19367
+ if (!isRecord(value)) return true;
19368
+ return STATUSLINE_CONFIG_KEYS.some((key) => typeof value[key] !== "boolean");
19369
+ }
19370
+ function hasFsCode(err, code) {
19371
+ return isRecord(err) && err["code"] === code;
19372
+ }
19507
19373
  async function loadStatuslineConfig() {
19508
19374
  const p = resolveConfigPath();
19509
19375
  try {
19510
19376
  const raw = await fs4.readFile(p, "utf8");
19511
- return { ...DEFAULTS, ...JSON.parse(raw) };
19377
+ return normalizeStatuslineConfig(JSON.parse(raw));
19512
19378
  } catch {
19513
19379
  return { ...DEFAULTS };
19514
19380
  }
19515
19381
  }
19382
+ async function ensureStatuslineConfig() {
19383
+ const p = resolveConfigPath();
19384
+ try {
19385
+ const raw = await fs4.readFile(p, "utf8");
19386
+ const parsed = JSON.parse(raw);
19387
+ const cfg = normalizeStatuslineConfig(parsed);
19388
+ if (isMissingKnownStatuslineKeys(parsed)) {
19389
+ await saveStatuslineConfig(cfg);
19390
+ }
19391
+ return cfg;
19392
+ } catch (err) {
19393
+ if (hasFsCode(err, "ENOENT")) {
19394
+ const cfg = { ...DEFAULTS };
19395
+ await saveStatuslineConfig(cfg);
19396
+ return cfg;
19397
+ }
19398
+ return { ...DEFAULTS };
19399
+ }
19400
+ }
19516
19401
  async function saveStatuslineConfig(cfg) {
19517
19402
  const p = resolveConfigPath();
19518
19403
  try {
@@ -19528,27 +19413,43 @@ async function saveStatuslineConfig(cfg) {
19528
19413
  }
19529
19414
  }
19530
19415
  var ITEM_DESCRIPTIONS = {
19416
+ version: "WrongStack version chip",
19417
+ state: "Agent run state / thinking spinner",
19418
+ model: "Current provider/model id",
19531
19419
  todos: "Todo items (pending/in-progress/done counts)",
19532
19420
  plan: "Plan board items (open/in-progress/done)",
19533
19421
  tasks: "Task board items (structured work with type/priority)",
19534
19422
  fleet: "Fleet agent status (running/idle/pending/completed)",
19423
+ fleet_agents: "Per-agent live detail row",
19535
19424
  git: "Git branch name",
19536
19425
  elapsed: "Session elapsed time",
19537
19426
  context: "Context window usage (input tokens)",
19427
+ tokens: "Input/output token counters",
19428
+ cache: "Prompt cache hit ratio",
19538
19429
  cost: "Token cost estimate (input/output/total)",
19539
- working_dir: "Current working directory"
19430
+ queue: "Queued prompt count",
19431
+ processes: "Tracked shell/process count",
19432
+ hint: "Transient status hint text",
19433
+ index: "Codebase indexing status",
19434
+ breaker: "Process breaker countdown",
19435
+ working_dir: "Current working directory",
19436
+ project: "Project name",
19437
+ yolo: "YOLO permission mode",
19438
+ autonomy: "Autonomy mode",
19439
+ eternal_stage: "Autonomy stage",
19440
+ goal: "Active goal summary",
19441
+ mode: "Active agent mode label",
19442
+ auto_proceed: "Auto-proceed countdown",
19443
+ sessions: "Live session count",
19444
+ tools: "Registered tool count",
19445
+ token_saving: "Token-saving mode indicator",
19446
+ brain: "Brain arbiter decisions",
19447
+ mailbox: "Mailbox unread messages and peers",
19448
+ enhance: "Prompt-enhance countdown",
19449
+ debug_stream: "Stream debug telemetry",
19450
+ next_steps: "Next-step auto-submit countdown"
19540
19451
  };
19541
- var ALL_CONFIG_KEYS = [
19542
- "todos",
19543
- "plan",
19544
- "tasks",
19545
- "fleet",
19546
- "git",
19547
- "elapsed",
19548
- "context",
19549
- "cost",
19550
- "working_dir"
19551
- ];
19452
+ var ALL_CONFIG_KEYS = STATUSLINE_CONFIG_KEYS;
19552
19453
  function buildStatuslineCommand(deps) {
19553
19454
  return {
19554
19455
  name: "statusline",
@@ -21903,6 +21804,391 @@ function summarize(value, name) {
21903
21804
 
21904
21805
  // src/boot.ts
21905
21806
  init_project_utils();
21807
+
21808
+ // src/auth-menu/openai-codex-oauth.ts
21809
+ init_provider_config_utils();
21810
+ var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
21811
+ var AUTH_BASE_URL = "https://auth.openai.com";
21812
+ var AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;
21813
+ var TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;
21814
+ var REDIRECT_PORT = 1455;
21815
+ var REDIRECT_HOST = "127.0.0.1";
21816
+ var REDIRECT_PATH = "/auth/callback";
21817
+ var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}${REDIRECT_PATH}`;
21818
+ var SCOPE = "openid profile email offline_access";
21819
+ var JWT_CLAIM_PATH = "https://api.openai.com/auth";
21820
+ var ORIGINATOR = "wrongstack";
21821
+ var CODEX_PROVIDER_ID = "openai-codex";
21822
+ var CODEX_BASE_URL = "https://chatgpt.com/backend-api";
21823
+ function base64url(buf) {
21824
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
21825
+ }
21826
+ function generatePkce() {
21827
+ const verifier = base64url(randomBytes(32));
21828
+ const challenge = base64url(createHash("sha256").update(verifier).digest());
21829
+ return { verifier, challenge };
21830
+ }
21831
+ function createState() {
21832
+ return randomBytes(16).toString("hex");
21833
+ }
21834
+ function buildAuthorizeUrl(challenge, state) {
21835
+ const url = new URL(AUTHORIZE_URL);
21836
+ url.searchParams.set("response_type", "code");
21837
+ url.searchParams.set("client_id", CLIENT_ID);
21838
+ url.searchParams.set("redirect_uri", REDIRECT_URI);
21839
+ url.searchParams.set("scope", SCOPE);
21840
+ url.searchParams.set("code_challenge", challenge);
21841
+ url.searchParams.set("code_challenge_method", "S256");
21842
+ url.searchParams.set("state", state);
21843
+ url.searchParams.set("id_token_add_organizations", "true");
21844
+ url.searchParams.set("codex_cli_simplified_flow", "true");
21845
+ url.searchParams.set("originator", ORIGINATOR);
21846
+ return url.toString();
21847
+ }
21848
+ function decodeJwtPayload(token) {
21849
+ try {
21850
+ const parts = token.split(".");
21851
+ if (parts.length !== 3) return null;
21852
+ const json = Buffer.from(parts[1], "base64url").toString("utf8");
21853
+ return JSON.parse(json);
21854
+ } catch {
21855
+ return null;
21856
+ }
21857
+ }
21858
+ function extractAccountId(token) {
21859
+ const payload = decodeJwtPayload(token);
21860
+ const auth = payload?.[JWT_CLAIM_PATH];
21861
+ const id = auth?.chatgpt_account_id;
21862
+ return typeof id === "string" && id.length > 0 ? id : null;
21863
+ }
21864
+ var FALLBACK_CODEX_MODELS = [
21865
+ { id: "gpt-5.5", name: "gpt-5.5" },
21866
+ { id: "gpt-5.4", name: "gpt-5.4" },
21867
+ { id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
21868
+ { id: "gpt-5.3-codex-spark", name: "gpt-5.3-codex-spark" }
21869
+ ];
21870
+ var CODEX_CATALOG_FAMILIES = /* @__PURE__ */ new Set(["gpt-codex", "gpt-codex-spark"]);
21871
+ function fallbackCodexModelIds() {
21872
+ return FALLBACK_CODEX_MODELS.map((m) => m.id);
21873
+ }
21874
+ function fallbackCodexProviderModels() {
21875
+ return FALLBACK_CODEX_MODELS.map((m) => ({ id: m.id, name: m.name }));
21876
+ }
21877
+ function filterCurrentCodexModelIds(ids) {
21878
+ const available = new Set(ids);
21879
+ return FALLBACK_CODEX_MODELS.map((m) => m.id).filter((id) => available.has(id));
21880
+ }
21881
+ function isCodexCatalogModel(model) {
21882
+ return typeof model.family === "string" && CODEX_CATALOG_FAMILIES.has(model.family);
21883
+ }
21884
+ async function resolveCodexModels(modelsRegistry, accessToken, baseUrl, signal) {
21885
+ const token = typeof accessToken === "string" ? accessToken : await accessToken;
21886
+ const live = filterCurrentCodexModelIds(await fetchCodexModels(token, baseUrl, signal));
21887
+ if (live.length > 0) return live;
21888
+ try {
21889
+ const openaiProvider = await modelsRegistry.getProvider("openai");
21890
+ if (openaiProvider) {
21891
+ const catalog = openaiProvider.models.filter(isCodexCatalogModel).map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0);
21892
+ const currentCatalog = filterCurrentCodexModelIds(catalog);
21893
+ if (currentCatalog.length > 0) return currentCatalog;
21894
+ }
21895
+ } catch {
21896
+ }
21897
+ return fallbackCodexModelIds();
21898
+ }
21899
+ async function fetchCodexModels(accessToken, baseUrl, signal) {
21900
+ const url = `${(baseUrl).replace(/\/+$/, "")}/models`;
21901
+ try {
21902
+ const res = await fetch(url, {
21903
+ headers: {
21904
+ accept: "application/json",
21905
+ authorization: `Bearer ${accessToken}`,
21906
+ originator: "wrongstack",
21907
+ "OpenAI-Beta": "responses=experimental"
21908
+ },
21909
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(8e3)]) : AbortSignal.timeout(8e3)
21910
+ });
21911
+ if (!res.ok) return [];
21912
+ const json = await res.json();
21913
+ if (!json) return [];
21914
+ const rawList = "data" in json && Array.isArray(json.data) ? json.data : "models" in json && Array.isArray(json.models) ? json.models : [];
21915
+ const ids = [];
21916
+ for (const entry of rawList) {
21917
+ if (!entry || typeof entry !== "object") continue;
21918
+ const id = entry.id;
21919
+ if (typeof id === "string" && id.length > 0) ids.push(id);
21920
+ }
21921
+ return ids;
21922
+ } catch {
21923
+ return [];
21924
+ }
21925
+ }
21926
+ async function readTokens(res, op) {
21927
+ if (!res.ok) {
21928
+ const text = await res.text().catch(() => "");
21929
+ throw new Error(`Codex token ${op} failed (${res.status}): ${text || res.statusText}`);
21930
+ }
21931
+ const json = await res.json();
21932
+ if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
21933
+ throw new Error(`Codex token ${op} response missing fields`);
21934
+ }
21935
+ return {
21936
+ access: json.access_token,
21937
+ refresh: json.refresh_token,
21938
+ expires: Date.now() + json.expires_in * 1e3
21939
+ };
21940
+ }
21941
+ async function exchangeAuthorizationCode(code, verifier, signal) {
21942
+ const res = await fetch(TOKEN_URL, {
21943
+ method: "POST",
21944
+ headers: { "content-type": "application/x-www-form-urlencoded" },
21945
+ body: new URLSearchParams({
21946
+ grant_type: "authorization_code",
21947
+ client_id: CLIENT_ID,
21948
+ code,
21949
+ code_verifier: verifier,
21950
+ redirect_uri: REDIRECT_URI
21951
+ }).toString(),
21952
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
21953
+ });
21954
+ return readTokens(res, "exchange");
21955
+ }
21956
+ function callbackHtml(ok, message) {
21957
+ const heading = ok ? "Authentication successful" : "Authentication failed";
21958
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>${heading}</title><style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#09090b;color:#fafafa;font-family:ui-sans-serif,system-ui,sans-serif;text-align:center}h1{font-size:26px;margin:0 0 8px}p{color:#a1a1aa}</style></head><body><main><h1>${heading}</h1><p>${message}</p></main></body></html>`;
21959
+ }
21960
+ function startLoopbackServer(state) {
21961
+ let resolveCode = () => {
21962
+ };
21963
+ const codePromise = new Promise((resolve13) => {
21964
+ let settled = false;
21965
+ resolveCode = (v) => {
21966
+ if (settled) return;
21967
+ settled = true;
21968
+ resolve13(v);
21969
+ };
21970
+ });
21971
+ const server = createServer((req2, res) => {
21972
+ let url;
21973
+ try {
21974
+ url = new URL(req2.url ?? "", `http://${REDIRECT_HOST}`);
21975
+ } catch {
21976
+ res.statusCode = 400;
21977
+ res.end();
21978
+ return;
21979
+ }
21980
+ if (url.pathname !== REDIRECT_PATH) {
21981
+ res.statusCode = 404;
21982
+ res.setHeader("content-type", "text/html; charset=utf-8");
21983
+ res.end(callbackHtml(false, "Callback route not found."));
21984
+ return;
21985
+ }
21986
+ res.setHeader("content-type", "text/html; charset=utf-8");
21987
+ const err = url.searchParams.get("error");
21988
+ if (err) {
21989
+ res.statusCode = 400;
21990
+ res.end(callbackHtml(false, `Authorization error: ${err}`));
21991
+ resolveCode(null);
21992
+ return;
21993
+ }
21994
+ if (url.searchParams.get("state") !== state) {
21995
+ res.statusCode = 400;
21996
+ res.end(callbackHtml(false, "State mismatch \u2014 please restart the login."));
21997
+ resolveCode(null);
21998
+ return;
21999
+ }
22000
+ const code = url.searchParams.get("code");
22001
+ if (!code) {
22002
+ res.statusCode = 400;
22003
+ res.end(callbackHtml(false, "Missing authorization code."));
22004
+ return;
22005
+ }
22006
+ res.statusCode = 200;
22007
+ res.end(callbackHtml(true, "You can close this window and return to the terminal."));
22008
+ resolveCode(code);
22009
+ });
22010
+ return new Promise((resolve13) => {
22011
+ server.on("error", () => {
22012
+ resolveCode(null);
22013
+ resolve13({
22014
+ bound: false,
22015
+ waitForCode: () => Promise.resolve(null),
22016
+ close: () => {
22017
+ try {
22018
+ server.close();
22019
+ } catch {
22020
+ }
22021
+ }
22022
+ });
22023
+ });
22024
+ server.listen(REDIRECT_PORT, REDIRECT_HOST, () => {
22025
+ resolve13({
22026
+ bound: true,
22027
+ waitForCode: () => codePromise,
22028
+ close: () => {
22029
+ resolveCode(null);
22030
+ try {
22031
+ server.close();
22032
+ } catch {
22033
+ }
22034
+ }
22035
+ });
22036
+ });
22037
+ });
22038
+ }
22039
+ function openBrowser(url) {
22040
+ try {
22041
+ const platform3 = process.platform;
22042
+ const { command: command2, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
22043
+ const child = spawn(command2, args, { stdio: "ignore", windowsHide: true });
22044
+ child.on("error", () => {
22045
+ });
22046
+ child.unref();
22047
+ } catch {
22048
+ }
22049
+ }
22050
+ function parseAuthorizationInput(input) {
22051
+ const value = input.trim();
22052
+ if (!value) return {};
22053
+ try {
22054
+ const url = new URL(value);
22055
+ return {
22056
+ code: url.searchParams.get("code") ?? void 0,
22057
+ state: url.searchParams.get("state") ?? void 0
22058
+ };
22059
+ } catch {
22060
+ }
22061
+ if (value.includes("code=")) {
22062
+ const params = new URLSearchParams(value);
22063
+ return {
22064
+ code: params.get("code") ?? void 0,
22065
+ state: params.get("state") ?? void 0
22066
+ };
22067
+ }
22068
+ return { code: value };
22069
+ }
22070
+ async function runCodexOAuthLogin(deps, opts = {}) {
22071
+ const providerId = opts.providerId ?? CODEX_PROVIDER_ID;
22072
+ const pkce = generatePkce();
22073
+ const state = createState();
22074
+ const authorizeUrl = buildAuthorizeUrl(pkce.challenge, state);
22075
+ const ac = new AbortController();
22076
+ const onSig = () => ac.abort();
22077
+ process.on("SIGINT", onSig);
22078
+ const server = await startLoopbackServer(state);
22079
+ deps.renderer.write(
22080
+ color.bold(`
22081
+ Sign in with ChatGPT \u2014 ${color.cyan(providerId)}
22082
+ `) + color.dim(" Uses your ChatGPT Plus/Pro/Team subscription (not an API key).\n") + color.amber(" \u26A0 Using a subscription outside the official Codex client may violate\n") + color.amber(" OpenAI\u2019s Terms \u2014 your account could be rate-limited or banned.\n") + color.dim(" Sanctioned programmatic use = an API key: ") + color.bold("wstack auth openai") + color.dim("\n\n") + color.bold(` ${"\u2500".repeat(56)}
22083
+ `) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${authorizeUrl}
22084
+ `) + color.bold(` ${"\u2500".repeat(56)}
22085
+
22086
+ `)
22087
+ );
22088
+ if (server.bound) {
22089
+ openBrowser(authorizeUrl);
22090
+ deps.renderer.write(
22091
+ color.dim(" A browser window should open. Waiting for you to finish signing in...\n") + color.dim(" (Listening on http://localhost:1455 \u2014 press Ctrl+C to cancel.)\n")
22092
+ );
22093
+ } else {
22094
+ deps.renderer.write(
22095
+ color.amber(" \u26A0 Could not start the local callback listener (port 1455 in use).\n") + color.dim(" After signing in, copy the full redirect URL from your browser\n") + color.dim(" (it starts with http://localhost:1455/auth/callback) and paste it below.\n")
22096
+ );
22097
+ }
22098
+ let code;
22099
+ try {
22100
+ if (server.bound) {
22101
+ const got = await server.waitForCode();
22102
+ if (got) code = got;
22103
+ }
22104
+ if (!code) {
22105
+ const input = (await deps.reader.readLine(
22106
+ `
22107
+ ${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
22108
+ )).trim();
22109
+ if (input.toLowerCase() === "q" || input === "") {
22110
+ deps.renderer.write(color.dim(" Cancelled.\n"));
22111
+ return 1;
22112
+ }
22113
+ const parsed = parseAuthorizationInput(input);
22114
+ if (parsed.state && parsed.state !== state) {
22115
+ deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
22116
+ return 1;
22117
+ }
22118
+ code = parsed.code;
22119
+ }
22120
+ if (!code) {
22121
+ deps.renderer.writeError(" No authorization code received.");
22122
+ return 1;
22123
+ }
22124
+ deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
22125
+ const tokens = await exchangeAuthorizationCode(code, pkce.verifier, ac.signal);
22126
+ const accountId = extractAccountId(tokens.access);
22127
+ if (!accountId) {
22128
+ deps.renderer.writeError(
22129
+ " Signed in, but the token has no ChatGPT account id.\n This account may not have Codex/ChatGPT subscription access."
22130
+ );
22131
+ return 1;
22132
+ }
22133
+ deps.renderer.write(color.dim(" Fetching available models...\n"));
22134
+ const models = await resolveCodexModels(
22135
+ deps.modelsRegistry,
22136
+ tokens.access,
22137
+ CODEX_BASE_URL,
22138
+ ac.signal
22139
+ );
22140
+ const saved = await saveCodexTokens(deps, providerId, tokens, accountId, models);
22141
+ if (!saved) return 1;
22142
+ const modelHint = models[0] ?? "gpt-5.5";
22143
+ deps.renderer.write(color.green("\n \u2713 Signed in with ChatGPT!\n"));
22144
+ deps.renderer.writeInfo(
22145
+ ` Saved as provider ${color.bold(providerId)}${models.length > 0 ? ` (${models.length} models)` : ""}.
22146
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
22147
+ ` + color.dim(" Tokens refresh automatically before they expire.\n")
22148
+ );
22149
+ return 0;
22150
+ } catch (err) {
22151
+ const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
22152
+ deps.renderer.writeError(` Login failed: ${msg}`);
22153
+ return 1;
22154
+ } finally {
22155
+ server.close();
22156
+ process.off("SIGINT", onSig);
22157
+ }
22158
+ }
22159
+ async function saveCodexTokens(deps, providerId, tokens, accountId, models) {
22160
+ const entry = {
22161
+ label: "oauth-default",
22162
+ apiKey: tokens.access,
22163
+ createdAt: nowIso(),
22164
+ authMethod: "oauth",
22165
+ expiresAt: new Date(tokens.expires).toISOString(),
22166
+ refreshToken: tokens.refresh,
22167
+ tokenType: "bearer",
22168
+ scope: SCOPE,
22169
+ accountId
22170
+ };
22171
+ try {
22172
+ await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
22173
+ const existing = all[providerId];
22174
+ const p = existing ? { ...existing } : { type: providerId };
22175
+ p.family = "openai-codex";
22176
+ if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;
22177
+ p.models = [...models];
22178
+ const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
22179
+ keys.push(entry);
22180
+ writeKeysBack(p, keys);
22181
+ p.activeKey = entry.label;
22182
+ all[providerId] = p;
22183
+ });
22184
+ return true;
22185
+ } catch (err) {
22186
+ deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
22187
+ return false;
22188
+ }
22189
+ }
22190
+
22191
+ // src/wiring/provider.ts
21906
22192
  async function setupProvider(params) {
21907
22193
  const { config, modelsRegistry, logger } = params;
21908
22194
  const savedProviderCfg = config.providers?.[config.provider];
@@ -21912,13 +22198,36 @@ async function setupProvider(params) {
21912
22198
  }
21913
22199
  if (!resolvedProvider) {
21914
22200
  if (savedProviderCfg?.family) {
22201
+ const family = savedProviderCfg.family;
22202
+ const savedModels = savedProviderCfg.models;
22203
+ let models;
22204
+ if (savedModels && savedModels.length > 0) {
22205
+ models = savedModels.map((m) => ({ id: m, name: m }));
22206
+ } else if (family === "openai-codex") {
22207
+ const openaiProvider = await modelsRegistry.getProvider("openai").catch(() => void 0);
22208
+ if (openaiProvider) {
22209
+ const catalogById = new Map(
22210
+ openaiProvider.models.filter(isCodexCatalogModel).map((m) => [m.id, { id: m.id, name: m.name }])
22211
+ );
22212
+ const catalogModels = filterCurrentCodexModelIds(catalogById.keys()).map((id) => catalogById.get(id)).filter((m) => Boolean(m));
22213
+ if (catalogModels.length > 0) {
22214
+ models = catalogModels;
22215
+ } else {
22216
+ models = fallbackCodexProviderModels();
22217
+ }
22218
+ } else {
22219
+ models = fallbackCodexProviderModels();
22220
+ }
22221
+ } else {
22222
+ models = [];
22223
+ }
21915
22224
  resolvedProvider = {
21916
22225
  id: config.provider,
21917
22226
  name: config.provider,
21918
- family: savedProviderCfg.family,
22227
+ family,
21919
22228
  apiBase: savedProviderCfg.baseUrl,
21920
22229
  envVars: savedProviderCfg.envVars ?? [],
21921
- models: (savedProviderCfg.models ?? []).map((m) => ({ id: m, name: m })),
22230
+ models,
21922
22231
  npm: void 0
21923
22232
  };
21924
22233
  } else {
@@ -22667,38 +22976,38 @@ init_provider_config_utils();
22667
22976
 
22668
22977
  // src/auth-menu/anthropic-oauth.ts
22669
22978
  init_provider_config_utils();
22670
- var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
22671
- var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
22672
- var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
22673
- var REDIRECT_PORT = 53692;
22674
- var REDIRECT_HOST = "127.0.0.1";
22675
- var REDIRECT_PATH = "/callback";
22676
- var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}${REDIRECT_PATH}`;
22979
+ var CLIENT_ID2 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
22980
+ var AUTHORIZE_URL2 = "https://claude.ai/oauth/authorize";
22981
+ var TOKEN_URL2 = "https://platform.claude.com/v1/oauth/token";
22982
+ var REDIRECT_PORT2 = 53692;
22983
+ var REDIRECT_HOST2 = "127.0.0.1";
22984
+ var REDIRECT_PATH2 = "/callback";
22985
+ var REDIRECT_URI2 = `http://localhost:${REDIRECT_PORT2}${REDIRECT_PATH2}`;
22677
22986
  var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
22678
22987
  var CLAUDE_PROVIDER_ID = "anthropic-oauth";
22679
22988
  var CLAUDE_BASE_URL = "https://api.anthropic.com";
22680
- function base64url(buf) {
22989
+ function base64url2(buf) {
22681
22990
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
22682
22991
  }
22683
- function generatePkce() {
22684
- const verifier = base64url(randomBytes(32));
22685
- const challenge = base64url(createHash("sha256").update(verifier).digest());
22992
+ function generatePkce2() {
22993
+ const verifier = base64url2(randomBytes(32));
22994
+ const challenge = base64url2(createHash("sha256").update(verifier).digest());
22686
22995
  return { verifier, challenge };
22687
22996
  }
22688
- function buildAuthorizeUrl(challenge, verifier) {
22997
+ function buildAuthorizeUrl2(challenge, verifier) {
22689
22998
  const params = new URLSearchParams({
22690
22999
  code: "true",
22691
- client_id: CLIENT_ID,
23000
+ client_id: CLIENT_ID2,
22692
23001
  response_type: "code",
22693
- redirect_uri: REDIRECT_URI,
23002
+ redirect_uri: REDIRECT_URI2,
22694
23003
  scope: SCOPES,
22695
23004
  code_challenge: challenge,
22696
23005
  code_challenge_method: "S256",
22697
23006
  state: verifier
22698
23007
  });
22699
- return `${AUTHORIZE_URL}?${params.toString()}`;
23008
+ return `${AUTHORIZE_URL2}?${params.toString()}`;
22700
23009
  }
22701
- function parseAuthorizationInput(input) {
23010
+ function parseAuthorizationInput2(input) {
22702
23011
  const value = input.trim();
22703
23012
  if (!value) return {};
22704
23013
  try {
@@ -22722,7 +23031,7 @@ function parseAuthorizationInput(input) {
22722
23031
  }
22723
23032
  return { code: value };
22724
23033
  }
22725
- async function readTokens(res, op) {
23034
+ async function readTokens2(res, op) {
22726
23035
  if (!res.ok) {
22727
23036
  const text = await res.text().catch(() => "");
22728
23037
  throw new Error(`Claude token ${op} failed (${res.status}): ${text || res.statusText}`);
@@ -22737,21 +23046,21 @@ async function readTokens(res, op) {
22737
23046
  expires: Date.now() + json.expires_in * 1e3
22738
23047
  };
22739
23048
  }
22740
- async function exchangeAuthorizationCode(code, state, verifier, signal) {
22741
- const res = await fetch(TOKEN_URL, {
23049
+ async function exchangeAuthorizationCode2(code, state, verifier, signal) {
23050
+ const res = await fetch(TOKEN_URL2, {
22742
23051
  method: "POST",
22743
23052
  headers: { "content-type": "application/json", accept: "application/json" },
22744
23053
  body: JSON.stringify({
22745
23054
  grant_type: "authorization_code",
22746
- client_id: CLIENT_ID,
23055
+ client_id: CLIENT_ID2,
22747
23056
  code,
22748
23057
  state,
22749
- redirect_uri: REDIRECT_URI,
23058
+ redirect_uri: REDIRECT_URI2,
22750
23059
  code_verifier: verifier
22751
23060
  }),
22752
23061
  signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
22753
23062
  });
22754
- return readTokens(res, "exchange");
23063
+ return readTokens2(res, "exchange");
22755
23064
  }
22756
23065
  async function fetchClaudeModels(accessToken, signal) {
22757
23066
  try {
@@ -22772,11 +23081,11 @@ async function fetchClaudeModels(accessToken, signal) {
22772
23081
  return [];
22773
23082
  }
22774
23083
  }
22775
- function callbackHtml(ok, message) {
23084
+ function callbackHtml2(ok, message) {
22776
23085
  const heading = ok ? "Authentication successful" : "Authentication failed";
22777
23086
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>${heading}</title><style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#09090b;color:#fafafa;font-family:ui-sans-serif,system-ui,sans-serif;text-align:center}h1{font-size:26px;margin:0 0 8px}p{color:#a1a1aa}</style></head><body><main><h1>${heading}</h1><p>${message}</p></main></body></html>`;
22778
23087
  }
22779
- function startLoopbackServer(expectedState) {
23088
+ function startLoopbackServer2(expectedState) {
22780
23089
  let resolveCode = () => {
22781
23090
  };
22782
23091
  const codePromise = new Promise((resolve13) => {
@@ -22790,23 +23099,23 @@ function startLoopbackServer(expectedState) {
22790
23099
  const server = createServer((req2, res) => {
22791
23100
  let url;
22792
23101
  try {
22793
- url = new URL(req2.url ?? "", `http://${REDIRECT_HOST}`);
23102
+ url = new URL(req2.url ?? "", `http://${REDIRECT_HOST2}`);
22794
23103
  } catch {
22795
23104
  res.statusCode = 400;
22796
23105
  res.end();
22797
23106
  return;
22798
23107
  }
22799
- if (url.pathname !== REDIRECT_PATH) {
23108
+ if (url.pathname !== REDIRECT_PATH2) {
22800
23109
  res.statusCode = 404;
22801
23110
  res.setHeader("content-type", "text/html; charset=utf-8");
22802
- res.end(callbackHtml(false, "Callback route not found."));
23111
+ res.end(callbackHtml2(false, "Callback route not found."));
22803
23112
  return;
22804
23113
  }
22805
23114
  res.setHeader("content-type", "text/html; charset=utf-8");
22806
23115
  const err = url.searchParams.get("error");
22807
23116
  if (err) {
22808
23117
  res.statusCode = 400;
22809
- res.end(callbackHtml(false, `Authorization error: ${err}`));
23118
+ res.end(callbackHtml2(false, `Authorization error: ${err}`));
22810
23119
  resolveCode(null);
22811
23120
  return;
22812
23121
  }
@@ -22814,17 +23123,17 @@ function startLoopbackServer(expectedState) {
22814
23123
  const state = url.searchParams.get("state");
22815
23124
  if (!code || !state) {
22816
23125
  res.statusCode = 400;
22817
- res.end(callbackHtml(false, "Missing code or state."));
23126
+ res.end(callbackHtml2(false, "Missing code or state."));
22818
23127
  return;
22819
23128
  }
22820
23129
  if (state !== expectedState) {
22821
23130
  res.statusCode = 400;
22822
- res.end(callbackHtml(false, "State mismatch \u2014 please restart the login."));
23131
+ res.end(callbackHtml2(false, "State mismatch \u2014 please restart the login."));
22823
23132
  resolveCode(null);
22824
23133
  return;
22825
23134
  }
22826
23135
  res.statusCode = 200;
22827
- res.end(callbackHtml(true, "You can close this window and return to the terminal."));
23136
+ res.end(callbackHtml2(true, "You can close this window and return to the terminal."));
22828
23137
  resolveCode({ code, state });
22829
23138
  });
22830
23139
  return new Promise((resolve13) => {
@@ -22841,7 +23150,7 @@ function startLoopbackServer(expectedState) {
22841
23150
  }
22842
23151
  });
22843
23152
  });
22844
- server.listen(REDIRECT_PORT, REDIRECT_HOST, () => {
23153
+ server.listen(REDIRECT_PORT2, REDIRECT_HOST2, () => {
22845
23154
  resolve13({
22846
23155
  bound: true,
22847
23156
  waitForCode: () => codePromise,
@@ -22856,7 +23165,7 @@ function startLoopbackServer(expectedState) {
22856
23165
  });
22857
23166
  });
22858
23167
  }
22859
- function openBrowser(url) {
23168
+ function openBrowser2(url) {
22860
23169
  try {
22861
23170
  const platform3 = process.platform;
22862
23171
  const { command: command2, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
@@ -22869,13 +23178,13 @@ function openBrowser(url) {
22869
23178
  }
22870
23179
  async function runClaudeOAuthLogin(deps, opts = {}) {
22871
23180
  const providerId = opts.providerId ?? CLAUDE_PROVIDER_ID;
22872
- const { verifier, challenge } = generatePkce();
23181
+ const { verifier, challenge } = generatePkce2();
22873
23182
  const state = verifier;
22874
- const authorizeUrl = buildAuthorizeUrl(challenge, verifier);
23183
+ const authorizeUrl = buildAuthorizeUrl2(challenge, verifier);
22875
23184
  const ac = new AbortController();
22876
23185
  const onSig = () => ac.abort();
22877
23186
  process.on("SIGINT", onSig);
22878
- const server = await startLoopbackServer(state);
23187
+ const server = await startLoopbackServer2(state);
22879
23188
  deps.renderer.write(
22880
23189
  color.bold(`
22881
23190
  Sign in with Claude \u2014 ${color.cyan(providerId)}
@@ -22886,7 +23195,7 @@ async function runClaudeOAuthLogin(deps, opts = {}) {
22886
23195
  `)
22887
23196
  );
22888
23197
  if (server.bound) {
22889
- openBrowser(authorizeUrl);
23198
+ openBrowser2(authorizeUrl);
22890
23199
  deps.renderer.write(
22891
23200
  color.dim(" A browser window should open. Waiting for you to finish signing in...\n") + color.dim(" (Listening on http://localhost:53692 \u2014 press Ctrl+C to cancel.)\n")
22892
23201
  );
@@ -22904,527 +23213,6 @@ async function runClaudeOAuthLogin(deps, opts = {}) {
22904
23213
  if (!code) {
22905
23214
  const input = (await deps.reader.readLine(
22906
23215
  `
22907
- ${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
22908
- )).trim();
22909
- if (input.toLowerCase() === "q" || input === "") {
22910
- deps.renderer.write(color.dim(" Cancelled.\n"));
22911
- return 1;
22912
- }
22913
- const parsed = parseAuthorizationInput(input);
22914
- if (parsed.state && parsed.state !== state) {
22915
- deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
22916
- return 1;
22917
- }
22918
- code = parsed.code;
22919
- }
22920
- if (!code) {
22921
- deps.renderer.writeError(" No authorization code received.");
22922
- return 1;
22923
- }
22924
- deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
22925
- const tokens = await exchangeAuthorizationCode(code, state, verifier, ac.signal);
22926
- const models = await fetchClaudeModels(tokens.access, ac.signal);
22927
- const saved = await saveClaudeTokens(deps, providerId, tokens, models);
22928
- if (!saved) return 1;
22929
- deps.renderer.write(color.green("\n \u2713 Signed in with Claude!\n"));
22930
- const modelHint = models.find((m) => m.includes("sonnet")) ?? models[0] ?? "claude-sonnet-4-6";
22931
- deps.renderer.writeInfo(
22932
- ` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
22933
- Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
22934
- ` + color.dim(" Tokens refresh automatically before they expire.\n")
22935
- );
22936
- return 0;
22937
- } catch (err) {
22938
- const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
22939
- deps.renderer.writeError(` Login failed: ${msg}`);
22940
- return 1;
22941
- } finally {
22942
- server.close();
22943
- process.off("SIGINT", onSig);
22944
- }
22945
- }
22946
- async function saveClaudeTokens(deps, providerId, tokens, models) {
22947
- const entry = {
22948
- label: "oauth-default",
22949
- apiKey: tokens.access,
22950
- createdAt: nowIso(),
22951
- authMethod: "oauth",
22952
- expiresAt: new Date(tokens.expires).toISOString(),
22953
- refreshToken: tokens.refresh,
22954
- tokenType: "bearer",
22955
- scope: SCOPES
22956
- };
22957
- try {
22958
- await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
22959
- const existing = all[providerId];
22960
- const p = existing ? { ...existing } : { type: providerId };
22961
- p.family = "anthropic-oauth";
22962
- if (!p.baseUrl) p.baseUrl = CLAUDE_BASE_URL;
22963
- if (models.length > 0) p.models = models;
22964
- else if (!p.models || p.models.length === 0)
22965
- p.models = ["claude-sonnet-4-6", "claude-opus-4-8"];
22966
- const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
22967
- keys.push(entry);
22968
- writeKeysBack(p, keys);
22969
- p.activeKey = entry.label;
22970
- all[providerId] = p;
22971
- });
22972
- return true;
22973
- } catch (err) {
22974
- deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
22975
- return false;
22976
- }
22977
- }
22978
-
22979
- // src/auth-menu/github-copilot-oauth.ts
22980
- init_provider_config_utils();
22981
- var CLIENT_ID2 = "Iv1.b507a08c87ecfe98";
22982
- var DEVICE_CODE_URL = "https://github.com/login/device/code";
22983
- var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
22984
- var COPILOT_HEADERS = {
22985
- "User-Agent": "GitHubCopilotChat/0.35.0",
22986
- "Editor-Version": "vscode/1.107.0",
22987
- "Editor-Plugin-Version": "copilot-chat/0.35.0",
22988
- "Copilot-Integration-Id": "vscode-chat"
22989
- };
22990
- var COPILOT_API_VERSION = "2026-06-01";
22991
- var COPILOT_PROVIDER_ID = "github-copilot";
22992
- function openBrowser2(url) {
22993
- try {
22994
- const platform3 = process.platform;
22995
- const { command: command2, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
22996
- const child = spawn(command2, args, { stdio: "ignore", windowsHide: true });
22997
- child.on("error", () => {
22998
- });
22999
- child.unref();
23000
- } catch {
23001
- }
23002
- }
23003
- function sleep(ms, signal) {
23004
- return new Promise((resolve13, reject) => {
23005
- if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
23006
- const t = setTimeout(resolve13, ms);
23007
- signal.addEventListener(
23008
- "abort",
23009
- () => {
23010
- clearTimeout(t);
23011
- reject(new DOMException("Aborted", "AbortError"));
23012
- },
23013
- { once: true }
23014
- );
23015
- });
23016
- }
23017
- async function startDeviceFlow(signal) {
23018
- const res = await fetch(DEVICE_CODE_URL, {
23019
- method: "POST",
23020
- headers: {
23021
- accept: "application/json",
23022
- "content-type": "application/x-www-form-urlencoded",
23023
- "user-agent": COPILOT_HEADERS["User-Agent"]
23024
- },
23025
- body: new URLSearchParams({ client_id: CLIENT_ID2, scope: "read:user" }).toString(),
23026
- signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
23027
- });
23028
- if (!res.ok) throw new Error(`GitHub device-code request failed (${res.status})`);
23029
- const json = await res.json();
23030
- if (!json?.device_code || !json.user_code || !json.verification_uri || typeof json.expires_in !== "number") {
23031
- throw new Error("Invalid device-code response");
23032
- }
23033
- return {
23034
- device_code: json.device_code,
23035
- user_code: json.user_code,
23036
- verification_uri: json.verification_uri,
23037
- interval: json.interval ?? 5,
23038
- expires_in: json.expires_in
23039
- };
23040
- }
23041
- async function pollForGitHubToken(device, signal) {
23042
- let intervalMs = device.interval * 1e3;
23043
- const expiresAt = Date.now() + device.expires_in * 1e3;
23044
- while (Date.now() < expiresAt) {
23045
- await sleep(intervalMs, signal);
23046
- const res = await fetch(ACCESS_TOKEN_URL, {
23047
- method: "POST",
23048
- headers: {
23049
- accept: "application/json",
23050
- "content-type": "application/x-www-form-urlencoded",
23051
- "user-agent": COPILOT_HEADERS["User-Agent"]
23052
- },
23053
- body: new URLSearchParams({
23054
- client_id: CLIENT_ID2,
23055
- device_code: device.device_code,
23056
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
23057
- }).toString(),
23058
- signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
23059
- });
23060
- const json = await res.json().catch(() => ({}));
23061
- if (json.access_token) return json.access_token;
23062
- if (json.error === "authorization_pending") continue;
23063
- if (json.error === "slow_down") {
23064
- intervalMs += 5e3;
23065
- continue;
23066
- }
23067
- throw new Error(`Device flow failed: ${json.error ?? "unknown error"}`);
23068
- }
23069
- throw new Error("Device code expired \u2014 please restart the login.");
23070
- }
23071
- function isUsableCopilotChatModel(item) {
23072
- if (typeof item.id !== "string" || item.id.length === 0) return false;
23073
- const cap = item.capabilities;
23074
- if (cap?.type !== "chat") return false;
23075
- if (cap.supports?.tool_calls !== true) return false;
23076
- const eps = item.supported_endpoints;
23077
- if (Array.isArray(eps) && !eps.includes("/chat/completions")) return false;
23078
- if (item.policy?.state === "disabled") return false;
23079
- if (item.vendor === "Experimental") return false;
23080
- return true;
23081
- }
23082
- async function fetchCopilotModels(copilotToken, signal) {
23083
- try {
23084
- const base = copilotBaseUrlFromToken(copilotToken);
23085
- const res = await fetch(`${base}/models`, {
23086
- headers: {
23087
- accept: "application/json",
23088
- authorization: `Bearer ${copilotToken}`,
23089
- "X-GitHub-Api-Version": COPILOT_API_VERSION,
23090
- ...COPILOT_HEADERS
23091
- },
23092
- signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
23093
- });
23094
- if (!res.ok) return [];
23095
- const json = await res.json();
23096
- const data = json?.data;
23097
- if (!Array.isArray(data)) return [];
23098
- const usable = data.filter(isUsableCopilotChatModel);
23099
- usable.sort((a, b) => copilotModelRank(a) - copilotModelRank(b));
23100
- return usable.map((m) => m.id);
23101
- } catch {
23102
- return [];
23103
- }
23104
- }
23105
- function copilotModelRank(item) {
23106
- if (item.is_chat_default === true) return 0;
23107
- if (item.is_chat_fallback === true) return 1;
23108
- return 2;
23109
- }
23110
- async function runCopilotOAuthLogin(deps, opts = {}) {
23111
- const providerId = opts.providerId ?? COPILOT_PROVIDER_ID;
23112
- const ac = new AbortController();
23113
- const onSig = () => ac.abort();
23114
- process.on("SIGINT", onSig);
23115
- try {
23116
- deps.renderer.write(
23117
- color.bold(`
23118
- Sign in with GitHub Copilot \u2014 ${color.cyan(providerId)}
23119
- `) + color.dim(" Uses your GitHub Copilot subscription (not an API key).\n") + color.amber(" \u26A0 Using Copilot outside its official editor integrations may violate\n") + color.amber(" GitHub\u2019s Terms \u2014 your account could be rate-limited or banned.\n\n")
23120
- );
23121
- const device = await startDeviceFlow(ac.signal);
23122
- deps.renderer.write(
23123
- color.bold(` ${"\u2500".repeat(56)}
23124
- `) + color.bold(" Open this URL and enter the code:\n") + color.cyan(` ${device.verification_uri}
23125
- `) + color.bold(" Code: ") + color.green(color.bold(device.user_code)) + "\n" + color.bold(` ${"\u2500".repeat(56)}
23126
-
23127
- `)
23128
- );
23129
- openBrowser2(device.verification_uri);
23130
- deps.renderer.write(color.dim(" Waiting for you to authorize in the browser...\n"));
23131
- const githubToken = await pollForGitHubToken(device, ac.signal);
23132
- deps.renderer.write(color.dim(" Authorized. Fetching your Copilot token...\n"));
23133
- const copilot = await refreshCopilotToken(githubToken, ac.signal);
23134
- const models = await fetchCopilotModels(copilot.token, ac.signal);
23135
- const saved = await saveCopilotTokens(
23136
- deps,
23137
- providerId,
23138
- copilot.token,
23139
- githubToken,
23140
- copilot.expires,
23141
- models
23142
- );
23143
- if (!saved) return 1;
23144
- deps.renderer.write(color.green("\n \u2713 Signed in with GitHub Copilot!\n"));
23145
- const modelHint = models[0] ?? "gpt-4o";
23146
- deps.renderer.writeInfo(
23147
- ` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
23148
- Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
23149
- ` + color.dim(" The Copilot token refreshes automatically.\n")
23150
- );
23151
- return 0;
23152
- } catch (err) {
23153
- const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
23154
- deps.renderer.writeError(` Login failed: ${msg}`);
23155
- return 1;
23156
- } finally {
23157
- process.off("SIGINT", onSig);
23158
- }
23159
- }
23160
- async function saveCopilotTokens(deps, providerId, copilotToken, githubToken, expires, models) {
23161
- const entry = {
23162
- label: "oauth-default",
23163
- apiKey: copilotToken,
23164
- createdAt: nowIso(),
23165
- authMethod: "oauth",
23166
- expiresAt: new Date(expires).toISOString(),
23167
- refreshToken: githubToken,
23168
- tokenType: "bearer"
23169
- };
23170
- try {
23171
- await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
23172
- const existing = all[providerId];
23173
- const p = existing ? { ...existing } : { type: providerId };
23174
- p.family = "github-copilot";
23175
- if (!p.baseUrl) p.baseUrl = copilotBaseUrlFromToken(copilotToken);
23176
- if (models.length > 0) p.models = models;
23177
- else if (!p.models || p.models.length === 0) p.models = ["gpt-4o"];
23178
- const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
23179
- keys.push(entry);
23180
- writeKeysBack(p, keys);
23181
- p.activeKey = entry.label;
23182
- all[providerId] = p;
23183
- });
23184
- return true;
23185
- } catch (err) {
23186
- deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
23187
- return false;
23188
- }
23189
- }
23190
-
23191
- // src/auth-menu/openai-codex-oauth.ts
23192
- init_provider_config_utils();
23193
- var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
23194
- var AUTH_BASE_URL = "https://auth.openai.com";
23195
- var AUTHORIZE_URL2 = `${AUTH_BASE_URL}/oauth/authorize`;
23196
- var TOKEN_URL2 = `${AUTH_BASE_URL}/oauth/token`;
23197
- var REDIRECT_PORT2 = 1455;
23198
- var REDIRECT_HOST2 = "127.0.0.1";
23199
- var REDIRECT_PATH2 = "/auth/callback";
23200
- var REDIRECT_URI2 = `http://localhost:${REDIRECT_PORT2}${REDIRECT_PATH2}`;
23201
- var SCOPE = "openid profile email offline_access";
23202
- var JWT_CLAIM_PATH = "https://api.openai.com/auth";
23203
- var ORIGINATOR = "wrongstack";
23204
- var CODEX_PROVIDER_ID = "openai-codex";
23205
- var CODEX_BASE_URL = "https://chatgpt.com/backend-api";
23206
- function base64url2(buf) {
23207
- return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
23208
- }
23209
- function generatePkce2() {
23210
- const verifier = base64url2(randomBytes(32));
23211
- const challenge = base64url2(createHash("sha256").update(verifier).digest());
23212
- return { verifier, challenge };
23213
- }
23214
- function createState() {
23215
- return randomBytes(16).toString("hex");
23216
- }
23217
- function buildAuthorizeUrl2(challenge, state) {
23218
- const url = new URL(AUTHORIZE_URL2);
23219
- url.searchParams.set("response_type", "code");
23220
- url.searchParams.set("client_id", CLIENT_ID3);
23221
- url.searchParams.set("redirect_uri", REDIRECT_URI2);
23222
- url.searchParams.set("scope", SCOPE);
23223
- url.searchParams.set("code_challenge", challenge);
23224
- url.searchParams.set("code_challenge_method", "S256");
23225
- url.searchParams.set("state", state);
23226
- url.searchParams.set("id_token_add_organizations", "true");
23227
- url.searchParams.set("codex_cli_simplified_flow", "true");
23228
- url.searchParams.set("originator", ORIGINATOR);
23229
- return url.toString();
23230
- }
23231
- function decodeJwtPayload(token) {
23232
- try {
23233
- const parts = token.split(".");
23234
- if (parts.length !== 3) return null;
23235
- const json = Buffer.from(parts[1], "base64url").toString("utf8");
23236
- return JSON.parse(json);
23237
- } catch {
23238
- return null;
23239
- }
23240
- }
23241
- function extractAccountId(token) {
23242
- const payload = decodeJwtPayload(token);
23243
- const auth = payload?.[JWT_CLAIM_PATH];
23244
- const id = auth?.chatgpt_account_id;
23245
- return typeof id === "string" && id.length > 0 ? id : null;
23246
- }
23247
- async function readTokens2(res, op) {
23248
- if (!res.ok) {
23249
- const text = await res.text().catch(() => "");
23250
- throw new Error(`Codex token ${op} failed (${res.status}): ${text || res.statusText}`);
23251
- }
23252
- const json = await res.json();
23253
- if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
23254
- throw new Error(`Codex token ${op} response missing fields`);
23255
- }
23256
- return {
23257
- access: json.access_token,
23258
- refresh: json.refresh_token,
23259
- expires: Date.now() + json.expires_in * 1e3
23260
- };
23261
- }
23262
- async function exchangeAuthorizationCode2(code, verifier, signal) {
23263
- const res = await fetch(TOKEN_URL2, {
23264
- method: "POST",
23265
- headers: { "content-type": "application/x-www-form-urlencoded" },
23266
- body: new URLSearchParams({
23267
- grant_type: "authorization_code",
23268
- client_id: CLIENT_ID3,
23269
- code,
23270
- code_verifier: verifier,
23271
- redirect_uri: REDIRECT_URI2
23272
- }).toString(),
23273
- signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
23274
- });
23275
- return readTokens2(res, "exchange");
23276
- }
23277
- function callbackHtml2(ok, message) {
23278
- const heading = ok ? "Authentication successful" : "Authentication failed";
23279
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>${heading}</title><style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#09090b;color:#fafafa;font-family:ui-sans-serif,system-ui,sans-serif;text-align:center}h1{font-size:26px;margin:0 0 8px}p{color:#a1a1aa}</style></head><body><main><h1>${heading}</h1><p>${message}</p></main></body></html>`;
23280
- }
23281
- function startLoopbackServer2(state) {
23282
- let resolveCode = () => {
23283
- };
23284
- const codePromise = new Promise((resolve13) => {
23285
- let settled = false;
23286
- resolveCode = (v) => {
23287
- if (settled) return;
23288
- settled = true;
23289
- resolve13(v);
23290
- };
23291
- });
23292
- const server = createServer((req2, res) => {
23293
- let url;
23294
- try {
23295
- url = new URL(req2.url ?? "", `http://${REDIRECT_HOST2}`);
23296
- } catch {
23297
- res.statusCode = 400;
23298
- res.end();
23299
- return;
23300
- }
23301
- if (url.pathname !== REDIRECT_PATH2) {
23302
- res.statusCode = 404;
23303
- res.setHeader("content-type", "text/html; charset=utf-8");
23304
- res.end(callbackHtml2(false, "Callback route not found."));
23305
- return;
23306
- }
23307
- res.setHeader("content-type", "text/html; charset=utf-8");
23308
- const err = url.searchParams.get("error");
23309
- if (err) {
23310
- res.statusCode = 400;
23311
- res.end(callbackHtml2(false, `Authorization error: ${err}`));
23312
- resolveCode(null);
23313
- return;
23314
- }
23315
- if (url.searchParams.get("state") !== state) {
23316
- res.statusCode = 400;
23317
- res.end(callbackHtml2(false, "State mismatch \u2014 please restart the login."));
23318
- resolveCode(null);
23319
- return;
23320
- }
23321
- const code = url.searchParams.get("code");
23322
- if (!code) {
23323
- res.statusCode = 400;
23324
- res.end(callbackHtml2(false, "Missing authorization code."));
23325
- return;
23326
- }
23327
- res.statusCode = 200;
23328
- res.end(callbackHtml2(true, "You can close this window and return to the terminal."));
23329
- resolveCode(code);
23330
- });
23331
- return new Promise((resolve13) => {
23332
- server.on("error", () => {
23333
- resolveCode(null);
23334
- resolve13({
23335
- bound: false,
23336
- waitForCode: () => Promise.resolve(null),
23337
- close: () => {
23338
- try {
23339
- server.close();
23340
- } catch {
23341
- }
23342
- }
23343
- });
23344
- });
23345
- server.listen(REDIRECT_PORT2, REDIRECT_HOST2, () => {
23346
- resolve13({
23347
- bound: true,
23348
- waitForCode: () => codePromise,
23349
- close: () => {
23350
- resolveCode(null);
23351
- try {
23352
- server.close();
23353
- } catch {
23354
- }
23355
- }
23356
- });
23357
- });
23358
- });
23359
- }
23360
- function openBrowser3(url) {
23361
- try {
23362
- const platform3 = process.platform;
23363
- const { command: command2, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
23364
- const child = spawn(command2, args, { stdio: "ignore", windowsHide: true });
23365
- child.on("error", () => {
23366
- });
23367
- child.unref();
23368
- } catch {
23369
- }
23370
- }
23371
- function parseAuthorizationInput2(input) {
23372
- const value = input.trim();
23373
- if (!value) return {};
23374
- try {
23375
- const url = new URL(value);
23376
- return {
23377
- code: url.searchParams.get("code") ?? void 0,
23378
- state: url.searchParams.get("state") ?? void 0
23379
- };
23380
- } catch {
23381
- }
23382
- if (value.includes("code=")) {
23383
- const params = new URLSearchParams(value);
23384
- return {
23385
- code: params.get("code") ?? void 0,
23386
- state: params.get("state") ?? void 0
23387
- };
23388
- }
23389
- return { code: value };
23390
- }
23391
- async function runCodexOAuthLogin(deps, opts = {}) {
23392
- const providerId = opts.providerId ?? CODEX_PROVIDER_ID;
23393
- const pkce = generatePkce2();
23394
- const state = createState();
23395
- const authorizeUrl = buildAuthorizeUrl2(pkce.challenge, state);
23396
- const ac = new AbortController();
23397
- const onSig = () => ac.abort();
23398
- process.on("SIGINT", onSig);
23399
- const server = await startLoopbackServer2(state);
23400
- deps.renderer.write(
23401
- color.bold(`
23402
- Sign in with ChatGPT \u2014 ${color.cyan(providerId)}
23403
- `) + color.dim(" Uses your ChatGPT Plus/Pro/Team subscription (not an API key).\n") + color.amber(" \u26A0 Using a subscription outside the official Codex client may violate\n") + color.amber(" OpenAI\u2019s Terms \u2014 your account could be rate-limited or banned.\n") + color.dim(" Sanctioned programmatic use = an API key: ") + color.bold("wstack auth openai") + color.dim("\n\n") + color.bold(` ${"\u2500".repeat(56)}
23404
- `) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${authorizeUrl}
23405
- `) + color.bold(` ${"\u2500".repeat(56)}
23406
-
23407
- `)
23408
- );
23409
- if (server.bound) {
23410
- openBrowser3(authorizeUrl);
23411
- deps.renderer.write(
23412
- color.dim(" A browser window should open. Waiting for you to finish signing in...\n") + color.dim(" (Listening on http://localhost:1455 \u2014 press Ctrl+C to cancel.)\n")
23413
- );
23414
- } else {
23415
- deps.renderer.write(
23416
- color.amber(" \u26A0 Could not start the local callback listener (port 1455 in use).\n") + color.dim(" After signing in, copy the full redirect URL from your browser\n") + color.dim(" (it starts with http://localhost:1455/auth/callback) and paste it below.\n")
23417
- );
23418
- }
23419
- let code;
23420
- try {
23421
- if (server.bound) {
23422
- const got = await server.waitForCode();
23423
- if (got) code = got;
23424
- }
23425
- if (!code) {
23426
- const input = (await deps.reader.readLine(
23427
- `
23428
23216
  ${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
23429
23217
  )).trim();
23430
23218
  if (input.toLowerCase() === "q" || input === "") {
@@ -23443,20 +23231,15 @@ async function runCodexOAuthLogin(deps, opts = {}) {
23443
23231
  return 1;
23444
23232
  }
23445
23233
  deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
23446
- const tokens = await exchangeAuthorizationCode2(code, pkce.verifier, ac.signal);
23447
- const accountId = extractAccountId(tokens.access);
23448
- if (!accountId) {
23449
- deps.renderer.writeError(
23450
- " Signed in, but the token has no ChatGPT account id.\n This account may not have Codex/ChatGPT subscription access."
23451
- );
23452
- return 1;
23453
- }
23454
- const saved = await saveCodexTokens(deps, providerId, tokens, accountId);
23234
+ const tokens = await exchangeAuthorizationCode2(code, state, verifier, ac.signal);
23235
+ const models = await fetchClaudeModels(tokens.access, ac.signal);
23236
+ const saved = await saveClaudeTokens(deps, providerId, tokens, models);
23455
23237
  if (!saved) return 1;
23456
- deps.renderer.write(color.green("\n \u2713 Signed in with ChatGPT!\n"));
23238
+ deps.renderer.write(color.green("\n \u2713 Signed in with Claude!\n"));
23239
+ const modelHint = models.find((m) => m.includes("sonnet")) ?? models[0] ?? "claude-sonnet-4-6";
23457
23240
  deps.renderer.writeInfo(
23458
- ` Saved as provider ${color.bold(providerId)}.
23459
- Use: ${color.bold(`wstack --provider ${providerId} --model gpt-5.5`)} "<task>"
23241
+ ` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
23242
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
23460
23243
  ` + color.dim(" Tokens refresh automatically before they expire.\n")
23461
23244
  );
23462
23245
  return 0;
@@ -23469,7 +23252,7 @@ async function runCodexOAuthLogin(deps, opts = {}) {
23469
23252
  process.off("SIGINT", onSig);
23470
23253
  }
23471
23254
  }
23472
- async function saveCodexTokens(deps, providerId, tokens, accountId) {
23255
+ async function saveClaudeTokens(deps, providerId, tokens, models) {
23473
23256
  const entry = {
23474
23257
  label: "oauth-default",
23475
23258
  apiKey: tokens.access,
@@ -23478,18 +23261,229 @@ async function saveCodexTokens(deps, providerId, tokens, accountId) {
23478
23261
  expiresAt: new Date(tokens.expires).toISOString(),
23479
23262
  refreshToken: tokens.refresh,
23480
23263
  tokenType: "bearer",
23481
- scope: SCOPE,
23482
- accountId
23264
+ scope: SCOPES
23483
23265
  };
23484
23266
  try {
23485
23267
  await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
23486
23268
  const existing = all[providerId];
23487
23269
  const p = existing ? { ...existing } : { type: providerId };
23488
- p.family = "openai-codex";
23489
- if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;
23490
- if (!p.models || p.models.length === 0) {
23491
- p.models = ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark"];
23492
- }
23270
+ p.family = "anthropic-oauth";
23271
+ if (!p.baseUrl) p.baseUrl = CLAUDE_BASE_URL;
23272
+ if (models.length > 0) p.models = models;
23273
+ else if (!p.models || p.models.length === 0)
23274
+ p.models = ["claude-sonnet-4-6", "claude-opus-4-8"];
23275
+ const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
23276
+ keys.push(entry);
23277
+ writeKeysBack(p, keys);
23278
+ p.activeKey = entry.label;
23279
+ all[providerId] = p;
23280
+ });
23281
+ return true;
23282
+ } catch (err) {
23283
+ deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
23284
+ return false;
23285
+ }
23286
+ }
23287
+
23288
+ // src/auth-menu/github-copilot-oauth.ts
23289
+ init_provider_config_utils();
23290
+ var CLIENT_ID3 = "Iv1.b507a08c87ecfe98";
23291
+ var DEVICE_CODE_URL = "https://github.com/login/device/code";
23292
+ var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
23293
+ var COPILOT_HEADERS = {
23294
+ "User-Agent": "GitHubCopilotChat/0.35.0",
23295
+ "Editor-Version": "vscode/1.107.0",
23296
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
23297
+ "Copilot-Integration-Id": "vscode-chat"
23298
+ };
23299
+ var COPILOT_API_VERSION = "2026-06-01";
23300
+ var COPILOT_PROVIDER_ID = "github-copilot";
23301
+ function openBrowser3(url) {
23302
+ try {
23303
+ const platform3 = process.platform;
23304
+ const { command: command2, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
23305
+ const child = spawn(command2, args, { stdio: "ignore", windowsHide: true });
23306
+ child.on("error", () => {
23307
+ });
23308
+ child.unref();
23309
+ } catch {
23310
+ }
23311
+ }
23312
+ function sleep(ms, signal) {
23313
+ return new Promise((resolve13, reject) => {
23314
+ if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
23315
+ const t = setTimeout(resolve13, ms);
23316
+ signal.addEventListener(
23317
+ "abort",
23318
+ () => {
23319
+ clearTimeout(t);
23320
+ reject(new DOMException("Aborted", "AbortError"));
23321
+ },
23322
+ { once: true }
23323
+ );
23324
+ });
23325
+ }
23326
+ async function startDeviceFlow(signal) {
23327
+ const res = await fetch(DEVICE_CODE_URL, {
23328
+ method: "POST",
23329
+ headers: {
23330
+ accept: "application/json",
23331
+ "content-type": "application/x-www-form-urlencoded",
23332
+ "user-agent": COPILOT_HEADERS["User-Agent"]
23333
+ },
23334
+ body: new URLSearchParams({ client_id: CLIENT_ID3, scope: "read:user" }).toString(),
23335
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
23336
+ });
23337
+ if (!res.ok) throw new Error(`GitHub device-code request failed (${res.status})`);
23338
+ const json = await res.json();
23339
+ if (!json?.device_code || !json.user_code || !json.verification_uri || typeof json.expires_in !== "number") {
23340
+ throw new Error("Invalid device-code response");
23341
+ }
23342
+ return {
23343
+ device_code: json.device_code,
23344
+ user_code: json.user_code,
23345
+ verification_uri: json.verification_uri,
23346
+ interval: json.interval ?? 5,
23347
+ expires_in: json.expires_in
23348
+ };
23349
+ }
23350
+ async function pollForGitHubToken(device, signal) {
23351
+ let intervalMs = device.interval * 1e3;
23352
+ const expiresAt = Date.now() + device.expires_in * 1e3;
23353
+ while (Date.now() < expiresAt) {
23354
+ await sleep(intervalMs, signal);
23355
+ const res = await fetch(ACCESS_TOKEN_URL, {
23356
+ method: "POST",
23357
+ headers: {
23358
+ accept: "application/json",
23359
+ "content-type": "application/x-www-form-urlencoded",
23360
+ "user-agent": COPILOT_HEADERS["User-Agent"]
23361
+ },
23362
+ body: new URLSearchParams({
23363
+ client_id: CLIENT_ID3,
23364
+ device_code: device.device_code,
23365
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
23366
+ }).toString(),
23367
+ signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
23368
+ });
23369
+ const json = await res.json().catch(() => ({}));
23370
+ if (json.access_token) return json.access_token;
23371
+ if (json.error === "authorization_pending") continue;
23372
+ if (json.error === "slow_down") {
23373
+ intervalMs += 5e3;
23374
+ continue;
23375
+ }
23376
+ throw new Error(`Device flow failed: ${json.error ?? "unknown error"}`);
23377
+ }
23378
+ throw new Error("Device code expired \u2014 please restart the login.");
23379
+ }
23380
+ function isUsableCopilotChatModel(item) {
23381
+ if (typeof item.id !== "string" || item.id.length === 0) return false;
23382
+ const cap = item.capabilities;
23383
+ if (cap?.type !== "chat") return false;
23384
+ if (cap.supports?.tool_calls !== true) return false;
23385
+ const eps = item.supported_endpoints;
23386
+ if (Array.isArray(eps) && !eps.includes("/chat/completions")) return false;
23387
+ if (item.policy?.state === "disabled") return false;
23388
+ if (item.vendor === "Experimental") return false;
23389
+ return true;
23390
+ }
23391
+ async function fetchCopilotModels(copilotToken, signal) {
23392
+ try {
23393
+ const base = copilotBaseUrlFromToken(copilotToken);
23394
+ const res = await fetch(`${base}/models`, {
23395
+ headers: {
23396
+ accept: "application/json",
23397
+ authorization: `Bearer ${copilotToken}`,
23398
+ "X-GitHub-Api-Version": COPILOT_API_VERSION,
23399
+ ...COPILOT_HEADERS
23400
+ },
23401
+ signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
23402
+ });
23403
+ if (!res.ok) return [];
23404
+ const json = await res.json();
23405
+ const data = json?.data;
23406
+ if (!Array.isArray(data)) return [];
23407
+ const usable = data.filter(isUsableCopilotChatModel);
23408
+ usable.sort((a, b) => copilotModelRank(a) - copilotModelRank(b));
23409
+ return usable.map((m) => m.id);
23410
+ } catch {
23411
+ return [];
23412
+ }
23413
+ }
23414
+ function copilotModelRank(item) {
23415
+ if (item.is_chat_default === true) return 0;
23416
+ if (item.is_chat_fallback === true) return 1;
23417
+ return 2;
23418
+ }
23419
+ async function runCopilotOAuthLogin(deps, opts = {}) {
23420
+ const providerId = opts.providerId ?? COPILOT_PROVIDER_ID;
23421
+ const ac = new AbortController();
23422
+ const onSig = () => ac.abort();
23423
+ process.on("SIGINT", onSig);
23424
+ try {
23425
+ deps.renderer.write(
23426
+ color.bold(`
23427
+ Sign in with GitHub Copilot \u2014 ${color.cyan(providerId)}
23428
+ `) + color.dim(" Uses your GitHub Copilot subscription (not an API key).\n") + color.amber(" \u26A0 Using Copilot outside its official editor integrations may violate\n") + color.amber(" GitHub\u2019s Terms \u2014 your account could be rate-limited or banned.\n\n")
23429
+ );
23430
+ const device = await startDeviceFlow(ac.signal);
23431
+ deps.renderer.write(
23432
+ color.bold(` ${"\u2500".repeat(56)}
23433
+ `) + color.bold(" Open this URL and enter the code:\n") + color.cyan(` ${device.verification_uri}
23434
+ `) + color.bold(" Code: ") + color.green(color.bold(device.user_code)) + "\n" + color.bold(` ${"\u2500".repeat(56)}
23435
+
23436
+ `)
23437
+ );
23438
+ openBrowser3(device.verification_uri);
23439
+ deps.renderer.write(color.dim(" Waiting for you to authorize in the browser...\n"));
23440
+ const githubToken = await pollForGitHubToken(device, ac.signal);
23441
+ deps.renderer.write(color.dim(" Authorized. Fetching your Copilot token...\n"));
23442
+ const copilot = await refreshCopilotToken(githubToken, ac.signal);
23443
+ const models = await fetchCopilotModels(copilot.token, ac.signal);
23444
+ const saved = await saveCopilotTokens(
23445
+ deps,
23446
+ providerId,
23447
+ copilot.token,
23448
+ githubToken,
23449
+ copilot.expires,
23450
+ models
23451
+ );
23452
+ if (!saved) return 1;
23453
+ deps.renderer.write(color.green("\n \u2713 Signed in with GitHub Copilot!\n"));
23454
+ const modelHint = models[0] ?? "gpt-4o";
23455
+ deps.renderer.writeInfo(
23456
+ ` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
23457
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
23458
+ ` + color.dim(" The Copilot token refreshes automatically.\n")
23459
+ );
23460
+ return 0;
23461
+ } catch (err) {
23462
+ const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
23463
+ deps.renderer.writeError(` Login failed: ${msg}`);
23464
+ return 1;
23465
+ } finally {
23466
+ process.off("SIGINT", onSig);
23467
+ }
23468
+ }
23469
+ async function saveCopilotTokens(deps, providerId, copilotToken, githubToken, expires, models) {
23470
+ const entry = {
23471
+ label: "oauth-default",
23472
+ apiKey: copilotToken,
23473
+ createdAt: nowIso(),
23474
+ authMethod: "oauth",
23475
+ expiresAt: new Date(expires).toISOString(),
23476
+ refreshToken: githubToken,
23477
+ tokenType: "bearer"
23478
+ };
23479
+ try {
23480
+ await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
23481
+ const existing = all[providerId];
23482
+ const p = existing ? { ...existing } : { type: providerId };
23483
+ p.family = "github-copilot";
23484
+ if (!p.baseUrl) p.baseUrl = copilotBaseUrlFromToken(copilotToken);
23485
+ if (models.length > 0) p.models = models;
23486
+ else if (!p.models || p.models.length === 0) p.models = ["gpt-4o"];
23493
23487
  const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
23494
23488
  keys.push(entry);
23495
23489
  writeKeysBack(p, keys);
@@ -25170,7 +25164,7 @@ async function addMcpServer(args, deps) {
25170
25164
  const serverCfg = { ...factory };
25171
25165
  serverCfg.enabled = enable;
25172
25166
  const existing = await readJsonObjectFile(deps.paths.globalConfig);
25173
- const mcpServers = isRecord(existing.mcpServers) ? existing.mcpServers : {};
25167
+ const mcpServers = isRecord2(existing.mcpServers) ? existing.mcpServers : {};
25174
25168
  if (mcpServers[name])
25175
25169
  deps.renderer.writeWarning(`Server "${name}" already in config. Updating.
25176
25170
  `);
@@ -25190,7 +25184,7 @@ async function removeMcpServer(name, deps) {
25190
25184
  return 1;
25191
25185
  }
25192
25186
  const existing = await readJsonObjectFile(deps.paths.globalConfig);
25193
- const mcpServers = isRecord(existing.mcpServers) ? existing.mcpServers : {};
25187
+ const mcpServers = isRecord2(existing.mcpServers) ? existing.mcpServers : {};
25194
25188
  if (!mcpServers[name]) {
25195
25189
  deps.renderer.writeError(`Server "${name}" not in config.
25196
25190
  `);
@@ -25203,7 +25197,7 @@ async function removeMcpServer(name, deps) {
25203
25197
  `);
25204
25198
  return 0;
25205
25199
  }
25206
- function isRecord(value) {
25200
+ function isRecord2(value) {
25207
25201
  return typeof value === "object" && value !== null && !Array.isArray(value);
25208
25202
  }
25209
25203
  var MODEL_PROFILES = [
@@ -25455,13 +25449,12 @@ function roleCat(role) {
25455
25449
  }
25456
25450
  function createProviderForId(providerId, cfg) {
25457
25451
  const savedCfg = cfg.providers?.[providerId];
25458
- const resolvedProviderId = savedCfg?.type ?? providerId;
25459
25452
  const cfgWithType = Object.assign(
25460
- { type: resolvedProviderId },
25453
+ { type: providerId },
25461
25454
  savedCfg ?? { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }
25462
25455
  );
25463
25456
  try {
25464
- return makeProviderFromConfig(resolvedProviderId, cfgWithType);
25457
+ return makeProviderFromConfig(providerId, cfgWithType);
25465
25458
  } catch {
25466
25459
  return void 0;
25467
25460
  }
@@ -26237,8 +26230,8 @@ async function upsertPlugin(spec, opts, deps, verb) {
26237
26230
  if (idx >= 0) plugins[idx] = nextEntry;
26238
26231
  else plugins.push(nextEntry);
26239
26232
  const features = {
26240
- ...isRecord2(deps.config.features) ? deps.config.features : {},
26241
- ...isRecord2(existing.features) ? existing.features : {},
26233
+ ...isRecord3(deps.config.features) ? deps.config.features : {},
26234
+ ...isRecord3(existing.features) ? existing.features : {},
26242
26235
  plugins: true
26243
26236
  };
26244
26237
  existing.plugins = plugins;
@@ -26272,7 +26265,7 @@ async function removePlugin(spec, deps) {
26272
26265
  function errorResult(message) {
26273
26266
  return { code: 1, level: "error", message };
26274
26267
  }
26275
- function isRecord2(value) {
26268
+ function isRecord3(value) {
26276
26269
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
26277
26270
  }
26278
26271
 
@@ -28188,6 +28181,8 @@ function registerBuiltinTools(deps) {
28188
28181
  makeMailInboxTool({ projectDir: deps.wpaths.projectDir, events: deps.events })
28189
28182
  );
28190
28183
  applyToolDescriptionModes(deps.toolRegistry, deps.config.tools?.descriptionMode);
28184
+ configureExecPolicy(deps.config.tools?.exec ?? {});
28185
+ configureAutophasePolicy(deps.config.tools?.exec ?? {});
28191
28186
  }
28192
28187
 
28193
28188
  // src/cli-eternal-flag.ts
@@ -28291,6 +28286,9 @@ function applyNodeEnvDefault() {
28291
28286
  process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] = "1";
28292
28287
  }
28293
28288
  }
28289
+ function applySessionShellDefault() {
28290
+ ensureSessionShell();
28291
+ }
28294
28292
  async function applyPrintUpdateNotice(initialUpdateInfo) {
28295
28293
  return await printUpdateNotice(initialUpdateInfo);
28296
28294
  }
@@ -28302,6 +28300,7 @@ async function applyDebugStreamSeed(config) {
28302
28300
  }
28303
28301
  async function runPreflight(config, initialUpdateInfo) {
28304
28302
  applyNodeEnvDefault();
28303
+ applySessionShellDefault();
28305
28304
  const updateInfo = await applyPrintUpdateNotice(initialUpdateInfo);
28306
28305
  await applyDebugStreamSeed(config);
28307
28306
  return {
@@ -29481,19 +29480,26 @@ async function resumeSession(ctx, sessionId) {
29481
29480
  }
29482
29481
  function createSettingsAdapter(ctx) {
29483
29482
  const { configStore, wpaths, fleetStreamController, applyLiveSettings } = ctx;
29483
+ const deriveFsAccess = deriveFsAccessPair;
29484
29484
  function getSettings() {
29485
29485
  const cfg = configStore.get();
29486
29486
  const autonomy = cfg.autonomy;
29487
29487
  const rawMode = autonomy?.defaultMode;
29488
29488
  const mode = rawMode === "suggest" || rawMode === "auto" ? rawMode : "off";
29489
29489
  const modelRuntime = cfg.modelRuntime;
29490
+ const contextModeRaw = cfg.context?.mode;
29491
+ const contextMode = contextModeRaw === "frugal" || contextModeRaw === "deep" || contextModeRaw === "archival" ? contextModeRaw : "balanced";
29490
29492
  const reasoningEffortRaw = modelRuntime?.reasoning?.effort;
29491
29493
  const reasoningEffort = reasoningEffortRaw === "none" || reasoningEffortRaw === "minimal" || reasoningEffortRaw === "low" || reasoningEffortRaw === "medium" || reasoningEffortRaw === "high" || reasoningEffortRaw === "xhigh" || reasoningEffortRaw === "max" ? reasoningEffortRaw : "high";
29494
+ const featuresAllow = cfg.features?.allowOutsideProjectRoot;
29495
+ const toolsRestrict = cfg.tools?.restrictToProjectRoot;
29496
+ const resolvedAllow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
29497
+ const resolvedRestrict = !resolvedAllow;
29492
29498
  return {
29493
29499
  mode,
29494
29500
  delayMs: autonomy?.autoProceedDelayMs ?? 45e3,
29495
29501
  titleAnimation: autonomy?.terminalTitleAnimation !== false,
29496
- yolo: autonomy?.yolo ?? false,
29502
+ yolo: cfg.yolo ?? (autonomy?.yolo ?? false),
29497
29503
  streamFleet: autonomy?.streamFleet !== false,
29498
29504
  chime: autonomy?.chime ?? false,
29499
29505
  confirmExit: autonomy?.confirmExit !== false,
@@ -29504,15 +29510,16 @@ function createSettingsAdapter(ctx) {
29504
29510
  featureSkills: cfg.features?.skills !== false,
29505
29511
  featureModelsRegistry: cfg.features?.modelsRegistry !== false,
29506
29512
  featureTokenSaving: normalizeTokenSavingTier(cfg.features?.tokenSavingMode),
29507
- allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
29513
+ allowOutsideProjectRoot: resolvedAllow,
29508
29514
  contextAutoCompact: cfg.context?.autoCompact !== false,
29509
29515
  contextStrategy: cfg.context?.strategy ?? "hybrid",
29510
- maxConcurrent: cfg.maxConcurrent ?? 0,
29516
+ contextMode,
29517
+ maxConcurrent: cfg.maxConcurrent ?? 4,
29511
29518
  logLevel: cfg.log?.level ?? "info",
29512
29519
  auditLevel: cfg.session?.auditLevel ?? "standard",
29513
29520
  indexOnStart: cfg.indexing?.onSessionStart !== false,
29514
29521
  maxIterations: cfg.tools?.maxIterations ?? 500,
29515
- restrictFsToRoot: cfg.tools?.restrictToProjectRoot ?? false,
29522
+ restrictFsToRoot: resolvedRestrict,
29516
29523
  autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
29517
29524
  debugStream: cfg.debugStream ?? false,
29518
29525
  statuslineMode: autonomy?.statuslineMode === "minimum" ? "minimum" : "detailed",
@@ -29533,48 +29540,46 @@ function createSettingsAdapter(ctx) {
29533
29540
  }
29534
29541
  async function saveSettings(s) {
29535
29542
  try {
29536
- await persistAutonomySetting(
29537
- {
29538
- configStore,
29539
- globalConfigPath: wpaths.globalConfig,
29540
- inProjectConfigPath: wpaths.inProjectConfig,
29541
- vault: noOpVault
29542
- },
29543
- (autonomy) => {
29544
- autonomy.defaultMode = s.mode;
29545
- autonomy.autoProceedDelayMs = s.delayMs;
29546
- const a = autonomy;
29547
- a["terminalTitleAnimation"] = s.titleAnimation ?? true;
29548
- a["yolo"] = s.yolo ?? false;
29549
- a["streamFleet"] = s.streamFleet ?? true;
29550
- a["chime"] = s.chime ?? false;
29551
- a["confirmExit"] = s.confirmExit ?? true;
29552
- if (s.mouseMode !== void 0) a["mouseMode"] = s.mouseMode;
29553
- if (s.enhanceEnabled !== void 0) a["enhance"] = s.enhanceEnabled;
29554
- if (s.enhanceLanguage !== void 0) a["enhanceLanguage"] = s.enhanceLanguage;
29555
- if (s.statuslineMode !== void 0) a["statuslineMode"] = s.statuslineMode;
29556
- if (s.thinkingWord !== void 0) a["thinkingWord"] = normalizeTuiThinkingWord(s.thinkingWord);
29557
- if (s.autonomyNextPrompt !== void 0) a["autonomyNextPrompt"] = s.autonomyNextPrompt;
29558
- if (s.autoProceedMaxIterations !== void 0)
29559
- a["autoProceedMaxIterations"] = s.autoProceedMaxIterations;
29560
- }
29561
- );
29562
- 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.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
29543
+ 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.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) {
29563
29544
  const configScope = s.configScope ?? configStore.get().configScope ?? "global";
29564
29545
  const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
29565
29546
  let raw;
29566
29547
  try {
29567
29548
  raw = await fs4.readFile(targetPath, "utf8");
29568
29549
  } catch (err) {
29569
- throw new Error(
29570
- `Failed to read config at ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
29571
- { cause: err }
29572
- );
29550
+ if (err.code !== "ENOENT") {
29551
+ throw new Error(
29552
+ `Failed to read config at ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
29553
+ { cause: err }
29554
+ );
29555
+ }
29556
+ raw = "{}";
29573
29557
  }
29574
29558
  const parsed = JSON.parse(raw);
29575
29559
  const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
29560
+ const autonomy = decrypted.autonomy ?? {};
29561
+ if (s.mode !== void 0) autonomy.defaultMode = s.mode;
29562
+ if (s.delayMs !== void 0) autonomy.autoProceedDelayMs = s.delayMs;
29563
+ if (s.titleAnimation !== void 0) autonomy.terminalTitleAnimation = s.titleAnimation;
29564
+ if (s.yolo !== void 0) autonomy.yolo = s.yolo;
29565
+ if (s.streamFleet !== void 0) autonomy.streamFleet = s.streamFleet;
29566
+ if (s.chime !== void 0) autonomy.chime = s.chime;
29567
+ if (s.confirmExit !== void 0) autonomy.confirmExit = s.confirmExit;
29568
+ if (s.mouseMode !== void 0) autonomy.mouseMode = s.mouseMode;
29569
+ if (s.enhanceDelayMs !== void 0) autonomy.enhanceDelayMs = s.enhanceDelayMs;
29570
+ if (s.enhanceEnabled !== void 0) autonomy.enhance = s.enhanceEnabled;
29571
+ if (s.enhanceLanguage !== void 0) autonomy.enhanceLanguage = s.enhanceLanguage;
29572
+ if (s.statuslineMode !== void 0) autonomy.statuslineMode = s.statuslineMode;
29573
+ if (s.thinkingWord !== void 0)
29574
+ autonomy.thinkingWord = normalizeTuiThinkingWord(s.thinkingWord);
29575
+ if (s.autonomyNextPrompt !== void 0) autonomy.autonomyNextPrompt = s.autonomyNextPrompt;
29576
+ if (s.autoProceedMaxIterations !== void 0)
29577
+ autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
29578
+ decrypted.autonomy = autonomy;
29576
29579
  if (s.nextPrediction !== void 0) decrypted.nextPrediction = s.nextPrediction;
29577
- 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) {
29580
+ if (s.yolo !== void 0) decrypted.yolo = s.yolo;
29581
+ const fsAccess = deriveFsAccess(s);
29582
+ 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 || fsAccess !== void 0) {
29578
29583
  const feats = decrypted.features ?? {};
29579
29584
  if (s.featureMcp !== void 0) feats.mcp = s.featureMcp;
29580
29585
  if (s.featurePlugins !== void 0) feats.plugins = s.featurePlugins;
@@ -29582,15 +29587,17 @@ function createSettingsAdapter(ctx) {
29582
29587
  if (s.featureSkills !== void 0) feats.skills = s.featureSkills;
29583
29588
  if (s.featureModelsRegistry !== void 0) feats.modelsRegistry = s.featureModelsRegistry;
29584
29589
  if (s.featureTokenSaving !== void 0) feats.tokenSavingMode = s.featureTokenSaving;
29585
- if (s.allowOutsideProjectRoot !== void 0) feats.allowOutsideProjectRoot = s.allowOutsideProjectRoot;
29590
+ if (fsAccess !== void 0) feats.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
29586
29591
  decrypted.features = feats;
29587
29592
  }
29588
- if (s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0) {
29593
+ if (s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0) {
29589
29594
  const c = decrypted.context ?? {};
29590
29595
  if (s.contextAutoCompact !== void 0) c.autoCompact = s.contextAutoCompact;
29591
29596
  if (s.contextStrategy !== void 0) c.strategy = s.contextStrategy;
29597
+ if (s.contextMode !== void 0) c.mode = s.contextMode;
29592
29598
  decrypted.context = c;
29593
29599
  }
29600
+ if (s.maxConcurrent !== void 0) decrypted.maxConcurrent = s.maxConcurrent;
29594
29601
  if (s.logLevel !== void 0) {
29595
29602
  const log = decrypted.log ?? {};
29596
29603
  log.level = s.logLevel;
@@ -29606,32 +29613,38 @@ function createSettingsAdapter(ctx) {
29606
29613
  idx.onSessionStart = s.indexOnStart;
29607
29614
  decrypted.indexing = idx;
29608
29615
  }
29609
- if (s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0) {
29616
+ if (s.maxIterations !== void 0 || fsAccess !== void 0) {
29610
29617
  const tools = decrypted.tools ?? {};
29611
29618
  if (s.maxIterations !== void 0) tools.maxIterations = s.maxIterations;
29612
- if (s.restrictFsToRoot !== void 0) tools.restrictToProjectRoot = s.restrictFsToRoot;
29619
+ if (fsAccess !== void 0) tools.restrictToProjectRoot = fsAccess.restrictToProjectRoot;
29613
29620
  decrypted.tools = tools;
29614
29621
  }
29615
- if (s.restrictFsToRoot !== void 0) {
29616
- const features = decrypted.features ?? {};
29617
- features.allowOutsideProjectRoot = !s.restrictFsToRoot;
29618
- decrypted.features = features;
29619
- }
29620
29622
  if (s.debugStream !== void 0) {
29621
29623
  decrypted.debugStream = s.debugStream;
29622
29624
  const { setDebugStreamEnabled } = await import('@wrongstack/providers');
29623
29625
  setDebugStreamEnabled(s.debugStream);
29624
29626
  }
29625
29627
  if (s.configScope !== void 0) decrypted.configScope = s.configScope;
29626
- if (s.enhanceDelayMs !== void 0) {
29627
- const autonomy = decrypted.autonomy ?? {};
29628
- autonomy.enhanceDelayMs = s.enhanceDelayMs;
29629
- decrypted.autonomy = autonomy;
29630
- }
29631
- if (s.autoProceedMaxIterations !== void 0) {
29632
- const autonomy = decrypted.autonomy ?? {};
29633
- autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
29634
- decrypted.autonomy = autonomy;
29628
+ if (s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0) {
29629
+ const modelRuntime = decrypted.modelRuntime ?? {};
29630
+ if (s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0) {
29631
+ const reasoning = modelRuntime.reasoning ?? {};
29632
+ if (s.reasoningMode !== void 0) reasoning.mode = s.reasoningMode;
29633
+ if (s.reasoningEffort !== void 0) reasoning.effort = s.reasoningEffort;
29634
+ if (s.reasoningPreserve !== void 0) reasoning.preserve = s.reasoningPreserve;
29635
+ modelRuntime.reasoning = reasoning;
29636
+ }
29637
+ if (s.cacheTtl !== void 0) {
29638
+ const cache = modelRuntime.cache ?? {};
29639
+ if (s.cacheTtl === "default") {
29640
+ delete cache.ttl;
29641
+ } else {
29642
+ cache.ttl = s.cacheTtl;
29643
+ }
29644
+ if (Object.keys(cache).length > 0) modelRuntime.cache = cache;
29645
+ else delete modelRuntime.cache;
29646
+ }
29647
+ decrypted.modelRuntime = modelRuntime;
29635
29648
  }
29636
29649
  const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
29637
29650
  const encrypted = encryptConfigSecrets$1(toWrite, noOpVault);
@@ -29639,19 +29652,63 @@ function createSettingsAdapter(ctx) {
29639
29652
  await fs4.mkdir(path4.dirname(targetPath), { recursive: true });
29640
29653
  }
29641
29654
  await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
29655
+ const currentConfig = configStore.get();
29656
+ const nextModelRuntime = {
29657
+ ...currentConfig.modelRuntime,
29658
+ ...decrypted.modelRuntime ?? {}
29659
+ };
29660
+ if (s.cacheTtl === "default") {
29661
+ delete nextModelRuntime.cache;
29662
+ }
29642
29663
  configStore.update({
29643
29664
  ...s.nextPrediction !== void 0 ? { nextPrediction: s.nextPrediction } : {},
29644
- ...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 ? { features: decrypted.features } : {},
29645
- ...s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 ? { context: decrypted.context } : {},
29646
- ...s.logLevel !== void 0 ? { log: decrypted.log } : {},
29647
- ...s.auditLevel !== void 0 ? { session: decrypted.session } : {},
29648
- ...s.indexOnStart !== void 0 ? { indexing: decrypted.indexing } : {},
29649
- ...s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 ? { tools: decrypted.tools } : {},
29665
+ ...s.yolo !== void 0 ? { yolo: s.yolo } : {},
29666
+ ...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0 ? {
29667
+ features: {
29668
+ ...currentConfig.features,
29669
+ ...decrypted.features ?? {}
29670
+ }
29671
+ } : {},
29672
+ ...s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 ? {
29673
+ context: {
29674
+ ...currentConfig.context,
29675
+ ...decrypted.context ?? {}
29676
+ }
29677
+ } : {},
29678
+ ...s.maxConcurrent !== void 0 ? { maxConcurrent: s.maxConcurrent } : {},
29679
+ ...s.logLevel !== void 0 ? {
29680
+ log: {
29681
+ ...currentConfig.log,
29682
+ ...decrypted.log ?? {}
29683
+ }
29684
+ } : {},
29685
+ ...s.auditLevel !== void 0 ? {
29686
+ session: {
29687
+ ...currentConfig.session,
29688
+ ...decrypted.session ?? {}
29689
+ }
29690
+ } : {},
29691
+ ...s.indexOnStart !== void 0 ? {
29692
+ indexing: {
29693
+ ...currentConfig.indexing,
29694
+ ...decrypted.indexing ?? {}
29695
+ }
29696
+ } : {},
29697
+ ...s.maxIterations !== void 0 || fsAccess !== void 0 ? {
29698
+ tools: {
29699
+ ...currentConfig.tools,
29700
+ ...decrypted.tools ?? {}
29701
+ }
29702
+ } : {},
29650
29703
  ...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
29651
29704
  ...s.configScope !== void 0 ? { configScope: s.configScope } : {},
29652
- ...s.enhanceDelayMs !== void 0 ? { autonomy: { ...configStore.get().autonomy ?? {}, enhanceDelayMs: s.enhanceDelayMs } } : {},
29653
- ...s.enhanceEnabled !== void 0 ? { autonomy: { ...configStore.get().autonomy ?? {}, enhance: s.enhanceEnabled } } : {},
29654
- ...s.enhanceLanguage !== void 0 ? { autonomy: { ...configStore.get().autonomy ?? {}, enhanceLanguage: s.enhanceLanguage } } : {}
29705
+ autonomy: {
29706
+ ...currentConfig.autonomy,
29707
+ ...decrypted.autonomy ?? {}
29708
+ },
29709
+ ...s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 ? {
29710
+ modelRuntime: nextModelRuntime
29711
+ } : {}
29655
29712
  });
29656
29713
  }
29657
29714
  if (s.streamFleet !== void 0) fleetStreamController?.setEnabled(s.streamFleet);
@@ -32069,12 +32126,22 @@ var MultiAgentHost = class _MultiAgentHost {
32069
32126
  };
32070
32127
  }
32071
32128
  const tools = subCfg.tools ? [...subCfg.tools] : void 0;
32129
+ const subTokenCounter = new DefaultTokenCounter({
32130
+ registry: this.deps.modelsRegistry,
32131
+ providerId: effProvider,
32132
+ events
32133
+ });
32072
32134
  const ctx = new Context({
32073
32135
  systemPrompt: baseSystem,
32074
32136
  provider,
32075
32137
  session: subSession,
32076
32138
  signal: new AbortController().signal,
32077
- tokenCounter: this.deps.tokenCounter,
32139
+ // Keep per-request context pressure isolated. The leader statusline
32140
+ // reads currentRequestTokens() from the leader counter; sharing it with
32141
+ // subagents lets the latest subagent provider call overwrite the ctx
32142
+ // chip even though subagent ctx is reported separately as
32143
+ // subagent.ctx_pct.
32144
+ tokenCounter: subTokenCounter,
32078
32145
  cwd: subCwd,
32079
32146
  projectRoot: this.deps.projectRoot,
32080
32147
  // Subagents inherit the leader's filesystem-access scope.
@@ -33137,6 +33204,22 @@ function createAgent(params) {
33137
33204
  tracer: params.tracer
33138
33205
  });
33139
33206
  }
33207
+ function resolveProviderCfg(config, providerId) {
33208
+ const savedCfg = config.providers?.[providerId];
33209
+ const cfg = {
33210
+ ...savedCfg,
33211
+ apiKey: savedCfg?.apiKey ?? config.apiKey,
33212
+ baseUrl: savedCfg?.baseUrl ?? config.baseUrl,
33213
+ type: providerId
33214
+ };
33215
+ const factoryType = savedCfg?.type ?? providerId;
33216
+ return { cfg, factoryType };
33217
+ }
33218
+ function buildProviderForId(args, providerId) {
33219
+ const { cfg, factoryType } = resolveProviderCfg(args.config, providerId);
33220
+ const useRegistry = !!args.config.features.modelsRegistry && args.providerRegistry.has(factoryType);
33221
+ return useRegistry ? args.providerRegistry.create(cfg) : makeProviderFromConfig(providerId, cfg);
33222
+ }
33140
33223
 
33141
33224
  // src/cli-main.ts
33142
33225
  init_plugins();
@@ -33588,6 +33671,7 @@ function wireEventWiring(deps) {
33588
33671
  // src/cli-main.ts
33589
33672
  async function main(argv) {
33590
33673
  applyNodeEnvDefault();
33674
+ applySessionShellDefault();
33591
33675
  const earlyFlags = parseArgs(argv).flags;
33592
33676
  const earlyExit = await handleHelpVersionShortCircuit(argv);
33593
33677
  if (earlyExit !== null) return earlyExit;
@@ -34135,27 +34219,16 @@ async function main(argv) {
34135
34219
  if (depWatcherDispose) {
34136
34220
  teardownHandlers.push(depWatcherDispose);
34137
34221
  }
34138
- const resolveProviderCfg = (providerId) => {
34139
- const savedCfg = config.providers?.[providerId];
34140
- const resolvedProviderId = savedCfg?.type ?? providerId;
34141
- const cfgWithType = {
34142
- ...savedCfg ?? { type: providerId, apiKey: config.apiKey, baseUrl: config.baseUrl },
34143
- type: resolvedProviderId
34144
- };
34145
- return { resolvedProviderId, cfgWithType };
34146
- };
34147
- const buildProviderForId = (providerId) => {
34148
- const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
34149
- return config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
34150
- };
34222
+ const resolveProviderCfg2 = (providerId) => resolveProviderCfg(config, providerId);
34223
+ const buildProviderForId2 = (providerId) => buildProviderForId({ config, providerRegistry }, providerId);
34151
34224
  const refreshMaxContextFor = async (providerId, modelId) => {
34152
- const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
34153
- await refreshMaxContext(resolvedProviderId, modelId, cfgWithType);
34225
+ const { cfg } = resolveProviderCfg2(providerId);
34226
+ await refreshMaxContext(providerId, modelId, cfg);
34154
34227
  };
34155
34228
  agent.extensions.register(
34156
34229
  createFallbackModelExtension({
34157
34230
  getConfig: () => config,
34158
- buildProvider: buildProviderForId,
34231
+ buildProvider: buildProviderForId2,
34159
34232
  onModelSwitch: refreshMaxContextFor,
34160
34233
  events,
34161
34234
  logger
@@ -34170,7 +34243,7 @@ async function main(argv) {
34170
34243
  }
34171
34244
  const switchProviderAndModel = async (providerId, modelId) => {
34172
34245
  try {
34173
- context.provider = buildProviderForId(providerId);
34246
+ context.provider = buildProviderForId2(providerId);
34174
34247
  context.model = modelId;
34175
34248
  config = patchConfig(config, { provider: providerId, model: modelId });
34176
34249
  configStore.update({ provider: providerId, model: modelId });
@@ -34544,10 +34617,9 @@ async function main(argv) {
34544
34617
  get: () => loadStatuslineConfig(),
34545
34618
  set: (cfg) => saveStatuslineConfig(cfg)
34546
34619
  };
34547
- const hiddenItemsFromConfig = await loadStatuslineConfig();
34620
+ const hiddenItemsFromConfig = await ensureStatuslineConfig();
34548
34621
  const hiddenItemsList = [];
34549
- const ALL_ITEMS = ["todos", "plan", "tasks", "fleet", "git", "elapsed", "context", "cost"];
34550
- for (const k of ALL_ITEMS) {
34622
+ for (const k of STATUSLINE_CONFIG_KEYS) {
34551
34623
  if (!hiddenItemsFromConfig[k]) hiddenItemsList.push(k);
34552
34624
  }
34553
34625
  const statuslineHiddenItems = hiddenItemsList;
@@ -34555,11 +34627,10 @@ async function main(argv) {
34555
34627
  const setStatuslineHiddenItems = (items) => {
34556
34628
  currentHiddenItems = items;
34557
34629
  };
34558
- const ALL_STATUSLINE_KEYS = ["todos", "plan", "tasks", "fleet", "git", "elapsed", "context", "cost", "working_dir"];
34559
34630
  const saveStatuslineHiddenItems = async (items) => {
34560
34631
  currentHiddenItems = items;
34561
34632
  const cfg = { ...DEFAULTS };
34562
- for (const k of ALL_STATUSLINE_KEYS) {
34633
+ for (const k of STATUSLINE_CONFIG_KEYS) {
34563
34634
  cfg[k] = !items.includes(k);
34564
34635
  }
34565
34636
  await saveStatuslineConfig(cfg);
@@ -34610,7 +34681,7 @@ async function main(argv) {
34610
34681
  llmModel: config.model,
34611
34682
  createProvider: (pid) => {
34612
34683
  try {
34613
- return buildProviderForId(pid);
34684
+ return buildProviderForId2(pid);
34614
34685
  } catch {
34615
34686
  return void 0;
34616
34687
  }
@@ -35238,16 +35309,18 @@ Restart WrongStack to load or unload plugin code in this session.`;
35238
35309
  ...errorRing.map((e) => ` [${e.ts}] ${e.phase} ${e.code} \u2014 ${e.message}`)
35239
35310
  ];
35240
35311
  const liveCfg = configStore.get();
35312
+ const liveFamily = liveCfg.providers?.[liveCfg.provider]?.family;
35241
35313
  return [
35242
35314
  `${color.bold("WrongStack diag")}`,
35243
35315
  ` provider: ${liveCfg.provider} / ${context.model}`,
35316
+ liveFamily ? ` family: ${liveFamily}` : null,
35244
35317
  ` projectRoot: ${projectRoot}`,
35245
35318
  ` tokens: in ${u.input} out ${u.output} cacheR ${u.cacheRead ?? 0}`,
35246
35319
  ` cost: $${cost.total.toFixed(4)}`,
35247
35320
  ` tools: ${toolRegistry.list().length}`,
35248
35321
  ` mcpServers: ${mcpRegistry.list().length}`,
35249
35322
  ...errSection
35250
- ].join("\n");
35323
+ ].filter((line) => line !== null).join("\n");
35251
35324
  },
35252
35325
  onStats: () => stats.format(),
35253
35326
  generateCommitMessage: async (diff) => {
@@ -35529,12 +35602,26 @@ Restart WrongStack to load or unload plugin code in this session.`;
35529
35602
  if (s.contextAutoCompact !== void 0) {
35530
35603
  autoCompactor?.setEnabled(s.contextAutoCompact);
35531
35604
  }
35532
- if (s.restrictFsToRoot !== void 0) {
35533
- context.allowOutsideProjectRoot = !s.restrictFsToRoot;
35534
- config = patchConfig(config, {
35535
- features: { ...config.features, allowOutsideProjectRoot: !s.restrictFsToRoot },
35536
- tools: { ...config.tools, restrictToProjectRoot: s.restrictFsToRoot }
35537
- });
35605
+ if (s.maxConcurrent !== void 0 && s.maxConcurrent > 0) {
35606
+ multiAgentHost.setMaxConcurrent(s.maxConcurrent);
35607
+ events.emit("concurrency.changed", { n: s.maxConcurrent });
35608
+ config = patchConfig(config, { maxConcurrent: s.maxConcurrent });
35609
+ }
35610
+ if (s.restrictFsToRoot !== void 0 || s.allowOutsideProjectRoot !== void 0) {
35611
+ const fsAccess = deriveFsAccessPair(s);
35612
+ if (fsAccess) {
35613
+ context.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
35614
+ config = patchConfig(config, {
35615
+ features: {
35616
+ ...config.features,
35617
+ allowOutsideProjectRoot: fsAccess.allowOutsideProjectRoot
35618
+ },
35619
+ tools: {
35620
+ ...config.tools,
35621
+ restrictToProjectRoot: fsAccess.restrictToProjectRoot
35622
+ }
35623
+ });
35624
+ }
35538
35625
  }
35539
35626
  } catch {
35540
35627
  }