@wrongstack/cli 0.273.0 → 0.274.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
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, CODEX_MODELS, 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
+ import { access, readFile } from 'fs/promises';
4
5
  import { decryptConfigSecrets, encryptConfigSecrets, DefaultSecretVault as DefaultSecretVault$1, isSecretField } from '@wrongstack/core/security';
5
6
  import * as path4 from 'path';
6
7
  import { join } from 'path';
@@ -12,14 +13,14 @@ import * as os7 from 'os';
12
13
  import os7__default from 'os';
13
14
  import { WebSocketServer, WebSocket } from 'ws';
14
15
  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';
16
+ import { envFlag, findFreePort, AutoPhaseWebSocketHandler, WorktreeWebSocketHandler, SpecsWebSocketHandler, SddBoardWebSocketHandler, SddWizardWebSocketHandler, buildSddWizardDeps, resolveAuthToken, buildWebUIAccessUrl, 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
17
  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';
18
+ import { setOAuthTokenPersister, capabilitiesFor, makeProviderFromConfig, buildProviderFactoriesFromRegistry, withCatalogCapabilities, refreshCopilotToken, copilotBaseUrlFromToken } from '@wrongstack/providers';
19
+ 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
20
  import { DefaultSessionStore } from '@wrongstack/core/storage';
20
21
  import { probeLocalLlm } from '@wrongstack/runtime/probe';
21
- import * as fs3 from 'fs';
22
- import { watch, writeFileSync, existsSync, realpathSync, readFileSync } from 'fs';
22
+ import * as fs37 from 'fs';
23
+ import { watch, writeFileSync, existsSync, realpathSync } from 'fs';
23
24
  import { SkillInstaller } from '@wrongstack/core/skills';
24
25
  import { spawn, execFile, execFileSync } from 'child_process';
25
26
  import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
@@ -166,7 +167,7 @@ function unknownSubcommand(cmd, valid, name) {
166
167
  const list = valid.join(", ");
167
168
  return name ? `Unknown subcommand "${cmd}" for /${name}. Valid: ${list}.` : `Unknown subcommand "${cmd}". Valid: ${list}.`;
168
169
  }
169
- async function pathExists(file) {
170
+ async function pathExists2(file) {
170
171
  try {
171
172
  await fs4.access(file);
172
173
  return true;
@@ -221,10 +222,10 @@ async function detectPackageManager2(root, declared) {
221
222
  const name = declared.split("@")[0];
222
223
  if (name) return name;
223
224
  }
224
- if (await pathExists(path4.join(root, "pnpm-lock.yaml"))) return "pnpm";
225
- if (await pathExists(path4.join(root, "bun.lockb"))) return "bun";
226
- if (await pathExists(path4.join(root, "bun.lock"))) return "bun";
227
- if (await pathExists(path4.join(root, "yarn.lock"))) return "yarn";
225
+ if (await pathExists2(path4.join(root, "pnpm-lock.yaml"))) return "pnpm";
226
+ if (await pathExists2(path4.join(root, "bun.lockb"))) return "bun";
227
+ if (await pathExists2(path4.join(root, "bun.lock"))) return "bun";
228
+ if (await pathExists2(path4.join(root, "yarn.lock"))) return "yarn";
228
229
  return "npm";
229
230
  }
230
231
  function hasUsableScript(scripts, name) {
@@ -299,14 +300,14 @@ async function detectProjectFacts(root) {
299
300
  } catch {
300
301
  }
301
302
  try {
302
- if (!await pathExists(path4.join(root, "pyproject.toml"))) throw new Error("not python");
303
+ if (!await pathExists2(path4.join(root, "pyproject.toml"))) throw new Error("not python");
303
304
  facts.test ??= "pytest";
304
305
  facts.lint ??= "ruff check .";
305
306
  facts.hints.push("pyproject.toml");
306
307
  } catch {
307
308
  }
308
309
  try {
309
- if (!await pathExists(path4.join(root, "go.mod"))) throw new Error("not go");
310
+ if (!await pathExists2(path4.join(root, "go.mod"))) throw new Error("not go");
310
311
  facts.build ??= "go build ./...";
311
312
  facts.test ??= "go test ./...";
312
313
  facts.run ??= "go run .";
@@ -314,7 +315,7 @@ async function detectProjectFacts(root) {
314
315
  } catch {
315
316
  }
316
317
  try {
317
- if (!await pathExists(path4.join(root, "Cargo.toml"))) throw new Error("not rust");
318
+ if (!await pathExists2(path4.join(root, "Cargo.toml"))) throw new Error("not rust");
318
319
  facts.build ??= "cargo build";
319
320
  facts.test ??= "cargo test";
320
321
  facts.lint ??= "cargo clippy";
@@ -342,23 +343,23 @@ async function detectProjectFacts(root) {
342
343
  } catch {
343
344
  }
344
345
  try {
345
- if (!await pathExists(path4.join(root, "pom.xml"))) throw new Error("not maven");
346
+ if (!await pathExists2(path4.join(root, "pom.xml"))) throw new Error("not maven");
346
347
  facts.build ??= "mvn package";
347
348
  facts.test ??= "mvn test";
348
349
  facts.hints.push("pom.xml");
349
350
  } catch {
350
351
  }
351
352
  try {
352
- const hasGradle = await pathExists(path4.join(root, "build.gradle")) || await pathExists(path4.join(root, "build.gradle.kts"));
353
+ const hasGradle = await pathExists2(path4.join(root, "build.gradle")) || await pathExists2(path4.join(root, "build.gradle.kts"));
353
354
  if (!hasGradle) throw new Error("not gradle");
354
- const g = await pathExists(path4.join(root, "gradlew")) ? "./gradlew" : "gradle";
355
+ const g = await pathExists2(path4.join(root, "gradlew")) ? "./gradlew" : "gradle";
355
356
  facts.build ??= `${g} build`;
356
357
  facts.test ??= `${g} test`;
357
358
  facts.hints.push("Gradle");
358
359
  } catch {
359
360
  }
360
361
  try {
361
- const hasDotnet = await pathExists(path4.join(root, "global.json")) || await hasRootFileWithSuffix(root, [".csproj", ".fsproj", ".sln"]);
362
+ const hasDotnet = await pathExists2(path4.join(root, "global.json")) || await hasRootFileWithSuffix(root, [".csproj", ".fsproj", ".sln"]);
362
363
  if (!hasDotnet) throw new Error("not dotnet");
363
364
  facts.build ??= "dotnet build";
364
365
  facts.test ??= "dotnet test";
@@ -367,7 +368,7 @@ async function detectProjectFacts(root) {
367
368
  } catch {
368
369
  }
369
370
  try {
370
- if (!await pathExists(path4.join(root, "mix.exs"))) throw new Error("not elixir");
371
+ if (!await pathExists2(path4.join(root, "mix.exs"))) throw new Error("not elixir");
371
372
  facts.build ??= "mix compile";
372
373
  facts.test ??= "mix test";
373
374
  facts.lint ??= "mix format --check-formatted";
@@ -376,14 +377,14 @@ async function detectProjectFacts(root) {
376
377
  } catch {
377
378
  }
378
379
  try {
379
- if (!await pathExists(path4.join(root, "pubspec.yaml"))) throw new Error("not dart");
380
+ if (!await pathExists2(path4.join(root, "pubspec.yaml"))) throw new Error("not dart");
380
381
  facts.test ??= "dart test";
381
382
  facts.lint ??= "dart analyze";
382
383
  facts.hints.push("pubspec.yaml");
383
384
  } catch {
384
385
  }
385
386
  try {
386
- const hasDeno = await pathExists(path4.join(root, "deno.json")) || await pathExists(path4.join(root, "deno.jsonc"));
387
+ const hasDeno = await pathExists2(path4.join(root, "deno.json")) || await pathExists2(path4.join(root, "deno.jsonc"));
387
388
  if (!hasDeno) throw new Error("not deno");
388
389
  facts.test ??= "deno test";
389
390
  facts.lint ??= "deno lint";
@@ -391,7 +392,7 @@ async function detectProjectFacts(root) {
391
392
  } catch {
392
393
  }
393
394
  try {
394
- if (!await pathExists(path4.join(root, "Package.swift"))) throw new Error("not swift");
395
+ if (!await pathExists2(path4.join(root, "Package.swift"))) throw new Error("not swift");
395
396
  facts.build ??= "swift build";
396
397
  facts.test ??= "swift test";
397
398
  facts.run ??= "swift run";
@@ -399,20 +400,20 @@ async function detectProjectFacts(root) {
399
400
  } catch {
400
401
  }
401
402
  try {
402
- if (!await pathExists(path4.join(root, "Gemfile"))) throw new Error("not ruby");
403
- if (await pathExists(path4.join(root, "Rakefile"))) facts.test ??= "bundle exec rake test";
403
+ if (!await pathExists2(path4.join(root, "Gemfile"))) throw new Error("not ruby");
404
+ if (await pathExists2(path4.join(root, "Rakefile"))) facts.test ??= "bundle exec rake test";
404
405
  facts.hints.push("Gemfile");
405
406
  } catch {
406
407
  }
407
408
  try {
408
- if (!await pathExists(path4.join(root, "CMakeLists.txt"))) throw new Error("not cmake");
409
+ if (!await pathExists2(path4.join(root, "CMakeLists.txt"))) throw new Error("not cmake");
409
410
  facts.build ??= "cmake -B build && cmake --build build";
410
411
  facts.test ??= "ctest --test-dir build";
411
412
  facts.hints.push("CMakeLists.txt");
412
413
  } catch {
413
414
  }
414
415
  try {
415
- const hasPip = await pathExists(path4.join(root, "requirements.txt")) || await pathExists(path4.join(root, "setup.py")) || await pathExists(path4.join(root, "setup.cfg"));
416
+ const hasPip = await pathExists2(path4.join(root, "requirements.txt")) || await pathExists2(path4.join(root, "setup.py")) || await pathExists2(path4.join(root, "setup.cfg"));
416
417
  if (!hasPip) throw new Error("not pip");
417
418
  facts.test ??= "pytest";
418
419
  facts.hints.push("requirements.txt");
@@ -5521,7 +5522,11 @@ function registerWebuiInstance(p, deps = {}) {
5521
5522
  projectRoot: p.projectRoot,
5522
5523
  projectName: path4.basename(p.projectRoot) || p.projectRoot,
5523
5524
  startedAt: p.startedAt,
5524
- url: `http://${p.host}:${p.httpPort}`
5525
+ url: buildWebUIAccessUrl({
5526
+ host: p.host,
5527
+ port: p.httpPort,
5528
+ publicUrl: p.publicUrl
5529
+ })
5525
5530
  },
5526
5531
  p.registryBaseDir
5527
5532
  ).catch(() => {
@@ -5530,7 +5535,12 @@ function registerWebuiInstance(p, deps = {}) {
5530
5535
  function announceWebuiReady(p) {
5531
5536
  const log = p.log ?? ((m) => console.log(m));
5532
5537
  const launch = p.openBrowserFn ?? openBrowser$1;
5533
- const openUrl = p.wsToken ? `http://${p.host}:${p.httpPort}?token=${encodeURIComponent(p.wsToken)}` : `http://${p.host}:${p.httpPort}`;
5538
+ const openUrl = buildWebUIAccessUrl({
5539
+ host: p.host,
5540
+ port: p.httpPort,
5541
+ token: p.wsToken,
5542
+ publicUrl: p.publicUrl
5543
+ });
5534
5544
  p.server.on("listening", () => {
5535
5545
  log(
5536
5546
  `
@@ -5667,7 +5677,9 @@ function startStaticServe(opts, deps = {}) {
5667
5677
  wsPort: opts.wsPort,
5668
5678
  globalRoot: opts.globalRoot,
5669
5679
  onFleetPing: opts.onFleetPing,
5670
- apiToken: opts.apiToken
5680
+ publicWsUrl: opts.publicWsUrl,
5681
+ apiToken: opts.apiToken,
5682
+ requireToken: opts.requireToken
5671
5683
  });
5672
5684
  server.listen(opts.httpPort, opts.host);
5673
5685
  return { server, port: opts.httpPort };
@@ -7298,7 +7310,10 @@ __export(webui_server_exports, {
7298
7310
  runWebUI: () => runWebUI
7299
7311
  });
7300
7312
  async function runWebUI(opts) {
7301
- const host = "127.0.0.1";
7313
+ const host = opts.host ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
7314
+ const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
7315
+ const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
7316
+ const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
7302
7317
  const requestedWsPort = opts.port ?? 3457;
7303
7318
  const requestedHttpPort = opts.httpPort ?? 3456;
7304
7319
  const strictPort = process.env["WEBUI_STRICT_PORT"] === "1" || process.env["WEBUI_STRICT_PORT"] === "true";
@@ -7435,7 +7450,11 @@ async function runWebUI(opts) {
7435
7450
  });
7436
7451
  }
7437
7452
  });
7438
- const mailbox = new GlobalMailbox(projectDir, opts.events, () => webuiHqConnection?.getPublisher());
7453
+ const mailbox = new GlobalMailbox(
7454
+ projectDir,
7455
+ opts.events,
7456
+ () => webuiHqConnection?.getPublisher()
7457
+ );
7439
7458
  webuiClientId = `webui@${crypto2.randomUUID().slice(0, 8)}`;
7440
7459
  const projectName = opts.projectRoot ? path4.basename(opts.projectRoot) : "unknown";
7441
7460
  await mailbox.registerClient({
@@ -7468,7 +7487,21 @@ async function runWebUI(opts) {
7468
7487
  webuiHqConnection = void 0;
7469
7488
  };
7470
7489
  registerWebuiClient();
7471
- const wsToken = generateAuthToken();
7490
+ const wsToken = resolveAuthToken(opts.accessToken);
7491
+ const publicHostnames = [publicUrl, publicWsUrl].map((value) => {
7492
+ if (!value) return void 0;
7493
+ try {
7494
+ return new URL(value).hostname;
7495
+ } catch {
7496
+ return void 0;
7497
+ }
7498
+ }).filter((value) => Boolean(value));
7499
+ const accessUrl = buildWebUIAccessUrl({
7500
+ host,
7501
+ port: httpPort,
7502
+ token: wsToken,
7503
+ publicUrl
7504
+ });
7472
7505
  const wss = new WebSocketServer({ port, host, maxPayload: 1 * 1024 * 1024 });
7473
7506
  console.log(`[WebUI] WebSocket server starting on ws://${host}:${port}`);
7474
7507
  let fleetBroadcastCli = null;
@@ -7480,7 +7513,9 @@ async function runWebUI(opts) {
7480
7513
  onFleetPing: () => {
7481
7514
  void fleetBroadcastCli?.();
7482
7515
  },
7483
- apiToken: wsToken
7516
+ publicWsUrl,
7517
+ apiToken: wsToken,
7518
+ requireToken
7484
7519
  });
7485
7520
  if (httpServer) {
7486
7521
  announceWebuiReady({
@@ -7489,7 +7524,8 @@ async function runWebUI(opts) {
7489
7524
  httpPort,
7490
7525
  wsPort,
7491
7526
  open: !!opts.open,
7492
- wsToken
7527
+ wsToken,
7528
+ publicUrl
7493
7529
  });
7494
7530
  } else {
7495
7531
  console.warn(
@@ -7503,6 +7539,7 @@ async function runWebUI(opts) {
7503
7539
  host,
7504
7540
  httpPort,
7505
7541
  wsPort,
7542
+ publicUrl,
7506
7543
  projectRoot: opts.projectRoot,
7507
7544
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7508
7545
  registryBaseDir
@@ -8270,11 +8307,11 @@ ${text}` : text;
8270
8307
  broadcast,
8271
8308
  log: (m) => console.log(m)
8272
8309
  };
8273
- return new Promise((resolve13) => {
8310
+ const stopped = new Promise((resolve13) => {
8274
8311
  wss.on("listening", () => {
8275
8312
  console.log(`[WebUI] WebSocket server running on ws://${host}:${port}`);
8276
8313
  setupEvents();
8277
- opts.onListening?.({ httpPort, wsPort, host });
8314
+ opts.onListening?.({ httpPort, wsPort, host, url: accessUrl });
8278
8315
  const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
8279
8316
  if (globalRoot) {
8280
8317
  const broadcastSessions = async () => {
@@ -8344,7 +8381,10 @@ ${text}` : text;
8344
8381
  remoteAddress: req2.socket.remoteAddress,
8345
8382
  cookieHeader: req2.headers.cookie,
8346
8383
  wsHost: host,
8347
- expectedToken: wsToken
8384
+ expectedToken: wsToken,
8385
+ requireToken,
8386
+ allowedHostnames: publicHostnames,
8387
+ allowBrowserUrlToken: Boolean(publicWsUrl)
8348
8388
  });
8349
8389
  if (!ok) {
8350
8390
  ws.close(4003, "Forbidden");
@@ -8437,572 +8477,312 @@ ${text}` : text;
8437
8477
  });
8438
8478
  registerWebuiSignalHandlers(signalShutdown);
8439
8479
  });
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
- )
8480
+ const noop = () => {
8481
+ };
8482
+ const projectRootFor = () => opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
8483
+ const wsRoutes = {
8484
+ // ── Core connection ──
8485
+ user_message: (msg, ws) => handleUserMessage(
8486
+ connectionCtx,
8487
+ ws,
8488
+ msg.payload.content
8489
+ ),
8490
+ abort: (_msg, ws) => handleAbort(connectionCtx, ws),
8491
+ ping: (_msg, ws) => handlePing(connectionCtx, ws),
8492
+ "tool.confirm_result": (msg, _ws) => {
8493
+ const { id, decision } = msg.payload;
8494
+ handleToolConfirmResult(connectionCtx, id, decision);
8495
+ },
8496
+ "webui.shutdown": () => {
8497
+ console.log("[WebUI] Shutdown requested from client");
8498
+ shutdown();
8499
+ },
8500
+ // ── Providers / keys ──
8501
+ "providers.list": (_msg, ws) => handleProvidersList(wsHandlerCtx, ws),
8502
+ "provider.models": (msg, ws) => handleProviderModels(
8503
+ wsHandlerCtx,
8504
+ ws,
8505
+ msg.payload.providerId
8506
+ ),
8507
+ "providers.saved": (_msg, ws) => handleProvidersSaved(wsHandlerCtx, ws),
8508
+ "key.add": (msg, ws) => {
8509
+ const m = msg;
8510
+ handleKeyUpsert(wsHandlerCtx, ws, m.payload.providerId, m.payload.label, m.payload.apiKey);
8511
+ },
8512
+ "key.update": (msg, ws) => {
8513
+ const m = msg;
8514
+ handleKeyUpsert(wsHandlerCtx, ws, m.payload.providerId, m.payload.label, m.payload.apiKey);
8515
+ },
8516
+ "key.delete": (msg, ws) => {
8517
+ const m = msg;
8518
+ handleKeyDelete(wsHandlerCtx, ws, m.payload.providerId, m.payload.label);
8519
+ },
8520
+ "key.set_active": (msg, ws) => {
8521
+ const m = msg;
8522
+ handleKeySetActive(wsHandlerCtx, ws, m.payload.providerId, m.payload.label);
8523
+ },
8524
+ "provider.add": (msg, ws) => handleProviderAdd(
8525
+ wsHandlerCtx,
8526
+ ws,
8527
+ msg.payload
8528
+ ),
8529
+ "provider.remove": (msg, ws) => handleProviderRemove(
8530
+ wsHandlerCtx,
8531
+ ws,
8532
+ msg.payload.providerId
8533
+ ),
8534
+ "provider.clear_models": (msg, ws) => handleProviderClearModels(
8535
+ wsHandlerCtx,
8536
+ ws,
8537
+ msg.payload.providerId
8538
+ ),
8539
+ "provider.undo_clear": (msg, ws) => {
8540
+ const m = msg;
8541
+ handleProviderUndoClear(wsHandlerCtx, ws, m.payload.providerId, m.payload.previousModels);
8542
+ },
8543
+ "provider.update": (msg, ws) => handleProviderUpdate(
8544
+ wsHandlerCtx,
8545
+ ws,
8546
+ msg.payload
8547
+ ),
8548
+ "provider.probe": (msg, ws) => {
8549
+ const m = msg;
8550
+ handleProviderProbe(wsHandlerCtx, ws, m.payload.providerId, m.payload.timeoutMs);
8551
+ },
8552
+ // ── Todos / goals / plans / tasks ──
8553
+ "todos.get": (_msg, ws) => handleTodosGet(worklistCtx, ws),
8554
+ "todos.clear": (_msg, ws) => handleTodosClear(worklistCtx, ws),
8555
+ "todos.remove": (msg, ws) => handleTodosRemove(
8556
+ worklistCtx,
8557
+ ws,
8558
+ msg.payload
8559
+ ),
8560
+ "todo.update": (msg, ws) => handleTodoUpdate(
8561
+ worklistCtx,
8562
+ ws,
8563
+ msg.payload
8564
+ ),
8565
+ "goal.get": (_msg, ws) => handleGoalGet(sessionsCtx),
8566
+ "plan.get": (_msg, ws) => handlePlanGet(worklistCtx, ws),
8567
+ "plan.template_use": (msg, ws) => handlePlanTemplateUse(
8568
+ worklistCtx,
8569
+ ws,
8570
+ msg.payload.template
8571
+ ),
8572
+ "plan.item.update": (msg, ws) => handlePlanItemUpdate(
8573
+ worklistCtx,
8574
+ ws,
8575
+ msg.payload
8576
+ ),
8577
+ "tasks.get": (_msg, ws) => handleTasksGet(worklistCtx, ws),
8578
+ "task.update": (msg, ws) => handleTaskUpdate(
8579
+ worklistCtx,
8580
+ ws,
8581
+ msg.payload
8582
+ ),
8583
+ // ── Sessions ──
8584
+ "sessions.list": (msg, ws) => handleSessionsList(
8585
+ sessionsCtx,
8586
+ ws,
8587
+ msg.payload?.limit ?? 50
8588
+ ),
8589
+ "session.new": (_msg, ws) => handleSessionNew(sessionsCtx),
8590
+ "session.delete": (msg, ws) => handleSessionDelete(sessionsCtx, ws, msg.payload.id),
8591
+ "session.save": (_msg, ws) => handleSessionSave(sessionsCtx, ws),
8592
+ "session.resume": (msg, ws) => handleSessionResume(sessionsCtx, ws, msg.payload.id),
8593
+ "session.checkpoints": (_msg, ws) => handleSessionCheckpoints(sessionsCtx, ws),
8594
+ "session.rewind": (msg, ws) => handleSessionRewind(
8595
+ sessionsCtx,
8596
+ ws,
8597
+ msg.payload.checkpointIndex
8598
+ ),
8599
+ // ── Context ──
8600
+ "context.clear": (_msg, ws) => handleContextClear(contextHandlerCtx, ws),
8601
+ "context.debug": (_msg, ws) => handleContextDebug(contextHandlerCtx, ws),
8602
+ "context.compact": (msg, ws) => handleContextCompact(
8603
+ contextHandlerCtx,
8604
+ ws,
8605
+ !!msg.payload?.aggressive
8606
+ ),
8607
+ "context.repair": (_msg, ws) => handleContextRepair(contextHandlerCtx, ws),
8608
+ "context.modes.list": (_msg, ws) => handleContextModesList(contextHandlerCtx, ws),
8609
+ "context.mode.switch": (msg, ws) => handleContextModeSwitch(
8610
+ contextHandlerCtx,
8611
+ ws,
8612
+ msg.payload.id
8613
+ ),
8614
+ "context.mode.create": (msg, ws) => handleContextModeCreate(
8615
+ contextHandlerCtx,
8616
+ ws,
8617
+ msg.payload
8618
+ ),
8619
+ "context.mode.update": (msg, ws) => handleContextModeUpdate(
8620
+ contextHandlerCtx,
8621
+ ws,
8622
+ msg.payload
8623
+ ),
8624
+ "context.mode.delete": (msg, ws) => handleContextModeDelete(
8625
+ contextHandlerCtx,
8626
+ ws,
8627
+ msg.payload.id
8628
+ ),
8629
+ // ── Agent config: modes / models ──
8630
+ "modes.list": (_msg, ws) => handleModesList(agentConfigCtx, ws),
8631
+ "mode.switch": (msg, ws) => handleModeSwitch(agentConfigCtx, ws, msg.payload.id),
8632
+ "model.switch": (msg, ws) => handleModelSwitch(
8633
+ agentConfigCtx,
8634
+ ws,
8635
+ msg.payload
8636
+ ),
8637
+ "model.refine": (msg, ws) => handleModelRefine(agentConfigCtx, ws, msg.payload.text),
8638
+ // ── Process management ──
8639
+ "process.list": (_msg, ws) => handleProcessList(wsCommon, ws),
8640
+ "process.kill": (msg, ws) => handleProcessKill(wsCommon, ws, msg.payload.pid),
8641
+ "process.killAll": (_msg, ws) => handleProcessKillAll(wsCommon, ws),
8642
+ // ── Diagnostics / introspection ──
8643
+ "diag.get": (_msg, ws) => handleDiagGet(introspectionCtx, ws),
8644
+ "stats.get": (_msg, ws) => handleStatsGet(introspectionCtx, ws),
8645
+ "tools.list": (_msg, ws) => handleToolsList(introspectionCtx, ws),
8646
+ // ── Autonomy ──
8647
+ "autonomy.switch": (msg, ws) => handleAutonomySwitch(prefsCtx, ws, msg.payload.mode),
8648
+ // ── Brain ──
8649
+ "brain.status": (_msg, ws) => handleBrainStatus(brainCtx, ws),
8650
+ "brain.risk": (msg, ws) => handleBrainRisk(brainCtx, ws, msg.payload?.level ?? ""),
8651
+ "brain.ask": (msg, ws) => handleBrainAsk(brainCtx, ws, msg.payload?.question),
8652
+ // ── Preferences ──
8653
+ "prefs.get": (_msg, ws) => handlePrefsGet(prefsCtx, ws),
8654
+ "prefs.update": (msg, ws) => handlePrefsUpdate(prefsCtx, ws, msg.payload),
8655
+ // ── File operations (delegated to shared file-handlers.ts) ──
8656
+ "files.list": (msg, ws) => handleFilesList(ws, msg, projectRootFor()),
8657
+ "files.tree": (msg, ws) => handleFilesTree(ws, msg, projectRootFor()),
8658
+ "files.read": (msg, ws) => handleFilesRead(ws, msg, projectRootFor()),
8659
+ "files.write": (msg, ws) => handleFilesWrite(ws, msg, projectRootFor()),
8660
+ "completion.request": (msg, ws) => handleCompletionRequest(ws, msg, {
8661
+ projectRoot: projectRootFor(),
8662
+ provider: opts.agent.ctx.provider,
8663
+ model: opts.agent.ctx.model,
8664
+ indexDir: typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
8665
+ lspCompletion: createToolLspCompletionSource(
8666
+ opts.agent.ctx.tools.find((tool) => tool.name === "lsp_completion"),
8667
+ opts.agent.ctx
8668
+ )
8669
+ }),
8670
+ // ── Memory (guarded — opts.memoryStore may be undefined) ──
8671
+ "memory.list": (_msg, ws) => {
8672
+ if (!opts.memoryStore) {
8673
+ send(ws, {
8674
+ type: "memory.list",
8675
+ payload: { text: "", error: "Memory store not available" }
8645
8676
  });
8677
+ return;
8646
8678
  }
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;
8679
+ return handleMemoryList(ws, opts.memoryStore);
8680
+ },
8681
+ "memory.remember": (msg, ws) => {
8682
+ if (!opts.memoryStore) {
8683
+ sendResult10(ws, false, "Memory store not available");
8684
+ return;
8982
8685
  }
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;
8686
+ return handleMemoryRemember(ws, msg, opts.memoryStore);
8687
+ },
8688
+ "memory.forget": (msg, ws) => {
8689
+ if (!opts.memoryStore) {
8690
+ sendResult10(ws, false, "Memory store not available");
8691
+ return;
9005
8692
  }
8693
+ return handleMemoryForget(ws, msg, opts.memoryStore);
8694
+ },
8695
+ // ── MCP operations (shared handlers from @wrongstack/webui/server) ──
8696
+ "mcp.list": (msg, ws) => handleMcpList(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8697
+ "mcp.add": (msg, ws) => handleMcpAdd(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8698
+ "mcp.remove": (msg, ws) => handleMcpRemove(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8699
+ "mcp.update": (msg, ws) => handleMcpUpdate(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8700
+ "mcp.wake": (msg, ws) => handleMcpWake(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8701
+ "mcp.sleep": (msg, ws) => handleMcpSleep(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8702
+ "mcp.discover": (msg, ws) => handleMcpDiscover(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8703
+ "mcp.enable": (msg, ws) => handleMcpEnable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8704
+ "mcp.disable": (msg, ws) => handleMcpDisable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8705
+ "mcp.restart": (msg, ws) => handleMcpRestart(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry),
8706
+ // ── Skills ──
8707
+ "skills.list": (_msg, ws) => handleSkillsList(introspectionCtx, ws),
8708
+ "skills.content": (msg, ws) => handleSkillsContent(ws, skillsCtx, msg),
8709
+ "skills.install": (msg, ws) => handleSkillsInstall(ws, skillsCtx, msg),
8710
+ "skills.uninstall": (msg, ws) => handleSkillsUninstall(ws, skillsCtx, msg),
8711
+ "skills.update": (msg, ws) => handleSkillsUpdate(ws, skillsCtx, msg),
8712
+ "skills.create": (msg, ws) => handleSkillsCreate(ws, skillsCtx, msg),
8713
+ "skills.edit": (msg, ws) => handleSkillsEdit(ws, skillsCtx, msg),
8714
+ "skills.export": (_msg, ws) => handleSkillsExport(ws, skillsCtx),
8715
+ // ── Projects / working dir ──
8716
+ "projects.list": (_msg, ws) => handleProjectsList(projectsCtx, ws),
8717
+ "projects.select": (msg, ws) => handleProjectsSelect(
8718
+ projectsCtx,
8719
+ ws,
8720
+ msg.payload
8721
+ ),
8722
+ "projects.add": (msg, ws) => handleProjectsAdd(
8723
+ projectsCtx,
8724
+ ws,
8725
+ msg.payload
8726
+ ),
8727
+ "working_dir.set": (msg, ws) => handleWorkingDirSet(projectsCtx, ws, msg.payload.path),
8728
+ // ── Git ──
8729
+ "git.changes": (_msg, ws) => handleGitChanges(ws, projectRootFor()),
8730
+ "git.diff": (msg, ws) => handleGitDiff(
8731
+ ws,
8732
+ projectRootFor(),
8733
+ msg.payload?.path ?? ""
8734
+ ),
8735
+ "git.info": (_msg, ws) => handleGitInfo(ws, projectRootFor()),
8736
+ // ── Shell ──
8737
+ "shell.open": async (msg, ws) => {
8738
+ const result = await handleShellOpen(
8739
+ msg.payload,
8740
+ consoleLogger
8741
+ );
8742
+ sendResult10(ws, result.success, result.message);
8743
+ },
8744
+ // ── Mailbox ──
8745
+ "mailbox.messages": (msg, ws) => handleMailboxMessages(mailboxCtx, msg, ws),
8746
+ "mailbox.agents": (msg, ws) => handleMailboxAgents(mailboxCtx, msg, ws),
8747
+ "mailbox.clear": (_msg, ws) => handleMailboxClear(mailboxCtx, ws),
8748
+ "mailbox.purge": (msg, ws) => handleMailboxPurge(mailboxCtx, msg, ws),
8749
+ // ── Silent no-ops (standalone server wires real handlers) ──
8750
+ "collab.join": noop,
8751
+ "collab.leave": noop,
8752
+ "collab.annotate": noop,
8753
+ "collab.resolve": noop,
8754
+ "collab.request_pause": noop,
8755
+ "collab.resume": noop,
8756
+ "collab.grant_control": noop,
8757
+ "collab.inject_tool": noop,
8758
+ "terminal.create": noop,
8759
+ "terminal.input": noop,
8760
+ "terminal.resize": noop,
8761
+ "terminal.close": noop
8762
+ };
8763
+ async function handleMessage(ws, _client, msg) {
8764
+ const handler = wsRoutes[msg.type];
8765
+ if (handler) {
8766
+ await handler(msg, ws);
8767
+ return;
8768
+ }
8769
+ const msgType = msg.type;
8770
+ if (msgType.startsWith("autophase.")) {
8771
+ await autoPhaseHandler.handleMessage(
8772
+ msg
8773
+ );
8774
+ } else if (msgType.startsWith("specs.")) {
8775
+ await specsHandler.handleMessage(msg);
8776
+ } else if (msgType.startsWith("sdd.board.")) {
8777
+ await sddBoardHandler.handleMessage(
8778
+ msg
8779
+ );
8780
+ } else if (msgType.startsWith("sdd.spec.") || msgType.startsWith("sdd.run.")) {
8781
+ await sddWizardHandler?.handleMessage(
8782
+ msg
8783
+ );
8784
+ } else {
8785
+ console.debug(`[WebUI] Unhandled message type: ${msgType}`);
9006
8786
  }
9007
8787
  }
9008
8788
  function send(ws, msg) {
@@ -9032,6 +8812,7 @@ ${text}` : text;
9032
8812
  function sendResult10(ws, success, message) {
9033
8813
  send(ws, { type: "key.operation_result", payload: { success, message } });
9034
8814
  }
8815
+ return stopped;
9035
8816
  }
9036
8817
  var init_webui_server = __esm({
9037
8818
  "src/webui-server.ts"() {
@@ -9049,6 +8830,14 @@ var init_webui_server = __esm({
9049
8830
 
9050
8831
  // src/cli-main.ts
9051
8832
  init_provider_config_utils();
8833
+ async function pathExists(p) {
8834
+ try {
8835
+ await access(p);
8836
+ return true;
8837
+ } catch {
8838
+ return false;
8839
+ }
8840
+ }
9052
8841
  var DEFAULT_TASK_CONCURRENCY = 2;
9053
8842
  function resolveTaskConcurrency() {
9054
8843
  const raw = Number.parseInt(process.env["WRONGSTACK_AUTOPHASE_TASK_CONCURRENCY"] ?? "", 10);
@@ -9073,8 +8862,13 @@ function gitText(args, cwd) {
9073
8862
  return;
9074
8863
  }
9075
8864
  const chunks = [];
8865
+ let total = 0;
9076
8866
  const emit = (c) => {
9077
- if (chunks.join("").length < MAX_CMD_OUTPUT) chunks.push(c.toString());
8867
+ if (total < MAX_CMD_OUTPUT) {
8868
+ const s = c.toString();
8869
+ chunks.push(s);
8870
+ total += s.length;
8871
+ }
9078
8872
  };
9079
8873
  child.stdout?.on("data", emit);
9080
8874
  child.stderr?.on("data", emit);
@@ -9086,7 +8880,20 @@ async function isGitRepo(cwd) {
9086
8880
  const { code, out } = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
9087
8881
  return code === 0 && out.trim() === "true";
9088
8882
  }
9089
- var AUTOPHASE_SAFE_CMDS = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun"]);
8883
+ var AUTOPHASE_BASE_SAFE_CMDS = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun"]);
8884
+ var autophaseAllowed = new Set(AUTOPHASE_BASE_SAFE_CMDS);
8885
+ function configureAutophasePolicy(opts = {}) {
8886
+ const next = new Set(AUTOPHASE_BASE_SAFE_CMDS);
8887
+ for (const c of opts.allow ?? []) {
8888
+ const n = c.trim();
8889
+ if (n) next.add(n);
8890
+ }
8891
+ for (const c of opts.deny ?? []) next.delete(c.trim());
8892
+ autophaseAllowed = next;
8893
+ }
8894
+ function isAutophaseCommandAllowed(cmd) {
8895
+ return autophaseAllowed.has(cmd.trim());
8896
+ }
9090
8897
  var DESTRUCTIVE_PATTERNS = [
9091
8898
  /\brm\s+-rf\s+\//,
9092
8899
  /\bdangerously\s+(?:force|reset|--hard)\b/,
@@ -9100,10 +8907,10 @@ var DESTRUCTIVE_PATTERNS = [
9100
8907
  // $(...) subshell
9101
8908
  ];
9102
8909
  function runCmd(cmd, args, cwd, shell = false) {
9103
- if (!AUTOPHASE_SAFE_CMDS.has(cmd)) {
8910
+ if (!isAutophaseCommandAllowed(cmd)) {
9104
8911
  return Promise.resolve({
9105
8912
  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.`
8913
+ 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
8914
  });
9108
8915
  }
9109
8916
  const fullCmd = [cmd, ...args].join(" ");
@@ -9146,15 +8953,21 @@ function runCmd(cmd, args, cwd, shell = false) {
9146
8953
  });
9147
8954
  });
9148
8955
  }
9149
- function detectPackageManager(root) {
9150
- if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
9151
- if (existsSync(join(root, "yarn.lock"))) return "yarn";
9152
- if (existsSync(join(root, "bun.lockb"))) return "bun";
8956
+ async function detectPackageManager(root) {
8957
+ const [pnpm, yarn, bun] = await Promise.all([
8958
+ pathExists(join(root, "pnpm-lock.yaml")),
8959
+ pathExists(join(root, "yarn.lock")),
8960
+ pathExists(join(root, "bun.lockb"))
8961
+ ]);
8962
+ if (pnpm) return "pnpm";
8963
+ if (yarn) return "yarn";
8964
+ if (bun) return "bun";
9153
8965
  return "npm";
9154
8966
  }
9155
- function readScripts(cwd) {
8967
+ async function readScripts(cwd) {
9156
8968
  try {
9157
- const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
8969
+ const raw = await readFile(join(cwd, "package.json"), "utf8");
8970
+ const pkg = JSON.parse(raw);
9158
8971
  return pkg.scripts ?? {};
9159
8972
  } catch {
9160
8973
  return {};
@@ -9230,7 +9043,7 @@ function createAutoPhaseHost(deps) {
9230
9043
  ].join("\n");
9231
9044
  }
9232
9045
  async function runVerify(cwd) {
9233
- if (!existsSync(join(cwd, "node_modules")) && !existsSync(join(deps.projectRoot, "node_modules"))) {
9046
+ if (!await pathExists(join(cwd, "node_modules")) && !await pathExists(join(deps.projectRoot, "node_modules"))) {
9234
9047
  return { ok: true, output: "verify skipped: node_modules not found" };
9235
9048
  }
9236
9049
  const custom = process.env["WRONGSTACK_AUTOPHASE_VERIFY_CMD"]?.trim();
@@ -9239,8 +9052,8 @@ function createAutoPhaseHost(deps) {
9239
9052
  return res.code === 0 ? { ok: true } : { ok: false, output: `[verify] exited ${res.code}
9240
9053
  ${res.out}` };
9241
9054
  }
9242
- const pm = detectPackageManager(deps.projectRoot);
9243
- const scripts = readScripts(cwd);
9055
+ const pm = await detectPackageManager(deps.projectRoot);
9056
+ const scripts = await readScripts(cwd);
9244
9057
  const steps = ["typecheck", "lint"].filter((s) => typeof scripts[s] === "string");
9245
9058
  if (steps.length === 0) return { ok: true, output: "verify skipped: no typecheck/lint script" };
9246
9059
  for (const step of steps) {
@@ -9378,8 +9191,9 @@ ${res.out}` };
9378
9191
  // WRONGSTACK_AUTOPHASE_TASK_CONCURRENCY (1 = strictly sequential).
9379
9192
  maxConcurrentTasks: resolveTaskConcurrency()
9380
9193
  });
9381
- const onUntyped = deps.events.on;
9382
- const offUntyped = deps.events.off;
9194
+ const busOn = deps.events;
9195
+ const onUntyped = (event, handler) => busOn.on(event, handler);
9196
+ const offUntyped = (event, handler) => busOn.off(event, handler);
9383
9197
  const finalizeActiveRun = () => {
9384
9198
  if (active?.graph.id !== graph.id) return;
9385
9199
  active.unsubscribe();
@@ -9519,6 +9333,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
9519
9333
  "metrics",
9520
9334
  "webui",
9521
9335
  "open",
9336
+ "webui-require-token",
9337
+ "require-token",
9522
9338
  "no-check",
9523
9339
  "no-models-refresh",
9524
9340
  "director",
@@ -9987,27 +9803,28 @@ function shouldSuppress(flags) {
9987
9803
  return false;
9988
9804
  }
9989
9805
  var wrap = (n) => (n % GROUPS.length + GROUPS.length) % GROUPS.length;
9990
- function pickGroupIndex(opts) {
9806
+ async function pickGroupIndex(opts) {
9991
9807
  if (typeof opts.groupIndex === "number") return wrap(opts.groupIndex);
9992
9808
  if (opts.cursorFile) {
9993
9809
  try {
9994
9810
  let current = 0;
9995
9811
  try {
9996
- const parsed = Number.parseInt(fs3.readFileSync(opts.cursorFile, "utf8").trim(), 10);
9812
+ const raw = await fs4.readFile(opts.cursorFile, "utf8");
9813
+ const parsed = Number.parseInt(raw.trim(), 10);
9997
9814
  if (Number.isFinite(parsed)) current = wrap(parsed);
9998
9815
  } catch {
9999
9816
  }
10000
- fs3.mkdirSync(path4.dirname(opts.cursorFile), { recursive: true });
10001
- fs3.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
9817
+ await fs4.mkdir(path4.dirname(opts.cursorFile), { recursive: true });
9818
+ await fs4.writeFile(opts.cursorFile, String(wrap(current + 1)));
10002
9819
  return current;
10003
9820
  } catch {
10004
9821
  }
10005
9822
  }
10006
9823
  return Math.floor(Math.random() * GROUPS.length);
10007
9824
  }
10008
- function printLaunchHints(renderer, flags, opts = {}) {
9825
+ async function printLaunchHints(renderer, flags, opts = {}) {
10009
9826
  if (shouldSuppress(flags)) return;
10010
- const idx = pickGroupIndex(opts);
9827
+ const idx = await pickGroupIndex(opts);
10011
9828
  const group = GROUPS[idx];
10012
9829
  if (!group) return;
10013
9830
  const lines = [];
@@ -12232,6 +12049,7 @@ var KNOWN_TOP_LEVEL_KEYS = [
12232
12049
  "tools",
12233
12050
  "mcpServers",
12234
12051
  "fallbackModels",
12052
+ "fallbackAuto",
12235
12053
  "hooks",
12236
12054
  "plugins",
12237
12055
  "log",
@@ -12244,8 +12062,12 @@ var KNOWN_TOP_LEVEL_KEYS = [
12244
12062
  "debugStream",
12245
12063
  "configScope",
12246
12064
  "indexing",
12065
+ "circuitBreaker",
12066
+ "adaptiveConcurrency",
12247
12067
  "launch",
12248
12068
  "session",
12069
+ "modelRuntime",
12070
+ "hq",
12249
12071
  "sync",
12250
12072
  "extensions"
12251
12073
  ];
@@ -12345,17 +12167,17 @@ function diagnoseConfig(cfg, plugins = []) {
12345
12167
  delete fixed["maxConcurrent"];
12346
12168
  findings.push({
12347
12169
  path: "maxConcurrent",
12348
- problem: `expected a positive integer, got ${JSON.stringify(v)}`,
12170
+ problem: `expected a non-negative integer, got ${JSON.stringify(v)}`,
12349
12171
  severity: "error",
12350
12172
  fix: "removed (built-in default applies)"
12351
12173
  });
12352
12174
  } else {
12353
- const clamped = Math.max(1, Math.floor(n));
12175
+ const clamped = Math.max(0, Math.floor(n));
12354
12176
  if (clamped !== v) {
12355
12177
  fixed["maxConcurrent"] = clamped;
12356
12178
  findings.push({
12357
12179
  path: "maxConcurrent",
12358
- problem: `expected a positive integer, got ${JSON.stringify(v)}`,
12180
+ problem: `expected a non-negative integer, got ${JSON.stringify(v)}`,
12359
12181
  severity: "error",
12360
12182
  fix: `set to ${clamped}`
12361
12183
  });
@@ -12604,7 +12426,8 @@ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
12604
12426
  "indexing",
12605
12427
  "tools",
12606
12428
  "launch",
12607
- "circuitBreaker"
12429
+ "circuitBreaker",
12430
+ "modelRuntime"
12608
12431
  ]);
12609
12432
  function filterSafeForProject(cfg) {
12610
12433
  const out = {};
@@ -12746,6 +12569,17 @@ async function persistTelegramConfig(deps, mutator) {
12746
12569
  extensions
12747
12570
  });
12748
12571
  }
12572
+ function deriveFsAccessPair(input) {
12573
+ if (input.allowOutsideProjectRoot !== void 0) {
12574
+ const allow = input.allowOutsideProjectRoot;
12575
+ return { allowOutsideProjectRoot: allow, restrictToProjectRoot: !allow };
12576
+ }
12577
+ if (input.restrictFsToRoot !== void 0) {
12578
+ const restrict = input.restrictFsToRoot;
12579
+ return { allowOutsideProjectRoot: !restrict, restrictToProjectRoot: restrict };
12580
+ }
12581
+ return void 0;
12582
+ }
12749
12583
 
12750
12584
  // src/slash-commands/doctor.ts
12751
12585
  init_helpers();
@@ -18635,7 +18469,7 @@ function buildSettingsCommand(opts) {
18635
18469
  " /settings context-strategy hybrid|intelligent|selective Compactor strategy",
18636
18470
  " /settings context-auto-compact on|off Auto-compact context when thresholds crossed",
18637
18471
  " /settings token-saving off|minimal|light|medium|aggressive Token-saving mode",
18638
- " /settings max-concurrent <n> Max concurrent subagents (0 = unlimited)",
18472
+ " /settings max-concurrent <n> Max concurrent subagents (0 = default)",
18639
18473
  " /settings title-animation on|off Terminal title animation",
18640
18474
  " /settings reasoning auto|on|off Reasoning mode (auto = provider default)",
18641
18475
  " /settings reasoning-effort none|minimal|low|medium|high|xhigh|max Reasoning effort",
@@ -18647,7 +18481,7 @@ function buildSettingsCommand(opts) {
18647
18481
  " /settings hq-raw on|off Send raw content previews to HQ",
18648
18482
  " /settings defaults Show built-in default values",
18649
18483
  "",
18650
- "Settings are persisted to ~/.wrongstack/config.json."
18484
+ "Settings are persisted to the active config scope: global (~/.wrongstack/config.json) or project (<project>/.wrongstack/config.json)."
18651
18485
  ].join("\n");
18652
18486
  function currentView() {
18653
18487
  const autonomy = opts.configStore.get().autonomy;
@@ -18670,7 +18504,7 @@ function buildSettingsCommand(opts) {
18670
18504
  const contextAutoCompact = context?.autoCompact !== false;
18671
18505
  const features = opts.configStore.get().features;
18672
18506
  const tokenSavingTier = features?.tokenSavingMode ?? "off";
18673
- const maxConcurrent = opts.configStore.get().maxConcurrent ?? 0;
18507
+ const maxConcurrent = opts.configStore.get().maxConcurrent ?? 4;
18674
18508
  const modelRuntime = opts.configStore.get().modelRuntime;
18675
18509
  const reasoningMode = modelRuntime?.reasoning?.mode ?? "auto";
18676
18510
  const reasoningEffort = modelRuntime?.reasoning?.effort ?? "(unset)";
@@ -18680,6 +18514,7 @@ function buildSettingsCommand(opts) {
18680
18514
  const hqEnabled = hq?.enabled === true;
18681
18515
  const hqUrl = hq?.url ?? "(auto/local)";
18682
18516
  const hqToken = hq?.token ? `${hq.token.slice(0, 6)}\u2026${hq.token.slice(-4)} (${hq.token.length} chars)` : "(auto/local)";
18517
+ const persistedTo = configScope === "project" ? "<project>/.wrongstack/config.json" : "~/.wrongstack/config.json";
18683
18518
  return [
18684
18519
  `${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
18685
18520
  "",
@@ -18698,7 +18533,7 @@ function buildSettingsCommand(opts) {
18698
18533
  ` context strategy: ${color.cyan(contextStrategy)} ${color.dim("change: /settings context-strategy hybrid|intelligent|selective")}`,
18699
18534
  ` context auto-compact: ${contextAutoCompact ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings context-auto-compact on|off")}`,
18700
18535
  ` 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>")}`,
18536
+ ` max-concurrent: ${color.cyan(maxConcurrent === 0 ? "default" : String(maxConcurrent))} ${color.dim("change: /settings max-concurrent <n>")}`,
18702
18537
  ` reasoning mode: ${color.cyan(reasoningMode)} ${color.dim("change: /settings reasoning auto|on|off")}`,
18703
18538
  ` reasoning effort: ${color.cyan(reasoningEffort)} ${color.dim("change: /settings reasoning-effort <level>")}`,
18704
18539
  ` reasoning preserve: ${reasoningPreserve ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings reasoning-preserve on|off")}`,
@@ -18708,7 +18543,7 @@ function buildSettingsCommand(opts) {
18708
18543
  ` HQ token: ${color.cyan(hqToken)} ${color.dim("change: /settings hq-token <token>")}`,
18709
18544
  ` HQ raw content: ${hq?.rawContent === true ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hq-raw on|off")}`,
18710
18545
  "",
18711
- color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
18546
+ color.dim(` Persisted to ${persistedTo} \xB7 /settings help for more`)
18712
18547
  ].join("\n");
18713
18548
  }
18714
18549
  return {
@@ -18739,6 +18574,7 @@ function buildSettingsCommand(opts) {
18739
18574
  ` iteration timeout: ${color.cyan("5 min")}`,
18740
18575
  ` session timeout: ${color.cyan("30 min")}`,
18741
18576
  ` max iterations: ${color.cyan("100")}`,
18577
+ ` max concurrent: ${color.cyan("4")}`,
18742
18578
  ` semver default part: ${color.cyan("patch")}`
18743
18579
  ].join("\n")
18744
18580
  };
@@ -18879,12 +18715,13 @@ function buildSettingsCommand(opts) {
18879
18715
  return { message: `${color.amber("Usage:")} /settings fs-access unrestricted|project` };
18880
18716
  }
18881
18717
  const restrict = raw === "project";
18718
+ const fsAccess = deriveFsAccessPair({ restrictFsToRoot: restrict });
18882
18719
  await persistConfigSetting(persistDeps, (cfg) => {
18883
18720
  const tools = cfg.tools ?? {};
18884
- tools.restrictToProjectRoot = restrict;
18721
+ tools.restrictToProjectRoot = fsAccess.restrictToProjectRoot;
18885
18722
  cfg.tools = tools;
18886
18723
  const features = cfg.features ?? {};
18887
- features.allowOutsideProjectRoot = !restrict;
18724
+ features.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
18888
18725
  cfg.features = features;
18889
18726
  });
18890
18727
  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 +18891,20 @@ function buildSettingsCommand(opts) {
19054
18891
  const raw = rest[0];
19055
18892
  if (raw === void 0) {
19056
18893
  return {
19057
- message: `${color.amber("Usage:")} /settings max-concurrent <n> ${color.dim("(0 = unlimited)")}`
18894
+ message: `${color.amber("Usage:")} /settings max-concurrent <n> ${color.dim("(0 = default)")}`
19058
18895
  };
19059
18896
  }
19060
18897
  const n = Number.parseInt(raw, 10);
19061
18898
  if (Number.isNaN(n) || n < 0) {
19062
18899
  return {
19063
- message: `${color.red("Invalid number")}: "${raw}". Enter a non-negative integer (0 = unlimited)`
18900
+ message: `${color.red("Invalid number")}: "${raw}". Enter a non-negative integer (0 = default)`
19064
18901
  };
19065
18902
  }
19066
18903
  await persistConfigSetting(persistDeps, (cfg) => {
19067
18904
  cfg.maxConcurrent = n;
19068
18905
  });
19069
18906
  return {
19070
- message: `${color.green("\u2713")} max-concurrent \u2192 ${color.cyan(n === 0 ? "unlimited" : String(n))} ${color.dim("max concurrent subagents")}`
18907
+ message: `${color.green("\u2713")} max-concurrent \u2192 ${color.cyan(n === 0 ? "default" : String(n))} ${color.dim("max concurrent subagents")}`
19071
18908
  };
19072
18909
  }
19073
18910
  if (sub === "title-animation") {
@@ -19491,28 +19328,129 @@ function buildDirectorCommand(opts) {
19491
19328
  }
19492
19329
  var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
19493
19330
  var DEFAULTS = {
19331
+ version: true,
19332
+ state: true,
19333
+ model: true,
19494
19334
  todos: true,
19495
19335
  plan: true,
19496
19336
  tasks: true,
19497
19337
  fleet: true,
19338
+ fleet_agents: true,
19498
19339
  git: true,
19499
19340
  elapsed: true,
19500
19341
  context: true,
19342
+ tokens: true,
19343
+ cache: true,
19501
19344
  cost: true,
19502
- working_dir: true
19345
+ queue: true,
19346
+ processes: true,
19347
+ hint: true,
19348
+ index: true,
19349
+ breaker: true,
19350
+ working_dir: true,
19351
+ project: true,
19352
+ yolo: true,
19353
+ autonomy: true,
19354
+ eternal_stage: true,
19355
+ goal: true,
19356
+ mode: true,
19357
+ auto_proceed: true,
19358
+ sessions: true,
19359
+ tools: true,
19360
+ token_saving: true,
19361
+ brain: true,
19362
+ mailbox: true,
19363
+ enhance: true,
19364
+ debug_stream: true,
19365
+ next_steps: true
19503
19366
  };
19367
+ var STATUSLINE_CONFIG_KEYS = [
19368
+ "version",
19369
+ "state",
19370
+ "model",
19371
+ "context",
19372
+ "tokens",
19373
+ "cache",
19374
+ "cost",
19375
+ "queue",
19376
+ "processes",
19377
+ "hint",
19378
+ "index",
19379
+ "breaker",
19380
+ "yolo",
19381
+ "autonomy",
19382
+ "eternal_stage",
19383
+ "elapsed",
19384
+ "project",
19385
+ "working_dir",
19386
+ "goal",
19387
+ "mode",
19388
+ "auto_proceed",
19389
+ "git",
19390
+ "sessions",
19391
+ "tools",
19392
+ "token_saving",
19393
+ "todos",
19394
+ "plan",
19395
+ "tasks",
19396
+ "fleet",
19397
+ "brain",
19398
+ "debug_stream",
19399
+ "enhance",
19400
+ "next_steps",
19401
+ "mailbox",
19402
+ "fleet_agents"
19403
+ ];
19504
19404
  function resolveConfigPath() {
19505
19405
  return process.env[CONFIG_ENV] ?? path4.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
19506
19406
  }
19407
+ function isRecord(value) {
19408
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19409
+ }
19410
+ function normalizeStatuslineConfig(value) {
19411
+ const cfg = { ...DEFAULTS };
19412
+ if (!isRecord(value)) return cfg;
19413
+ for (const key of STATUSLINE_CONFIG_KEYS) {
19414
+ const raw = value[key];
19415
+ if (typeof raw === "boolean") cfg[key] = raw;
19416
+ }
19417
+ return cfg;
19418
+ }
19419
+ function isMissingKnownStatuslineKeys(value) {
19420
+ if (!isRecord(value)) return true;
19421
+ return STATUSLINE_CONFIG_KEYS.some((key) => typeof value[key] !== "boolean");
19422
+ }
19423
+ function hasFsCode(err, code) {
19424
+ return isRecord(err) && err["code"] === code;
19425
+ }
19507
19426
  async function loadStatuslineConfig() {
19508
19427
  const p = resolveConfigPath();
19509
19428
  try {
19510
19429
  const raw = await fs4.readFile(p, "utf8");
19511
- return { ...DEFAULTS, ...JSON.parse(raw) };
19430
+ return normalizeStatuslineConfig(JSON.parse(raw));
19512
19431
  } catch {
19513
19432
  return { ...DEFAULTS };
19514
19433
  }
19515
19434
  }
19435
+ async function ensureStatuslineConfig() {
19436
+ const p = resolveConfigPath();
19437
+ try {
19438
+ const raw = await fs4.readFile(p, "utf8");
19439
+ const parsed = JSON.parse(raw);
19440
+ const cfg = normalizeStatuslineConfig(parsed);
19441
+ if (isMissingKnownStatuslineKeys(parsed)) {
19442
+ await saveStatuslineConfig(cfg);
19443
+ }
19444
+ return cfg;
19445
+ } catch (err) {
19446
+ if (hasFsCode(err, "ENOENT")) {
19447
+ const cfg = { ...DEFAULTS };
19448
+ await saveStatuslineConfig(cfg);
19449
+ return cfg;
19450
+ }
19451
+ return { ...DEFAULTS };
19452
+ }
19453
+ }
19516
19454
  async function saveStatuslineConfig(cfg) {
19517
19455
  const p = resolveConfigPath();
19518
19456
  try {
@@ -19528,27 +19466,43 @@ async function saveStatuslineConfig(cfg) {
19528
19466
  }
19529
19467
  }
19530
19468
  var ITEM_DESCRIPTIONS = {
19469
+ version: "WrongStack version chip",
19470
+ state: "Agent run state / thinking spinner",
19471
+ model: "Current provider/model id",
19531
19472
  todos: "Todo items (pending/in-progress/done counts)",
19532
19473
  plan: "Plan board items (open/in-progress/done)",
19533
19474
  tasks: "Task board items (structured work with type/priority)",
19534
19475
  fleet: "Fleet agent status (running/idle/pending/completed)",
19476
+ fleet_agents: "Per-agent live detail row",
19535
19477
  git: "Git branch name",
19536
19478
  elapsed: "Session elapsed time",
19537
19479
  context: "Context window usage (input tokens)",
19480
+ tokens: "Input/output token counters",
19481
+ cache: "Prompt cache hit ratio",
19538
19482
  cost: "Token cost estimate (input/output/total)",
19539
- working_dir: "Current working directory"
19483
+ queue: "Queued prompt count",
19484
+ processes: "Tracked shell/process count",
19485
+ hint: "Transient status hint text",
19486
+ index: "Codebase indexing status",
19487
+ breaker: "Process breaker countdown",
19488
+ working_dir: "Current working directory",
19489
+ project: "Project name",
19490
+ yolo: "YOLO permission mode",
19491
+ autonomy: "Autonomy mode",
19492
+ eternal_stage: "Autonomy stage",
19493
+ goal: "Active goal summary",
19494
+ mode: "Active agent mode label",
19495
+ auto_proceed: "Auto-proceed countdown",
19496
+ sessions: "Live session count",
19497
+ tools: "Registered tool count",
19498
+ token_saving: "Token-saving mode indicator",
19499
+ brain: "Brain arbiter decisions",
19500
+ mailbox: "Mailbox unread messages and peers",
19501
+ enhance: "Prompt-enhance countdown",
19502
+ debug_stream: "Stream debug telemetry",
19503
+ next_steps: "Next-step auto-submit countdown"
19540
19504
  };
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
- ];
19505
+ var ALL_CONFIG_KEYS = STATUSLINE_CONFIG_KEYS;
19552
19506
  function buildStatuslineCommand(deps) {
19553
19507
  return {
19554
19508
  name: "statusline",
@@ -21903,6 +21857,388 @@ function summarize(value, name) {
21903
21857
 
21904
21858
  // src/boot.ts
21905
21859
  init_project_utils();
21860
+
21861
+ // src/auth-menu/openai-codex-oauth.ts
21862
+ init_provider_config_utils();
21863
+ var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
21864
+ var AUTH_BASE_URL = "https://auth.openai.com";
21865
+ var AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;
21866
+ var TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;
21867
+ var REDIRECT_PORT = 1455;
21868
+ var REDIRECT_HOST = "127.0.0.1";
21869
+ var REDIRECT_PATH = "/auth/callback";
21870
+ var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}${REDIRECT_PATH}`;
21871
+ var SCOPE = "openid profile email offline_access";
21872
+ var JWT_CLAIM_PATH = "https://api.openai.com/auth";
21873
+ var ORIGINATOR = "wrongstack";
21874
+ var CODEX_PROVIDER_ID = "openai-codex";
21875
+ var CODEX_BASE_URL = "https://chatgpt.com/backend-api";
21876
+ function base64url(buf) {
21877
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
21878
+ }
21879
+ function generatePkce() {
21880
+ const verifier = base64url(randomBytes(32));
21881
+ const challenge = base64url(createHash("sha256").update(verifier).digest());
21882
+ return { verifier, challenge };
21883
+ }
21884
+ function createState() {
21885
+ return randomBytes(16).toString("hex");
21886
+ }
21887
+ function buildAuthorizeUrl(challenge, state) {
21888
+ const url = new URL(AUTHORIZE_URL);
21889
+ url.searchParams.set("response_type", "code");
21890
+ url.searchParams.set("client_id", CLIENT_ID);
21891
+ url.searchParams.set("redirect_uri", REDIRECT_URI);
21892
+ url.searchParams.set("scope", SCOPE);
21893
+ url.searchParams.set("code_challenge", challenge);
21894
+ url.searchParams.set("code_challenge_method", "S256");
21895
+ url.searchParams.set("state", state);
21896
+ url.searchParams.set("id_token_add_organizations", "true");
21897
+ url.searchParams.set("codex_cli_simplified_flow", "true");
21898
+ url.searchParams.set("originator", ORIGINATOR);
21899
+ return url.toString();
21900
+ }
21901
+ function decodeJwtPayload(token) {
21902
+ try {
21903
+ const parts = token.split(".");
21904
+ if (parts.length !== 3) return null;
21905
+ const json = Buffer.from(parts[1], "base64url").toString("utf8");
21906
+ return JSON.parse(json);
21907
+ } catch {
21908
+ return null;
21909
+ }
21910
+ }
21911
+ function extractAccountId(token) {
21912
+ const payload = decodeJwtPayload(token);
21913
+ const auth = payload?.[JWT_CLAIM_PATH];
21914
+ const id = auth?.chatgpt_account_id;
21915
+ return typeof id === "string" && id.length > 0 ? id : null;
21916
+ }
21917
+ var FALLBACK_CODEX_MODELS = CODEX_MODELS.map(
21918
+ (m) => ({ id: m.id, name: m.name })
21919
+ );
21920
+ var CODEX_CATALOG_FAMILIES = /* @__PURE__ */ new Set(["gpt-codex", "gpt-codex-spark"]);
21921
+ function fallbackCodexModelIds() {
21922
+ return FALLBACK_CODEX_MODELS.map((m) => m.id);
21923
+ }
21924
+ function fallbackCodexProviderModels() {
21925
+ return FALLBACK_CODEX_MODELS.map((m) => ({ id: m.id, name: m.name }));
21926
+ }
21927
+ function filterCurrentCodexModelIds(ids) {
21928
+ const available = new Set(ids);
21929
+ return FALLBACK_CODEX_MODELS.map((m) => m.id).filter((id) => available.has(id));
21930
+ }
21931
+ function isCodexCatalogModel(model) {
21932
+ return typeof model.family === "string" && CODEX_CATALOG_FAMILIES.has(model.family);
21933
+ }
21934
+ async function resolveCodexModels(modelsRegistry, accessToken, baseUrl, signal) {
21935
+ const token = typeof accessToken === "string" ? accessToken : await accessToken;
21936
+ const live = filterCurrentCodexModelIds(await fetchCodexModels(token, baseUrl, signal));
21937
+ if (live.length > 0) return live;
21938
+ try {
21939
+ const openaiProvider = await modelsRegistry.getProvider("openai");
21940
+ if (openaiProvider) {
21941
+ const catalog = openaiProvider.models.filter(isCodexCatalogModel).map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0);
21942
+ const currentCatalog = filterCurrentCodexModelIds(catalog);
21943
+ if (currentCatalog.length > 0) return currentCatalog;
21944
+ }
21945
+ } catch {
21946
+ }
21947
+ return fallbackCodexModelIds();
21948
+ }
21949
+ async function fetchCodexModels(accessToken, baseUrl, signal) {
21950
+ const url = `${(baseUrl).replace(/\/+$/, "")}/models`;
21951
+ try {
21952
+ const res = await fetch(url, {
21953
+ headers: {
21954
+ accept: "application/json",
21955
+ authorization: `Bearer ${accessToken}`,
21956
+ originator: "wrongstack",
21957
+ "OpenAI-Beta": "responses=experimental"
21958
+ },
21959
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(8e3)]) : AbortSignal.timeout(8e3)
21960
+ });
21961
+ if (!res.ok) return [];
21962
+ const json = await res.json();
21963
+ if (!json) return [];
21964
+ const rawList = "data" in json && Array.isArray(json.data) ? json.data : "models" in json && Array.isArray(json.models) ? json.models : [];
21965
+ const ids = [];
21966
+ for (const entry of rawList) {
21967
+ if (!entry || typeof entry !== "object") continue;
21968
+ const id = entry.id;
21969
+ if (typeof id === "string" && id.length > 0) ids.push(id);
21970
+ }
21971
+ return ids;
21972
+ } catch {
21973
+ return [];
21974
+ }
21975
+ }
21976
+ async function readTokens(res, op) {
21977
+ if (!res.ok) {
21978
+ const text = await res.text().catch(() => "");
21979
+ throw new Error(`Codex token ${op} failed (${res.status}): ${text || res.statusText}`);
21980
+ }
21981
+ const json = await res.json();
21982
+ if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
21983
+ throw new Error(`Codex token ${op} response missing fields`);
21984
+ }
21985
+ return {
21986
+ access: json.access_token,
21987
+ refresh: json.refresh_token,
21988
+ expires: Date.now() + json.expires_in * 1e3
21989
+ };
21990
+ }
21991
+ async function exchangeAuthorizationCode(code, verifier, signal) {
21992
+ const res = await fetch(TOKEN_URL, {
21993
+ method: "POST",
21994
+ headers: { "content-type": "application/x-www-form-urlencoded" },
21995
+ body: new URLSearchParams({
21996
+ grant_type: "authorization_code",
21997
+ client_id: CLIENT_ID,
21998
+ code,
21999
+ code_verifier: verifier,
22000
+ redirect_uri: REDIRECT_URI
22001
+ }).toString(),
22002
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
22003
+ });
22004
+ return readTokens(res, "exchange");
22005
+ }
22006
+ function callbackHtml(ok, message) {
22007
+ const heading = ok ? "Authentication successful" : "Authentication failed";
22008
+ 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>`;
22009
+ }
22010
+ function startLoopbackServer(state) {
22011
+ let resolveCode = () => {
22012
+ };
22013
+ const codePromise = new Promise((resolve13) => {
22014
+ let settled = false;
22015
+ resolveCode = (v) => {
22016
+ if (settled) return;
22017
+ settled = true;
22018
+ resolve13(v);
22019
+ };
22020
+ });
22021
+ const server = createServer((req2, res) => {
22022
+ let url;
22023
+ try {
22024
+ url = new URL(req2.url ?? "", `http://${REDIRECT_HOST}`);
22025
+ } catch {
22026
+ res.statusCode = 400;
22027
+ res.end();
22028
+ return;
22029
+ }
22030
+ if (url.pathname !== REDIRECT_PATH) {
22031
+ res.statusCode = 404;
22032
+ res.setHeader("content-type", "text/html; charset=utf-8");
22033
+ res.end(callbackHtml(false, "Callback route not found."));
22034
+ return;
22035
+ }
22036
+ res.setHeader("content-type", "text/html; charset=utf-8");
22037
+ const err = url.searchParams.get("error");
22038
+ if (err) {
22039
+ res.statusCode = 400;
22040
+ res.end(callbackHtml(false, `Authorization error: ${err}`));
22041
+ resolveCode(null);
22042
+ return;
22043
+ }
22044
+ if (url.searchParams.get("state") !== state) {
22045
+ res.statusCode = 400;
22046
+ res.end(callbackHtml(false, "State mismatch \u2014 please restart the login."));
22047
+ resolveCode(null);
22048
+ return;
22049
+ }
22050
+ const code = url.searchParams.get("code");
22051
+ if (!code) {
22052
+ res.statusCode = 400;
22053
+ res.end(callbackHtml(false, "Missing authorization code."));
22054
+ return;
22055
+ }
22056
+ res.statusCode = 200;
22057
+ res.end(callbackHtml(true, "You can close this window and return to the terminal."));
22058
+ resolveCode(code);
22059
+ });
22060
+ return new Promise((resolve13) => {
22061
+ server.on("error", () => {
22062
+ resolveCode(null);
22063
+ resolve13({
22064
+ bound: false,
22065
+ waitForCode: () => Promise.resolve(null),
22066
+ close: () => {
22067
+ try {
22068
+ server.close();
22069
+ } catch {
22070
+ }
22071
+ }
22072
+ });
22073
+ });
22074
+ server.listen(REDIRECT_PORT, REDIRECT_HOST, () => {
22075
+ resolve13({
22076
+ bound: true,
22077
+ waitForCode: () => codePromise,
22078
+ close: () => {
22079
+ resolveCode(null);
22080
+ try {
22081
+ server.close();
22082
+ } catch {
22083
+ }
22084
+ }
22085
+ });
22086
+ });
22087
+ });
22088
+ }
22089
+ function openBrowser(url) {
22090
+ try {
22091
+ const platform3 = process.platform;
22092
+ const { command: command2, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
22093
+ const child = spawn(command2, args, { stdio: "ignore", windowsHide: true });
22094
+ child.on("error", () => {
22095
+ });
22096
+ child.unref();
22097
+ } catch {
22098
+ }
22099
+ }
22100
+ function parseAuthorizationInput(input) {
22101
+ const value = input.trim();
22102
+ if (!value) return {};
22103
+ try {
22104
+ const url = new URL(value);
22105
+ return {
22106
+ code: url.searchParams.get("code") ?? void 0,
22107
+ state: url.searchParams.get("state") ?? void 0
22108
+ };
22109
+ } catch {
22110
+ }
22111
+ if (value.includes("code=")) {
22112
+ const params = new URLSearchParams(value);
22113
+ return {
22114
+ code: params.get("code") ?? void 0,
22115
+ state: params.get("state") ?? void 0
22116
+ };
22117
+ }
22118
+ return { code: value };
22119
+ }
22120
+ async function runCodexOAuthLogin(deps, opts = {}) {
22121
+ const providerId = opts.providerId ?? CODEX_PROVIDER_ID;
22122
+ const pkce = generatePkce();
22123
+ const state = createState();
22124
+ const authorizeUrl = buildAuthorizeUrl(pkce.challenge, state);
22125
+ const ac = new AbortController();
22126
+ const onSig = () => ac.abort();
22127
+ process.on("SIGINT", onSig);
22128
+ const server = await startLoopbackServer(state);
22129
+ deps.renderer.write(
22130
+ color.bold(`
22131
+ Sign in with ChatGPT \u2014 ${color.cyan(providerId)}
22132
+ `) + 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)}
22133
+ `) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${authorizeUrl}
22134
+ `) + color.bold(` ${"\u2500".repeat(56)}
22135
+
22136
+ `)
22137
+ );
22138
+ if (server.bound) {
22139
+ openBrowser(authorizeUrl);
22140
+ deps.renderer.write(
22141
+ 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")
22142
+ );
22143
+ } else {
22144
+ deps.renderer.write(
22145
+ 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")
22146
+ );
22147
+ }
22148
+ let code;
22149
+ try {
22150
+ if (server.bound) {
22151
+ const got = await server.waitForCode();
22152
+ if (got) code = got;
22153
+ }
22154
+ if (!code) {
22155
+ const input = (await deps.reader.readLine(
22156
+ `
22157
+ ${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
22158
+ )).trim();
22159
+ if (input.toLowerCase() === "q" || input === "") {
22160
+ deps.renderer.write(color.dim(" Cancelled.\n"));
22161
+ return 1;
22162
+ }
22163
+ const parsed = parseAuthorizationInput(input);
22164
+ if (parsed.state && parsed.state !== state) {
22165
+ deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
22166
+ return 1;
22167
+ }
22168
+ code = parsed.code;
22169
+ }
22170
+ if (!code) {
22171
+ deps.renderer.writeError(" No authorization code received.");
22172
+ return 1;
22173
+ }
22174
+ deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
22175
+ const tokens = await exchangeAuthorizationCode(code, pkce.verifier, ac.signal);
22176
+ const accountId = extractAccountId(tokens.access);
22177
+ if (!accountId) {
22178
+ deps.renderer.writeError(
22179
+ " Signed in, but the token has no ChatGPT account id.\n This account may not have Codex/ChatGPT subscription access."
22180
+ );
22181
+ return 1;
22182
+ }
22183
+ deps.renderer.write(color.dim(" Fetching available models...\n"));
22184
+ const models = await resolveCodexModels(
22185
+ deps.modelsRegistry,
22186
+ tokens.access,
22187
+ CODEX_BASE_URL,
22188
+ ac.signal
22189
+ );
22190
+ const saved = await saveCodexTokens(deps, providerId, tokens, accountId, models);
22191
+ if (!saved) return 1;
22192
+ const modelHint = models[0] ?? "gpt-5.5";
22193
+ deps.renderer.write(color.green("\n \u2713 Signed in with ChatGPT!\n"));
22194
+ deps.renderer.writeInfo(
22195
+ ` Saved as provider ${color.bold(providerId)}${models.length > 0 ? ` (${models.length} models)` : ""}.
22196
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
22197
+ ` + color.dim(" Tokens refresh automatically before they expire.\n")
22198
+ );
22199
+ return 0;
22200
+ } catch (err) {
22201
+ const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
22202
+ deps.renderer.writeError(` Login failed: ${msg}`);
22203
+ return 1;
22204
+ } finally {
22205
+ server.close();
22206
+ process.off("SIGINT", onSig);
22207
+ }
22208
+ }
22209
+ async function saveCodexTokens(deps, providerId, tokens, accountId, models) {
22210
+ const entry = {
22211
+ label: "oauth-default",
22212
+ apiKey: tokens.access,
22213
+ createdAt: nowIso(),
22214
+ authMethod: "oauth",
22215
+ expiresAt: new Date(tokens.expires).toISOString(),
22216
+ refreshToken: tokens.refresh,
22217
+ tokenType: "bearer",
22218
+ scope: SCOPE,
22219
+ accountId
22220
+ };
22221
+ try {
22222
+ await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
22223
+ const existing = all[providerId];
22224
+ const p = existing ? { ...existing } : { type: providerId };
22225
+ p.family = "openai-codex";
22226
+ if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;
22227
+ p.models = [...models];
22228
+ const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
22229
+ keys.push(entry);
22230
+ writeKeysBack(p, keys);
22231
+ p.activeKey = entry.label;
22232
+ all[providerId] = p;
22233
+ });
22234
+ return true;
22235
+ } catch (err) {
22236
+ deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
22237
+ return false;
22238
+ }
22239
+ }
22240
+
22241
+ // src/wiring/provider.ts
21906
22242
  async function setupProvider(params) {
21907
22243
  const { config, modelsRegistry, logger } = params;
21908
22244
  const savedProviderCfg = config.providers?.[config.provider];
@@ -21912,13 +22248,36 @@ async function setupProvider(params) {
21912
22248
  }
21913
22249
  if (!resolvedProvider) {
21914
22250
  if (savedProviderCfg?.family) {
22251
+ const family = savedProviderCfg.family;
22252
+ const savedModels = savedProviderCfg.models;
22253
+ let models;
22254
+ if (savedModels && savedModels.length > 0) {
22255
+ models = savedModels.map((m) => ({ id: m, name: m }));
22256
+ } else if (family === "openai-codex") {
22257
+ const openaiProvider = await modelsRegistry.getProvider("openai").catch(() => void 0);
22258
+ if (openaiProvider) {
22259
+ const catalogById = new Map(
22260
+ openaiProvider.models.filter(isCodexCatalogModel).map((m) => [m.id, { id: m.id, name: m.name }])
22261
+ );
22262
+ const catalogModels = filterCurrentCodexModelIds(catalogById.keys()).map((id) => catalogById.get(id)).filter((m) => Boolean(m));
22263
+ if (catalogModels.length > 0) {
22264
+ models = catalogModels;
22265
+ } else {
22266
+ models = fallbackCodexProviderModels();
22267
+ }
22268
+ } else {
22269
+ models = fallbackCodexProviderModels();
22270
+ }
22271
+ } else {
22272
+ models = [];
22273
+ }
21915
22274
  resolvedProvider = {
21916
22275
  id: config.provider,
21917
22276
  name: config.provider,
21918
- family: savedProviderCfg.family,
22277
+ family,
21919
22278
  apiBase: savedProviderCfg.baseUrl,
21920
22279
  envVars: savedProviderCfg.envVars ?? [],
21921
- models: (savedProviderCfg.models ?? []).map((m) => ({ id: m, name: m })),
22280
+ models,
21922
22281
  npm: void 0
21923
22282
  };
21924
22283
  } else {
@@ -21965,6 +22324,15 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
21965
22324
  } catch (err) {
21966
22325
  throw new Error(`Failed to create provider: ${err instanceof Error ? err.message : err}`);
21967
22326
  }
22327
+ if (config.features.modelsRegistry) {
22328
+ provider = await withCatalogCapabilities(
22329
+ modelsRegistry,
22330
+ config.provider,
22331
+ provider,
22332
+ { ...providerConfig, type: config.provider, model: config.model },
22333
+ logger
22334
+ );
22335
+ }
21968
22336
  return { resolvedProvider, provider, providerRegistry };
21969
22337
  }
21970
22338
 
@@ -22667,38 +23035,38 @@ init_provider_config_utils();
22667
23035
 
22668
23036
  // src/auth-menu/anthropic-oauth.ts
22669
23037
  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}`;
23038
+ var CLIENT_ID2 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
23039
+ var AUTHORIZE_URL2 = "https://claude.ai/oauth/authorize";
23040
+ var TOKEN_URL2 = "https://platform.claude.com/v1/oauth/token";
23041
+ var REDIRECT_PORT2 = 53692;
23042
+ var REDIRECT_HOST2 = "127.0.0.1";
23043
+ var REDIRECT_PATH2 = "/callback";
23044
+ var REDIRECT_URI2 = `http://localhost:${REDIRECT_PORT2}${REDIRECT_PATH2}`;
22677
23045
  var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
22678
23046
  var CLAUDE_PROVIDER_ID = "anthropic-oauth";
22679
23047
  var CLAUDE_BASE_URL = "https://api.anthropic.com";
22680
- function base64url(buf) {
23048
+ function base64url2(buf) {
22681
23049
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
22682
23050
  }
22683
- function generatePkce() {
22684
- const verifier = base64url(randomBytes(32));
22685
- const challenge = base64url(createHash("sha256").update(verifier).digest());
23051
+ function generatePkce2() {
23052
+ const verifier = base64url2(randomBytes(32));
23053
+ const challenge = base64url2(createHash("sha256").update(verifier).digest());
22686
23054
  return { verifier, challenge };
22687
23055
  }
22688
- function buildAuthorizeUrl(challenge, verifier) {
23056
+ function buildAuthorizeUrl2(challenge, verifier) {
22689
23057
  const params = new URLSearchParams({
22690
23058
  code: "true",
22691
- client_id: CLIENT_ID,
23059
+ client_id: CLIENT_ID2,
22692
23060
  response_type: "code",
22693
- redirect_uri: REDIRECT_URI,
23061
+ redirect_uri: REDIRECT_URI2,
22694
23062
  scope: SCOPES,
22695
23063
  code_challenge: challenge,
22696
23064
  code_challenge_method: "S256",
22697
23065
  state: verifier
22698
23066
  });
22699
- return `${AUTHORIZE_URL}?${params.toString()}`;
23067
+ return `${AUTHORIZE_URL2}?${params.toString()}`;
22700
23068
  }
22701
- function parseAuthorizationInput(input) {
23069
+ function parseAuthorizationInput2(input) {
22702
23070
  const value = input.trim();
22703
23071
  if (!value) return {};
22704
23072
  try {
@@ -22722,7 +23090,7 @@ function parseAuthorizationInput(input) {
22722
23090
  }
22723
23091
  return { code: value };
22724
23092
  }
22725
- async function readTokens(res, op) {
23093
+ async function readTokens2(res, op) {
22726
23094
  if (!res.ok) {
22727
23095
  const text = await res.text().catch(() => "");
22728
23096
  throw new Error(`Claude token ${op} failed (${res.status}): ${text || res.statusText}`);
@@ -22737,21 +23105,21 @@ async function readTokens(res, op) {
22737
23105
  expires: Date.now() + json.expires_in * 1e3
22738
23106
  };
22739
23107
  }
22740
- async function exchangeAuthorizationCode(code, state, verifier, signal) {
22741
- const res = await fetch(TOKEN_URL, {
23108
+ async function exchangeAuthorizationCode2(code, state, verifier, signal) {
23109
+ const res = await fetch(TOKEN_URL2, {
22742
23110
  method: "POST",
22743
23111
  headers: { "content-type": "application/json", accept: "application/json" },
22744
23112
  body: JSON.stringify({
22745
23113
  grant_type: "authorization_code",
22746
- client_id: CLIENT_ID,
23114
+ client_id: CLIENT_ID2,
22747
23115
  code,
22748
23116
  state,
22749
- redirect_uri: REDIRECT_URI,
23117
+ redirect_uri: REDIRECT_URI2,
22750
23118
  code_verifier: verifier
22751
23119
  }),
22752
23120
  signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
22753
23121
  });
22754
- return readTokens(res, "exchange");
23122
+ return readTokens2(res, "exchange");
22755
23123
  }
22756
23124
  async function fetchClaudeModels(accessToken, signal) {
22757
23125
  try {
@@ -22772,11 +23140,11 @@ async function fetchClaudeModels(accessToken, signal) {
22772
23140
  return [];
22773
23141
  }
22774
23142
  }
22775
- function callbackHtml(ok, message) {
23143
+ function callbackHtml2(ok, message) {
22776
23144
  const heading = ok ? "Authentication successful" : "Authentication failed";
22777
23145
  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
23146
  }
22779
- function startLoopbackServer(expectedState) {
23147
+ function startLoopbackServer2(expectedState) {
22780
23148
  let resolveCode = () => {
22781
23149
  };
22782
23150
  const codePromise = new Promise((resolve13) => {
@@ -22790,23 +23158,23 @@ function startLoopbackServer(expectedState) {
22790
23158
  const server = createServer((req2, res) => {
22791
23159
  let url;
22792
23160
  try {
22793
- url = new URL(req2.url ?? "", `http://${REDIRECT_HOST}`);
23161
+ url = new URL(req2.url ?? "", `http://${REDIRECT_HOST2}`);
22794
23162
  } catch {
22795
23163
  res.statusCode = 400;
22796
23164
  res.end();
22797
23165
  return;
22798
23166
  }
22799
- if (url.pathname !== REDIRECT_PATH) {
23167
+ if (url.pathname !== REDIRECT_PATH2) {
22800
23168
  res.statusCode = 404;
22801
23169
  res.setHeader("content-type", "text/html; charset=utf-8");
22802
- res.end(callbackHtml(false, "Callback route not found."));
23170
+ res.end(callbackHtml2(false, "Callback route not found."));
22803
23171
  return;
22804
23172
  }
22805
23173
  res.setHeader("content-type", "text/html; charset=utf-8");
22806
23174
  const err = url.searchParams.get("error");
22807
23175
  if (err) {
22808
23176
  res.statusCode = 400;
22809
- res.end(callbackHtml(false, `Authorization error: ${err}`));
23177
+ res.end(callbackHtml2(false, `Authorization error: ${err}`));
22810
23178
  resolveCode(null);
22811
23179
  return;
22812
23180
  }
@@ -22814,17 +23182,17 @@ function startLoopbackServer(expectedState) {
22814
23182
  const state = url.searchParams.get("state");
22815
23183
  if (!code || !state) {
22816
23184
  res.statusCode = 400;
22817
- res.end(callbackHtml(false, "Missing code or state."));
23185
+ res.end(callbackHtml2(false, "Missing code or state."));
22818
23186
  return;
22819
23187
  }
22820
23188
  if (state !== expectedState) {
22821
23189
  res.statusCode = 400;
22822
- res.end(callbackHtml(false, "State mismatch \u2014 please restart the login."));
23190
+ res.end(callbackHtml2(false, "State mismatch \u2014 please restart the login."));
22823
23191
  resolveCode(null);
22824
23192
  return;
22825
23193
  }
22826
23194
  res.statusCode = 200;
22827
- res.end(callbackHtml(true, "You can close this window and return to the terminal."));
23195
+ res.end(callbackHtml2(true, "You can close this window and return to the terminal."));
22828
23196
  resolveCode({ code, state });
22829
23197
  });
22830
23198
  return new Promise((resolve13) => {
@@ -22841,7 +23209,7 @@ function startLoopbackServer(expectedState) {
22841
23209
  }
22842
23210
  });
22843
23211
  });
22844
- server.listen(REDIRECT_PORT, REDIRECT_HOST, () => {
23212
+ server.listen(REDIRECT_PORT2, REDIRECT_HOST2, () => {
22845
23213
  resolve13({
22846
23214
  bound: true,
22847
23215
  waitForCode: () => codePromise,
@@ -22856,7 +23224,7 @@ function startLoopbackServer(expectedState) {
22856
23224
  });
22857
23225
  });
22858
23226
  }
22859
- function openBrowser(url) {
23227
+ function openBrowser2(url) {
22860
23228
  try {
22861
23229
  const platform3 = process.platform;
22862
23230
  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 +23237,13 @@ function openBrowser(url) {
22869
23237
  }
22870
23238
  async function runClaudeOAuthLogin(deps, opts = {}) {
22871
23239
  const providerId = opts.providerId ?? CLAUDE_PROVIDER_ID;
22872
- const { verifier, challenge } = generatePkce();
23240
+ const { verifier, challenge } = generatePkce2();
22873
23241
  const state = verifier;
22874
- const authorizeUrl = buildAuthorizeUrl(challenge, verifier);
23242
+ const authorizeUrl = buildAuthorizeUrl2(challenge, verifier);
22875
23243
  const ac = new AbortController();
22876
23244
  const onSig = () => ac.abort();
22877
23245
  process.on("SIGINT", onSig);
22878
- const server = await startLoopbackServer(state);
23246
+ const server = await startLoopbackServer2(state);
22879
23247
  deps.renderer.write(
22880
23248
  color.bold(`
22881
23249
  Sign in with Claude \u2014 ${color.cyan(providerId)}
@@ -22886,7 +23254,7 @@ async function runClaudeOAuthLogin(deps, opts = {}) {
22886
23254
  `)
22887
23255
  );
22888
23256
  if (server.bound) {
22889
- openBrowser(authorizeUrl);
23257
+ openBrowser2(authorizeUrl);
22890
23258
  deps.renderer.write(
22891
23259
  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
23260
  );
@@ -22904,527 +23272,6 @@ async function runClaudeOAuthLogin(deps, opts = {}) {
22904
23272
  if (!code) {
22905
23273
  const input = (await deps.reader.readLine(
22906
23274
  `
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
23275
  ${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
23429
23276
  )).trim();
23430
23277
  if (input.toLowerCase() === "q" || input === "") {
@@ -23443,20 +23290,15 @@ async function runCodexOAuthLogin(deps, opts = {}) {
23443
23290
  return 1;
23444
23291
  }
23445
23292
  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);
23293
+ const tokens = await exchangeAuthorizationCode2(code, state, verifier, ac.signal);
23294
+ const models = await fetchClaudeModels(tokens.access, ac.signal);
23295
+ const saved = await saveClaudeTokens(deps, providerId, tokens, models);
23455
23296
  if (!saved) return 1;
23456
- deps.renderer.write(color.green("\n \u2713 Signed in with ChatGPT!\n"));
23297
+ deps.renderer.write(color.green("\n \u2713 Signed in with Claude!\n"));
23298
+ const modelHint = models.find((m) => m.includes("sonnet")) ?? models[0] ?? "claude-sonnet-4-6";
23457
23299
  deps.renderer.writeInfo(
23458
- ` Saved as provider ${color.bold(providerId)}.
23459
- Use: ${color.bold(`wstack --provider ${providerId} --model gpt-5.5`)} "<task>"
23300
+ ` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
23301
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
23460
23302
  ` + color.dim(" Tokens refresh automatically before they expire.\n")
23461
23303
  );
23462
23304
  return 0;
@@ -23469,7 +23311,7 @@ async function runCodexOAuthLogin(deps, opts = {}) {
23469
23311
  process.off("SIGINT", onSig);
23470
23312
  }
23471
23313
  }
23472
- async function saveCodexTokens(deps, providerId, tokens, accountId) {
23314
+ async function saveClaudeTokens(deps, providerId, tokens, models) {
23473
23315
  const entry = {
23474
23316
  label: "oauth-default",
23475
23317
  apiKey: tokens.access,
@@ -23478,18 +23320,229 @@ async function saveCodexTokens(deps, providerId, tokens, accountId) {
23478
23320
  expiresAt: new Date(tokens.expires).toISOString(),
23479
23321
  refreshToken: tokens.refresh,
23480
23322
  tokenType: "bearer",
23481
- scope: SCOPE,
23482
- accountId
23323
+ scope: SCOPES
23483
23324
  };
23484
23325
  try {
23485
23326
  await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
23486
23327
  const existing = all[providerId];
23487
23328
  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
- }
23329
+ p.family = "anthropic-oauth";
23330
+ if (!p.baseUrl) p.baseUrl = CLAUDE_BASE_URL;
23331
+ if (models.length > 0) p.models = models;
23332
+ else if (!p.models || p.models.length === 0)
23333
+ p.models = ["claude-sonnet-4-6", "claude-opus-4-8"];
23334
+ const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
23335
+ keys.push(entry);
23336
+ writeKeysBack(p, keys);
23337
+ p.activeKey = entry.label;
23338
+ all[providerId] = p;
23339
+ });
23340
+ return true;
23341
+ } catch (err) {
23342
+ deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
23343
+ return false;
23344
+ }
23345
+ }
23346
+
23347
+ // src/auth-menu/github-copilot-oauth.ts
23348
+ init_provider_config_utils();
23349
+ var CLIENT_ID3 = "Iv1.b507a08c87ecfe98";
23350
+ var DEVICE_CODE_URL = "https://github.com/login/device/code";
23351
+ var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
23352
+ var COPILOT_HEADERS = {
23353
+ "User-Agent": "GitHubCopilotChat/0.35.0",
23354
+ "Editor-Version": "vscode/1.107.0",
23355
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
23356
+ "Copilot-Integration-Id": "vscode-chat"
23357
+ };
23358
+ var COPILOT_API_VERSION = "2026-06-01";
23359
+ var COPILOT_PROVIDER_ID = "github-copilot";
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 sleep(ms, signal) {
23372
+ return new Promise((resolve13, reject) => {
23373
+ if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
23374
+ const t = setTimeout(resolve13, ms);
23375
+ signal.addEventListener(
23376
+ "abort",
23377
+ () => {
23378
+ clearTimeout(t);
23379
+ reject(new DOMException("Aborted", "AbortError"));
23380
+ },
23381
+ { once: true }
23382
+ );
23383
+ });
23384
+ }
23385
+ async function startDeviceFlow(signal) {
23386
+ const res = await fetch(DEVICE_CODE_URL, {
23387
+ method: "POST",
23388
+ headers: {
23389
+ accept: "application/json",
23390
+ "content-type": "application/x-www-form-urlencoded",
23391
+ "user-agent": COPILOT_HEADERS["User-Agent"]
23392
+ },
23393
+ body: new URLSearchParams({ client_id: CLIENT_ID3, scope: "read:user" }).toString(),
23394
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
23395
+ });
23396
+ if (!res.ok) throw new Error(`GitHub device-code request failed (${res.status})`);
23397
+ const json = await res.json();
23398
+ if (!json?.device_code || !json.user_code || !json.verification_uri || typeof json.expires_in !== "number") {
23399
+ throw new Error("Invalid device-code response");
23400
+ }
23401
+ return {
23402
+ device_code: json.device_code,
23403
+ user_code: json.user_code,
23404
+ verification_uri: json.verification_uri,
23405
+ interval: json.interval ?? 5,
23406
+ expires_in: json.expires_in
23407
+ };
23408
+ }
23409
+ async function pollForGitHubToken(device, signal) {
23410
+ let intervalMs = device.interval * 1e3;
23411
+ const expiresAt = Date.now() + device.expires_in * 1e3;
23412
+ while (Date.now() < expiresAt) {
23413
+ await sleep(intervalMs, signal);
23414
+ const res = await fetch(ACCESS_TOKEN_URL, {
23415
+ method: "POST",
23416
+ headers: {
23417
+ accept: "application/json",
23418
+ "content-type": "application/x-www-form-urlencoded",
23419
+ "user-agent": COPILOT_HEADERS["User-Agent"]
23420
+ },
23421
+ body: new URLSearchParams({
23422
+ client_id: CLIENT_ID3,
23423
+ device_code: device.device_code,
23424
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
23425
+ }).toString(),
23426
+ signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
23427
+ });
23428
+ const json = await res.json().catch(() => ({}));
23429
+ if (json.access_token) return json.access_token;
23430
+ if (json.error === "authorization_pending") continue;
23431
+ if (json.error === "slow_down") {
23432
+ intervalMs += 5e3;
23433
+ continue;
23434
+ }
23435
+ throw new Error(`Device flow failed: ${json.error ?? "unknown error"}`);
23436
+ }
23437
+ throw new Error("Device code expired \u2014 please restart the login.");
23438
+ }
23439
+ function isUsableCopilotChatModel(item) {
23440
+ if (typeof item.id !== "string" || item.id.length === 0) return false;
23441
+ const cap = item.capabilities;
23442
+ if (cap?.type !== "chat") return false;
23443
+ if (cap.supports?.tool_calls !== true) return false;
23444
+ const eps = item.supported_endpoints;
23445
+ if (Array.isArray(eps) && !eps.includes("/chat/completions")) return false;
23446
+ if (item.policy?.state === "disabled") return false;
23447
+ if (item.vendor === "Experimental") return false;
23448
+ return true;
23449
+ }
23450
+ async function fetchCopilotModels(copilotToken, signal) {
23451
+ try {
23452
+ const base = copilotBaseUrlFromToken(copilotToken);
23453
+ const res = await fetch(`${base}/models`, {
23454
+ headers: {
23455
+ accept: "application/json",
23456
+ authorization: `Bearer ${copilotToken}`,
23457
+ "X-GitHub-Api-Version": COPILOT_API_VERSION,
23458
+ ...COPILOT_HEADERS
23459
+ },
23460
+ signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
23461
+ });
23462
+ if (!res.ok) return [];
23463
+ const json = await res.json();
23464
+ const data = json?.data;
23465
+ if (!Array.isArray(data)) return [];
23466
+ const usable = data.filter(isUsableCopilotChatModel);
23467
+ usable.sort((a, b) => copilotModelRank(a) - copilotModelRank(b));
23468
+ return usable.map((m) => m.id);
23469
+ } catch {
23470
+ return [];
23471
+ }
23472
+ }
23473
+ function copilotModelRank(item) {
23474
+ if (item.is_chat_default === true) return 0;
23475
+ if (item.is_chat_fallback === true) return 1;
23476
+ return 2;
23477
+ }
23478
+ async function runCopilotOAuthLogin(deps, opts = {}) {
23479
+ const providerId = opts.providerId ?? COPILOT_PROVIDER_ID;
23480
+ const ac = new AbortController();
23481
+ const onSig = () => ac.abort();
23482
+ process.on("SIGINT", onSig);
23483
+ try {
23484
+ deps.renderer.write(
23485
+ color.bold(`
23486
+ Sign in with GitHub Copilot \u2014 ${color.cyan(providerId)}
23487
+ `) + 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")
23488
+ );
23489
+ const device = await startDeviceFlow(ac.signal);
23490
+ deps.renderer.write(
23491
+ color.bold(` ${"\u2500".repeat(56)}
23492
+ `) + color.bold(" Open this URL and enter the code:\n") + color.cyan(` ${device.verification_uri}
23493
+ `) + color.bold(" Code: ") + color.green(color.bold(device.user_code)) + "\n" + color.bold(` ${"\u2500".repeat(56)}
23494
+
23495
+ `)
23496
+ );
23497
+ openBrowser3(device.verification_uri);
23498
+ deps.renderer.write(color.dim(" Waiting for you to authorize in the browser...\n"));
23499
+ const githubToken = await pollForGitHubToken(device, ac.signal);
23500
+ deps.renderer.write(color.dim(" Authorized. Fetching your Copilot token...\n"));
23501
+ const copilot = await refreshCopilotToken(githubToken, ac.signal);
23502
+ const models = await fetchCopilotModels(copilot.token, ac.signal);
23503
+ const saved = await saveCopilotTokens(
23504
+ deps,
23505
+ providerId,
23506
+ copilot.token,
23507
+ githubToken,
23508
+ copilot.expires,
23509
+ models
23510
+ );
23511
+ if (!saved) return 1;
23512
+ deps.renderer.write(color.green("\n \u2713 Signed in with GitHub Copilot!\n"));
23513
+ const modelHint = models[0] ?? "gpt-4o";
23514
+ deps.renderer.writeInfo(
23515
+ ` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
23516
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
23517
+ ` + color.dim(" The Copilot token refreshes automatically.\n")
23518
+ );
23519
+ return 0;
23520
+ } catch (err) {
23521
+ const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
23522
+ deps.renderer.writeError(` Login failed: ${msg}`);
23523
+ return 1;
23524
+ } finally {
23525
+ process.off("SIGINT", onSig);
23526
+ }
23527
+ }
23528
+ async function saveCopilotTokens(deps, providerId, copilotToken, githubToken, expires, models) {
23529
+ const entry = {
23530
+ label: "oauth-default",
23531
+ apiKey: copilotToken,
23532
+ createdAt: nowIso(),
23533
+ authMethod: "oauth",
23534
+ expiresAt: new Date(expires).toISOString(),
23535
+ refreshToken: githubToken,
23536
+ tokenType: "bearer"
23537
+ };
23538
+ try {
23539
+ await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
23540
+ const existing = all[providerId];
23541
+ const p = existing ? { ...existing } : { type: providerId };
23542
+ p.family = "github-copilot";
23543
+ if (!p.baseUrl) p.baseUrl = copilotBaseUrlFromToken(copilotToken);
23544
+ if (models.length > 0) p.models = models;
23545
+ else if (!p.models || p.models.length === 0) p.models = ["gpt-4o"];
23493
23546
  const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
23494
23547
  keys.push(entry);
23495
23548
  writeKeysBack(p, keys);
@@ -25170,7 +25223,7 @@ async function addMcpServer(args, deps) {
25170
25223
  const serverCfg = { ...factory };
25171
25224
  serverCfg.enabled = enable;
25172
25225
  const existing = await readJsonObjectFile(deps.paths.globalConfig);
25173
- const mcpServers = isRecord(existing.mcpServers) ? existing.mcpServers : {};
25226
+ const mcpServers = isRecord2(existing.mcpServers) ? existing.mcpServers : {};
25174
25227
  if (mcpServers[name])
25175
25228
  deps.renderer.writeWarning(`Server "${name}" already in config. Updating.
25176
25229
  `);
@@ -25190,7 +25243,7 @@ async function removeMcpServer(name, deps) {
25190
25243
  return 1;
25191
25244
  }
25192
25245
  const existing = await readJsonObjectFile(deps.paths.globalConfig);
25193
- const mcpServers = isRecord(existing.mcpServers) ? existing.mcpServers : {};
25246
+ const mcpServers = isRecord2(existing.mcpServers) ? existing.mcpServers : {};
25194
25247
  if (!mcpServers[name]) {
25195
25248
  deps.renderer.writeError(`Server "${name}" not in config.
25196
25249
  `);
@@ -25203,7 +25256,7 @@ async function removeMcpServer(name, deps) {
25203
25256
  `);
25204
25257
  return 0;
25205
25258
  }
25206
- function isRecord(value) {
25259
+ function isRecord2(value) {
25207
25260
  return typeof value === "object" && value !== null && !Array.isArray(value);
25208
25261
  }
25209
25262
  var MODEL_PROFILES = [
@@ -25455,13 +25508,12 @@ function roleCat(role) {
25455
25508
  }
25456
25509
  function createProviderForId(providerId, cfg) {
25457
25510
  const savedCfg = cfg.providers?.[providerId];
25458
- const resolvedProviderId = savedCfg?.type ?? providerId;
25459
25511
  const cfgWithType = Object.assign(
25460
- { type: resolvedProviderId },
25512
+ { type: providerId },
25461
25513
  savedCfg ?? { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }
25462
25514
  );
25463
25515
  try {
25464
- return makeProviderFromConfig(resolvedProviderId, cfgWithType);
25516
+ return makeProviderFromConfig(providerId, cfgWithType);
25465
25517
  } catch {
25466
25518
  return void 0;
25467
25519
  }
@@ -26237,8 +26289,8 @@ async function upsertPlugin(spec, opts, deps, verb) {
26237
26289
  if (idx >= 0) plugins[idx] = nextEntry;
26238
26290
  else plugins.push(nextEntry);
26239
26291
  const features = {
26240
- ...isRecord2(deps.config.features) ? deps.config.features : {},
26241
- ...isRecord2(existing.features) ? existing.features : {},
26292
+ ...isRecord3(deps.config.features) ? deps.config.features : {},
26293
+ ...isRecord3(existing.features) ? existing.features : {},
26242
26294
  plugins: true
26243
26295
  };
26244
26296
  existing.plugins = plugins;
@@ -26272,7 +26324,7 @@ async function removePlugin(spec, deps) {
26272
26324
  function errorResult(message) {
26273
26325
  return { code: 1, level: "error", message };
26274
26326
  }
26275
- function isRecord2(value) {
26327
+ function isRecord3(value) {
26276
26328
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
26277
26329
  }
26278
26330
 
@@ -27131,37 +27183,38 @@ async function listFleetRuns(deps) {
27131
27183
  return 1;
27132
27184
  }
27133
27185
  const runs = [];
27134
- for (const id of entries) {
27135
- const runDir = path4.join(deps.paths.projectSessions, id);
27136
- let stat8;
27137
- try {
27138
- stat8 = await fs4.stat(runDir);
27139
- } catch {
27140
- continue;
27141
- }
27142
- if (!stat8.isDirectory()) continue;
27143
- let manifest = false;
27144
- let checkpoint = false;
27145
- let subagentCount = 0;
27146
- let subagentsDir;
27147
- try {
27148
- await fs4.access(path4.join(runDir, "fleet.json"));
27149
- manifest = true;
27150
- } catch {
27151
- }
27152
- try {
27153
- await fs4.access(path4.join(runDir, "checkpoint.json"));
27154
- checkpoint = true;
27155
- } catch {
27156
- }
27157
- try {
27158
- subagentsDir = path4.join(runDir, "subagents");
27159
- const files = await fs4.readdir(subagentsDir);
27160
- subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
27161
- } catch {
27162
- }
27163
- runs.push({ id, manifest, checkpoint, subagents: subagentCount });
27164
- }
27186
+ const perEntry = await Promise.all(
27187
+ entries.map(async (id) => {
27188
+ const runDir = path4.join(deps.paths.projectSessions, id);
27189
+ try {
27190
+ const stat8 = await fs4.stat(runDir);
27191
+ if (!stat8.isDirectory()) return null;
27192
+ } catch {
27193
+ return null;
27194
+ }
27195
+ const [manifestResult, checkpointResult, subagentFilesResult] = await Promise.all([
27196
+ fs4.access(path4.join(runDir, "fleet.json")).then(
27197
+ () => true,
27198
+ () => false
27199
+ ),
27200
+ fs4.access(path4.join(runDir, "checkpoint.json")).then(
27201
+ () => true,
27202
+ () => false
27203
+ ),
27204
+ fs4.readdir(path4.join(runDir, "subagents")).then(
27205
+ (files) => files.filter((f) => f.endsWith(".jsonl")).length,
27206
+ () => 0
27207
+ )
27208
+ ]);
27209
+ return {
27210
+ id,
27211
+ manifest: manifestResult,
27212
+ checkpoint: checkpointResult,
27213
+ subagents: subagentFilesResult
27214
+ };
27215
+ })
27216
+ );
27217
+ for (const r of perEntry) if (r) runs.push(r);
27165
27218
  if (runs.length === 0) {
27166
27219
  deps.renderer.write("No fleet runs found.\n");
27167
27220
  return 0;
@@ -27275,17 +27328,22 @@ Fleet Run: ${runId}
27275
27328
  deps.renderer.write(`
27276
27329
  Subagent transcripts (${subagentFiles.length}):
27277
27330
  `);
27278
- for (const f of subagentFiles.sort()) {
27279
- const filePath = path4.join(subagentsDir, f);
27280
- let size;
27281
- try {
27282
- const s = await fs4.stat(filePath);
27283
- size = s.size;
27284
- } catch {
27285
- size = 0;
27286
- }
27331
+ const sortedFiles = subagentFiles.sort();
27332
+ const sizes = await Promise.all(
27333
+ sortedFiles.map(async (f) => {
27334
+ const filePath = path4.join(subagentsDir, f);
27335
+ try {
27336
+ const s = await fs4.stat(filePath);
27337
+ return s.size;
27338
+ } catch {
27339
+ return 0;
27340
+ }
27341
+ })
27342
+ );
27343
+ for (let i = 0; i < sortedFiles.length; i++) {
27344
+ const size = sizes[i] ?? 0;
27287
27345
  const sizeStr = size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)}MB` : `${(size / 1024).toFixed(0)}KB`;
27288
- deps.renderer.write(` ${color.dim(f)} ${color.dim(sizeStr)}
27346
+ deps.renderer.write(` ${color.dim(sortedFiles[i] ?? "")} ${color.dim(sizeStr)}
27289
27347
  `);
27290
27348
  }
27291
27349
  } else {
@@ -27689,9 +27747,10 @@ var helpCmd = async (_args, deps) => {
27689
27747
  " --yolo-destructive Deprecated \u2014 YOLO now auto-approves everything by default",
27690
27748
  " --tui / --no-tui Force or disable TUI mode",
27691
27749
  " --mouse Full mouse mode in the TUI (in-app scroll + clickable UI)",
27692
- " --webui [--port <n>] [--open] Serve the browser UI + WS bridge (prints a URL,",
27693
- " --open pops the browser; shares this terminal's",
27694
- " agent; auto-picks free ports)",
27750
+ " --webui [--host <h>] [--webui-port <n>] [--ws-port <n>] [--webui-token <t>] [--open]",
27751
+ " [--webui-public-url <url>] [--webui-public-ws-url <url>] [--webui-require-token]",
27752
+ " Serve the browser UI + WS bridge (prints a token URL,",
27753
+ " --open pops the browser; shares this terminal's agent)",
27695
27754
  ' --eternal "<mission>" Start an eternal-autonomy loop',
27696
27755
  " --no-hints Hide launch hints",
27697
27756
  " --skip-index Skip codebase indexing and the large-codebase prompt"
@@ -28046,7 +28105,7 @@ async function boot(argv) {
28046
28105
  await persistLaunchChoices(wpaths.globalConfig, toPersist);
28047
28106
  } catch {
28048
28107
  }
28049
- printLaunchHints(renderer, flags, {
28108
+ await printLaunchHints(renderer, flags, {
28050
28109
  cursorFile: path4.join(wpaths.cacheDir, "hint-cursor")
28051
28110
  });
28052
28111
  } else {
@@ -28188,6 +28247,8 @@ function registerBuiltinTools(deps) {
28188
28247
  makeMailInboxTool({ projectDir: deps.wpaths.projectDir, events: deps.events })
28189
28248
  );
28190
28249
  applyToolDescriptionModes(deps.toolRegistry, deps.config.tools?.descriptionMode);
28250
+ configureExecPolicy(deps.config.tools?.exec ?? {});
28251
+ configureAutophasePolicy(deps.config.tools?.exec ?? {});
28191
28252
  }
28192
28253
 
28193
28254
  // src/cli-eternal-flag.ts
@@ -28291,6 +28352,9 @@ function applyNodeEnvDefault() {
28291
28352
  process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] = "1";
28292
28353
  }
28293
28354
  }
28355
+ function applySessionShellDefault() {
28356
+ ensureSessionShell();
28357
+ }
28294
28358
  async function applyPrintUpdateNotice(initialUpdateInfo) {
28295
28359
  return await printUpdateNotice(initialUpdateInfo);
28296
28360
  }
@@ -28302,6 +28366,7 @@ async function applyDebugStreamSeed(config) {
28302
28366
  }
28303
28367
  async function runPreflight(config, initialUpdateInfo) {
28304
28368
  applyNodeEnvDefault();
28369
+ applySessionShellDefault();
28305
28370
  const updateInfo = await applyPrintUpdateNotice(initialUpdateInfo);
28306
28371
  await applyDebugStreamSeed(config);
28307
28372
  return {
@@ -28786,11 +28851,79 @@ async function runWebUIDispatch(ctx) {
28786
28851
  agent.disableInteractiveConfirmation();
28787
28852
  renderer.setSilent(true);
28788
28853
  const { runWebUI: runWebUI2 } = await Promise.resolve().then(() => (init_webui_server(), webui_server_exports));
28854
+ const flagValue = (names) => {
28855
+ for (const name of names) {
28856
+ if (!Object.prototype.hasOwnProperty.call(flags, name)) continue;
28857
+ const value = flags[name];
28858
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
28859
+ throw new Error(`--${name} requires a value`);
28860
+ }
28861
+ return void 0;
28862
+ };
28863
+ const flagBoolean = (names) => {
28864
+ for (const name of names) {
28865
+ if (!Object.prototype.hasOwnProperty.call(flags, name)) continue;
28866
+ const value = flags[name];
28867
+ if (value === void 0) continue;
28868
+ if (typeof value === "boolean") return value;
28869
+ const normalized = value.trim().toLowerCase();
28870
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
28871
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
28872
+ throw new Error(`--${name} must be a boolean value`);
28873
+ }
28874
+ return void 0;
28875
+ };
28876
+ const envFlag2 = (name) => {
28877
+ const value = process.env[name]?.trim().toLowerCase();
28878
+ return value === "1" || value === "true" || value === "yes" || value === "on";
28879
+ };
28880
+ const parsePort = (value, fallback, label) => {
28881
+ if (value === void 0) return fallback;
28882
+ const parsed = Number.parseInt(value, 10);
28883
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
28884
+ throw new Error(`${label} must be a port between 1 and 65535`);
28885
+ }
28886
+ return parsed;
28887
+ };
28888
+ let webuiHost;
28889
+ let webuiHttpPort;
28890
+ let webuiWsPort;
28891
+ let webuiAccessToken;
28892
+ let webuiPublicUrl;
28893
+ let webuiPublicWsUrl;
28894
+ let webuiRequireToken;
28895
+ try {
28896
+ webuiHost = flagValue(["webui-host", "host"]) ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
28897
+ webuiHttpPort = parsePort(
28898
+ flagValue(["webui-port", "http-port"]) ?? process.env["WEBUI_PORT"] ?? process.env["PORT"],
28899
+ 3456,
28900
+ "--webui-port"
28901
+ );
28902
+ webuiWsPort = parsePort(
28903
+ flagValue(["ws-port"]) ?? flagValue(["port"]) ?? process.env["WS_PORT"],
28904
+ 3457,
28905
+ "--ws-port"
28906
+ );
28907
+ webuiAccessToken = flagValue(["webui-token"]) ?? process.env["WEBUI_TOKEN"] ?? process.env["WEBUI_AUTH_TOKEN"];
28908
+ webuiPublicUrl = flagValue(["webui-public-url", "public-url"]) ?? process.env["WEBUI_PUBLIC_URL"];
28909
+ webuiPublicWsUrl = flagValue(["webui-public-ws-url", "public-ws-url"]) ?? process.env["WEBUI_PUBLIC_WS_URL"];
28910
+ webuiRequireToken = flagBoolean(["webui-require-token", "require-token"]) ?? envFlag2("WEBUI_REQUIRE_TOKEN");
28911
+ } catch (err) {
28912
+ renderer.setSilent(false);
28913
+ renderer.writeInfo(color.red(` ${err instanceof Error ? err.message : String(err)}`));
28914
+ return 1;
28915
+ }
28789
28916
  const webuiPromise = runWebUI2({
28790
28917
  agent,
28791
28918
  events,
28792
28919
  session,
28793
- port: Number.parseInt(String(flags.port ?? "3457"), 10),
28920
+ host: webuiHost,
28921
+ port: webuiWsPort,
28922
+ httpPort: webuiHttpPort,
28923
+ accessToken: webuiAccessToken,
28924
+ publicUrl: webuiPublicUrl,
28925
+ publicWsUrl: webuiPublicWsUrl,
28926
+ requireToken: webuiRequireToken,
28794
28927
  projectRoot,
28795
28928
  appConfig: config,
28796
28929
  open: !!flags.open,
@@ -28814,16 +28947,13 @@ async function runWebUIDispatch(ctx) {
28814
28947
  needsSetup,
28815
28948
  sddSubagentFactory,
28816
28949
  // Print the "open this" banner only once the server is actually
28817
- // listening, using the RESOLVED ports. The requested port
28818
- // (flags.port) auto-advances past busy ports inside runWebUI, so a
28819
- // banner printed up-front with flags.port lies whenever 3456/3457 are
28820
- // taken (a second instance, leftover sockets). Bind is 127.0.0.1-only,
28821
- // so the host must be the literal IPv4 loopback — `localhost` resolves
28822
- // to `::1` first on Windows and never reaches the server.
28823
- onListening: ({ httpPort: boundHttpPort }) => {
28950
+ // listening, using the RESOLVED ports. Requested ports auto-advance past
28951
+ // busy ports inside runWebUI, so a banner printed up-front lies whenever
28952
+ // 3456/3457 are taken (a second instance, leftover sockets).
28953
+ onListening: ({ url }) => {
28824
28954
  renderer.writeInfo(
28825
28955
  color.green(
28826
- ` \u2726 WebUI running \u2192 ${color.bold(`http://127.0.0.1:${boundHttpPort}`)}`
28956
+ ` \u2726 WebUI running \u2192 ${color.bold(url)}`
28827
28957
  )
28828
28958
  );
28829
28959
  renderer.writeInfo(
@@ -29481,19 +29611,26 @@ async function resumeSession(ctx, sessionId) {
29481
29611
  }
29482
29612
  function createSettingsAdapter(ctx) {
29483
29613
  const { configStore, wpaths, fleetStreamController, applyLiveSettings } = ctx;
29614
+ const deriveFsAccess = deriveFsAccessPair;
29484
29615
  function getSettings() {
29485
29616
  const cfg = configStore.get();
29486
29617
  const autonomy = cfg.autonomy;
29487
29618
  const rawMode = autonomy?.defaultMode;
29488
29619
  const mode = rawMode === "suggest" || rawMode === "auto" ? rawMode : "off";
29489
29620
  const modelRuntime = cfg.modelRuntime;
29621
+ const contextModeRaw = cfg.context?.mode;
29622
+ const contextMode = contextModeRaw === "frugal" || contextModeRaw === "deep" || contextModeRaw === "archival" ? contextModeRaw : "balanced";
29490
29623
  const reasoningEffortRaw = modelRuntime?.reasoning?.effort;
29491
29624
  const reasoningEffort = reasoningEffortRaw === "none" || reasoningEffortRaw === "minimal" || reasoningEffortRaw === "low" || reasoningEffortRaw === "medium" || reasoningEffortRaw === "high" || reasoningEffortRaw === "xhigh" || reasoningEffortRaw === "max" ? reasoningEffortRaw : "high";
29625
+ const featuresAllow = cfg.features?.allowOutsideProjectRoot;
29626
+ const toolsRestrict = cfg.tools?.restrictToProjectRoot;
29627
+ const resolvedAllow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
29628
+ const resolvedRestrict = !resolvedAllow;
29492
29629
  return {
29493
29630
  mode,
29494
29631
  delayMs: autonomy?.autoProceedDelayMs ?? 45e3,
29495
29632
  titleAnimation: autonomy?.terminalTitleAnimation !== false,
29496
- yolo: autonomy?.yolo ?? false,
29633
+ yolo: cfg.yolo ?? (autonomy?.yolo ?? false),
29497
29634
  streamFleet: autonomy?.streamFleet !== false,
29498
29635
  chime: autonomy?.chime ?? false,
29499
29636
  confirmExit: autonomy?.confirmExit !== false,
@@ -29504,15 +29641,16 @@ function createSettingsAdapter(ctx) {
29504
29641
  featureSkills: cfg.features?.skills !== false,
29505
29642
  featureModelsRegistry: cfg.features?.modelsRegistry !== false,
29506
29643
  featureTokenSaving: normalizeTokenSavingTier(cfg.features?.tokenSavingMode),
29507
- allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
29644
+ allowOutsideProjectRoot: resolvedAllow,
29508
29645
  contextAutoCompact: cfg.context?.autoCompact !== false,
29509
29646
  contextStrategy: cfg.context?.strategy ?? "hybrid",
29510
- maxConcurrent: cfg.maxConcurrent ?? 0,
29647
+ contextMode,
29648
+ maxConcurrent: cfg.maxConcurrent ?? 4,
29511
29649
  logLevel: cfg.log?.level ?? "info",
29512
29650
  auditLevel: cfg.session?.auditLevel ?? "standard",
29513
29651
  indexOnStart: cfg.indexing?.onSessionStart !== false,
29514
29652
  maxIterations: cfg.tools?.maxIterations ?? 500,
29515
- restrictFsToRoot: cfg.tools?.restrictToProjectRoot ?? false,
29653
+ restrictFsToRoot: resolvedRestrict,
29516
29654
  autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
29517
29655
  debugStream: cfg.debugStream ?? false,
29518
29656
  statuslineMode: autonomy?.statuslineMode === "minimum" ? "minimum" : "detailed",
@@ -29533,48 +29671,46 @@ function createSettingsAdapter(ctx) {
29533
29671
  }
29534
29672
  async function saveSettings(s) {
29535
29673
  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) {
29674
+ 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
29675
  const configScope = s.configScope ?? configStore.get().configScope ?? "global";
29564
29676
  const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
29565
29677
  let raw;
29566
29678
  try {
29567
29679
  raw = await fs4.readFile(targetPath, "utf8");
29568
29680
  } catch (err) {
29569
- throw new Error(
29570
- `Failed to read config at ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
29571
- { cause: err }
29572
- );
29681
+ if (err.code !== "ENOENT") {
29682
+ throw new Error(
29683
+ `Failed to read config at ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
29684
+ { cause: err }
29685
+ );
29686
+ }
29687
+ raw = "{}";
29573
29688
  }
29574
29689
  const parsed = JSON.parse(raw);
29575
29690
  const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
29691
+ const autonomy = decrypted.autonomy ?? {};
29692
+ if (s.mode !== void 0) autonomy.defaultMode = s.mode;
29693
+ if (s.delayMs !== void 0) autonomy.autoProceedDelayMs = s.delayMs;
29694
+ if (s.titleAnimation !== void 0) autonomy.terminalTitleAnimation = s.titleAnimation;
29695
+ if (s.yolo !== void 0) autonomy.yolo = s.yolo;
29696
+ if (s.streamFleet !== void 0) autonomy.streamFleet = s.streamFleet;
29697
+ if (s.chime !== void 0) autonomy.chime = s.chime;
29698
+ if (s.confirmExit !== void 0) autonomy.confirmExit = s.confirmExit;
29699
+ if (s.mouseMode !== void 0) autonomy.mouseMode = s.mouseMode;
29700
+ if (s.enhanceDelayMs !== void 0) autonomy.enhanceDelayMs = s.enhanceDelayMs;
29701
+ if (s.enhanceEnabled !== void 0) autonomy.enhance = s.enhanceEnabled;
29702
+ if (s.enhanceLanguage !== void 0) autonomy.enhanceLanguage = s.enhanceLanguage;
29703
+ if (s.statuslineMode !== void 0) autonomy.statuslineMode = s.statuslineMode;
29704
+ if (s.thinkingWord !== void 0)
29705
+ autonomy.thinkingWord = normalizeTuiThinkingWord(s.thinkingWord);
29706
+ if (s.autonomyNextPrompt !== void 0) autonomy.autonomyNextPrompt = s.autonomyNextPrompt;
29707
+ if (s.autoProceedMaxIterations !== void 0)
29708
+ autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
29709
+ decrypted.autonomy = autonomy;
29576
29710
  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) {
29711
+ if (s.yolo !== void 0) decrypted.yolo = s.yolo;
29712
+ const fsAccess = deriveFsAccess(s);
29713
+ 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
29714
  const feats = decrypted.features ?? {};
29579
29715
  if (s.featureMcp !== void 0) feats.mcp = s.featureMcp;
29580
29716
  if (s.featurePlugins !== void 0) feats.plugins = s.featurePlugins;
@@ -29582,15 +29718,17 @@ function createSettingsAdapter(ctx) {
29582
29718
  if (s.featureSkills !== void 0) feats.skills = s.featureSkills;
29583
29719
  if (s.featureModelsRegistry !== void 0) feats.modelsRegistry = s.featureModelsRegistry;
29584
29720
  if (s.featureTokenSaving !== void 0) feats.tokenSavingMode = s.featureTokenSaving;
29585
- if (s.allowOutsideProjectRoot !== void 0) feats.allowOutsideProjectRoot = s.allowOutsideProjectRoot;
29721
+ if (fsAccess !== void 0) feats.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
29586
29722
  decrypted.features = feats;
29587
29723
  }
29588
- if (s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0) {
29724
+ if (s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0) {
29589
29725
  const c = decrypted.context ?? {};
29590
29726
  if (s.contextAutoCompact !== void 0) c.autoCompact = s.contextAutoCompact;
29591
29727
  if (s.contextStrategy !== void 0) c.strategy = s.contextStrategy;
29728
+ if (s.contextMode !== void 0) c.mode = s.contextMode;
29592
29729
  decrypted.context = c;
29593
29730
  }
29731
+ if (s.maxConcurrent !== void 0) decrypted.maxConcurrent = s.maxConcurrent;
29594
29732
  if (s.logLevel !== void 0) {
29595
29733
  const log = decrypted.log ?? {};
29596
29734
  log.level = s.logLevel;
@@ -29606,32 +29744,38 @@ function createSettingsAdapter(ctx) {
29606
29744
  idx.onSessionStart = s.indexOnStart;
29607
29745
  decrypted.indexing = idx;
29608
29746
  }
29609
- if (s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0) {
29747
+ if (s.maxIterations !== void 0 || fsAccess !== void 0) {
29610
29748
  const tools = decrypted.tools ?? {};
29611
29749
  if (s.maxIterations !== void 0) tools.maxIterations = s.maxIterations;
29612
- if (s.restrictFsToRoot !== void 0) tools.restrictToProjectRoot = s.restrictFsToRoot;
29750
+ if (fsAccess !== void 0) tools.restrictToProjectRoot = fsAccess.restrictToProjectRoot;
29613
29751
  decrypted.tools = tools;
29614
29752
  }
29615
- if (s.restrictFsToRoot !== void 0) {
29616
- const features = decrypted.features ?? {};
29617
- features.allowOutsideProjectRoot = !s.restrictFsToRoot;
29618
- decrypted.features = features;
29619
- }
29620
29753
  if (s.debugStream !== void 0) {
29621
29754
  decrypted.debugStream = s.debugStream;
29622
29755
  const { setDebugStreamEnabled } = await import('@wrongstack/providers');
29623
29756
  setDebugStreamEnabled(s.debugStream);
29624
29757
  }
29625
29758
  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;
29759
+ if (s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0) {
29760
+ const modelRuntime = decrypted.modelRuntime ?? {};
29761
+ if (s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0) {
29762
+ const reasoning = modelRuntime.reasoning ?? {};
29763
+ if (s.reasoningMode !== void 0) reasoning.mode = s.reasoningMode;
29764
+ if (s.reasoningEffort !== void 0) reasoning.effort = s.reasoningEffort;
29765
+ if (s.reasoningPreserve !== void 0) reasoning.preserve = s.reasoningPreserve;
29766
+ modelRuntime.reasoning = reasoning;
29767
+ }
29768
+ if (s.cacheTtl !== void 0) {
29769
+ const cache = modelRuntime.cache ?? {};
29770
+ if (s.cacheTtl === "default") {
29771
+ delete cache.ttl;
29772
+ } else {
29773
+ cache.ttl = s.cacheTtl;
29774
+ }
29775
+ if (Object.keys(cache).length > 0) modelRuntime.cache = cache;
29776
+ else delete modelRuntime.cache;
29777
+ }
29778
+ decrypted.modelRuntime = modelRuntime;
29635
29779
  }
29636
29780
  const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
29637
29781
  const encrypted = encryptConfigSecrets$1(toWrite, noOpVault);
@@ -29639,19 +29783,63 @@ function createSettingsAdapter(ctx) {
29639
29783
  await fs4.mkdir(path4.dirname(targetPath), { recursive: true });
29640
29784
  }
29641
29785
  await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
29786
+ const currentConfig = configStore.get();
29787
+ const nextModelRuntime = {
29788
+ ...currentConfig.modelRuntime,
29789
+ ...decrypted.modelRuntime ?? {}
29790
+ };
29791
+ if (s.cacheTtl === "default") {
29792
+ delete nextModelRuntime.cache;
29793
+ }
29642
29794
  configStore.update({
29643
29795
  ...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 } : {},
29796
+ ...s.yolo !== void 0 ? { yolo: s.yolo } : {},
29797
+ ...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 ? {
29798
+ features: {
29799
+ ...currentConfig.features,
29800
+ ...decrypted.features ?? {}
29801
+ }
29802
+ } : {},
29803
+ ...s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 ? {
29804
+ context: {
29805
+ ...currentConfig.context,
29806
+ ...decrypted.context ?? {}
29807
+ }
29808
+ } : {},
29809
+ ...s.maxConcurrent !== void 0 ? { maxConcurrent: s.maxConcurrent } : {},
29810
+ ...s.logLevel !== void 0 ? {
29811
+ log: {
29812
+ ...currentConfig.log,
29813
+ ...decrypted.log ?? {}
29814
+ }
29815
+ } : {},
29816
+ ...s.auditLevel !== void 0 ? {
29817
+ session: {
29818
+ ...currentConfig.session,
29819
+ ...decrypted.session ?? {}
29820
+ }
29821
+ } : {},
29822
+ ...s.indexOnStart !== void 0 ? {
29823
+ indexing: {
29824
+ ...currentConfig.indexing,
29825
+ ...decrypted.indexing ?? {}
29826
+ }
29827
+ } : {},
29828
+ ...s.maxIterations !== void 0 || fsAccess !== void 0 ? {
29829
+ tools: {
29830
+ ...currentConfig.tools,
29831
+ ...decrypted.tools ?? {}
29832
+ }
29833
+ } : {},
29650
29834
  ...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
29651
29835
  ...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 } } : {}
29836
+ autonomy: {
29837
+ ...currentConfig.autonomy,
29838
+ ...decrypted.autonomy ?? {}
29839
+ },
29840
+ ...s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 ? {
29841
+ modelRuntime: nextModelRuntime
29842
+ } : {}
29655
29843
  });
29656
29844
  }
29657
29845
  if (s.streamFleet !== void 0) fleetStreamController?.setEnabled(s.streamFleet);
@@ -32069,12 +32257,22 @@ var MultiAgentHost = class _MultiAgentHost {
32069
32257
  };
32070
32258
  }
32071
32259
  const tools = subCfg.tools ? [...subCfg.tools] : void 0;
32260
+ const subTokenCounter = new DefaultTokenCounter({
32261
+ registry: this.deps.modelsRegistry,
32262
+ providerId: effProvider,
32263
+ events
32264
+ });
32072
32265
  const ctx = new Context({
32073
32266
  systemPrompt: baseSystem,
32074
32267
  provider,
32075
32268
  session: subSession,
32076
32269
  signal: new AbortController().signal,
32077
- tokenCounter: this.deps.tokenCounter,
32270
+ // Keep per-request context pressure isolated. The leader statusline
32271
+ // reads currentRequestTokens() from the leader counter; sharing it with
32272
+ // subagents lets the latest subagent provider call overwrite the ctx
32273
+ // chip even though subagent ctx is reported separately as
32274
+ // subagent.ctx_pct.
32275
+ tokenCounter: subTokenCounter,
32078
32276
  cwd: subCwd,
32079
32277
  projectRoot: this.deps.projectRoot,
32080
32278
  // Subagents inherit the leader's filesystem-access scope.
@@ -32922,7 +33120,7 @@ async function setupCodebaseIndexing(deps) {
32922
33120
  let watcher;
32923
33121
  if (idx.watchExternal) {
32924
33122
  try {
32925
- watcher = fs3.watch(projectRoot, { recursive: true }, (_event, filename) => {
33123
+ watcher = fs37.watch(projectRoot, { recursive: true }, (_event, filename) => {
32926
33124
  if (!filename) return;
32927
33125
  const rel = filename.toString();
32928
33126
  if (isIgnored(rel)) return;
@@ -33137,6 +33335,22 @@ function createAgent(params) {
33137
33335
  tracer: params.tracer
33138
33336
  });
33139
33337
  }
33338
+ function resolveProviderCfg(config, providerId) {
33339
+ const savedCfg = config.providers?.[providerId];
33340
+ const cfg = {
33341
+ ...savedCfg,
33342
+ apiKey: savedCfg?.apiKey ?? config.apiKey,
33343
+ baseUrl: savedCfg?.baseUrl ?? config.baseUrl,
33344
+ type: providerId
33345
+ };
33346
+ const factoryType = savedCfg?.type ?? providerId;
33347
+ return { cfg, factoryType };
33348
+ }
33349
+ function buildProviderForId(args, providerId) {
33350
+ const { cfg, factoryType } = resolveProviderCfg(args.config, providerId);
33351
+ const useRegistry = !!args.config.features.modelsRegistry && args.providerRegistry.has(factoryType);
33352
+ return useRegistry ? args.providerRegistry.create(cfg) : makeProviderFromConfig(providerId, cfg);
33353
+ }
33140
33354
 
33141
33355
  // src/cli-main.ts
33142
33356
  init_plugins();
@@ -33588,6 +33802,7 @@ function wireEventWiring(deps) {
33588
33802
  // src/cli-main.ts
33589
33803
  async function main(argv) {
33590
33804
  applyNodeEnvDefault();
33805
+ applySessionShellDefault();
33591
33806
  const earlyFlags = parseArgs(argv).flags;
33592
33807
  const earlyExit = await handleHelpVersionShortCircuit(argv);
33593
33808
  if (earlyExit !== null) return earlyExit;
@@ -34135,27 +34350,16 @@ async function main(argv) {
34135
34350
  if (depWatcherDispose) {
34136
34351
  teardownHandlers.push(depWatcherDispose);
34137
34352
  }
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
- };
34353
+ const resolveProviderCfg2 = (providerId) => resolveProviderCfg(config, providerId);
34354
+ const buildProviderForId2 = (providerId) => buildProviderForId({ config, providerRegistry }, providerId);
34151
34355
  const refreshMaxContextFor = async (providerId, modelId) => {
34152
- const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
34153
- await refreshMaxContext(resolvedProviderId, modelId, cfgWithType);
34356
+ const { cfg } = resolveProviderCfg2(providerId);
34357
+ await refreshMaxContext(providerId, modelId, cfg);
34154
34358
  };
34155
34359
  agent.extensions.register(
34156
34360
  createFallbackModelExtension({
34157
34361
  getConfig: () => config,
34158
- buildProvider: buildProviderForId,
34362
+ buildProvider: buildProviderForId2,
34159
34363
  onModelSwitch: refreshMaxContextFor,
34160
34364
  events,
34161
34365
  logger
@@ -34170,11 +34374,12 @@ async function main(argv) {
34170
34374
  }
34171
34375
  const switchProviderAndModel = async (providerId, modelId) => {
34172
34376
  try {
34173
- context.provider = buildProviderForId(providerId);
34377
+ context.provider = buildProviderForId2(providerId);
34174
34378
  context.model = modelId;
34175
34379
  config = patchConfig(config, { provider: providerId, model: modelId });
34176
34380
  configStore.update({ provider: providerId, model: modelId });
34177
34381
  await refreshMaxContextFor(providerId, modelId);
34382
+ await refreshActiveReasoningConfig(providerId, modelId);
34178
34383
  return null;
34179
34384
  } catch (err) {
34180
34385
  return err instanceof Error ? err.message : String(err);
@@ -34544,10 +34749,9 @@ async function main(argv) {
34544
34749
  get: () => loadStatuslineConfig(),
34545
34750
  set: (cfg) => saveStatuslineConfig(cfg)
34546
34751
  };
34547
- const hiddenItemsFromConfig = await loadStatuslineConfig();
34752
+ const hiddenItemsFromConfig = await ensureStatuslineConfig();
34548
34753
  const hiddenItemsList = [];
34549
- const ALL_ITEMS = ["todos", "plan", "tasks", "fleet", "git", "elapsed", "context", "cost"];
34550
- for (const k of ALL_ITEMS) {
34754
+ for (const k of STATUSLINE_CONFIG_KEYS) {
34551
34755
  if (!hiddenItemsFromConfig[k]) hiddenItemsList.push(k);
34552
34756
  }
34553
34757
  const statuslineHiddenItems = hiddenItemsList;
@@ -34555,11 +34759,10 @@ async function main(argv) {
34555
34759
  const setStatuslineHiddenItems = (items) => {
34556
34760
  currentHiddenItems = items;
34557
34761
  };
34558
- const ALL_STATUSLINE_KEYS = ["todos", "plan", "tasks", "fleet", "git", "elapsed", "context", "cost", "working_dir"];
34559
34762
  const saveStatuslineHiddenItems = async (items) => {
34560
34763
  currentHiddenItems = items;
34561
34764
  const cfg = { ...DEFAULTS };
34562
- for (const k of ALL_STATUSLINE_KEYS) {
34765
+ for (const k of STATUSLINE_CONFIG_KEYS) {
34563
34766
  cfg[k] = !items.includes(k);
34564
34767
  }
34565
34768
  await saveStatuslineConfig(cfg);
@@ -34610,7 +34813,7 @@ async function main(argv) {
34610
34813
  llmModel: config.model,
34611
34814
  createProvider: (pid) => {
34612
34815
  try {
34613
- return buildProviderForId(pid);
34816
+ return buildProviderForId2(pid);
34614
34817
  } catch {
34615
34818
  return void 0;
34616
34819
  }
@@ -34870,29 +35073,36 @@ ${color.dim("\u2500".repeat(40))}` : "";
34870
35073
  return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
34871
35074
  }
34872
35075
  const found = [];
34873
- for (const runId of runDirs) {
34874
- const runDir = path4.join(subagentsRoot, runId);
34875
- let files;
34876
- try {
34877
- files = await fs4.readdir(runDir);
34878
- } catch {
34879
- continue;
34880
- }
34881
- for (const f of files) {
34882
- if (!f.endsWith(".jsonl")) continue;
34883
- const full = path4.join(runDir, f);
35076
+ const runResults = await Promise.all(
35077
+ runDirs.map(async (runId) => {
35078
+ const runDir = path4.join(subagentsRoot, runId);
35079
+ let files;
34884
35080
  try {
34885
- const stat8 = await fs4.stat(full);
34886
- found.push({
34887
- runId,
34888
- subagentId: f.replace(/\.jsonl$/, ""),
34889
- file: full,
34890
- size: stat8.size
34891
- });
35081
+ files = await fs4.readdir(runDir);
34892
35082
  } catch {
35083
+ return [];
34893
35084
  }
34894
- }
34895
- }
35085
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
35086
+ const fileResults = await Promise.all(
35087
+ jsonlFiles.map(async (f) => {
35088
+ const full = path4.join(runDir, f);
35089
+ try {
35090
+ const stat8 = await fs4.stat(full);
35091
+ return {
35092
+ runId,
35093
+ subagentId: f.replace(/\.jsonl$/, ""),
35094
+ file: full,
35095
+ size: stat8.size
35096
+ };
35097
+ } catch {
35098
+ return null;
35099
+ }
35100
+ })
35101
+ );
35102
+ return fileResults.filter((r) => r !== null);
35103
+ })
35104
+ );
35105
+ for (const runResult of runResults) found.push(...runResult);
34896
35106
  if (found.length === 0) {
34897
35107
  return "No subagent transcripts found on disk.";
34898
35108
  }
@@ -35238,16 +35448,18 @@ Restart WrongStack to load or unload plugin code in this session.`;
35238
35448
  ...errorRing.map((e) => ` [${e.ts}] ${e.phase} ${e.code} \u2014 ${e.message}`)
35239
35449
  ];
35240
35450
  const liveCfg = configStore.get();
35451
+ const liveFamily = liveCfg.providers?.[liveCfg.provider]?.family;
35241
35452
  return [
35242
35453
  `${color.bold("WrongStack diag")}`,
35243
35454
  ` provider: ${liveCfg.provider} / ${context.model}`,
35455
+ liveFamily ? ` family: ${liveFamily}` : null,
35244
35456
  ` projectRoot: ${projectRoot}`,
35245
35457
  ` tokens: in ${u.input} out ${u.output} cacheR ${u.cacheRead ?? 0}`,
35246
35458
  ` cost: $${cost.total.toFixed(4)}`,
35247
35459
  ` tools: ${toolRegistry.list().length}`,
35248
35460
  ` mcpServers: ${mcpRegistry.list().length}`,
35249
35461
  ...errSection
35250
- ].join("\n");
35462
+ ].filter((line) => line !== null).join("\n");
35251
35463
  },
35252
35464
  onStats: () => stats.format(),
35253
35465
  generateCommitMessage: async (diff) => {
@@ -35282,12 +35494,28 @@ Restart WrongStack to load or unload plugin code in this session.`;
35282
35494
  const core = await import('@wrongstack/core');
35283
35495
  let worktrees;
35284
35496
  if (process.env["WRONGSTACK_SDD_WORKTREES"] !== "0") {
35285
- const { spawnSync } = await import('child_process');
35286
- const inGit = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
35287
- cwd: projectRoot,
35288
- encoding: "utf8",
35289
- windowsHide: true
35290
- }).stdout?.trim() === "true";
35497
+ const inGit = await new Promise((resolve13) => {
35498
+ let resolved = false;
35499
+ const settle = (v) => {
35500
+ if (resolved) return;
35501
+ resolved = true;
35502
+ resolve13(v);
35503
+ };
35504
+ try {
35505
+ const child = spawn("git", ["rev-parse", "--is-inside-work-tree"], {
35506
+ cwd: projectRoot,
35507
+ env: process.env,
35508
+ stdio: ["ignore", "pipe", "pipe"],
35509
+ windowsHide: true
35510
+ });
35511
+ const chunks = [];
35512
+ child.stdout?.on("data", (c) => chunks.push(c.toString()));
35513
+ child.on("error", () => settle(false));
35514
+ child.on("close", (code) => settle(code === 0 && chunks.join("").trim() === "true"));
35515
+ } catch {
35516
+ settle(false);
35517
+ }
35518
+ });
35291
35519
  if (inGit) worktrees = new core.WorktreeManager({ projectRoot, events });
35292
35520
  }
35293
35521
  const boardStore = new core.SddBoardStore({ baseDir: wpaths.projectSddBoards });
@@ -35529,12 +35757,26 @@ Restart WrongStack to load or unload plugin code in this session.`;
35529
35757
  if (s.contextAutoCompact !== void 0) {
35530
35758
  autoCompactor?.setEnabled(s.contextAutoCompact);
35531
35759
  }
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
- });
35760
+ if (s.maxConcurrent !== void 0 && s.maxConcurrent > 0) {
35761
+ multiAgentHost.setMaxConcurrent(s.maxConcurrent);
35762
+ events.emit("concurrency.changed", { n: s.maxConcurrent });
35763
+ config = patchConfig(config, { maxConcurrent: s.maxConcurrent });
35764
+ }
35765
+ if (s.restrictFsToRoot !== void 0 || s.allowOutsideProjectRoot !== void 0) {
35766
+ const fsAccess = deriveFsAccessPair(s);
35767
+ if (fsAccess) {
35768
+ context.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
35769
+ config = patchConfig(config, {
35770
+ features: {
35771
+ ...config.features,
35772
+ allowOutsideProjectRoot: fsAccess.allowOutsideProjectRoot
35773
+ },
35774
+ tools: {
35775
+ ...config.tools,
35776
+ restrictToProjectRoot: fsAccess.restrictToProjectRoot
35777
+ }
35778
+ });
35779
+ }
35538
35780
  }
35539
35781
  } catch {
35540
35782
  }