@wrongstack/cli 0.265.1 → 0.267.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1584 -380
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
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, DefaultSecretScrubber, projectHash, wstackGlobalRoot, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, resolveProviderModelList, DefaultLogger, DefaultModelsRegistry, isStdinTTY, atomicWrite, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, WIDE_SUBAGENT_CAPABILITIES, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, AutonomousCoordinator, noOpVault, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
|
|
2
3
|
import * as fsp5 from 'fs/promises';
|
|
4
|
+
import { decryptConfigSecrets, encryptConfigSecrets, DefaultSecretVault, isSecretField } from '@wrongstack/core/security';
|
|
3
5
|
import * as path4 from 'path';
|
|
4
6
|
import { join } from 'path';
|
|
5
|
-
import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, projectHash, wstackGlobalRoot, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, DefaultLogger, DefaultModelsRegistry, isStdinTTY, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, AutonomousCoordinator, noOpVault, decryptConfigSecrets, encryptConfigSecrets, atomicWrite, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
|
|
6
|
-
import { decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DefaultSecretVault, isSecretField } from '@wrongstack/core/security';
|
|
7
7
|
import * as crypto3 from 'crypto';
|
|
8
8
|
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import os__default from 'os';
|
|
12
|
-
import { findFreePort, AutoPhaseWebSocketHandler, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
|
|
13
|
-
import { makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry } from '@wrongstack/providers';
|
|
12
|
+
import { findFreePort, AutoPhaseWebSocketHandler, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleGitDiff, handleGitChanges, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMcpRestart, handleMcpDisable, handleMcpEnable, handleMcpDiscover, handleMcpSleep, handleMcpWake, handleMcpUpdate, handleMcpRemove, handleMcpAdd, handleMcpList, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser as openBrowser$1, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
|
|
13
|
+
import { setOAuthTokenPersister, makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry, refreshCopilotToken, copilotBaseUrlFromToken } from '@wrongstack/providers';
|
|
14
14
|
import { toErrorMessage } from '@wrongstack/core/utils';
|
|
15
15
|
import { getProcessRegistry, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
|
|
16
16
|
import { DefaultSessionStore } from '@wrongstack/core/storage';
|
|
17
17
|
import { probeLocalLlm } from '@wrongstack/runtime/probe';
|
|
18
|
-
import * as
|
|
18
|
+
import * as fs3 from 'fs';
|
|
19
19
|
import { watch, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
20
20
|
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
21
21
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
@@ -28,6 +28,7 @@ import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, runEnsemble, renderEnsembleT
|
|
|
28
28
|
import { parseNextSteps } from '@wrongstack/tui';
|
|
29
29
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
30
30
|
import { SubagentBudget } from '@wrongstack/core/coordination';
|
|
31
|
+
import { createServer } from 'http';
|
|
31
32
|
import { loadBenchConfig, reportHeaderLine, readSummary, renderMarkdownReport, createPolyglotSuite, createSwebenchSuite, runBenchmark, writeJsonArtifacts, collectCellPredictions, writePredictionsJsonl, gradePolyglot, gradeSwebench } from '@wrongstack/bench';
|
|
32
33
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
33
34
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
@@ -53,6 +54,108 @@ var __export = (target, all) => {
|
|
|
53
54
|
for (var name in all)
|
|
54
55
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
55
56
|
};
|
|
57
|
+
function normalizeKeys(cfg) {
|
|
58
|
+
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
59
|
+
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
60
|
+
}
|
|
61
|
+
if (typeof cfg.apiKey === "string" && cfg.apiKey.length > 0) {
|
|
62
|
+
return [{ label: "default", apiKey: cfg.apiKey, createdAt: "" }];
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
function writeKeysBack(cfg, keys) {
|
|
67
|
+
if (keys.length === 0) {
|
|
68
|
+
delete cfg.apiKeys;
|
|
69
|
+
delete cfg.apiKey;
|
|
70
|
+
delete cfg.activeKey;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
cfg.apiKeys = keys;
|
|
74
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
|
|
75
|
+
delete cfg.apiKey;
|
|
76
|
+
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
77
|
+
cfg.activeKey = active.label;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function resolveActiveApiKey(cfg) {
|
|
81
|
+
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
82
|
+
const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
|
|
83
|
+
return (active ?? cfg.apiKeys[0])?.apiKey;
|
|
84
|
+
}
|
|
85
|
+
return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
|
|
86
|
+
}
|
|
87
|
+
function activeLabel(cfg, keys) {
|
|
88
|
+
if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
|
|
89
|
+
return keys[0]?.label;
|
|
90
|
+
}
|
|
91
|
+
function maskedKey(key) {
|
|
92
|
+
if (!key) return color.dim("\u2014");
|
|
93
|
+
if (key.length <= 8) return color.dim("\u2022".repeat(key.length));
|
|
94
|
+
const head = key.slice(0, 4);
|
|
95
|
+
const tail = key.slice(-4);
|
|
96
|
+
return `${color.dim(head + "\u2026")}${tail}`;
|
|
97
|
+
}
|
|
98
|
+
function nowIso() {
|
|
99
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
100
|
+
}
|
|
101
|
+
async function loadConfigProviders(configPath2, vault, opts) {
|
|
102
|
+
const warn = opts?.warn;
|
|
103
|
+
let raw;
|
|
104
|
+
try {
|
|
105
|
+
raw = await fsp5.readFile(configPath2, "utf8");
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (err.code !== "ENOENT") {
|
|
108
|
+
warn?.(`Could not read ${configPath2}: ${err.message}. Treating as empty.`);
|
|
109
|
+
}
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(raw);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
warn?.(`Config at ${configPath2} is not valid JSON: ${err.message}`);
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
120
|
+
return decrypted.providers ?? {};
|
|
121
|
+
}
|
|
122
|
+
async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
123
|
+
let raw;
|
|
124
|
+
let fileExists2 = true;
|
|
125
|
+
try {
|
|
126
|
+
raw = await fsp5.readFile(configPath2, "utf8");
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (err.code !== "ENOENT") {
|
|
129
|
+
throw new Error(`Refusing to mutate ${configPath2}: ${err.message}`, {
|
|
130
|
+
cause: err
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
fileExists2 = false;
|
|
134
|
+
raw = "{}";
|
|
135
|
+
}
|
|
136
|
+
let parsed;
|
|
137
|
+
try {
|
|
138
|
+
parsed = JSON.parse(raw);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (fileExists2) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
143
|
+
{ cause: err }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
parsed = {};
|
|
147
|
+
}
|
|
148
|
+
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
149
|
+
const providers = decrypted.providers ?? {};
|
|
150
|
+
mutator(providers);
|
|
151
|
+
decrypted.providers = providers;
|
|
152
|
+
const encrypted = encryptConfigSecrets(decrypted, vault);
|
|
153
|
+
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
154
|
+
}
|
|
155
|
+
var init_provider_config_utils = __esm({
|
|
156
|
+
"src/provider-config-utils.ts"() {
|
|
157
|
+
}
|
|
158
|
+
});
|
|
56
159
|
function parseSubcommand(args) {
|
|
57
160
|
const parts = args.trim().split(/\s+/);
|
|
58
161
|
return { cmd: (parts[0] ?? "").toLowerCase(), rest: parts.slice(1) };
|
|
@@ -524,108 +627,6 @@ var init_helpers = __esm({
|
|
|
524
627
|
ENTRY_BASENAMES = /* @__PURE__ */ new Set(["main", "index", "app", "cli", "server", "__main__"]);
|
|
525
628
|
}
|
|
526
629
|
});
|
|
527
|
-
function normalizeKeys(cfg) {
|
|
528
|
-
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
529
|
-
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
530
|
-
}
|
|
531
|
-
if (typeof cfg.apiKey === "string" && cfg.apiKey.length > 0) {
|
|
532
|
-
return [{ label: "default", apiKey: cfg.apiKey, createdAt: "" }];
|
|
533
|
-
}
|
|
534
|
-
return [];
|
|
535
|
-
}
|
|
536
|
-
function writeKeysBack(cfg, keys) {
|
|
537
|
-
if (keys.length === 0) {
|
|
538
|
-
delete cfg.apiKeys;
|
|
539
|
-
delete cfg.apiKey;
|
|
540
|
-
delete cfg.activeKey;
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
cfg.apiKeys = keys;
|
|
544
|
-
const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
|
|
545
|
-
delete cfg.apiKey;
|
|
546
|
-
if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
|
|
547
|
-
cfg.activeKey = active.label;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
function resolveActiveApiKey(cfg) {
|
|
551
|
-
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
552
|
-
const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
|
|
553
|
-
return (active ?? cfg.apiKeys[0])?.apiKey;
|
|
554
|
-
}
|
|
555
|
-
return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
|
|
556
|
-
}
|
|
557
|
-
function activeLabel(cfg, keys) {
|
|
558
|
-
if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
|
|
559
|
-
return keys[0]?.label;
|
|
560
|
-
}
|
|
561
|
-
function maskedKey(key) {
|
|
562
|
-
if (!key) return color.dim("\u2014");
|
|
563
|
-
if (key.length <= 8) return color.dim("\u2022".repeat(key.length));
|
|
564
|
-
const head = key.slice(0, 4);
|
|
565
|
-
const tail = key.slice(-4);
|
|
566
|
-
return `${color.dim(head + "\u2026")}${tail}`;
|
|
567
|
-
}
|
|
568
|
-
function nowIso() {
|
|
569
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
570
|
-
}
|
|
571
|
-
async function loadConfigProviders(configPath2, vault, opts) {
|
|
572
|
-
const warn = opts?.warn;
|
|
573
|
-
let raw;
|
|
574
|
-
try {
|
|
575
|
-
raw = await fsp5.readFile(configPath2, "utf8");
|
|
576
|
-
} catch (err) {
|
|
577
|
-
if (err.code !== "ENOENT") {
|
|
578
|
-
warn?.(`Could not read ${configPath2}: ${err.message}. Treating as empty.`);
|
|
579
|
-
}
|
|
580
|
-
return {};
|
|
581
|
-
}
|
|
582
|
-
let parsed;
|
|
583
|
-
try {
|
|
584
|
-
parsed = JSON.parse(raw);
|
|
585
|
-
} catch (err) {
|
|
586
|
-
warn?.(`Config at ${configPath2} is not valid JSON: ${err.message}`);
|
|
587
|
-
return {};
|
|
588
|
-
}
|
|
589
|
-
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
590
|
-
return decrypted.providers ?? {};
|
|
591
|
-
}
|
|
592
|
-
async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
593
|
-
let raw;
|
|
594
|
-
let fileExists2 = true;
|
|
595
|
-
try {
|
|
596
|
-
raw = await fsp5.readFile(configPath2, "utf8");
|
|
597
|
-
} catch (err) {
|
|
598
|
-
if (err.code !== "ENOENT") {
|
|
599
|
-
throw new Error(`Refusing to mutate ${configPath2}: ${err.message}`, {
|
|
600
|
-
cause: err
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
fileExists2 = false;
|
|
604
|
-
raw = "{}";
|
|
605
|
-
}
|
|
606
|
-
let parsed;
|
|
607
|
-
try {
|
|
608
|
-
parsed = JSON.parse(raw);
|
|
609
|
-
} catch (err) {
|
|
610
|
-
if (fileExists2) {
|
|
611
|
-
throw new Error(
|
|
612
|
-
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
613
|
-
{ cause: err }
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
parsed = {};
|
|
617
|
-
}
|
|
618
|
-
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
619
|
-
const providers = decrypted.providers ?? {};
|
|
620
|
-
mutator(providers);
|
|
621
|
-
decrypted.providers = providers;
|
|
622
|
-
const encrypted = encryptConfigSecrets$1(decrypted, vault);
|
|
623
|
-
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
624
|
-
}
|
|
625
|
-
var init_provider_config_utils = __esm({
|
|
626
|
-
"src/provider-config-utils.ts"() {
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
630
|
function createApi(ownerName, base) {
|
|
630
631
|
return new DefaultPluginAPI({ ownerName, ...base });
|
|
631
632
|
}
|
|
@@ -654,26 +655,26 @@ function fmtDuration(ms) {
|
|
|
654
655
|
const remMin = m - h * 60;
|
|
655
656
|
return `${h}h${remMin}m`;
|
|
656
657
|
}
|
|
657
|
-
function fmtTaskResultLine(r,
|
|
658
|
+
function fmtTaskResultLine(r, color77) {
|
|
658
659
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
659
660
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
660
661
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
661
662
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
662
|
-
const errKindChip = errKind ?
|
|
663
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
663
|
+
const errKindChip = errKind ? color77.dim(` [${errKind}]`) : "";
|
|
664
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color77.dim(errTail)}` : "";
|
|
664
665
|
switch (r.status) {
|
|
665
666
|
case "success":
|
|
666
|
-
return { mark:
|
|
667
|
+
return { mark: color77.green("\u2713"), stats, tail: "" };
|
|
667
668
|
case "timeout":
|
|
668
669
|
return {
|
|
669
|
-
mark:
|
|
670
|
-
stats: `${
|
|
670
|
+
mark: color77.yellow("\u23F1"),
|
|
671
|
+
stats: `${color77.yellow("timeout")} ${stats}`,
|
|
671
672
|
tail: errSnip
|
|
672
673
|
};
|
|
673
674
|
case "stopped":
|
|
674
|
-
return { mark:
|
|
675
|
+
return { mark: color77.dim("\u2298"), stats: `${color77.dim("stopped")} ${stats}`, tail: errSnip };
|
|
675
676
|
case "failed":
|
|
676
|
-
return { mark:
|
|
677
|
+
return { mark: color77.red("\u2717"), stats: `${color77.red("failed")} ${stats}`, tail: errSnip };
|
|
677
678
|
}
|
|
678
679
|
}
|
|
679
680
|
var init_utils = __esm({
|
|
@@ -3028,6 +3029,23 @@ var init_update_check = __esm({
|
|
|
3028
3029
|
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3029
3030
|
}
|
|
3030
3031
|
});
|
|
3032
|
+
|
|
3033
|
+
// src/webui-server/cost-helpers.ts
|
|
3034
|
+
function getCostRates(model) {
|
|
3035
|
+
const cost = model?.cost;
|
|
3036
|
+
return {
|
|
3037
|
+
input: cost?.input ?? 0,
|
|
3038
|
+
output: cost?.output ?? 0,
|
|
3039
|
+
cacheRead: cost?.cache_read ?? 0
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
function computeUsageCost(usage, rates) {
|
|
3043
|
+
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
3044
|
+
}
|
|
3045
|
+
var init_cost_helpers = __esm({
|
|
3046
|
+
"src/webui-server/cost-helpers.ts"() {
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3031
3049
|
function registerWebuiInstance(p, deps = {}) {
|
|
3032
3050
|
const register = deps.registerFn ?? registerInstance;
|
|
3033
3051
|
void register(
|
|
@@ -3047,7 +3065,7 @@ function registerWebuiInstance(p, deps = {}) {
|
|
|
3047
3065
|
}
|
|
3048
3066
|
function announceWebuiReady(p) {
|
|
3049
3067
|
const log = p.log ?? ((m) => console.log(m));
|
|
3050
|
-
const launch = p.openBrowserFn ?? openBrowser;
|
|
3068
|
+
const launch = p.openBrowserFn ?? openBrowser$1;
|
|
3051
3069
|
const openUrl = p.wsToken ? `http://${p.host}:${p.httpPort}?token=${encodeURIComponent(p.wsToken)}` : `http://${p.host}:${p.httpPort}`;
|
|
3052
3070
|
p.server.on("listening", () => {
|
|
3053
3071
|
log(
|
|
@@ -3100,6 +3118,40 @@ var init_lifecycle = __esm({
|
|
|
3100
3118
|
"src/webui-server/lifecycle.ts"() {
|
|
3101
3119
|
}
|
|
3102
3120
|
});
|
|
3121
|
+
|
|
3122
|
+
// src/webui-server/logger-shim.ts
|
|
3123
|
+
var structuredLine, consoleLogger;
|
|
3124
|
+
var init_logger_shim = __esm({
|
|
3125
|
+
"src/webui-server/logger-shim.ts"() {
|
|
3126
|
+
structuredLine = (level, message) => JSON.stringify({
|
|
3127
|
+
level,
|
|
3128
|
+
event: "webui.autophase",
|
|
3129
|
+
message,
|
|
3130
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3131
|
+
});
|
|
3132
|
+
consoleLogger = {
|
|
3133
|
+
level: "debug",
|
|
3134
|
+
error(msg, _ctx) {
|
|
3135
|
+
console.error(structuredLine("error", msg));
|
|
3136
|
+
},
|
|
3137
|
+
warn(msg, _ctx) {
|
|
3138
|
+
console.warn(structuredLine("warn", msg));
|
|
3139
|
+
},
|
|
3140
|
+
info(msg, _ctx) {
|
|
3141
|
+
console.log(structuredLine("info", msg));
|
|
3142
|
+
},
|
|
3143
|
+
debug(msg, _ctx) {
|
|
3144
|
+
console.debug(structuredLine("debug", msg));
|
|
3145
|
+
},
|
|
3146
|
+
trace(msg, _ctx) {
|
|
3147
|
+
console.debug(structuredLine("trace", msg));
|
|
3148
|
+
},
|
|
3149
|
+
child(_bindings) {
|
|
3150
|
+
return this;
|
|
3151
|
+
}
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
});
|
|
3103
3155
|
function getVault(globalConfigPath) {
|
|
3104
3156
|
const keyFile = path4.join(path4.dirname(globalConfigPath ?? ""), ".key");
|
|
3105
3157
|
return new DefaultSecretVault({ keyFile });
|
|
@@ -3618,23 +3670,6 @@ var init_context = __esm({
|
|
|
3618
3670
|
init_context_breakdown();
|
|
3619
3671
|
}
|
|
3620
3672
|
});
|
|
3621
|
-
|
|
3622
|
-
// src/webui-server/cost-helpers.ts
|
|
3623
|
-
function getCostRates(model) {
|
|
3624
|
-
const cost = model?.cost;
|
|
3625
|
-
return {
|
|
3626
|
-
input: cost?.input ?? 0,
|
|
3627
|
-
output: cost?.output ?? 0,
|
|
3628
|
-
cacheRead: cost?.cache_read ?? 0
|
|
3629
|
-
};
|
|
3630
|
-
}
|
|
3631
|
-
function computeUsageCost(usage, rates) {
|
|
3632
|
-
return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
|
|
3633
|
-
}
|
|
3634
|
-
var init_cost_helpers = __esm({
|
|
3635
|
-
"src/webui-server/cost-helpers.ts"() {
|
|
3636
|
-
}
|
|
3637
|
-
});
|
|
3638
3673
|
async function handleSkillsList(ctx, ws) {
|
|
3639
3674
|
if (!ctx.skillLoader) {
|
|
3640
3675
|
ctx.send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
|
|
@@ -4046,29 +4081,15 @@ async function handleProviderModels(ctx, ws, providerId) {
|
|
|
4046
4081
|
return;
|
|
4047
4082
|
}
|
|
4048
4083
|
try {
|
|
4049
|
-
const
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
}
|
|
4084
|
+
const saved = await ctx.providerStore.load();
|
|
4085
|
+
const cfg = saved[providerId];
|
|
4086
|
+
const catalogId = cfg?.type && cfg.type !== providerId ? cfg.type : providerId;
|
|
4087
|
+
const provider = await ctx.modelsRegistry.getProvider(catalogId);
|
|
4054
4088
|
ctx.send(ws, {
|
|
4055
4089
|
type: "provider.models",
|
|
4056
4090
|
payload: {
|
|
4057
4091
|
provider: providerId,
|
|
4058
|
-
models: provider
|
|
4059
|
-
id: m.id,
|
|
4060
|
-
name: m.name,
|
|
4061
|
-
releaseDate: m.release_date,
|
|
4062
|
-
contextWindow: m.limit?.context,
|
|
4063
|
-
inputCost: m.cost?.input,
|
|
4064
|
-
outputCost: m.cost?.output,
|
|
4065
|
-
capabilities: [
|
|
4066
|
-
...m.tool_call ? ["tools"] : [],
|
|
4067
|
-
...m.reasoning ? ["reasoning"] : [],
|
|
4068
|
-
...m.modalities?.input?.includes("image") ? ["vision"] : [],
|
|
4069
|
-
...m.open_weights ? ["open_weights"] : []
|
|
4070
|
-
]
|
|
4071
|
-
}))
|
|
4092
|
+
models: resolveProviderModelList(cfg?.models, provider)
|
|
4072
4093
|
}
|
|
4073
4094
|
});
|
|
4074
4095
|
} catch (err) {
|
|
@@ -4665,40 +4686,6 @@ var init_ws_handlers = __esm({
|
|
|
4665
4686
|
}
|
|
4666
4687
|
});
|
|
4667
4688
|
|
|
4668
|
-
// src/webui-server/logger-shim.ts
|
|
4669
|
-
var structuredLine, consoleLogger;
|
|
4670
|
-
var init_logger_shim = __esm({
|
|
4671
|
-
"src/webui-server/logger-shim.ts"() {
|
|
4672
|
-
structuredLine = (level, message) => JSON.stringify({
|
|
4673
|
-
level,
|
|
4674
|
-
event: "webui.autophase",
|
|
4675
|
-
message,
|
|
4676
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4677
|
-
});
|
|
4678
|
-
consoleLogger = {
|
|
4679
|
-
level: "debug",
|
|
4680
|
-
error(msg, _ctx) {
|
|
4681
|
-
console.error(structuredLine("error", msg));
|
|
4682
|
-
},
|
|
4683
|
-
warn(msg, _ctx) {
|
|
4684
|
-
console.warn(structuredLine("warn", msg));
|
|
4685
|
-
},
|
|
4686
|
-
info(msg, _ctx) {
|
|
4687
|
-
console.log(structuredLine("info", msg));
|
|
4688
|
-
},
|
|
4689
|
-
debug(msg, _ctx) {
|
|
4690
|
-
console.debug(structuredLine("debug", msg));
|
|
4691
|
-
},
|
|
4692
|
-
trace(msg, _ctx) {
|
|
4693
|
-
console.debug(structuredLine("trace", msg));
|
|
4694
|
-
},
|
|
4695
|
-
child(_bindings) {
|
|
4696
|
-
return this;
|
|
4697
|
-
}
|
|
4698
|
-
};
|
|
4699
|
-
}
|
|
4700
|
-
});
|
|
4701
|
-
|
|
4702
4689
|
// src/webui-server.ts
|
|
4703
4690
|
var webui_server_exports = {};
|
|
4704
4691
|
__export(webui_server_exports, {
|
|
@@ -4835,7 +4822,7 @@ async function runWebUI(opts) {
|
|
|
4835
4822
|
const vault = new DefaultSecretVault({
|
|
4836
4823
|
keyFile: path4.join(path4.dirname(configPath2), ".key")
|
|
4837
4824
|
});
|
|
4838
|
-
const decrypted = decryptConfigSecrets
|
|
4825
|
+
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
4839
4826
|
const autonomyCfg = decrypted.autonomy ?? {};
|
|
4840
4827
|
let autonomyTouched = false;
|
|
4841
4828
|
const setAutonomy = (key, val) => {
|
|
@@ -4910,13 +4897,16 @@ async function runWebUI(opts) {
|
|
|
4910
4897
|
if (tgTouched) {
|
|
4911
4898
|
const ext = decrypted.extensions ?? {};
|
|
4912
4899
|
const tg = ext["telegram"] ?? {};
|
|
4913
|
-
if (typeof payload["tgSessionEnd"] === "boolean")
|
|
4914
|
-
|
|
4915
|
-
if (typeof payload["
|
|
4900
|
+
if (typeof payload["tgSessionEnd"] === "boolean")
|
|
4901
|
+
tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
|
|
4902
|
+
if (typeof payload["tgDelegate"] === "boolean")
|
|
4903
|
+
tg["notifyOnDelegate"] = payload["tgDelegate"];
|
|
4904
|
+
if (typeof payload["tgLongToolMs"] === "number")
|
|
4905
|
+
tg["longToolThresholdMs"] = payload["tgLongToolMs"];
|
|
4916
4906
|
ext["telegram"] = tg;
|
|
4917
4907
|
decrypted.extensions = ext;
|
|
4918
4908
|
}
|
|
4919
|
-
const encrypted = encryptConfigSecrets
|
|
4909
|
+
const encrypted = encryptConfigSecrets(decrypted, vault);
|
|
4920
4910
|
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4921
4911
|
};
|
|
4922
4912
|
const next = prefWriteLock.then(write);
|
|
@@ -5615,7 +5605,13 @@ async function runWebUI(opts) {
|
|
|
5615
5605
|
case "key.add":
|
|
5616
5606
|
case "key.update": {
|
|
5617
5607
|
const m = msg;
|
|
5618
|
-
await handleKeyUpsert(
|
|
5608
|
+
await handleKeyUpsert(
|
|
5609
|
+
wsHandlerCtx,
|
|
5610
|
+
ws,
|
|
5611
|
+
m.payload.providerId,
|
|
5612
|
+
m.payload.label,
|
|
5613
|
+
m.payload.apiKey
|
|
5614
|
+
);
|
|
5619
5615
|
break;
|
|
5620
5616
|
}
|
|
5621
5617
|
case "key.delete": {
|
|
@@ -5819,52 +5815,39 @@ async function runWebUI(opts) {
|
|
|
5819
5815
|
}
|
|
5820
5816
|
return handleMemoryForget(ws, msg, opts.memoryStore);
|
|
5821
5817
|
}
|
|
5822
|
-
// ── MCP operations —
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
const raw = await fsp5.readFile(opts.globalConfigPath, "utf8");
|
|
5828
|
-
const cfg = JSON.parse(raw);
|
|
5829
|
-
const mcpServers = cfg.mcpServers;
|
|
5830
|
-
if (mcpServers) {
|
|
5831
|
-
for (const [name, serverCfg] of Object.entries(mcpServers)) {
|
|
5832
|
-
servers.push({
|
|
5833
|
-
name,
|
|
5834
|
-
transport: serverCfg.transport ?? "stdio",
|
|
5835
|
-
status: "stopped",
|
|
5836
|
-
enabled: serverCfg.enabled ?? true,
|
|
5837
|
-
// Conditional spreads keep these absent (not explicitly
|
|
5838
|
-
// `undefined`) to satisfy exactOptionalPropertyTypes.
|
|
5839
|
-
...serverCfg.description !== void 0 && { description: serverCfg.description },
|
|
5840
|
-
...serverCfg.allowedTools !== void 0 && { tools: serverCfg.allowedTools }
|
|
5841
|
-
});
|
|
5842
|
-
}
|
|
5843
|
-
}
|
|
5844
|
-
} catch {
|
|
5845
|
-
}
|
|
5846
|
-
}
|
|
5847
|
-
send(ws, { type: "mcp.list", payload: { servers } });
|
|
5818
|
+
// ── MCP operations — delegated to the shared handlers in
|
|
5819
|
+
// @wrongstack/webui/server, which run against the same on-disk config
|
|
5820
|
+
// and the live MCPRegistry the agent loop + `/mcp` use. ──
|
|
5821
|
+
case "mcp.list":
|
|
5822
|
+
await handleMcpList(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5848
5823
|
break;
|
|
5849
|
-
}
|
|
5850
5824
|
case "mcp.add":
|
|
5825
|
+
await handleMcpAdd(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5826
|
+
break;
|
|
5851
5827
|
case "mcp.remove":
|
|
5828
|
+
await handleMcpRemove(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5829
|
+
break;
|
|
5852
5830
|
case "mcp.update":
|
|
5831
|
+
await handleMcpUpdate(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5832
|
+
break;
|
|
5853
5833
|
case "mcp.wake":
|
|
5834
|
+
await handleMcpWake(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5835
|
+
break;
|
|
5854
5836
|
case "mcp.sleep":
|
|
5837
|
+
await handleMcpSleep(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5838
|
+
break;
|
|
5855
5839
|
case "mcp.discover":
|
|
5840
|
+
await handleMcpDiscover(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5841
|
+
break;
|
|
5856
5842
|
case "mcp.enable":
|
|
5843
|
+
await handleMcpEnable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5844
|
+
break;
|
|
5857
5845
|
case "mcp.disable":
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
success: false,
|
|
5863
|
-
message: 'MCP management operations require the standalone WebUI server. Please run "wrongstack webui" instead of "wrongstack --webui".'
|
|
5864
|
-
}
|
|
5865
|
-
});
|
|
5846
|
+
await handleMcpDisable(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5847
|
+
break;
|
|
5848
|
+
case "mcp.restart":
|
|
5849
|
+
await handleMcpRestart(ws, msg, opts.globalConfigPath ?? "", opts.mcpRegistry);
|
|
5866
5850
|
break;
|
|
5867
|
-
}
|
|
5868
5851
|
case "skills.list": {
|
|
5869
5852
|
await handleSkillsList(introspectionCtx, ws);
|
|
5870
5853
|
break;
|
|
@@ -6050,6 +6033,17 @@ async function runWebUI(opts) {
|
|
|
6050
6033
|
);
|
|
6051
6034
|
break;
|
|
6052
6035
|
}
|
|
6036
|
+
case "git.changes": {
|
|
6037
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
6038
|
+
await handleGitChanges(ws, projectRoot);
|
|
6039
|
+
break;
|
|
6040
|
+
}
|
|
6041
|
+
case "git.diff": {
|
|
6042
|
+
const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
6043
|
+
const filePath = msg.payload?.path ?? "";
|
|
6044
|
+
await handleGitDiff(ws, projectRoot, filePath);
|
|
6045
|
+
break;
|
|
6046
|
+
}
|
|
6053
6047
|
case "shell.open": {
|
|
6054
6048
|
const result = await handleShellOpen(
|
|
6055
6049
|
msg.payload,
|
|
@@ -6059,7 +6053,11 @@ async function runWebUI(opts) {
|
|
|
6059
6053
|
break;
|
|
6060
6054
|
}
|
|
6061
6055
|
case "model.refine": {
|
|
6062
|
-
await handleModelRefine(
|
|
6056
|
+
await handleModelRefine(
|
|
6057
|
+
agentConfigCtx,
|
|
6058
|
+
ws,
|
|
6059
|
+
msg.payload.text
|
|
6060
|
+
);
|
|
6063
6061
|
break;
|
|
6064
6062
|
}
|
|
6065
6063
|
case "webui.shutdown":
|
|
@@ -6246,14 +6244,17 @@ async function runWebUI(opts) {
|
|
|
6246
6244
|
}
|
|
6247
6245
|
var init_webui_server = __esm({
|
|
6248
6246
|
"src/webui-server.ts"() {
|
|
6247
|
+
init_cost_helpers();
|
|
6249
6248
|
init_lifecycle();
|
|
6249
|
+
init_logger_shim();
|
|
6250
6250
|
init_provider_config();
|
|
6251
6251
|
init_static_serve();
|
|
6252
6252
|
init_ws_handlers();
|
|
6253
|
-
init_logger_shim();
|
|
6254
|
-
init_cost_helpers();
|
|
6255
6253
|
}
|
|
6256
6254
|
});
|
|
6255
|
+
|
|
6256
|
+
// src/cli-main.ts
|
|
6257
|
+
init_provider_config_utils();
|
|
6257
6258
|
var WORKTREE_PHASE_CONCURRENCY = 4;
|
|
6258
6259
|
var MAX_CMD_OUTPUT = 2e5;
|
|
6259
6260
|
function gitText(args, cwd) {
|
|
@@ -7157,12 +7158,12 @@ function pickGroupIndex(opts) {
|
|
|
7157
7158
|
try {
|
|
7158
7159
|
let current = 0;
|
|
7159
7160
|
try {
|
|
7160
|
-
const parsed = Number.parseInt(
|
|
7161
|
+
const parsed = Number.parseInt(fs3.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
7161
7162
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
7162
7163
|
} catch {
|
|
7163
7164
|
}
|
|
7164
|
-
|
|
7165
|
-
|
|
7165
|
+
fs3.mkdirSync(path4.dirname(opts.cursorFile), { recursive: true });
|
|
7166
|
+
fs3.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
7166
7167
|
return current;
|
|
7167
7168
|
} catch {
|
|
7168
7169
|
}
|
|
@@ -7649,7 +7650,19 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
|
|
|
7649
7650
|
families.set(p.family, list);
|
|
7650
7651
|
}
|
|
7651
7652
|
const ordered = [];
|
|
7652
|
-
const
|
|
7653
|
+
const preferredOrder = [
|
|
7654
|
+
"anthropic",
|
|
7655
|
+
"anthropic-oauth",
|
|
7656
|
+
"openai",
|
|
7657
|
+
"openai-codex",
|
|
7658
|
+
"github-copilot",
|
|
7659
|
+
"google",
|
|
7660
|
+
"openai-compatible"
|
|
7661
|
+
];
|
|
7662
|
+
const familyOrder = [
|
|
7663
|
+
...preferredOrder.filter((f) => families.has(f)),
|
|
7664
|
+
...[...families.keys()].filter((f) => !preferredOrder.includes(f))
|
|
7665
|
+
];
|
|
7653
7666
|
let idx = 1;
|
|
7654
7667
|
let defaultIdx;
|
|
7655
7668
|
renderer.write("\n");
|
|
@@ -9742,7 +9755,7 @@ async function persistAutonomySetting(deps, mutator) {
|
|
|
9742
9755
|
}
|
|
9743
9756
|
parsed = {};
|
|
9744
9757
|
}
|
|
9745
|
-
const decrypted = decryptConfigSecrets(parsed, deps.vault);
|
|
9758
|
+
const decrypted = decryptConfigSecrets$1(parsed, deps.vault);
|
|
9746
9759
|
const autonomy = decrypted.autonomy ?? {};
|
|
9747
9760
|
mutator(
|
|
9748
9761
|
autonomy
|
|
@@ -9754,7 +9767,7 @@ async function persistAutonomySetting(deps, mutator) {
|
|
|
9754
9767
|
await ensureProjectDir(actualTarget);
|
|
9755
9768
|
}
|
|
9756
9769
|
const toWrite = actualTarget === deps.globalConfigPath ? decrypted : filterSafeForProject(decrypted);
|
|
9757
|
-
const encrypted = encryptConfigSecrets(toWrite, deps.vault);
|
|
9770
|
+
const encrypted = encryptConfigSecrets$1(toWrite, deps.vault);
|
|
9758
9771
|
await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
9759
9772
|
deps.configStore.update({
|
|
9760
9773
|
autonomy: decrypted.autonomy
|
|
@@ -9793,7 +9806,7 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
9793
9806
|
}
|
|
9794
9807
|
parsed = {};
|
|
9795
9808
|
}
|
|
9796
|
-
const decrypted = decryptConfigSecrets(parsed, deps.vault);
|
|
9809
|
+
const decrypted = decryptConfigSecrets$1(parsed, deps.vault);
|
|
9797
9810
|
mutator(decrypted);
|
|
9798
9811
|
const newScope = decrypted.configScope;
|
|
9799
9812
|
const actualTarget = newScope === "project" && deps.inProjectConfigPath ? deps.inProjectConfigPath : newScope === "global" ? deps.globalConfigPath : targetPath;
|
|
@@ -9801,7 +9814,7 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
9801
9814
|
await ensureProjectDir(actualTarget);
|
|
9802
9815
|
}
|
|
9803
9816
|
const toWrite = actualTarget === deps.globalConfigPath ? decrypted : filterSafeForProject(decrypted);
|
|
9804
|
-
const encrypted = encryptConfigSecrets(toWrite, deps.vault);
|
|
9817
|
+
const encrypted = encryptConfigSecrets$1(toWrite, deps.vault);
|
|
9805
9818
|
await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
9806
9819
|
deps.configStore.update(decrypted);
|
|
9807
9820
|
}
|
|
@@ -9828,13 +9841,13 @@ async function persistTelegramConfig(deps, mutator) {
|
|
|
9828
9841
|
}
|
|
9829
9842
|
parsed = {};
|
|
9830
9843
|
}
|
|
9831
|
-
const decrypted = decryptConfigSecrets(parsed, deps.vault);
|
|
9844
|
+
const decrypted = decryptConfigSecrets$1(parsed, deps.vault);
|
|
9832
9845
|
const extensions = decrypted.extensions ?? {};
|
|
9833
9846
|
const telegram = extensions.telegram ?? {};
|
|
9834
9847
|
mutator(telegram);
|
|
9835
9848
|
extensions.telegram = telegram;
|
|
9836
9849
|
decrypted.extensions = extensions;
|
|
9837
|
-
const encrypted = encryptConfigSecrets(decrypted, deps.vault);
|
|
9850
|
+
const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
|
|
9838
9851
|
await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
9839
9852
|
deps.configStore.update({
|
|
9840
9853
|
extensions
|
|
@@ -10300,9 +10313,9 @@ async function patchGlobalConfig(globalConfigPath, mutate) {
|
|
|
10300
10313
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
10301
10314
|
parsed = {};
|
|
10302
10315
|
}
|
|
10303
|
-
const decrypted = decryptConfigSecrets(parsed, noOpVault);
|
|
10316
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
10304
10317
|
mutate(decrypted);
|
|
10305
|
-
const encrypted = encryptConfigSecrets(decrypted, noOpVault);
|
|
10318
|
+
const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
|
|
10306
10319
|
await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
10307
10320
|
return decrypted;
|
|
10308
10321
|
}
|
|
@@ -11765,6 +11778,67 @@ function handleFleetHelp(opts) {
|
|
|
11765
11778
|
return { message: msg };
|
|
11766
11779
|
}
|
|
11767
11780
|
|
|
11781
|
+
// src/slash-commands/f-keys.ts
|
|
11782
|
+
var F_PANELS = {
|
|
11783
|
+
"1": { action: "projectPickerOpen", label: "project switcher" },
|
|
11784
|
+
"2": { action: "toggleMonitor", label: "fleet orchestration monitor" },
|
|
11785
|
+
"3": { action: "toggleAgentsMonitor", label: "agents live monitor" },
|
|
11786
|
+
"4": { action: "toggleWorktreeMonitor", label: "worktree monitor" },
|
|
11787
|
+
"5": { action: "togglePlanPanel", label: "autonomy settings" },
|
|
11788
|
+
"6": { action: "toggleTodosMonitor", label: "todos monitor overlay" },
|
|
11789
|
+
"7": { action: "toggleQueuePanel", label: "queue panel" },
|
|
11790
|
+
"8": { action: "toggleProcessList", label: "process list overlay" },
|
|
11791
|
+
"9": { action: "toggleGoalPanel", label: "goal panel" },
|
|
11792
|
+
"10": { action: "toggleSessionsPanel", label: "live sessions panel" },
|
|
11793
|
+
"11": { action: "toggleCoordinatorMonitor", label: "coordinator monitor" },
|
|
11794
|
+
"12": { action: "statuslineOpen", label: "status line picker" }
|
|
11795
|
+
};
|
|
11796
|
+
function buildFKeysCommand(opts) {
|
|
11797
|
+
return {
|
|
11798
|
+
name: "f",
|
|
11799
|
+
description: "Open F-key panels (F1\u2013F12). Type /f for numbered options.",
|
|
11800
|
+
hidden: false,
|
|
11801
|
+
async run(args) {
|
|
11802
|
+
const n = args.trim();
|
|
11803
|
+
if (!n) {
|
|
11804
|
+
const lines = ["F-key panels:"];
|
|
11805
|
+
for (const [num, { label }] of Object.entries(F_PANELS)) {
|
|
11806
|
+
lines.push(` /f ${num} \u2014 ${label}`);
|
|
11807
|
+
}
|
|
11808
|
+
lines.push("", "Or use /f1 \u2026 /f12 directly (hidden from the picker).");
|
|
11809
|
+
return { message: lines.join("\n") };
|
|
11810
|
+
}
|
|
11811
|
+
const entry = F_PANELS[n];
|
|
11812
|
+
if (!entry) {
|
|
11813
|
+
return { message: `Unknown F-key: ${n}. Use /f to list available panels (1\u201312).` };
|
|
11814
|
+
}
|
|
11815
|
+
if (opts.onPanelOpen.current) {
|
|
11816
|
+
const ok = opts.onPanelOpen.current(entry.action);
|
|
11817
|
+
if (ok) return {};
|
|
11818
|
+
}
|
|
11819
|
+
return { message: `Opening ${entry.label}\u2026 (REPL/headless mode \u2014 panel may not be available)` };
|
|
11820
|
+
}
|
|
11821
|
+
};
|
|
11822
|
+
}
|
|
11823
|
+
function buildFKeyAliasCommands(opts) {
|
|
11824
|
+
return Object.entries(F_PANELS).map(([num, { action, label }]) => {
|
|
11825
|
+
const cmd = {
|
|
11826
|
+
name: `f${num}`,
|
|
11827
|
+
description: `Open ${label} (same as F${num})`,
|
|
11828
|
+
hidden: true,
|
|
11829
|
+
// not shown in the main slash picker
|
|
11830
|
+
async run() {
|
|
11831
|
+
if (opts.onPanelOpen.current) {
|
|
11832
|
+
const ok = opts.onPanelOpen.current(action);
|
|
11833
|
+
if (ok) return {};
|
|
11834
|
+
}
|
|
11835
|
+
return { message: `Opening ${label}\u2026` };
|
|
11836
|
+
}
|
|
11837
|
+
};
|
|
11838
|
+
return cmd;
|
|
11839
|
+
});
|
|
11840
|
+
}
|
|
11841
|
+
|
|
11768
11842
|
// src/slash-commands/goal-refiner.ts
|
|
11769
11843
|
async function refineGoal(rawGoal, provider, model) {
|
|
11770
11844
|
const prompt = buildRefinementPrompt(rawGoal);
|
|
@@ -13604,9 +13678,9 @@ async function patchGlobalConfig2(globalConfigPath, mutate) {
|
|
|
13604
13678
|
}
|
|
13605
13679
|
parsed = {};
|
|
13606
13680
|
}
|
|
13607
|
-
const decrypted = decryptConfigSecrets(parsed, noOpVault);
|
|
13681
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
13608
13682
|
mutate(decrypted);
|
|
13609
|
-
const encrypted = encryptConfigSecrets(decrypted, noOpVault);
|
|
13683
|
+
const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
|
|
13610
13684
|
await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
13611
13685
|
return decrypted;
|
|
13612
13686
|
}
|
|
@@ -14541,9 +14615,9 @@ async function patchGlobalConfig3(globalConfigPath, mutate) {
|
|
|
14541
14615
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
14542
14616
|
parsed = {};
|
|
14543
14617
|
}
|
|
14544
|
-
const decrypted = decryptConfigSecrets(parsed, noOpVault);
|
|
14618
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
14545
14619
|
mutate(decrypted);
|
|
14546
|
-
const encrypted = encryptConfigSecrets(decrypted, noOpVault);
|
|
14620
|
+
const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
|
|
14547
14621
|
await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
14548
14622
|
return decrypted;
|
|
14549
14623
|
}
|
|
@@ -15708,6 +15782,12 @@ function buildSettingsCommand(opts) {
|
|
|
15708
15782
|
" /settings semver-part patch|minor|major|auto Default part for /semver and the semver_bump tool",
|
|
15709
15783
|
" /settings breaker on|off Enable/disable the process circuit breaker (gates bash/exec)",
|
|
15710
15784
|
" /settings breaker-timeout <seconds> Auto kill/reset delay when the breaker trips (0 = manual)",
|
|
15785
|
+
" /settings context-mode balanced|frugal|deep|archival Context window policy",
|
|
15786
|
+
" /settings context-strategy hybrid|intelligent|selective Compactor strategy",
|
|
15787
|
+
" /settings context-auto-compact on|off Auto-compact context when thresholds crossed",
|
|
15788
|
+
" /settings token-saving off|minimal|light|medium|aggressive Token-saving mode",
|
|
15789
|
+
" /settings max-concurrent <n> Max concurrent subagents (0 = unlimited)",
|
|
15790
|
+
" /settings title-animation on|off Terminal title animation",
|
|
15711
15791
|
" /settings defaults Show built-in default values",
|
|
15712
15792
|
"",
|
|
15713
15793
|
"Settings are persisted to ~/.wrongstack/config.json."
|
|
@@ -15727,6 +15807,13 @@ function buildSettingsCommand(opts) {
|
|
|
15727
15807
|
const cb = opts.configStore.get().circuitBreaker;
|
|
15728
15808
|
const breakerEnabled = cb?.enabled === true;
|
|
15729
15809
|
const breakerTimeout = cb?.autoKillResetMs ?? 6e4;
|
|
15810
|
+
const context = opts.configStore.get().context;
|
|
15811
|
+
const contextMode = context?.mode ?? "balanced";
|
|
15812
|
+
const contextStrategy = context?.strategy ?? "hybrid";
|
|
15813
|
+
const contextAutoCompact = context?.autoCompact !== false;
|
|
15814
|
+
const features = opts.configStore.get().features;
|
|
15815
|
+
const tokenSavingTier = features?.tokenSavingMode ?? "off";
|
|
15816
|
+
const maxConcurrent = opts.configStore.get().maxConcurrent ?? 0;
|
|
15730
15817
|
return [
|
|
15731
15818
|
`${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
|
|
15732
15819
|
"",
|
|
@@ -15741,6 +15828,11 @@ function buildSettingsCommand(opts) {
|
|
|
15741
15828
|
` refine-language: ${color.cyan(enhanceLanguage)} ${color.dim("change: /settings refine-language original|english")}`,
|
|
15742
15829
|
` semver default part: ${color.cyan(semverPart)} ${color.dim("change: /settings semver-part patch|minor|major|auto")}`,
|
|
15743
15830
|
` circuit breaker: ${breakerEnabled ? color.cyan("on") : color.dim("off")} (kill/reset ${breakerTimeout > 0 ? formatDelay(breakerTimeout) : color.dim("manual")}) ${color.dim("change: /settings breaker on|off")}`,
|
|
15831
|
+
` context mode: ${color.cyan(contextMode)} ${color.dim("change: /settings context-mode balanced|frugal|deep|archival")}`,
|
|
15832
|
+
` context strategy: ${color.cyan(contextStrategy)} ${color.dim("change: /settings context-strategy hybrid|intelligent|selective")}`,
|
|
15833
|
+
` context auto-compact: ${contextAutoCompact ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings context-auto-compact on|off")}`,
|
|
15834
|
+
` token-saving: ${color.cyan(tokenSavingTier)} ${color.dim("change: /settings token-saving off|minimal|light|medium|aggressive")}`,
|
|
15835
|
+
` max-concurrent: ${color.cyan(maxConcurrent === 0 ? "unlimited" : String(maxConcurrent))} ${color.dim("change: /settings max-concurrent <n>")}`,
|
|
15744
15836
|
"",
|
|
15745
15837
|
color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
|
|
15746
15838
|
].join("\n");
|
|
@@ -15748,7 +15840,7 @@ function buildSettingsCommand(opts) {
|
|
|
15748
15840
|
return {
|
|
15749
15841
|
name: "settings",
|
|
15750
15842
|
category: "Config",
|
|
15751
|
-
description: "View or change settings (auto-proceed
|
|
15843
|
+
description: "View or change settings (auto-proceed, autonomy, context, features, token-saving).",
|
|
15752
15844
|
help,
|
|
15753
15845
|
async run(args) {
|
|
15754
15846
|
const { cmd, rest } = parseSubcommand(args);
|
|
@@ -15970,8 +16062,101 @@ function buildSettingsCommand(opts) {
|
|
|
15970
16062
|
message: `${color.green("\u2713")} breaker kill/reset timeout \u2192 ${ms > 0 ? formatDelay(ms) : color.dim("manual")} ${color.dim(ms > 0 ? "statusline shows a countdown when the breaker trips" : "breaker trips require /kill reset")}`
|
|
15971
16063
|
};
|
|
15972
16064
|
}
|
|
16065
|
+
if (sub === "context-mode") {
|
|
16066
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16067
|
+
const modes = ["balanced", "frugal", "deep", "archival"];
|
|
16068
|
+
if (!modes.includes(raw)) {
|
|
16069
|
+
return { message: `${color.amber("Usage:")} /settings context-mode balanced|frugal|deep|archival` };
|
|
16070
|
+
}
|
|
16071
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
16072
|
+
const ctx = cfg.context ?? {};
|
|
16073
|
+
ctx.mode = raw;
|
|
16074
|
+
cfg.context = ctx;
|
|
16075
|
+
});
|
|
16076
|
+
return {
|
|
16077
|
+
message: `${color.green("\u2713")} context mode \u2192 ${color.cyan(raw)} ${color.dim("context window policy")}`
|
|
16078
|
+
};
|
|
16079
|
+
}
|
|
16080
|
+
if (sub === "context-strategy") {
|
|
16081
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16082
|
+
const strategies = ["hybrid", "intelligent", "selective"];
|
|
16083
|
+
if (!strategies.includes(raw)) {
|
|
16084
|
+
return { message: `${color.amber("Usage:")} /settings context-strategy hybrid|intelligent|selective` };
|
|
16085
|
+
}
|
|
16086
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
16087
|
+
const ctx = cfg.context ?? {};
|
|
16088
|
+
ctx.strategy = raw;
|
|
16089
|
+
cfg.context = ctx;
|
|
16090
|
+
});
|
|
16091
|
+
return {
|
|
16092
|
+
message: `${color.green("\u2713")} context strategy \u2192 ${color.cyan(raw)} ${color.dim("compactor strategy")}`
|
|
16093
|
+
};
|
|
16094
|
+
}
|
|
16095
|
+
if (sub === "context-auto-compact") {
|
|
16096
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16097
|
+
if (!["on", "off"].includes(raw)) {
|
|
16098
|
+
return { message: `${color.amber("Usage:")} /settings context-auto-compact on|off` };
|
|
16099
|
+
}
|
|
16100
|
+
const on = raw === "on";
|
|
16101
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
16102
|
+
const ctx = cfg.context ?? {};
|
|
16103
|
+
ctx.autoCompact = on;
|
|
16104
|
+
cfg.context = ctx;
|
|
16105
|
+
});
|
|
16106
|
+
return {
|
|
16107
|
+
message: `${color.green("\u2713")} context auto-compact \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim("auto-compact context when thresholds crossed")}`
|
|
16108
|
+
};
|
|
16109
|
+
}
|
|
16110
|
+
if (sub === "token-saving") {
|
|
16111
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16112
|
+
const tiers = ["off", "minimal", "light", "medium", "aggressive"];
|
|
16113
|
+
if (!tiers.includes(raw)) {
|
|
16114
|
+
return { message: `${color.amber("Usage:")} /settings token-saving off|minimal|light|medium|aggressive` };
|
|
16115
|
+
}
|
|
16116
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
16117
|
+
const feat = cfg.features ?? {};
|
|
16118
|
+
feat.tokenSavingMode = raw;
|
|
16119
|
+
cfg.features = feat;
|
|
16120
|
+
});
|
|
16121
|
+
return {
|
|
16122
|
+
message: `${color.green("\u2713")} token-saving \u2192 ${color.cyan(raw)} ${color.dim("token-saving mode")}`
|
|
16123
|
+
};
|
|
16124
|
+
}
|
|
16125
|
+
if (sub === "max-concurrent") {
|
|
16126
|
+
const raw = rest[0];
|
|
16127
|
+
if (raw === void 0) {
|
|
16128
|
+
return {
|
|
16129
|
+
message: `${color.amber("Usage:")} /settings max-concurrent <n> ${color.dim("(0 = unlimited)")}`
|
|
16130
|
+
};
|
|
16131
|
+
}
|
|
16132
|
+
const n = Number.parseInt(raw, 10);
|
|
16133
|
+
if (Number.isNaN(n) || n < 0) {
|
|
16134
|
+
return {
|
|
16135
|
+
message: `${color.red("Invalid number")}: "${raw}". Enter a non-negative integer (0 = unlimited)`
|
|
16136
|
+
};
|
|
16137
|
+
}
|
|
16138
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
16139
|
+
cfg.maxConcurrent = n;
|
|
16140
|
+
});
|
|
16141
|
+
return {
|
|
16142
|
+
message: `${color.green("\u2713")} max-concurrent \u2192 ${color.cyan(n === 0 ? "unlimited" : String(n))} ${color.dim("max concurrent subagents")}`
|
|
16143
|
+
};
|
|
16144
|
+
}
|
|
16145
|
+
if (sub === "title-animation") {
|
|
16146
|
+
const raw = (rest[0] ?? "").toLowerCase();
|
|
16147
|
+
if (!["on", "off"].includes(raw)) {
|
|
16148
|
+
return { message: `${color.amber("Usage:")} /settings title-animation on|off` };
|
|
16149
|
+
}
|
|
16150
|
+
const on = raw === "on";
|
|
16151
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
16152
|
+
cfg.titleAnimation = on;
|
|
16153
|
+
});
|
|
16154
|
+
return {
|
|
16155
|
+
message: `${color.green("\u2713")} title animation \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim("terminal title animation")}`
|
|
16156
|
+
};
|
|
16157
|
+
}
|
|
15973
16158
|
return {
|
|
15974
|
-
message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "fs-access", "refine", "refine-delay", "refine-language", "semver-part", "breaker", "breaker-timeout", "defaults"], "settings")}`
|
|
16159
|
+
message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "fs-access", "refine", "refine-delay", "refine-language", "semver-part", "breaker", "breaker-timeout", "context-mode", "context-strategy", "context-auto-compact", "token-saving", "max-concurrent", "title-animation", "defaults"], "settings")}`
|
|
15975
16160
|
};
|
|
15976
16161
|
} catch (err) {
|
|
15977
16162
|
return {
|
|
@@ -16609,8 +16794,10 @@ function buildTechStackTask(opts) {
|
|
|
16609
16794
|
"1. **Read** each package.json and extract ALL dependencies (dependencies +",
|
|
16610
16795
|
" devDependencies + peerDependencies). Include the workspace root.",
|
|
16611
16796
|
"",
|
|
16612
|
-
"2. **For every dependency**, look up its latest version from the npm registry
|
|
16613
|
-
|
|
16797
|
+
"2. **For every dependency**, look up its latest version from the npm registry",
|
|
16798
|
+
" using the `fetch` tool (NOT shell `curl`/`wget`, and NOT a Node script \u2014",
|
|
16799
|
+
" you run under a director and only the `fetch` tool is permitted for network):",
|
|
16800
|
+
' - Call the `fetch` tool with `url: "https://registry.npmjs.org/<package>/latest"`',
|
|
16614
16801
|
" - Extract the `version` field from the JSON response",
|
|
16615
16802
|
" - Also check `description`, `license`, and `time` fields for age/dead checks",
|
|
16616
16803
|
"",
|
|
@@ -16625,7 +16812,7 @@ function buildTechStackTask(opts) {
|
|
|
16625
16812
|
" - Flag dead packages (no release >2 years + critical issues)",
|
|
16626
16813
|
" - Prefer Node.js built-ins over third-party packages",
|
|
16627
16814
|
"",
|
|
16628
|
-
`5. **Write the report** to \`${outputPath}\` in the project root:`,
|
|
16815
|
+
`5. **Write the report** to \`${outputPath}\` in the project root using the \`write\` tool:`,
|
|
16629
16816
|
" - Markdown format: grouped by category, with version tables and warnings",
|
|
16630
16817
|
" - JSON format: structured array with name, current, latest, status, notes",
|
|
16631
16818
|
"",
|
|
@@ -16658,11 +16845,15 @@ function buildTechStackTask(opts) {
|
|
|
16658
16845
|
"",
|
|
16659
16846
|
"### Guardrails",
|
|
16660
16847
|
"",
|
|
16661
|
-
"-
|
|
16662
|
-
"
|
|
16848
|
+
"- Network access is ONLY via the `fetch` tool. The `bash`, `exec`, and other",
|
|
16849
|
+
" shell tools are denied for this subagent \u2014 do NOT try `curl`, `wget`, or a",
|
|
16850
|
+
" Node/`fetch()` script as a fallback; they will be blocked. If a `fetch`",
|
|
16851
|
+
" call fails, retry the `fetch` tool, do not switch transports.",
|
|
16852
|
+
"- File writes are ONLY via the `write` tool (it is permitted for this run).",
|
|
16853
|
+
"- Skip packages whose `fetch` returns HTTP 404 (private/internal packages).",
|
|
16663
16854
|
"- Deduplicate: same package in multiple package.json files = one row.",
|
|
16664
16855
|
"- Do NOT modify any files except writing the report.",
|
|
16665
|
-
"- Run in 2-3 iterations max.
|
|
16856
|
+
"- Run in 2-3 iterations max. Issue several `fetch` calls per turn where possible.",
|
|
16666
16857
|
"- **IMPORTANT**: Output the chat summary FIRST, then write the file. I need to see results."
|
|
16667
16858
|
].join("\n");
|
|
16668
16859
|
}
|
|
@@ -16717,6 +16908,15 @@ function buildTechStackCommand(opts) {
|
|
|
16717
16908
|
opts.renderer.writeWarning(msg);
|
|
16718
16909
|
return { message: msg };
|
|
16719
16910
|
}
|
|
16911
|
+
const hasFetchTool = opts.toolRegistry.list().some((t) => t.name === "fetch");
|
|
16912
|
+
if (!hasFetchTool) {
|
|
16913
|
+
opts.renderer.writeWarning(
|
|
16914
|
+
"The `fetch` tool is not registered in this session \u2014 a token-saving tier (minimal/light) omits it. The techstack subagent cannot query the npm registry without it. Raise the tier to `medium` or higher (/settings \u2192 token saving) and re-run /techstack."
|
|
16915
|
+
);
|
|
16916
|
+
return {
|
|
16917
|
+
message: "techstack aborted: `fetch` tool unavailable in the current token-saving tier."
|
|
16918
|
+
};
|
|
16919
|
+
}
|
|
16720
16920
|
const header = isInit ? "Tech Stack Init Audit" : "Tech Stack Audit";
|
|
16721
16921
|
const label = `${color.cyan("\u{1F50D}")} ${color.bold(header)} ${color.dim(`(${packageFiles.length} package files)`)}`;
|
|
16722
16922
|
opts.renderer.write(label);
|
|
@@ -16728,7 +16928,11 @@ function buildTechStackCommand(opts) {
|
|
|
16728
16928
|
);
|
|
16729
16929
|
try {
|
|
16730
16930
|
const name = isInit ? "techstack-init" : "techstack-audit";
|
|
16731
|
-
const summary = await opts.onSpawnAndWait(task, {
|
|
16931
|
+
const summary = await opts.onSpawnAndWait(task, {
|
|
16932
|
+
name,
|
|
16933
|
+
tools: ["read", "glob", "grep", "tree", "fetch", "write"],
|
|
16934
|
+
allowedCapabilities: ["fs.read", "net.outbound", "fs.write"]
|
|
16935
|
+
});
|
|
16732
16936
|
return { message: summary };
|
|
16733
16937
|
} catch (err) {
|
|
16734
16938
|
const msg = `Tech stack scan failed: ${toErrorMessage(err)}`;
|
|
@@ -17346,6 +17550,8 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
17346
17550
|
buildAgentsCommand(opts),
|
|
17347
17551
|
buildDirectorCommand(opts),
|
|
17348
17552
|
buildFleetCommand(opts),
|
|
17553
|
+
buildFKeysCommand(opts),
|
|
17554
|
+
...buildFKeyAliasCommands(opts),
|
|
17349
17555
|
buildEnhanceCommand(opts),
|
|
17350
17556
|
buildEnsembleCommand(),
|
|
17351
17557
|
buildMemoryCommand(opts),
|
|
@@ -17387,6 +17593,8 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
17387
17593
|
}),
|
|
17388
17594
|
getConfig: opts.statuslineConfig?.get ?? (async () => ({})),
|
|
17389
17595
|
setConfig: opts.statuslineConfig?.set ?? (async () => {
|
|
17596
|
+
}),
|
|
17597
|
+
saveStatuslineHiddenItems: opts.saveStatuslineHiddenItems ?? (async () => {
|
|
17390
17598
|
})
|
|
17391
17599
|
})
|
|
17392
17600
|
];
|
|
@@ -17486,9 +17694,9 @@ async function runProjectCheck(opts) {
|
|
|
17486
17694
|
}
|
|
17487
17695
|
if (answer2 === "y" || answer2 === "yes") {
|
|
17488
17696
|
try {
|
|
17489
|
-
const { spawn:
|
|
17697
|
+
const { spawn: spawn9 } = await import('child_process');
|
|
17490
17698
|
await new Promise((resolve11, reject) => {
|
|
17491
|
-
const child =
|
|
17699
|
+
const child = spawn9("git", ["init"], {
|
|
17492
17700
|
cwd,
|
|
17493
17701
|
signal: AbortSignal.timeout(1e4),
|
|
17494
17702
|
windowsHide: true
|
|
@@ -18560,6 +18768,10 @@ ${color.bold("WrongStack")} ${color.dim("\u2014 API key manager")}
|
|
|
18560
18768
|
`);
|
|
18561
18769
|
renderer.write(` ${color.bold("c")} Add a custom provider
|
|
18562
18770
|
`);
|
|
18771
|
+
renderer.write(
|
|
18772
|
+
` ${color.bold("s")} Sign in with a subscription ${color.dim("(ChatGPT / Claude / Copilot)")}
|
|
18773
|
+
`
|
|
18774
|
+
);
|
|
18563
18775
|
if (ids.length > 0) {
|
|
18564
18776
|
renderer.write(
|
|
18565
18777
|
` ${color.dim("1-")}${color.dim(String(ids.length))} ${color.bold("Manage a provider")}
|
|
@@ -18798,123 +19010,961 @@ async function addKeyForCatalogProvider(deps, chosen) {
|
|
|
18798
19010
|
return false;
|
|
18799
19011
|
}
|
|
18800
19012
|
}
|
|
18801
|
-
return addKeyForProvider(alias, deps, {
|
|
18802
|
-
type: chosen.id,
|
|
18803
|
-
family,
|
|
18804
|
-
baseUrl,
|
|
18805
|
-
envVars: chosen.envVars
|
|
18806
|
-
});
|
|
19013
|
+
return addKeyForProvider(alias, deps, {
|
|
19014
|
+
type: chosen.id,
|
|
19015
|
+
family,
|
|
19016
|
+
baseUrl,
|
|
19017
|
+
envVars: chosen.envVars
|
|
19018
|
+
});
|
|
19019
|
+
}
|
|
19020
|
+
async function addCustomProvider(deps) {
|
|
19021
|
+
deps.renderer.write(
|
|
19022
|
+
`
|
|
19023
|
+
${color.bold("Custom provider")} ${color.dim("\u2014 for local models or proxies not in the catalog.")}
|
|
19024
|
+
`
|
|
19025
|
+
);
|
|
19026
|
+
const type = (await deps.reader.readLine(
|
|
19027
|
+
` ${color.amber("?")} Provider id ${color.dim('(e.g. "local-llama", "my-proxy", q to quit)')}: `
|
|
19028
|
+
)).trim();
|
|
19029
|
+
if (!type || type === "q") return false;
|
|
19030
|
+
const existing = (await loadProviders(deps))[type];
|
|
19031
|
+
if (existing) {
|
|
19032
|
+
deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
|
|
19033
|
+
return false;
|
|
19034
|
+
}
|
|
19035
|
+
const familyRaw = (await deps.reader.readLine(
|
|
19036
|
+
` ${color.amber("?")} Wire family ${color.dim("(anthropic | openai | openai-compatible | google)")} ${color.dim("(q to quit)")}: `
|
|
19037
|
+
)).trim();
|
|
19038
|
+
if (familyRaw === "q") return false;
|
|
19039
|
+
const family = validateFamily(familyRaw);
|
|
19040
|
+
if (!family) {
|
|
19041
|
+
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
19042
|
+
return false;
|
|
19043
|
+
}
|
|
19044
|
+
const baseUrl = (await deps.reader.readLine(
|
|
19045
|
+
` ${color.amber("?")} Base URL ${color.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
19046
|
+
)).trim();
|
|
19047
|
+
const modelsRaw = (await deps.reader.readLine(
|
|
19048
|
+
` ${color.amber("?")} Model ids ${color.dim("(comma-separated, optional)")}: `
|
|
19049
|
+
)).trim();
|
|
19050
|
+
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
19051
|
+
const envVarsRaw = (await deps.reader.readLine(
|
|
19052
|
+
` ${color.amber("?")} Env var names ${color.dim("(comma-separated, optional)")}: `
|
|
19053
|
+
)).trim();
|
|
19054
|
+
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
19055
|
+
return addKeyForProvider(type, deps, {
|
|
19056
|
+
type,
|
|
19057
|
+
family,
|
|
19058
|
+
...baseUrl ? { baseUrl } : {},
|
|
19059
|
+
...models ? { models } : {},
|
|
19060
|
+
...envVars ? { envVars } : {}
|
|
19061
|
+
});
|
|
19062
|
+
}
|
|
19063
|
+
async function addManualEntry(deps) {
|
|
19064
|
+
const pid = (await deps.reader.readLine(` ${color.amber("?")} Provider id ${color.dim("[q to quit]")}: `)).trim();
|
|
19065
|
+
if (!pid || pid === "q") return false;
|
|
19066
|
+
const famRaw = (await deps.reader.readLine(
|
|
19067
|
+
` ${color.amber("?")} Family ${color.dim("(anthropic/openai/openai-compatible/google)")}: `
|
|
19068
|
+
)).trim();
|
|
19069
|
+
const family = validateFamily(famRaw);
|
|
19070
|
+
if (!family) {
|
|
19071
|
+
deps.renderer.writeError(`Invalid family: "${famRaw}"`);
|
|
19072
|
+
return false;
|
|
19073
|
+
}
|
|
19074
|
+
const baseUrl = (await deps.reader.readLine(` ${color.amber("?")} Base URL ${color.dim("(optional)")}: `)).trim();
|
|
19075
|
+
return addKeyForProvider(pid, deps, {
|
|
19076
|
+
type: pid,
|
|
19077
|
+
family,
|
|
19078
|
+
...baseUrl ? { baseUrl } : {}
|
|
19079
|
+
});
|
|
19080
|
+
}
|
|
19081
|
+
async function addKeyForProvider(providerId, deps, template) {
|
|
19082
|
+
const providers = await loadProviders(deps);
|
|
19083
|
+
const existing = providers[providerId];
|
|
19084
|
+
const existingKeys = existing ? normalizeKeys(existing) : [];
|
|
19085
|
+
const usedLabels = new Set(existingKeys.map((k) => k.label));
|
|
19086
|
+
const label = await promptForLabel(deps, usedLabels);
|
|
19087
|
+
if (!label) return false;
|
|
19088
|
+
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
19089
|
+
if (!apiKey) return false;
|
|
19090
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
19091
|
+
const existingProv = all[providerId] ?? {
|
|
19092
|
+
type: providerId,
|
|
19093
|
+
...template
|
|
19094
|
+
};
|
|
19095
|
+
if (!existingProv.type) existingProv.type = providerId;
|
|
19096
|
+
if (!existingProv.family && template.family) {
|
|
19097
|
+
existingProv.family = template.family;
|
|
19098
|
+
}
|
|
19099
|
+
if (!existingProv.baseUrl && template.baseUrl) {
|
|
19100
|
+
existingProv.baseUrl = template.baseUrl;
|
|
19101
|
+
}
|
|
19102
|
+
if (!existingProv.envVars && template.envVars) {
|
|
19103
|
+
existingProv.envVars = template.envVars;
|
|
19104
|
+
}
|
|
19105
|
+
const list = normalizeKeys(existingProv);
|
|
19106
|
+
list.push({ label, apiKey, createdAt: nowIso() });
|
|
19107
|
+
writeKeysBack(existingProv, list);
|
|
19108
|
+
if (!existingProv.activeKey) existingProv.activeKey = label;
|
|
19109
|
+
all[providerId] = existingProv;
|
|
19110
|
+
});
|
|
19111
|
+
deps.renderer.write(
|
|
19112
|
+
` ${color.green("\u2713")} Saved ${color.bold(providerId)}/${color.bold(label)}.
|
|
19113
|
+
`
|
|
19114
|
+
);
|
|
19115
|
+
deps.renderer.write(color.dim(` Launch: wstack --provider ${providerId} "<task>"
|
|
19116
|
+
`));
|
|
19117
|
+
return true;
|
|
19118
|
+
}
|
|
19119
|
+
async function promptForLabel(deps, usedLabels) {
|
|
19120
|
+
const defaultLabel = suggestLabel(usedLabels);
|
|
19121
|
+
const labelRaw = (await deps.reader.readLine(
|
|
19122
|
+
` ${color.amber("?")} Label for this key ${color.dim(`[${defaultLabel}]`)}: `
|
|
19123
|
+
)).trim();
|
|
19124
|
+
const label = labelRaw || defaultLabel;
|
|
19125
|
+
if (usedLabels.has(label)) {
|
|
19126
|
+
deps.renderer.writeError(`Label "${label}" is already used. Use update (u) instead.`);
|
|
19127
|
+
return null;
|
|
19128
|
+
}
|
|
19129
|
+
return label;
|
|
19130
|
+
}
|
|
19131
|
+
|
|
19132
|
+
// src/auth-menu/anthropic-oauth.ts
|
|
19133
|
+
init_provider_config_utils();
|
|
19134
|
+
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
19135
|
+
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
19136
|
+
var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
19137
|
+
var REDIRECT_PORT = 53692;
|
|
19138
|
+
var REDIRECT_HOST = "127.0.0.1";
|
|
19139
|
+
var REDIRECT_PATH = "/callback";
|
|
19140
|
+
var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}${REDIRECT_PATH}`;
|
|
19141
|
+
var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
19142
|
+
var CLAUDE_PROVIDER_ID = "anthropic-oauth";
|
|
19143
|
+
var CLAUDE_BASE_URL = "https://api.anthropic.com";
|
|
19144
|
+
function base64url(buf) {
|
|
19145
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
19146
|
+
}
|
|
19147
|
+
function generatePkce() {
|
|
19148
|
+
const verifier = base64url(randomBytes(32));
|
|
19149
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
19150
|
+
return { verifier, challenge };
|
|
19151
|
+
}
|
|
19152
|
+
function buildAuthorizeUrl(challenge, verifier) {
|
|
19153
|
+
const params = new URLSearchParams({
|
|
19154
|
+
code: "true",
|
|
19155
|
+
client_id: CLIENT_ID,
|
|
19156
|
+
response_type: "code",
|
|
19157
|
+
redirect_uri: REDIRECT_URI,
|
|
19158
|
+
scope: SCOPES,
|
|
19159
|
+
code_challenge: challenge,
|
|
19160
|
+
code_challenge_method: "S256",
|
|
19161
|
+
state: verifier
|
|
19162
|
+
});
|
|
19163
|
+
return `${AUTHORIZE_URL}?${params.toString()}`;
|
|
19164
|
+
}
|
|
19165
|
+
function parseAuthorizationInput(input) {
|
|
19166
|
+
const value = input.trim();
|
|
19167
|
+
if (!value) return {};
|
|
19168
|
+
try {
|
|
19169
|
+
const url = new URL(value);
|
|
19170
|
+
return {
|
|
19171
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
19172
|
+
state: url.searchParams.get("state") ?? void 0
|
|
19173
|
+
};
|
|
19174
|
+
} catch {
|
|
19175
|
+
}
|
|
19176
|
+
if (value.includes("#")) {
|
|
19177
|
+
const [code, state] = value.split("#", 2);
|
|
19178
|
+
return { code, state };
|
|
19179
|
+
}
|
|
19180
|
+
if (value.includes("code=")) {
|
|
19181
|
+
const params = new URLSearchParams(value);
|
|
19182
|
+
return {
|
|
19183
|
+
code: params.get("code") ?? void 0,
|
|
19184
|
+
state: params.get("state") ?? void 0
|
|
19185
|
+
};
|
|
19186
|
+
}
|
|
19187
|
+
return { code: value };
|
|
19188
|
+
}
|
|
19189
|
+
async function readTokens(res, op) {
|
|
19190
|
+
if (!res.ok) {
|
|
19191
|
+
const text = await res.text().catch(() => "");
|
|
19192
|
+
throw new Error(`Claude token ${op} failed (${res.status}): ${text || res.statusText}`);
|
|
19193
|
+
}
|
|
19194
|
+
const json = await res.json();
|
|
19195
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
19196
|
+
throw new Error(`Claude token ${op} response missing fields`);
|
|
19197
|
+
}
|
|
19198
|
+
return {
|
|
19199
|
+
access: json.access_token,
|
|
19200
|
+
refresh: json.refresh_token,
|
|
19201
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
19202
|
+
};
|
|
19203
|
+
}
|
|
19204
|
+
async function exchangeAuthorizationCode(code, state, verifier, signal) {
|
|
19205
|
+
const res = await fetch(TOKEN_URL, {
|
|
19206
|
+
method: "POST",
|
|
19207
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
19208
|
+
body: JSON.stringify({
|
|
19209
|
+
grant_type: "authorization_code",
|
|
19210
|
+
client_id: CLIENT_ID,
|
|
19211
|
+
code,
|
|
19212
|
+
state,
|
|
19213
|
+
redirect_uri: REDIRECT_URI,
|
|
19214
|
+
code_verifier: verifier
|
|
19215
|
+
}),
|
|
19216
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
|
|
19217
|
+
});
|
|
19218
|
+
return readTokens(res, "exchange");
|
|
19219
|
+
}
|
|
19220
|
+
async function fetchClaudeModels(accessToken, signal) {
|
|
19221
|
+
try {
|
|
19222
|
+
const res = await fetch(`${CLAUDE_BASE_URL}/v1/models?limit=100`, {
|
|
19223
|
+
headers: {
|
|
19224
|
+
accept: "application/json",
|
|
19225
|
+
authorization: `Bearer ${accessToken}`,
|
|
19226
|
+
"anthropic-version": "2023-06-01",
|
|
19227
|
+
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20"
|
|
19228
|
+
},
|
|
19229
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
|
|
19230
|
+
});
|
|
19231
|
+
if (!res.ok) return [];
|
|
19232
|
+
const json = await res.json();
|
|
19233
|
+
const ids = (json?.data ?? []).map((m) => m.id).filter((id) => typeof id === "string" && id.startsWith("claude-"));
|
|
19234
|
+
return ids;
|
|
19235
|
+
} catch {
|
|
19236
|
+
return [];
|
|
19237
|
+
}
|
|
19238
|
+
}
|
|
19239
|
+
function callbackHtml(ok, message) {
|
|
19240
|
+
const heading = ok ? "Authentication successful" : "Authentication failed";
|
|
19241
|
+
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>`;
|
|
19242
|
+
}
|
|
19243
|
+
function startLoopbackServer(expectedState) {
|
|
19244
|
+
let resolveCode = () => {
|
|
19245
|
+
};
|
|
19246
|
+
const codePromise = new Promise((resolve11) => {
|
|
19247
|
+
let settled = false;
|
|
19248
|
+
resolveCode = (v) => {
|
|
19249
|
+
if (settled) return;
|
|
19250
|
+
settled = true;
|
|
19251
|
+
resolve11(v);
|
|
19252
|
+
};
|
|
19253
|
+
});
|
|
19254
|
+
const server = createServer((req2, res) => {
|
|
19255
|
+
let url;
|
|
19256
|
+
try {
|
|
19257
|
+
url = new URL(req2.url ?? "", `http://${REDIRECT_HOST}`);
|
|
19258
|
+
} catch {
|
|
19259
|
+
res.statusCode = 400;
|
|
19260
|
+
res.end();
|
|
19261
|
+
return;
|
|
19262
|
+
}
|
|
19263
|
+
if (url.pathname !== REDIRECT_PATH) {
|
|
19264
|
+
res.statusCode = 404;
|
|
19265
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
19266
|
+
res.end(callbackHtml(false, "Callback route not found."));
|
|
19267
|
+
return;
|
|
19268
|
+
}
|
|
19269
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
19270
|
+
const err = url.searchParams.get("error");
|
|
19271
|
+
if (err) {
|
|
19272
|
+
res.statusCode = 400;
|
|
19273
|
+
res.end(callbackHtml(false, `Authorization error: ${err}`));
|
|
19274
|
+
resolveCode(null);
|
|
19275
|
+
return;
|
|
19276
|
+
}
|
|
19277
|
+
const code = url.searchParams.get("code");
|
|
19278
|
+
const state = url.searchParams.get("state");
|
|
19279
|
+
if (!code || !state) {
|
|
19280
|
+
res.statusCode = 400;
|
|
19281
|
+
res.end(callbackHtml(false, "Missing code or state."));
|
|
19282
|
+
return;
|
|
19283
|
+
}
|
|
19284
|
+
if (state !== expectedState) {
|
|
19285
|
+
res.statusCode = 400;
|
|
19286
|
+
res.end(callbackHtml(false, "State mismatch \u2014 please restart the login."));
|
|
19287
|
+
resolveCode(null);
|
|
19288
|
+
return;
|
|
19289
|
+
}
|
|
19290
|
+
res.statusCode = 200;
|
|
19291
|
+
res.end(callbackHtml(true, "You can close this window and return to the terminal."));
|
|
19292
|
+
resolveCode({ code, state });
|
|
19293
|
+
});
|
|
19294
|
+
return new Promise((resolve11) => {
|
|
19295
|
+
server.on("error", () => {
|
|
19296
|
+
resolveCode(null);
|
|
19297
|
+
resolve11({
|
|
19298
|
+
bound: false,
|
|
19299
|
+
waitForCode: () => Promise.resolve(null),
|
|
19300
|
+
close: () => {
|
|
19301
|
+
try {
|
|
19302
|
+
server.close();
|
|
19303
|
+
} catch {
|
|
19304
|
+
}
|
|
19305
|
+
}
|
|
19306
|
+
});
|
|
19307
|
+
});
|
|
19308
|
+
server.listen(REDIRECT_PORT, REDIRECT_HOST, () => {
|
|
19309
|
+
resolve11({
|
|
19310
|
+
bound: true,
|
|
19311
|
+
waitForCode: () => codePromise,
|
|
19312
|
+
close: () => {
|
|
19313
|
+
resolveCode(null);
|
|
19314
|
+
try {
|
|
19315
|
+
server.close();
|
|
19316
|
+
} catch {
|
|
19317
|
+
}
|
|
19318
|
+
}
|
|
19319
|
+
});
|
|
19320
|
+
});
|
|
19321
|
+
});
|
|
19322
|
+
}
|
|
19323
|
+
function openBrowser(url) {
|
|
19324
|
+
try {
|
|
19325
|
+
const platform3 = process.platform;
|
|
19326
|
+
const { command, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
|
|
19327
|
+
const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
|
|
19328
|
+
child.on("error", () => {
|
|
19329
|
+
});
|
|
19330
|
+
child.unref();
|
|
19331
|
+
} catch {
|
|
19332
|
+
}
|
|
19333
|
+
}
|
|
19334
|
+
async function runClaudeOAuthLogin(deps, opts = {}) {
|
|
19335
|
+
const providerId = opts.providerId ?? CLAUDE_PROVIDER_ID;
|
|
19336
|
+
const { verifier, challenge } = generatePkce();
|
|
19337
|
+
const state = verifier;
|
|
19338
|
+
const authorizeUrl = buildAuthorizeUrl(challenge, verifier);
|
|
19339
|
+
const ac = new AbortController();
|
|
19340
|
+
const onSig = () => ac.abort();
|
|
19341
|
+
process.on("SIGINT", onSig);
|
|
19342
|
+
const server = await startLoopbackServer(state);
|
|
19343
|
+
deps.renderer.write(
|
|
19344
|
+
color.bold(`
|
|
19345
|
+
Sign in with Claude \u2014 ${color.cyan(providerId)}
|
|
19346
|
+
`) + color.dim(" Uses your Claude Pro/Max subscription (not an API key).\n") + color.amber(" \u26A0 Using a subscription outside the official Claude Code client is against\n") + color.amber(" Anthropic\u2019s Terms \u2014 your account could be rate-limited or banned.\n") + color.dim(" Sanctioned programmatic use = an API key: ") + color.bold("wstack auth anthropic") + color.dim("\n\n") + color.bold(` ${"\u2500".repeat(56)}
|
|
19347
|
+
`) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${authorizeUrl}
|
|
19348
|
+
`) + color.bold(` ${"\u2500".repeat(56)}
|
|
19349
|
+
|
|
19350
|
+
`)
|
|
19351
|
+
);
|
|
19352
|
+
if (server.bound) {
|
|
19353
|
+
openBrowser(authorizeUrl);
|
|
19354
|
+
deps.renderer.write(
|
|
19355
|
+
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")
|
|
19356
|
+
);
|
|
19357
|
+
} else {
|
|
19358
|
+
deps.renderer.write(
|
|
19359
|
+
color.amber(" \u26A0 Could not start the local callback listener (port 53692 in use).\n") + color.dim(" After signing in, paste the full redirect URL (or the code) below.\n")
|
|
19360
|
+
);
|
|
19361
|
+
}
|
|
19362
|
+
let code;
|
|
19363
|
+
try {
|
|
19364
|
+
if (server.bound) {
|
|
19365
|
+
const got = await server.waitForCode();
|
|
19366
|
+
if (got) code = got.code;
|
|
19367
|
+
}
|
|
19368
|
+
if (!code) {
|
|
19369
|
+
const input = (await deps.reader.readLine(
|
|
19370
|
+
`
|
|
19371
|
+
${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
|
|
19372
|
+
)).trim();
|
|
19373
|
+
if (input.toLowerCase() === "q" || input === "") {
|
|
19374
|
+
deps.renderer.write(color.dim(" Cancelled.\n"));
|
|
19375
|
+
return 1;
|
|
19376
|
+
}
|
|
19377
|
+
const parsed = parseAuthorizationInput(input);
|
|
19378
|
+
if (parsed.state && parsed.state !== state) {
|
|
19379
|
+
deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
|
|
19380
|
+
return 1;
|
|
19381
|
+
}
|
|
19382
|
+
code = parsed.code;
|
|
19383
|
+
}
|
|
19384
|
+
if (!code) {
|
|
19385
|
+
deps.renderer.writeError(" No authorization code received.");
|
|
19386
|
+
return 1;
|
|
19387
|
+
}
|
|
19388
|
+
deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
|
|
19389
|
+
const tokens = await exchangeAuthorizationCode(code, state, verifier, ac.signal);
|
|
19390
|
+
const models = await fetchClaudeModels(tokens.access, ac.signal);
|
|
19391
|
+
const saved = await saveClaudeTokens(deps, providerId, tokens, models);
|
|
19392
|
+
if (!saved) return 1;
|
|
19393
|
+
deps.renderer.write(color.green("\n \u2713 Signed in with Claude!\n"));
|
|
19394
|
+
const modelHint = models.find((m) => m.includes("sonnet")) ?? models[0] ?? "claude-sonnet-4-6";
|
|
19395
|
+
deps.renderer.writeInfo(
|
|
19396
|
+
` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
|
|
19397
|
+
Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
|
|
19398
|
+
` + color.dim(" Tokens refresh automatically before they expire.\n")
|
|
19399
|
+
);
|
|
19400
|
+
return 0;
|
|
19401
|
+
} catch (err) {
|
|
19402
|
+
const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
|
|
19403
|
+
deps.renderer.writeError(` Login failed: ${msg}`);
|
|
19404
|
+
return 1;
|
|
19405
|
+
} finally {
|
|
19406
|
+
server.close();
|
|
19407
|
+
process.off("SIGINT", onSig);
|
|
19408
|
+
}
|
|
19409
|
+
}
|
|
19410
|
+
async function saveClaudeTokens(deps, providerId, tokens, models) {
|
|
19411
|
+
const entry = {
|
|
19412
|
+
label: "oauth-default",
|
|
19413
|
+
apiKey: tokens.access,
|
|
19414
|
+
createdAt: nowIso(),
|
|
19415
|
+
authMethod: "oauth",
|
|
19416
|
+
expiresAt: new Date(tokens.expires).toISOString(),
|
|
19417
|
+
refreshToken: tokens.refresh,
|
|
19418
|
+
tokenType: "bearer",
|
|
19419
|
+
scope: SCOPES
|
|
19420
|
+
};
|
|
19421
|
+
try {
|
|
19422
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
19423
|
+
const existing = all[providerId];
|
|
19424
|
+
const p = existing ? { ...existing } : { type: providerId };
|
|
19425
|
+
p.family = "anthropic-oauth";
|
|
19426
|
+
if (!p.baseUrl) p.baseUrl = CLAUDE_BASE_URL;
|
|
19427
|
+
if (models.length > 0) p.models = models;
|
|
19428
|
+
else if (!p.models || p.models.length === 0)
|
|
19429
|
+
p.models = ["claude-sonnet-4-6", "claude-opus-4-8"];
|
|
19430
|
+
const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
|
|
19431
|
+
keys.push(entry);
|
|
19432
|
+
writeKeysBack(p, keys);
|
|
19433
|
+
p.activeKey = entry.label;
|
|
19434
|
+
all[providerId] = p;
|
|
19435
|
+
});
|
|
19436
|
+
return true;
|
|
19437
|
+
} catch (err) {
|
|
19438
|
+
deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
|
|
19439
|
+
return false;
|
|
19440
|
+
}
|
|
19441
|
+
}
|
|
19442
|
+
|
|
19443
|
+
// src/auth-menu/github-copilot-oauth.ts
|
|
19444
|
+
init_provider_config_utils();
|
|
19445
|
+
var CLIENT_ID2 = "Iv1.b507a08c87ecfe98";
|
|
19446
|
+
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
19447
|
+
var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
19448
|
+
var COPILOT_HEADERS = {
|
|
19449
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
19450
|
+
"Editor-Version": "vscode/1.107.0",
|
|
19451
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
19452
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
19453
|
+
};
|
|
19454
|
+
var COPILOT_API_VERSION = "2026-06-01";
|
|
19455
|
+
var COPILOT_PROVIDER_ID = "github-copilot";
|
|
19456
|
+
function openBrowser2(url) {
|
|
19457
|
+
try {
|
|
19458
|
+
const platform3 = process.platform;
|
|
19459
|
+
const { command, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
|
|
19460
|
+
const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
|
|
19461
|
+
child.on("error", () => {
|
|
19462
|
+
});
|
|
19463
|
+
child.unref();
|
|
19464
|
+
} catch {
|
|
19465
|
+
}
|
|
19466
|
+
}
|
|
19467
|
+
function sleep(ms, signal) {
|
|
19468
|
+
return new Promise((resolve11, reject) => {
|
|
19469
|
+
if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
|
|
19470
|
+
const t = setTimeout(resolve11, ms);
|
|
19471
|
+
signal.addEventListener(
|
|
19472
|
+
"abort",
|
|
19473
|
+
() => {
|
|
19474
|
+
clearTimeout(t);
|
|
19475
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
19476
|
+
},
|
|
19477
|
+
{ once: true }
|
|
19478
|
+
);
|
|
19479
|
+
});
|
|
19480
|
+
}
|
|
19481
|
+
async function startDeviceFlow(signal) {
|
|
19482
|
+
const res = await fetch(DEVICE_CODE_URL, {
|
|
19483
|
+
method: "POST",
|
|
19484
|
+
headers: {
|
|
19485
|
+
accept: "application/json",
|
|
19486
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
19487
|
+
"user-agent": COPILOT_HEADERS["User-Agent"]
|
|
19488
|
+
},
|
|
19489
|
+
body: new URLSearchParams({ client_id: CLIENT_ID2, scope: "read:user" }).toString(),
|
|
19490
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
|
|
19491
|
+
});
|
|
19492
|
+
if (!res.ok) throw new Error(`GitHub device-code request failed (${res.status})`);
|
|
19493
|
+
const json = await res.json();
|
|
19494
|
+
if (!json?.device_code || !json.user_code || !json.verification_uri || typeof json.expires_in !== "number") {
|
|
19495
|
+
throw new Error("Invalid device-code response");
|
|
19496
|
+
}
|
|
19497
|
+
return {
|
|
19498
|
+
device_code: json.device_code,
|
|
19499
|
+
user_code: json.user_code,
|
|
19500
|
+
verification_uri: json.verification_uri,
|
|
19501
|
+
interval: json.interval ?? 5,
|
|
19502
|
+
expires_in: json.expires_in
|
|
19503
|
+
};
|
|
19504
|
+
}
|
|
19505
|
+
async function pollForGitHubToken(device, signal) {
|
|
19506
|
+
let intervalMs = device.interval * 1e3;
|
|
19507
|
+
const expiresAt = Date.now() + device.expires_in * 1e3;
|
|
19508
|
+
while (Date.now() < expiresAt) {
|
|
19509
|
+
await sleep(intervalMs, signal);
|
|
19510
|
+
const res = await fetch(ACCESS_TOKEN_URL, {
|
|
19511
|
+
method: "POST",
|
|
19512
|
+
headers: {
|
|
19513
|
+
accept: "application/json",
|
|
19514
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
19515
|
+
"user-agent": COPILOT_HEADERS["User-Agent"]
|
|
19516
|
+
},
|
|
19517
|
+
body: new URLSearchParams({
|
|
19518
|
+
client_id: CLIENT_ID2,
|
|
19519
|
+
device_code: device.device_code,
|
|
19520
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
19521
|
+
}).toString(),
|
|
19522
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
19523
|
+
});
|
|
19524
|
+
const json = await res.json().catch(() => ({}));
|
|
19525
|
+
if (json.access_token) return json.access_token;
|
|
19526
|
+
if (json.error === "authorization_pending") continue;
|
|
19527
|
+
if (json.error === "slow_down") {
|
|
19528
|
+
intervalMs += 5e3;
|
|
19529
|
+
continue;
|
|
19530
|
+
}
|
|
19531
|
+
throw new Error(`Device flow failed: ${json.error ?? "unknown error"}`);
|
|
19532
|
+
}
|
|
19533
|
+
throw new Error("Device code expired \u2014 please restart the login.");
|
|
19534
|
+
}
|
|
19535
|
+
function isUsableCopilotChatModel(item) {
|
|
19536
|
+
if (typeof item.id !== "string" || item.id.length === 0) return false;
|
|
19537
|
+
const cap = item.capabilities;
|
|
19538
|
+
if (cap?.type !== "chat") return false;
|
|
19539
|
+
if (cap.supports?.tool_calls !== true) return false;
|
|
19540
|
+
const eps = item.supported_endpoints;
|
|
19541
|
+
if (Array.isArray(eps) && !eps.includes("/chat/completions")) return false;
|
|
19542
|
+
if (item.policy?.state === "disabled") return false;
|
|
19543
|
+
if (item.vendor === "Experimental") return false;
|
|
19544
|
+
return true;
|
|
19545
|
+
}
|
|
19546
|
+
async function fetchCopilotModels(copilotToken, signal) {
|
|
19547
|
+
try {
|
|
19548
|
+
const base = copilotBaseUrlFromToken(copilotToken);
|
|
19549
|
+
const res = await fetch(`${base}/models`, {
|
|
19550
|
+
headers: {
|
|
19551
|
+
accept: "application/json",
|
|
19552
|
+
authorization: `Bearer ${copilotToken}`,
|
|
19553
|
+
"X-GitHub-Api-Version": COPILOT_API_VERSION,
|
|
19554
|
+
...COPILOT_HEADERS
|
|
19555
|
+
},
|
|
19556
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
|
|
19557
|
+
});
|
|
19558
|
+
if (!res.ok) return [];
|
|
19559
|
+
const json = await res.json();
|
|
19560
|
+
const data = json?.data;
|
|
19561
|
+
if (!Array.isArray(data)) return [];
|
|
19562
|
+
const usable = data.filter(isUsableCopilotChatModel);
|
|
19563
|
+
usable.sort((a, b) => copilotModelRank(a) - copilotModelRank(b));
|
|
19564
|
+
return usable.map((m) => m.id);
|
|
19565
|
+
} catch {
|
|
19566
|
+
return [];
|
|
19567
|
+
}
|
|
19568
|
+
}
|
|
19569
|
+
function copilotModelRank(item) {
|
|
19570
|
+
if (item.is_chat_default === true) return 0;
|
|
19571
|
+
if (item.is_chat_fallback === true) return 1;
|
|
19572
|
+
return 2;
|
|
19573
|
+
}
|
|
19574
|
+
async function runCopilotOAuthLogin(deps, opts = {}) {
|
|
19575
|
+
const providerId = opts.providerId ?? COPILOT_PROVIDER_ID;
|
|
19576
|
+
const ac = new AbortController();
|
|
19577
|
+
const onSig = () => ac.abort();
|
|
19578
|
+
process.on("SIGINT", onSig);
|
|
19579
|
+
try {
|
|
19580
|
+
deps.renderer.write(
|
|
19581
|
+
color.bold(`
|
|
19582
|
+
Sign in with GitHub Copilot \u2014 ${color.cyan(providerId)}
|
|
19583
|
+
`) + 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")
|
|
19584
|
+
);
|
|
19585
|
+
const device = await startDeviceFlow(ac.signal);
|
|
19586
|
+
deps.renderer.write(
|
|
19587
|
+
color.bold(` ${"\u2500".repeat(56)}
|
|
19588
|
+
`) + color.bold(" Open this URL and enter the code:\n") + color.cyan(` ${device.verification_uri}
|
|
19589
|
+
`) + color.bold(" Code: ") + color.green(color.bold(device.user_code)) + "\n" + color.bold(` ${"\u2500".repeat(56)}
|
|
19590
|
+
|
|
19591
|
+
`)
|
|
19592
|
+
);
|
|
19593
|
+
openBrowser2(device.verification_uri);
|
|
19594
|
+
deps.renderer.write(color.dim(" Waiting for you to authorize in the browser...\n"));
|
|
19595
|
+
const githubToken = await pollForGitHubToken(device, ac.signal);
|
|
19596
|
+
deps.renderer.write(color.dim(" Authorized. Fetching your Copilot token...\n"));
|
|
19597
|
+
const copilot = await refreshCopilotToken(githubToken, ac.signal);
|
|
19598
|
+
const models = await fetchCopilotModels(copilot.token, ac.signal);
|
|
19599
|
+
const saved = await saveCopilotTokens(
|
|
19600
|
+
deps,
|
|
19601
|
+
providerId,
|
|
19602
|
+
copilot.token,
|
|
19603
|
+
githubToken,
|
|
19604
|
+
copilot.expires,
|
|
19605
|
+
models
|
|
19606
|
+
);
|
|
19607
|
+
if (!saved) return 1;
|
|
19608
|
+
deps.renderer.write(color.green("\n \u2713 Signed in with GitHub Copilot!\n"));
|
|
19609
|
+
const modelHint = models[0] ?? "gpt-4o";
|
|
19610
|
+
deps.renderer.writeInfo(
|
|
19611
|
+
` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
|
|
19612
|
+
Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
|
|
19613
|
+
` + color.dim(" The Copilot token refreshes automatically.\n")
|
|
19614
|
+
);
|
|
19615
|
+
return 0;
|
|
19616
|
+
} catch (err) {
|
|
19617
|
+
const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
|
|
19618
|
+
deps.renderer.writeError(` Login failed: ${msg}`);
|
|
19619
|
+
return 1;
|
|
19620
|
+
} finally {
|
|
19621
|
+
process.off("SIGINT", onSig);
|
|
19622
|
+
}
|
|
18807
19623
|
}
|
|
18808
|
-
async function
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
19624
|
+
async function saveCopilotTokens(deps, providerId, copilotToken, githubToken, expires, models) {
|
|
19625
|
+
const entry = {
|
|
19626
|
+
label: "oauth-default",
|
|
19627
|
+
apiKey: copilotToken,
|
|
19628
|
+
createdAt: nowIso(),
|
|
19629
|
+
authMethod: "oauth",
|
|
19630
|
+
expiresAt: new Date(expires).toISOString(),
|
|
19631
|
+
refreshToken: githubToken,
|
|
19632
|
+
tokenType: "bearer"
|
|
19633
|
+
};
|
|
19634
|
+
try {
|
|
19635
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
19636
|
+
const existing = all[providerId];
|
|
19637
|
+
const p = existing ? { ...existing } : { type: providerId };
|
|
19638
|
+
p.family = "github-copilot";
|
|
19639
|
+
if (!p.baseUrl) p.baseUrl = copilotBaseUrlFromToken(copilotToken);
|
|
19640
|
+
if (models.length > 0) p.models = models;
|
|
19641
|
+
else if (!p.models || p.models.length === 0) p.models = ["gpt-4o"];
|
|
19642
|
+
const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
|
|
19643
|
+
keys.push(entry);
|
|
19644
|
+
writeKeysBack(p, keys);
|
|
19645
|
+
p.activeKey = entry.label;
|
|
19646
|
+
all[providerId] = p;
|
|
19647
|
+
});
|
|
19648
|
+
return true;
|
|
19649
|
+
} catch (err) {
|
|
19650
|
+
deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
|
|
18821
19651
|
return false;
|
|
18822
19652
|
}
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
19653
|
+
}
|
|
19654
|
+
|
|
19655
|
+
// src/auth-menu/openai-codex-oauth.ts
|
|
19656
|
+
init_provider_config_utils();
|
|
19657
|
+
var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
19658
|
+
var AUTH_BASE_URL = "https://auth.openai.com";
|
|
19659
|
+
var AUTHORIZE_URL2 = `${AUTH_BASE_URL}/oauth/authorize`;
|
|
19660
|
+
var TOKEN_URL2 = `${AUTH_BASE_URL}/oauth/token`;
|
|
19661
|
+
var REDIRECT_PORT2 = 1455;
|
|
19662
|
+
var REDIRECT_HOST2 = "127.0.0.1";
|
|
19663
|
+
var REDIRECT_PATH2 = "/auth/callback";
|
|
19664
|
+
var REDIRECT_URI2 = `http://localhost:${REDIRECT_PORT2}${REDIRECT_PATH2}`;
|
|
19665
|
+
var SCOPE = "openid profile email offline_access";
|
|
19666
|
+
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
19667
|
+
var ORIGINATOR = "wrongstack";
|
|
19668
|
+
var CODEX_PROVIDER_ID = "openai-codex";
|
|
19669
|
+
var CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
19670
|
+
function base64url2(buf) {
|
|
19671
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
19672
|
+
}
|
|
19673
|
+
function generatePkce2() {
|
|
19674
|
+
const verifier = base64url2(randomBytes(32));
|
|
19675
|
+
const challenge = base64url2(createHash("sha256").update(verifier).digest());
|
|
19676
|
+
return { verifier, challenge };
|
|
19677
|
+
}
|
|
19678
|
+
function createState() {
|
|
19679
|
+
return randomBytes(16).toString("hex");
|
|
19680
|
+
}
|
|
19681
|
+
function buildAuthorizeUrl2(challenge, state) {
|
|
19682
|
+
const url = new URL(AUTHORIZE_URL2);
|
|
19683
|
+
url.searchParams.set("response_type", "code");
|
|
19684
|
+
url.searchParams.set("client_id", CLIENT_ID3);
|
|
19685
|
+
url.searchParams.set("redirect_uri", REDIRECT_URI2);
|
|
19686
|
+
url.searchParams.set("scope", SCOPE);
|
|
19687
|
+
url.searchParams.set("code_challenge", challenge);
|
|
19688
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
19689
|
+
url.searchParams.set("state", state);
|
|
19690
|
+
url.searchParams.set("id_token_add_organizations", "true");
|
|
19691
|
+
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
19692
|
+
url.searchParams.set("originator", ORIGINATOR);
|
|
19693
|
+
return url.toString();
|
|
19694
|
+
}
|
|
19695
|
+
function decodeJwtPayload(token) {
|
|
19696
|
+
try {
|
|
19697
|
+
const parts = token.split(".");
|
|
19698
|
+
if (parts.length !== 3) return null;
|
|
19699
|
+
const json = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
19700
|
+
return JSON.parse(json);
|
|
19701
|
+
} catch {
|
|
19702
|
+
return null;
|
|
18831
19703
|
}
|
|
18832
|
-
const baseUrl = (await deps.reader.readLine(
|
|
18833
|
-
` ${color.amber("?")} Base URL ${color.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
18834
|
-
)).trim();
|
|
18835
|
-
const modelsRaw = (await deps.reader.readLine(
|
|
18836
|
-
` ${color.amber("?")} Model ids ${color.dim("(comma-separated, optional)")}: `
|
|
18837
|
-
)).trim();
|
|
18838
|
-
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
18839
|
-
const envVarsRaw = (await deps.reader.readLine(
|
|
18840
|
-
` ${color.amber("?")} Env var names ${color.dim("(comma-separated, optional)")}: `
|
|
18841
|
-
)).trim();
|
|
18842
|
-
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
18843
|
-
return addKeyForProvider(type, deps, {
|
|
18844
|
-
type,
|
|
18845
|
-
family,
|
|
18846
|
-
...baseUrl ? { baseUrl } : {},
|
|
18847
|
-
...models ? { models } : {},
|
|
18848
|
-
...envVars ? { envVars } : {}
|
|
18849
|
-
});
|
|
18850
19704
|
}
|
|
18851
|
-
|
|
18852
|
-
const
|
|
18853
|
-
|
|
18854
|
-
const
|
|
18855
|
-
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
if (!
|
|
18859
|
-
|
|
18860
|
-
|
|
19705
|
+
function extractAccountId(token) {
|
|
19706
|
+
const payload = decodeJwtPayload(token);
|
|
19707
|
+
const auth = payload?.[JWT_CLAIM_PATH];
|
|
19708
|
+
const id = auth?.chatgpt_account_id;
|
|
19709
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
19710
|
+
}
|
|
19711
|
+
async function readTokens2(res, op) {
|
|
19712
|
+
if (!res.ok) {
|
|
19713
|
+
const text = await res.text().catch(() => "");
|
|
19714
|
+
throw new Error(`Codex token ${op} failed (${res.status}): ${text || res.statusText}`);
|
|
18861
19715
|
}
|
|
18862
|
-
const
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
19716
|
+
const json = await res.json();
|
|
19717
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
19718
|
+
throw new Error(`Codex token ${op} response missing fields`);
|
|
19719
|
+
}
|
|
19720
|
+
return {
|
|
19721
|
+
access: json.access_token,
|
|
19722
|
+
refresh: json.refresh_token,
|
|
19723
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
19724
|
+
};
|
|
19725
|
+
}
|
|
19726
|
+
async function exchangeAuthorizationCode2(code, verifier, signal) {
|
|
19727
|
+
const res = await fetch(TOKEN_URL2, {
|
|
19728
|
+
method: "POST",
|
|
19729
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
19730
|
+
body: new URLSearchParams({
|
|
19731
|
+
grant_type: "authorization_code",
|
|
19732
|
+
client_id: CLIENT_ID3,
|
|
19733
|
+
code,
|
|
19734
|
+
code_verifier: verifier,
|
|
19735
|
+
redirect_uri: REDIRECT_URI2
|
|
19736
|
+
}).toString(),
|
|
19737
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
|
|
18867
19738
|
});
|
|
19739
|
+
return readTokens2(res, "exchange");
|
|
18868
19740
|
}
|
|
18869
|
-
|
|
18870
|
-
const
|
|
18871
|
-
|
|
18872
|
-
|
|
18873
|
-
|
|
18874
|
-
|
|
18875
|
-
|
|
18876
|
-
const
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
19741
|
+
function callbackHtml2(ok, message) {
|
|
19742
|
+
const heading = ok ? "Authentication successful" : "Authentication failed";
|
|
19743
|
+
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>`;
|
|
19744
|
+
}
|
|
19745
|
+
function startLoopbackServer2(state) {
|
|
19746
|
+
let resolveCode = () => {
|
|
19747
|
+
};
|
|
19748
|
+
const codePromise = new Promise((resolve11) => {
|
|
19749
|
+
let settled = false;
|
|
19750
|
+
resolveCode = (v) => {
|
|
19751
|
+
if (settled) return;
|
|
19752
|
+
settled = true;
|
|
19753
|
+
resolve11(v);
|
|
18882
19754
|
};
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
19755
|
+
});
|
|
19756
|
+
const server = createServer((req2, res) => {
|
|
19757
|
+
let url;
|
|
19758
|
+
try {
|
|
19759
|
+
url = new URL(req2.url ?? "", `http://${REDIRECT_HOST2}`);
|
|
19760
|
+
} catch {
|
|
19761
|
+
res.statusCode = 400;
|
|
19762
|
+
res.end();
|
|
19763
|
+
return;
|
|
18886
19764
|
}
|
|
18887
|
-
if (
|
|
18888
|
-
|
|
19765
|
+
if (url.pathname !== REDIRECT_PATH2) {
|
|
19766
|
+
res.statusCode = 404;
|
|
19767
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
19768
|
+
res.end(callbackHtml2(false, "Callback route not found."));
|
|
19769
|
+
return;
|
|
18889
19770
|
}
|
|
18890
|
-
|
|
18891
|
-
|
|
19771
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
19772
|
+
const err = url.searchParams.get("error");
|
|
19773
|
+
if (err) {
|
|
19774
|
+
res.statusCode = 400;
|
|
19775
|
+
res.end(callbackHtml2(false, `Authorization error: ${err}`));
|
|
19776
|
+
resolveCode(null);
|
|
19777
|
+
return;
|
|
18892
19778
|
}
|
|
18893
|
-
|
|
18894
|
-
|
|
18895
|
-
|
|
18896
|
-
|
|
18897
|
-
|
|
19779
|
+
if (url.searchParams.get("state") !== state) {
|
|
19780
|
+
res.statusCode = 400;
|
|
19781
|
+
res.end(callbackHtml2(false, "State mismatch \u2014 please restart the login."));
|
|
19782
|
+
resolveCode(null);
|
|
19783
|
+
return;
|
|
19784
|
+
}
|
|
19785
|
+
const code = url.searchParams.get("code");
|
|
19786
|
+
if (!code) {
|
|
19787
|
+
res.statusCode = 400;
|
|
19788
|
+
res.end(callbackHtml2(false, "Missing authorization code."));
|
|
19789
|
+
return;
|
|
19790
|
+
}
|
|
19791
|
+
res.statusCode = 200;
|
|
19792
|
+
res.end(callbackHtml2(true, "You can close this window and return to the terminal."));
|
|
19793
|
+
resolveCode(code);
|
|
19794
|
+
});
|
|
19795
|
+
return new Promise((resolve11) => {
|
|
19796
|
+
server.on("error", () => {
|
|
19797
|
+
resolveCode(null);
|
|
19798
|
+
resolve11({
|
|
19799
|
+
bound: false,
|
|
19800
|
+
waitForCode: () => Promise.resolve(null),
|
|
19801
|
+
close: () => {
|
|
19802
|
+
try {
|
|
19803
|
+
server.close();
|
|
19804
|
+
} catch {
|
|
19805
|
+
}
|
|
19806
|
+
}
|
|
19807
|
+
});
|
|
19808
|
+
});
|
|
19809
|
+
server.listen(REDIRECT_PORT2, REDIRECT_HOST2, () => {
|
|
19810
|
+
resolve11({
|
|
19811
|
+
bound: true,
|
|
19812
|
+
waitForCode: () => codePromise,
|
|
19813
|
+
close: () => {
|
|
19814
|
+
resolveCode(null);
|
|
19815
|
+
try {
|
|
19816
|
+
server.close();
|
|
19817
|
+
} catch {
|
|
19818
|
+
}
|
|
19819
|
+
}
|
|
19820
|
+
});
|
|
19821
|
+
});
|
|
18898
19822
|
});
|
|
19823
|
+
}
|
|
19824
|
+
function openBrowser3(url) {
|
|
19825
|
+
try {
|
|
19826
|
+
const platform3 = process.platform;
|
|
19827
|
+
const { command, args } = platform3 === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform3 === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
|
|
19828
|
+
const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
|
|
19829
|
+
child.on("error", () => {
|
|
19830
|
+
});
|
|
19831
|
+
child.unref();
|
|
19832
|
+
} catch {
|
|
19833
|
+
}
|
|
19834
|
+
}
|
|
19835
|
+
function parseAuthorizationInput2(input) {
|
|
19836
|
+
const value = input.trim();
|
|
19837
|
+
if (!value) return {};
|
|
19838
|
+
try {
|
|
19839
|
+
const url = new URL(value);
|
|
19840
|
+
return {
|
|
19841
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
19842
|
+
state: url.searchParams.get("state") ?? void 0
|
|
19843
|
+
};
|
|
19844
|
+
} catch {
|
|
19845
|
+
}
|
|
19846
|
+
if (value.includes("code=")) {
|
|
19847
|
+
const params = new URLSearchParams(value);
|
|
19848
|
+
return {
|
|
19849
|
+
code: params.get("code") ?? void 0,
|
|
19850
|
+
state: params.get("state") ?? void 0
|
|
19851
|
+
};
|
|
19852
|
+
}
|
|
19853
|
+
return { code: value };
|
|
19854
|
+
}
|
|
19855
|
+
async function runCodexOAuthLogin(deps, opts = {}) {
|
|
19856
|
+
const providerId = opts.providerId ?? CODEX_PROVIDER_ID;
|
|
19857
|
+
const pkce = generatePkce2();
|
|
19858
|
+
const state = createState();
|
|
19859
|
+
const authorizeUrl = buildAuthorizeUrl2(pkce.challenge, state);
|
|
19860
|
+
const ac = new AbortController();
|
|
19861
|
+
const onSig = () => ac.abort();
|
|
19862
|
+
process.on("SIGINT", onSig);
|
|
19863
|
+
const server = await startLoopbackServer2(state);
|
|
18899
19864
|
deps.renderer.write(
|
|
18900
|
-
|
|
18901
|
-
|
|
19865
|
+
color.bold(`
|
|
19866
|
+
Sign in with ChatGPT \u2014 ${color.cyan(providerId)}
|
|
19867
|
+
`) + 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)}
|
|
19868
|
+
`) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${authorizeUrl}
|
|
19869
|
+
`) + color.bold(` ${"\u2500".repeat(56)}
|
|
19870
|
+
|
|
19871
|
+
`)
|
|
18902
19872
|
);
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
19873
|
+
if (server.bound) {
|
|
19874
|
+
openBrowser3(authorizeUrl);
|
|
19875
|
+
deps.renderer.write(
|
|
19876
|
+
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")
|
|
19877
|
+
);
|
|
19878
|
+
} else {
|
|
19879
|
+
deps.renderer.write(
|
|
19880
|
+
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")
|
|
19881
|
+
);
|
|
19882
|
+
}
|
|
19883
|
+
let code;
|
|
19884
|
+
try {
|
|
19885
|
+
if (server.bound) {
|
|
19886
|
+
const got = await server.waitForCode();
|
|
19887
|
+
if (got) code = got;
|
|
19888
|
+
}
|
|
19889
|
+
if (!code) {
|
|
19890
|
+
const input = (await deps.reader.readLine(
|
|
19891
|
+
`
|
|
19892
|
+
${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
|
|
19893
|
+
)).trim();
|
|
19894
|
+
if (input.toLowerCase() === "q" || input === "") {
|
|
19895
|
+
deps.renderer.write(color.dim(" Cancelled.\n"));
|
|
19896
|
+
return 1;
|
|
19897
|
+
}
|
|
19898
|
+
const parsed = parseAuthorizationInput2(input);
|
|
19899
|
+
if (parsed.state && parsed.state !== state) {
|
|
19900
|
+
deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
|
|
19901
|
+
return 1;
|
|
19902
|
+
}
|
|
19903
|
+
code = parsed.code;
|
|
19904
|
+
}
|
|
19905
|
+
if (!code) {
|
|
19906
|
+
deps.renderer.writeError(" No authorization code received.");
|
|
19907
|
+
return 1;
|
|
19908
|
+
}
|
|
19909
|
+
deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
|
|
19910
|
+
const tokens = await exchangeAuthorizationCode2(code, pkce.verifier, ac.signal);
|
|
19911
|
+
const accountId = extractAccountId(tokens.access);
|
|
19912
|
+
if (!accountId) {
|
|
19913
|
+
deps.renderer.writeError(
|
|
19914
|
+
" Signed in, but the token has no ChatGPT account id.\n This account may not have Codex/ChatGPT subscription access."
|
|
19915
|
+
);
|
|
19916
|
+
return 1;
|
|
19917
|
+
}
|
|
19918
|
+
const saved = await saveCodexTokens(deps, providerId, tokens, accountId);
|
|
19919
|
+
if (!saved) return 1;
|
|
19920
|
+
deps.renderer.write(color.green("\n \u2713 Signed in with ChatGPT!\n"));
|
|
19921
|
+
deps.renderer.writeInfo(
|
|
19922
|
+
` Saved as provider ${color.bold(providerId)}.
|
|
19923
|
+
Use: ${color.bold(`wstack --provider ${providerId} --model gpt-5.5`)} "<task>"
|
|
19924
|
+
` + color.dim(" Tokens refresh automatically before they expire.\n")
|
|
19925
|
+
);
|
|
19926
|
+
return 0;
|
|
19927
|
+
} catch (err) {
|
|
19928
|
+
const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
|
|
19929
|
+
deps.renderer.writeError(` Login failed: ${msg}`);
|
|
19930
|
+
return 1;
|
|
19931
|
+
} finally {
|
|
19932
|
+
server.close();
|
|
19933
|
+
process.off("SIGINT", onSig);
|
|
19934
|
+
}
|
|
18906
19935
|
}
|
|
18907
|
-
async function
|
|
18908
|
-
const
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
19936
|
+
async function saveCodexTokens(deps, providerId, tokens, accountId) {
|
|
19937
|
+
const entry = {
|
|
19938
|
+
label: "oauth-default",
|
|
19939
|
+
apiKey: tokens.access,
|
|
19940
|
+
createdAt: nowIso(),
|
|
19941
|
+
authMethod: "oauth",
|
|
19942
|
+
expiresAt: new Date(tokens.expires).toISOString(),
|
|
19943
|
+
refreshToken: tokens.refresh,
|
|
19944
|
+
tokenType: "bearer",
|
|
19945
|
+
scope: SCOPE,
|
|
19946
|
+
accountId
|
|
19947
|
+
};
|
|
19948
|
+
try {
|
|
19949
|
+
await mutateConfigProviders(deps.globalConfigPath, deps.vault, (all) => {
|
|
19950
|
+
const existing = all[providerId];
|
|
19951
|
+
const p = existing ? { ...existing } : { type: providerId };
|
|
19952
|
+
p.family = "openai-codex";
|
|
19953
|
+
if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;
|
|
19954
|
+
if (!p.models || p.models.length === 0) {
|
|
19955
|
+
p.models = ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark"];
|
|
19956
|
+
}
|
|
19957
|
+
const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
|
|
19958
|
+
keys.push(entry);
|
|
19959
|
+
writeKeysBack(p, keys);
|
|
19960
|
+
p.activeKey = entry.label;
|
|
19961
|
+
all[providerId] = p;
|
|
19962
|
+
});
|
|
19963
|
+
return true;
|
|
19964
|
+
} catch (err) {
|
|
19965
|
+
deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
|
|
19966
|
+
return false;
|
|
18916
19967
|
}
|
|
18917
|
-
return label;
|
|
18918
19968
|
}
|
|
18919
19969
|
|
|
18920
19970
|
// src/auth-menu/provider-menu.ts
|
|
@@ -19092,6 +20142,24 @@ function validKeyIndex(arg, max, deps, verb) {
|
|
|
19092
20142
|
}
|
|
19093
20143
|
|
|
19094
20144
|
// src/auth-menu/top-menu.ts
|
|
20145
|
+
async function runSignInMenu(deps) {
|
|
20146
|
+
deps.renderer.write(
|
|
20147
|
+
`
|
|
20148
|
+
${color.bold("Sign in with a subscription:")}
|
|
20149
|
+
` + color.amber(" \u26A0 Subscription tokens used outside official clients may violate provider\n") + color.amber(" Terms \u2014 your account could be rate-limited or banned. An API key is the\n") + color.dim(" sanctioned path for programmatic use.\n") + ` ${color.bold("1")} ChatGPT Plus/Pro ${color.dim("(\u2192 openai-codex)")}
|
|
20150
|
+
${color.bold("2")} Claude Pro/Max ${color.dim("(\u2192 anthropic-oauth)")}
|
|
20151
|
+
${color.bold("3")} GitHub Copilot ${color.dim("(\u2192 github-copilot)")}
|
|
20152
|
+
`
|
|
20153
|
+
);
|
|
20154
|
+
const pick = (await deps.reader.readLine(` ${color.amber("?")} Pick ${color.dim("(or b to go back)")}: `)).trim().toLowerCase();
|
|
20155
|
+
if (pick === "1" || pick === "chatgpt" || pick === "openai" || pick === "codex") {
|
|
20156
|
+
await runCodexOAuthLogin(deps);
|
|
20157
|
+
} else if (pick === "2" || pick === "claude" || pick === "anthropic") {
|
|
20158
|
+
await runClaudeOAuthLogin(deps);
|
|
20159
|
+
} else if (pick === "3" || pick === "copilot" || pick === "github") {
|
|
20160
|
+
await runCopilotOAuthLogin(deps);
|
|
20161
|
+
}
|
|
20162
|
+
}
|
|
19095
20163
|
async function runTopMenu(deps) {
|
|
19096
20164
|
for (; ; ) {
|
|
19097
20165
|
const providers = await loadProviders(deps);
|
|
@@ -19111,6 +20179,10 @@ ${color.amber("?")} Pick: `)).trim().toLowerCase();
|
|
|
19111
20179
|
await addCustomProvider(deps);
|
|
19112
20180
|
continue;
|
|
19113
20181
|
}
|
|
20182
|
+
if (choice === "s" || choice === "signin" || choice === "login") {
|
|
20183
|
+
await runSignInMenu(deps);
|
|
20184
|
+
continue;
|
|
20185
|
+
}
|
|
19114
20186
|
const idx = Number.parseInt(choice, 10);
|
|
19115
20187
|
if (!Number.isNaN(idx) && idx >= 1 && idx <= ids.length) {
|
|
19116
20188
|
const pid = ids[idx - 1];
|
|
@@ -19189,6 +20261,26 @@ var authCmd = async (args, deps) => {
|
|
|
19189
20261
|
}
|
|
19190
20262
|
return runAuthRemove(menuDeps, pid);
|
|
19191
20263
|
}
|
|
20264
|
+
if (first === "login") {
|
|
20265
|
+
const pid = (flags.positional[1] ?? "").toLowerCase();
|
|
20266
|
+
const codexAliases = /* @__PURE__ */ new Set(["", "openai", "codex", "chatgpt", "codex-cli", CODEX_PROVIDER_ID]);
|
|
20267
|
+
const claudeAliases = /* @__PURE__ */ new Set(["claude", "anthropic", "claude-pro", "claude-max", "anthropic-oauth"]);
|
|
20268
|
+
const copilotAliases = /* @__PURE__ */ new Set(["copilot", "github", "github-copilot", "gh"]);
|
|
20269
|
+
if (claudeAliases.has(pid)) {
|
|
20270
|
+
return runClaudeOAuthLogin(menuDeps);
|
|
20271
|
+
}
|
|
20272
|
+
if (copilotAliases.has(pid)) {
|
|
20273
|
+
return runCopilotOAuthLogin(menuDeps);
|
|
20274
|
+
}
|
|
20275
|
+
if (codexAliases.has(pid)) {
|
|
20276
|
+
return runCodexOAuthLogin(menuDeps);
|
|
20277
|
+
}
|
|
20278
|
+
deps.renderer.writeError("OAuth login is only supported for ChatGPT, Claude, and GitHub Copilot.");
|
|
20279
|
+
deps.renderer.write(
|
|
20280
|
+
color.dim(" Sign in with ChatGPT: ") + color.bold("wstack auth login chatgpt") + "\n" + color.dim(" Sign in with Claude: ") + color.bold("wstack auth login claude") + "\n" + color.dim(" Sign in with Copilot: ") + color.bold("wstack auth login copilot") + "\n" + color.dim(" For an API key instead: ") + color.bold(`wstack auth ${pid}`) + "\n"
|
|
20281
|
+
);
|
|
20282
|
+
return 1;
|
|
20283
|
+
}
|
|
19192
20284
|
return runAuthDirect(menuDeps, {
|
|
19193
20285
|
providerId: first,
|
|
19194
20286
|
label: flags.label,
|
|
@@ -21165,8 +22257,11 @@ var providersCmd = async (args, deps) => {
|
|
|
21165
22257
|
const all = await deps.modelsRegistry.listProviders();
|
|
21166
22258
|
const byFamily = {
|
|
21167
22259
|
anthropic: [],
|
|
22260
|
+
"anthropic-oauth": [],
|
|
21168
22261
|
openai: [],
|
|
21169
22262
|
"openai-compatible": [],
|
|
22263
|
+
"openai-codex": [],
|
|
22264
|
+
"github-copilot": [],
|
|
21170
22265
|
google: [],
|
|
21171
22266
|
unsupported: []
|
|
21172
22267
|
};
|
|
@@ -21370,11 +22465,11 @@ async function mutateModelsConfig(deps, mutator) {
|
|
|
21370
22465
|
}
|
|
21371
22466
|
parsed = {};
|
|
21372
22467
|
}
|
|
21373
|
-
const decrypted = decryptConfigSecrets(parsed, vault);
|
|
22468
|
+
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
21374
22469
|
const models = decrypted.models ?? {};
|
|
21375
22470
|
mutator(models);
|
|
21376
22471
|
decrypted.models = models;
|
|
21377
|
-
const encrypted = encryptConfigSecrets(decrypted, vault);
|
|
22472
|
+
const encrypted = encryptConfigSecrets$1(decrypted, vault);
|
|
21378
22473
|
await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
21379
22474
|
}
|
|
21380
22475
|
function parseSizeFlag(raw) {
|
|
@@ -22578,9 +23673,9 @@ async function checkGitInCwd(opts) {
|
|
|
22578
23673
|
const answer = (await reader.readLine(` ${color.amber("?")} Initialize one here? ${color.dim("[y/N]")} `)).trim().toLowerCase();
|
|
22579
23674
|
if (answer === "y" || answer === "yes") {
|
|
22580
23675
|
try {
|
|
22581
|
-
const { spawn:
|
|
23676
|
+
const { spawn: spawn9 } = await import('child_process');
|
|
22582
23677
|
await new Promise((resolve11, reject) => {
|
|
22583
|
-
const child =
|
|
23678
|
+
const child = spawn9("git", ["init"], {
|
|
22584
23679
|
cwd,
|
|
22585
23680
|
signal: AbortSignal.timeout(1e4),
|
|
22586
23681
|
windowsHide: true
|
|
@@ -22886,6 +23981,7 @@ function bindSystemPromptBuilder(deps) {
|
|
|
22886
23981
|
modeId: deps.modeId,
|
|
22887
23982
|
modePrompt: deps.modePrompt,
|
|
22888
23983
|
modelCapabilities: deps.modelCapabilities,
|
|
23984
|
+
tokenSavingMode: deps.tokenSavingMode,
|
|
22889
23985
|
planPath: () => deps.sessionRef.current ? deps.pathJoiner.join(
|
|
22890
23986
|
deps.paths.projectSessions,
|
|
22891
23987
|
`${deps.sessionRef.current.id}.plan.json`
|
|
@@ -22905,12 +24001,32 @@ function bindSystemPromptBuilder(deps) {
|
|
|
22905
24001
|
})
|
|
22906
24002
|
);
|
|
22907
24003
|
}
|
|
24004
|
+
var SIBLING_CATALOG = {
|
|
24005
|
+
"anthropic-oauth": "anthropic",
|
|
24006
|
+
"openai-codex": "openai",
|
|
24007
|
+
"github-copilot": "openai"
|
|
24008
|
+
};
|
|
22908
24009
|
async function resolveRuntimeMaxContext(input) {
|
|
22909
24010
|
const explicitContext = positiveNumber(input.config.context?.effectiveMaxContext);
|
|
22910
24011
|
if (explicitContext) return explicitContext;
|
|
22911
24012
|
const providerConfig = input.runtimeProviderConfig ?? input.config.providers?.[input.providerId];
|
|
22912
24013
|
const providerOverride = positiveNumber(readConfiguredMaxContext(providerConfig));
|
|
22913
24014
|
if (providerOverride) return providerOverride;
|
|
24015
|
+
const sibling = providerConfig?.family ? SIBLING_CATALOG[providerConfig.family] : void 0;
|
|
24016
|
+
if (sibling && input.modelsRegistry) {
|
|
24017
|
+
const mergedModels = mergeCustomModelDefs(providerConfig?.customModels, input.config.models);
|
|
24018
|
+
const caps = await capabilitiesFor(
|
|
24019
|
+
input.modelsRegistry,
|
|
24020
|
+
sibling,
|
|
24021
|
+
input.modelId,
|
|
24022
|
+
mergedModels
|
|
24023
|
+
).catch(() => void 0);
|
|
24024
|
+
const siblingMax = positiveNumber(caps?.maxContext);
|
|
24025
|
+
if (siblingMax) return siblingMax;
|
|
24026
|
+
const directModel = await input.modelsRegistry.getModel(sibling, input.modelId).catch(() => void 0);
|
|
24027
|
+
const directMax = positiveNumber(directModel?.capabilities.maxContext);
|
|
24028
|
+
if (directMax) return directMax;
|
|
24029
|
+
}
|
|
22914
24030
|
const catalogId = providerConfig?.type && providerConfig.type !== input.providerId ? providerConfig.type : input.providerId;
|
|
22915
24031
|
if (input.modelsRegistry) {
|
|
22916
24032
|
const topLevelBaseUrlApplies = input.providerId === input.config.provider;
|
|
@@ -24178,7 +25294,9 @@ async function execute(deps) {
|
|
|
24178
25294
|
enhanceController,
|
|
24179
25295
|
statuslineHiddenItems,
|
|
24180
25296
|
setStatuslineHiddenItems,
|
|
25297
|
+
saveStatuslineHiddenItems,
|
|
24181
25298
|
agentsMonitorController,
|
|
25299
|
+
onPanelOpen,
|
|
24182
25300
|
getYolo,
|
|
24183
25301
|
getAutonomy,
|
|
24184
25302
|
onAutonomy,
|
|
@@ -24629,6 +25747,11 @@ Reply with ONLY the JSON object.`
|
|
|
24629
25747
|
allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
|
|
24630
25748
|
contextAutoCompact: cfg.context?.autoCompact !== false,
|
|
24631
25749
|
contextStrategy: cfg.context?.strategy ?? "hybrid",
|
|
25750
|
+
contextMode: (() => {
|
|
25751
|
+
const m = cfg.context?.["mode"];
|
|
25752
|
+
return m === "frugal" || m === "deep" || m === "archival" ? m : "balanced";
|
|
25753
|
+
})(),
|
|
25754
|
+
maxConcurrent: cfg.maxConcurrent ?? 0,
|
|
24632
25755
|
logLevel: cfg.log?.level ?? "info",
|
|
24633
25756
|
auditLevel: cfg.session?.auditLevel ?? "standard",
|
|
24634
25757
|
indexOnStart: cfg.indexing?.onSessionStart !== false,
|
|
@@ -24685,7 +25808,7 @@ Reply with ONLY the JSON object.`
|
|
|
24685
25808
|
);
|
|
24686
25809
|
}
|
|
24687
25810
|
const parsed = JSON.parse(raw);
|
|
24688
|
-
const decrypted = decryptConfigSecrets(parsed, noOpVault);
|
|
25811
|
+
const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
|
|
24689
25812
|
if (s.nextPrediction !== void 0) {
|
|
24690
25813
|
decrypted.nextPrediction = s.nextPrediction;
|
|
24691
25814
|
}
|
|
@@ -24755,7 +25878,7 @@ Reply with ONLY the JSON object.`
|
|
|
24755
25878
|
decrypted.autonomy = autonomy;
|
|
24756
25879
|
}
|
|
24757
25880
|
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
24758
|
-
const encrypted = encryptConfigSecrets(toWrite, noOpVault);
|
|
25881
|
+
const encrypted = encryptConfigSecrets$1(toWrite, noOpVault);
|
|
24759
25882
|
if (targetPath !== wpaths.globalConfig) {
|
|
24760
25883
|
await fsp5.mkdir(path4.dirname(targetPath), { recursive: true });
|
|
24761
25884
|
}
|
|
@@ -24855,6 +25978,7 @@ Reply with ONLY the JSON object.`
|
|
|
24855
25978
|
enhanceController,
|
|
24856
25979
|
statuslineHiddenItems,
|
|
24857
25980
|
setStatuslineHiddenItems,
|
|
25981
|
+
saveStatuslineHiddenItems,
|
|
24858
25982
|
agentsMonitorController,
|
|
24859
25983
|
getLiveSessions: async () => {
|
|
24860
25984
|
const { SessionRegistry } = await import('@wrongstack/core');
|
|
@@ -25180,12 +26304,13 @@ ${parts.join("\n")}
|
|
|
25180
26304
|
// `wrongstack quick` sets flags.quick — open the F3 agents monitor by default.
|
|
25181
26305
|
initialAgentsMonitorOpen: !!flags.quick,
|
|
25182
26306
|
tokenSavingMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off",
|
|
25183
|
-
toolCount: agent.tools.list().length
|
|
26307
|
+
toolCount: agent.tools.list().length,
|
|
26308
|
+
onPanelOpen
|
|
25184
26309
|
});
|
|
25185
26310
|
if (code === PROJECT_SWITCH_EXIT_CODE && pendingProjectSwitch) {
|
|
25186
26311
|
const { root, name, resumeSessionId } = pendingProjectSwitch;
|
|
25187
26312
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
25188
|
-
const { spawn:
|
|
26313
|
+
const { spawn: spawn9 } = await import('child_process');
|
|
25189
26314
|
const { createRequire: createRequire8 } = await import('module');
|
|
25190
26315
|
let cliPath;
|
|
25191
26316
|
try {
|
|
@@ -25204,7 +26329,7 @@ ${parts.join("\n")}
|
|
|
25204
26329
|
const nodeExe = process.execPath;
|
|
25205
26330
|
const spawnArgs = [cliPath, "--no-interactive"];
|
|
25206
26331
|
if (resumeSessionId) spawnArgs.push("--resume", resumeSessionId);
|
|
25207
|
-
|
|
26332
|
+
spawn9(nodeExe, spawnArgs, {
|
|
25208
26333
|
cwd: root,
|
|
25209
26334
|
stdio: "ignore",
|
|
25210
26335
|
detached: true
|
|
@@ -25239,6 +26364,7 @@ ${parts.join("\n")}
|
|
|
25239
26364
|
open: !!flags.open,
|
|
25240
26365
|
modelsRegistry,
|
|
25241
26366
|
globalConfigPath: wpaths.globalConfig,
|
|
26367
|
+
mcpRegistry,
|
|
25242
26368
|
subscribeEternalIteration,
|
|
25243
26369
|
sessionStore,
|
|
25244
26370
|
sessionsDir: wpaths.projectSessions,
|
|
@@ -25748,8 +26874,9 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25748
26874
|
const baseRegistry = this.subagentToolRegistry(tools);
|
|
25749
26875
|
if (injectedFleetEmit) baseRegistry.register(injectedFleetEmit);
|
|
25750
26876
|
if (injectedFleetStatus) baseRegistry.register(injectedFleetStatus);
|
|
26877
|
+
const subAllowedCaps = this.resolveSubagentCapabilities(subCfg);
|
|
25751
26878
|
const toolExecutor = new ToolExecutor(baseRegistry, {
|
|
25752
|
-
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
26879
|
+
permissionPolicy: new AutoApprovePermissionPolicy(subAllowedCaps),
|
|
25753
26880
|
secretScrubber: this.deps.secretScrubber,
|
|
25754
26881
|
renderer: this.deps.renderer,
|
|
25755
26882
|
events,
|
|
@@ -25767,9 +26894,9 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25767
26894
|
context: ctx,
|
|
25768
26895
|
// Subagents cannot answer interactive permission prompts — they
|
|
25769
26896
|
// run under a director, not the user. Auto-approve everything
|
|
25770
|
-
//
|
|
25771
|
-
// the work when they invoked the leader.
|
|
25772
|
-
permissionPolicy: new AutoApprovePermissionPolicy(),
|
|
26897
|
+
// whose capability is in the (possibly widened) allowlist; the
|
|
26898
|
+
// user already authorized the work when they invoked the leader.
|
|
26899
|
+
permissionPolicy: new AutoApprovePermissionPolicy(subAllowedCaps),
|
|
25773
26900
|
toolExecutor
|
|
25774
26901
|
});
|
|
25775
26902
|
agent.extensions.register(
|
|
@@ -25915,6 +27042,36 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25915
27042
|
const allowSet = new Set(allow);
|
|
25916
27043
|
return all.filter((t) => allowSet.has(t.name));
|
|
25917
27044
|
}
|
|
27045
|
+
/**
|
|
27046
|
+
* Resolve the capability allowlist for a subagent's auto-approve policy.
|
|
27047
|
+
*
|
|
27048
|
+
* Precedence:
|
|
27049
|
+
* 1. Explicit `subCfg.allowedCapabilities` — the spawn site knows best
|
|
27050
|
+
* (e.g. `/techstack` grants exactly `fs.write` on top of the safe set).
|
|
27051
|
+
* 2. A scoped `subCfg.tools` slice — the granted tool slice IS the intended
|
|
27052
|
+
* capability grant. The leader/roster deliberately chose these tools, so
|
|
27053
|
+
* allow exactly the capabilities they declare (plus the read-only safe
|
|
27054
|
+
* floor). Without this, a role given the `write`/`build` tool presets
|
|
27055
|
+
* could *see* those tools but the policy would deny execution (`fs.write`
|
|
27056
|
+
* and `shell.*` are not in the read-only default), silently crippling
|
|
27057
|
+
* every code-writing / build role in the catalog.
|
|
27058
|
+
* 3. No tool restriction (full registry) → the WIDE working set
|
|
27059
|
+
* (`WIDE_SUBAGENT_CAPABILITIES`: read, write, net, shell, install). The
|
|
27060
|
+
* user authorized full developer work when they invoked the leader, so a
|
|
27061
|
+
* delegated agent runs the same toolchain end-to-end. The genuinely
|
|
27062
|
+
* blast-radius-escaping capabilities (fs.write.outside-project, mcp.proxy,
|
|
27063
|
+
* subagent.spawn, config.mutate) stay off and need an explicit (1) grant.
|
|
27064
|
+
*/
|
|
27065
|
+
resolveSubagentCapabilities(subCfg) {
|
|
27066
|
+
if (subCfg.allowedCapabilities) return subCfg.allowedCapabilities;
|
|
27067
|
+
const allow = subCfg.tools;
|
|
27068
|
+
if (!allow || allow.length === 0) return WIDE_SUBAGENT_CAPABILITIES;
|
|
27069
|
+
const caps = new Set(WIDE_SUBAGENT_CAPABILITIES);
|
|
27070
|
+
for (const tool of this.filterTools([...allow])) {
|
|
27071
|
+
for (const c of tool.capabilities ?? []) caps.add(c);
|
|
27072
|
+
}
|
|
27073
|
+
return [...caps];
|
|
27074
|
+
}
|
|
25918
27075
|
subagentToolRegistry(allow) {
|
|
25919
27076
|
if (!allow || allow.length === 0) return this.deps.toolRegistry;
|
|
25920
27077
|
const sub = new ToolRegistry();
|
|
@@ -25937,7 +27094,8 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
25937
27094
|
role: "general",
|
|
25938
27095
|
provider: opts?.provider,
|
|
25939
27096
|
model: opts?.model,
|
|
25940
|
-
tools: opts?.tools
|
|
27097
|
+
tools: opts?.tools,
|
|
27098
|
+
allowedCapabilities: opts?.allowedCapabilities
|
|
25941
27099
|
};
|
|
25942
27100
|
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig, description);
|
|
25943
27101
|
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
@@ -26563,7 +27721,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
26563
27721
|
let watcher;
|
|
26564
27722
|
if (idx.watchExternal) {
|
|
26565
27723
|
try {
|
|
26566
|
-
watcher =
|
|
27724
|
+
watcher = fs3.watch(projectRoot, { recursive: true }, (_event, filename) => {
|
|
26567
27725
|
if (!filename) return;
|
|
26568
27726
|
const rel = filename.toString();
|
|
26569
27727
|
if (isIgnored(rel)) return;
|
|
@@ -26971,7 +28129,17 @@ async function setupProvider(params) {
|
|
|
26971
28129
|
resolvedProvider = await modelsRegistry.getProvider(savedProviderCfg.type).catch(() => void 0);
|
|
26972
28130
|
}
|
|
26973
28131
|
if (!resolvedProvider) {
|
|
26974
|
-
if (
|
|
28132
|
+
if (savedProviderCfg?.family) {
|
|
28133
|
+
resolvedProvider = {
|
|
28134
|
+
id: config.provider,
|
|
28135
|
+
name: config.provider,
|
|
28136
|
+
family: savedProviderCfg.family,
|
|
28137
|
+
apiBase: savedProviderCfg.baseUrl,
|
|
28138
|
+
envVars: savedProviderCfg.envVars ?? [],
|
|
28139
|
+
models: (savedProviderCfg.models ?? []).map((m) => ({ id: m, name: m })),
|
|
28140
|
+
npm: void 0
|
|
28141
|
+
};
|
|
28142
|
+
} else {
|
|
26975
28143
|
logger.warn(
|
|
26976
28144
|
`Provider "${config.provider}" not found in models.dev. Continuing with raw config.`
|
|
26977
28145
|
);
|
|
@@ -27053,11 +28221,13 @@ async function resolveModeAndCapabilities(deps) {
|
|
|
27053
28221
|
).catch(() => void 0),
|
|
27054
28222
|
deps.modelsRegistry.getModel(deps.config.provider, deps.config.model).catch(() => void 0)
|
|
27055
28223
|
]);
|
|
27056
|
-
const
|
|
27057
|
-
|
|
27058
|
-
|
|
27059
|
-
|
|
27060
|
-
|
|
28224
|
+
const instanceCaps = provider.capabilities;
|
|
28225
|
+
const useInstanceCaps = !resolvedModel && !!instanceCaps;
|
|
28226
|
+
const modelCapabilities = resolvedCaps || useInstanceCaps ? {
|
|
28227
|
+
maxContextTokens: (useInstanceCaps ? instanceCaps?.maxContext : resolvedCaps?.maxContext) || instanceCaps?.maxContext || 0,
|
|
28228
|
+
supportsTools: useInstanceCaps ? !!instanceCaps?.tools : resolvedCaps?.tools ?? false,
|
|
28229
|
+
supportsVision: useInstanceCaps ? !!instanceCaps?.vision : resolvedCaps?.vision ?? false,
|
|
28230
|
+
supportsReasoning: resolvedModel?.capabilities.reasoning ?? instanceCaps?.reasoning ?? false
|
|
27061
28231
|
} : void 0;
|
|
27062
28232
|
return {
|
|
27063
28233
|
kind: "ok",
|
|
@@ -27105,6 +28275,21 @@ async function main(argv) {
|
|
|
27105
28275
|
} = ctx;
|
|
27106
28276
|
const { updateInfo: refreshedUpdateInfo } = await runPreflight(config, updateInfo);
|
|
27107
28277
|
updateInfo = refreshedUpdateInfo;
|
|
28278
|
+
setOAuthTokenPersister((providerId, creds) => {
|
|
28279
|
+
void mutateConfigProviders(wpaths.globalConfig, vault, (all) => {
|
|
28280
|
+
const p = all[providerId];
|
|
28281
|
+
if (!p) return;
|
|
28282
|
+
const keys = normalizeKeys(p);
|
|
28283
|
+
const active = p.activeKey ? keys.find((k) => k.label === p.activeKey) : keys[0];
|
|
28284
|
+
if (!active) return;
|
|
28285
|
+
active.apiKey = creds.accessToken;
|
|
28286
|
+
active.refreshToken = creds.refreshToken;
|
|
28287
|
+
active.expiresAt = new Date(creds.expiresAt).toISOString();
|
|
28288
|
+
if (creds.accountId) active.accountId = creds.accountId;
|
|
28289
|
+
writeKeysBack(p, keys);
|
|
28290
|
+
}).catch(() => {
|
|
28291
|
+
});
|
|
28292
|
+
});
|
|
27108
28293
|
const { events, container } = wireContainer({
|
|
27109
28294
|
config,
|
|
27110
28295
|
wpaths,
|
|
@@ -27168,6 +28353,7 @@ async function main(argv) {
|
|
|
27168
28353
|
modePrompt,
|
|
27169
28354
|
modelCapabilities,
|
|
27170
28355
|
skillsEnabled: config.features.skills,
|
|
28356
|
+
tokenSavingMode: config.features.tokenSavingMode,
|
|
27171
28357
|
paths: {
|
|
27172
28358
|
projectGoal: wpaths.projectGoal,
|
|
27173
28359
|
projectSessions: wpaths.projectSessions
|
|
@@ -27594,7 +28780,10 @@ async function main(argv) {
|
|
|
27594
28780
|
toolRegistry,
|
|
27595
28781
|
events,
|
|
27596
28782
|
log: logger,
|
|
27597
|
-
lazyMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off"
|
|
28783
|
+
lazyMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off",
|
|
28784
|
+
// Lazy-connect (per-server `lazy`) needs a manifest cache to register tools
|
|
28785
|
+
// cold; idle auto-sleep uses the default timeout.
|
|
28786
|
+
cacheDir: wpaths.cacheDir
|
|
27598
28787
|
});
|
|
27599
28788
|
if (config.features.mcp) {
|
|
27600
28789
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -27996,12 +29185,24 @@ async function main(argv) {
|
|
|
27996
29185
|
const setStatuslineHiddenItems = (items) => {
|
|
27997
29186
|
currentHiddenItems = items;
|
|
27998
29187
|
};
|
|
29188
|
+
const ALL_STATUSLINE_KEYS = ["todos", "plan", "tasks", "fleet", "git", "elapsed", "context", "cost", "working_dir"];
|
|
29189
|
+
const saveStatuslineHiddenItems = async (items) => {
|
|
29190
|
+
currentHiddenItems = items;
|
|
29191
|
+
const cfg = { ...DEFAULTS };
|
|
29192
|
+
for (const k of ALL_STATUSLINE_KEYS) {
|
|
29193
|
+
cfg[k] = !items.includes(k);
|
|
29194
|
+
}
|
|
29195
|
+
await saveStatuslineConfig(cfg);
|
|
29196
|
+
};
|
|
27999
29197
|
const agentsMonitorController = {
|
|
28000
29198
|
visible: false,
|
|
28001
29199
|
setVisible(visible) {
|
|
28002
29200
|
this.visible = visible;
|
|
28003
29201
|
}
|
|
28004
29202
|
};
|
|
29203
|
+
const onPanelOpen = {
|
|
29204
|
+
current: null
|
|
29205
|
+
};
|
|
28005
29206
|
const autoPhaseHost = createAutoPhaseHost({
|
|
28006
29207
|
multiAgentHost,
|
|
28007
29208
|
getConfig: () => config,
|
|
@@ -28045,7 +29246,9 @@ async function main(argv) {
|
|
|
28045
29246
|
statuslineConfig: statuslineConfigDeps,
|
|
28046
29247
|
statuslineHiddenItems: [...currentHiddenItems],
|
|
28047
29248
|
setStatuslineHiddenItems,
|
|
29249
|
+
saveStatuslineHiddenItems,
|
|
28048
29250
|
agentsMonitorController,
|
|
29251
|
+
onPanelOpen,
|
|
28049
29252
|
configStore,
|
|
28050
29253
|
reader,
|
|
28051
29254
|
brain,
|
|
@@ -28805,6 +30008,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
28805
30008
|
enhanceController,
|
|
28806
30009
|
statuslineHiddenItems,
|
|
28807
30010
|
setStatuslineHiddenItems,
|
|
30011
|
+
saveStatuslineHiddenItems,
|
|
28808
30012
|
getYolo: () => {
|
|
28809
30013
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
28810
30014
|
return policy.getYolo?.() ?? config.yolo ?? false;
|