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