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