@wrongstack/cli 0.54.1 → 0.63.4
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/README.md +121 -118
- package/dist/index.js +648 -93
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as path8 from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import * as fsp3 from 'fs/promises';
|
|
5
|
-
import { color, writeErr, DefaultTaskStore, TaskTracker, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, renderTaskGraph, SpecVersioning, DefaultSecretScrubber, atomicWrite, DefaultPathResolver, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, bootConfig as bootConfig$1, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode,
|
|
5
|
+
import { color, writeErr, DefaultTaskStore, TaskTracker, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, renderTaskGraph, SpecVersioning, DefaultSecretScrubber, atomicWrite, DefaultPathResolver, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, HookRegistry, HookRunner, SlashCommandRegistry, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, bootConfig as bootConfig$1, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, onResize, ERROR_CODES, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError } from '@wrongstack/core';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import * as os2 from 'os';
|
|
8
8
|
import os2__default from 'os';
|
|
@@ -10,21 +10,21 @@ import * as crypto2 from 'crypto';
|
|
|
10
10
|
import { randomUUID } from 'crypto';
|
|
11
11
|
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
12
12
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
13
|
-
import { MCPRegistry } from '@wrongstack/mcp';
|
|
13
|
+
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
14
14
|
import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
|
|
15
15
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
16
16
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import * as readline from 'readline';
|
|
19
|
-
import * as
|
|
19
|
+
import * as fs12 from 'fs';
|
|
20
20
|
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
21
21
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
22
22
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
23
23
|
import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
|
|
24
24
|
import { spawn } from 'child_process';
|
|
25
25
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
26
|
-
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
27
26
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
27
|
+
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
28
28
|
|
|
29
29
|
var __defProp = Object.defineProperty;
|
|
30
30
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -2345,29 +2345,43 @@ async function resolveRuntimeMaxContext(input) {
|
|
|
2345
2345
|
const providerConfig = input.runtimeProviderConfig ?? input.config.providers?.[input.providerId];
|
|
2346
2346
|
const providerOverride = positiveNumber(readConfiguredMaxContext(providerConfig));
|
|
2347
2347
|
if (providerOverride) return providerOverride;
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
input.
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2348
|
+
const catalogId = providerConfig?.type && providerConfig.type !== input.providerId ? providerConfig.type : input.providerId;
|
|
2349
|
+
if (input.modelsRegistry) {
|
|
2350
|
+
const topLevelBaseUrlApplies = input.providerId === input.config.provider;
|
|
2351
|
+
const configuredBaseUrl = providerConfig?.baseUrl ?? (topLevelBaseUrlApplies ? input.config.baseUrl : void 0);
|
|
2352
|
+
let divergesFromCatalog = false;
|
|
2353
|
+
if (configuredBaseUrl) {
|
|
2354
|
+
const resolved = await safeGetProvider(input.modelsRegistry, catalogId);
|
|
2355
|
+
divergesFromCatalog = normalizeBaseUrl(configuredBaseUrl) !== normalizeBaseUrl(resolved?.apiBase);
|
|
2356
|
+
}
|
|
2357
|
+
if (!divergesFromCatalog) {
|
|
2358
|
+
const mergedModels = mergeCustomModelDefs(providerConfig?.customModels, input.config.models);
|
|
2359
|
+
const caps = await capabilitiesFor(
|
|
2360
|
+
input.modelsRegistry,
|
|
2361
|
+
catalogId,
|
|
2362
|
+
input.modelId,
|
|
2363
|
+
mergedModels
|
|
2364
|
+
).catch(() => void 0);
|
|
2365
|
+
const catalogMax = positiveNumber(caps?.maxContext);
|
|
2366
|
+
if (catalogMax) return catalogMax;
|
|
2367
|
+
const directModel = await input.modelsRegistry.getModel(catalogId, input.modelId).catch(() => void 0);
|
|
2368
|
+
const directMax = positiveNumber(directModel?.capabilities.maxContext);
|
|
2369
|
+
if (directMax) return directMax;
|
|
2370
|
+
}
|
|
2368
2371
|
}
|
|
2369
2372
|
return positiveNumber(input.provider.capabilities.maxContext) ?? 0;
|
|
2370
2373
|
}
|
|
2374
|
+
async function safeGetProvider(registry, id) {
|
|
2375
|
+
try {
|
|
2376
|
+
return await registry.getProvider(id);
|
|
2377
|
+
} catch {
|
|
2378
|
+
return void 0;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
function normalizeBaseUrl(url) {
|
|
2382
|
+
if (!url) return "";
|
|
2383
|
+
return url.trim().toLowerCase().replace(/\/+$/, "");
|
|
2384
|
+
}
|
|
2371
2385
|
function readConfiguredMaxContext(providerConfig) {
|
|
2372
2386
|
if (!providerConfig || typeof providerConfig !== "object") return void 0;
|
|
2373
2387
|
const capabilities = providerConfig.capabilities;
|
|
@@ -2381,6 +2395,7 @@ function positiveNumber(value) {
|
|
|
2381
2395
|
// src/arg-parser.ts
|
|
2382
2396
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
2383
2397
|
"yolo",
|
|
2398
|
+
"yolo-destructive",
|
|
2384
2399
|
"force-all-yolo",
|
|
2385
2400
|
"verbose",
|
|
2386
2401
|
"trace",
|
|
@@ -2407,7 +2422,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
2407
2422
|
"autonomy",
|
|
2408
2423
|
"eternal",
|
|
2409
2424
|
"no-hints",
|
|
2410
|
-
"hints"
|
|
2425
|
+
"hints",
|
|
2426
|
+
"no-hooks"
|
|
2411
2427
|
]);
|
|
2412
2428
|
function parseArgs(argv) {
|
|
2413
2429
|
const flags = {};
|
|
@@ -2735,10 +2751,12 @@ function buildAutonomyCommand(opts) {
|
|
|
2735
2751
|
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
2736
2752
|
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
2737
2753
|
" eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
|
|
2738
|
-
" Force-enables YOLO
|
|
2754
|
+
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
2755
|
+
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
2739
2756
|
" parallel \u2014 Fan-out 4\u20138 subagents per tick. Each tick decomposes the goal,",
|
|
2740
2757
|
" spawns N agents, awaits results, aggregates. Requires /goal.",
|
|
2741
|
-
" Force-enables YOLO
|
|
2758
|
+
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
2759
|
+
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
2742
2760
|
"",
|
|
2743
2761
|
"Eternal stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
2744
2762
|
"Stage shown in real-time. Use /goal pause to pause, /goal resume to continue.",
|
|
@@ -2896,7 +2914,7 @@ function buildAutonomyCommand(opts) {
|
|
|
2896
2914
|
opts.onEternalStart(newMode);
|
|
2897
2915
|
const modeLabel = newMode === "eternal-parallel" ? `${color.magenta("PARALLEL")} mode` : `${color.red("ETERNAL")} mode`;
|
|
2898
2916
|
const msg2 = `Autonomy mode: ${modeLabel} \u2014 engine launching against goal: ${color.bold(goal.goal)}
|
|
2899
|
-
${color.dim("YOLO
|
|
2917
|
+
${color.dim("Regular YOLO enabled; destructive-gated calls still use the permission flow. Use /autonomy stop to end. Journal at /goal journal.")}`;
|
|
2900
2918
|
opts.renderer.write(msg2);
|
|
2901
2919
|
return { message: msg2 };
|
|
2902
2920
|
}
|
|
@@ -3373,6 +3391,11 @@ function buildContextCommand(opts) {
|
|
|
3373
3391
|
" /context Show counts: messages, est. tokens, tool calls, todos, read files.",
|
|
3374
3392
|
" /context detail As above, plus model, cwd, projectRoot, and the file list.",
|
|
3375
3393
|
" /context repair Repair orphan tool_use/tool_result blocks after manual compaction.",
|
|
3394
|
+
" /context limit Show effective context window for this session.",
|
|
3395
|
+
" /context limit <tokens> Set effective context window for this session (e.g. 220k).",
|
|
3396
|
+
" /context limit <tokens> --persist Persist effective context window to config.",
|
|
3397
|
+
" /context thresholds <warn> <soft> <hard> Set compaction thresholds (percent or decimal).",
|
|
3398
|
+
" /context thresholds <warn> <soft> <hard> --persist Persist thresholds to config.",
|
|
3376
3399
|
" /context mode List context-window modes.",
|
|
3377
3400
|
" /context mode <id> Switch context-window mode for this session."
|
|
3378
3401
|
].join("\n"),
|
|
@@ -3400,6 +3423,83 @@ ${formatContextWindowModeList(active)}`;
|
|
|
3400
3423
|
` empty msgs: removed ${repaired.report.removedMessages}`
|
|
3401
3424
|
].join("\n") : "Context repair: no orphan tool_use/tool_result blocks found.";
|
|
3402
3425
|
opts.renderer.write(`${msg2}
|
|
3426
|
+
`);
|
|
3427
|
+
return { message: msg2 };
|
|
3428
|
+
}
|
|
3429
|
+
if (trimmed === "limit") {
|
|
3430
|
+
const limit = readEffectiveLimit(ctx, opts);
|
|
3431
|
+
const msg2 = limit > 0 ? `Effective context window: ${limit.toLocaleString()} tokens` : "Effective context window: unknown (auto-compaction may be disabled).";
|
|
3432
|
+
opts.renderer.write(`${msg2}
|
|
3433
|
+
`);
|
|
3434
|
+
return { message: msg2 };
|
|
3435
|
+
}
|
|
3436
|
+
if (trimmed.startsWith("limit ")) {
|
|
3437
|
+
const persist = hasPersistFlag(trimmed);
|
|
3438
|
+
const raw = stripPersistFlag(trimmed.slice("limit ".length)).trim();
|
|
3439
|
+
const limit = parseTokenCount(raw);
|
|
3440
|
+
if (!limit) {
|
|
3441
|
+
const msg3 = `Invalid context limit "${raw}". Use a positive token count, e.g. 220k or 220000.`;
|
|
3442
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3443
|
+
`);
|
|
3444
|
+
return { message: msg3 };
|
|
3445
|
+
}
|
|
3446
|
+
ctx.meta["effectiveMaxContext"] = limit;
|
|
3447
|
+
const effective = opts.onContextLimit?.(limit) ?? limit;
|
|
3448
|
+
if (persist) {
|
|
3449
|
+
const error = await persistContextConfig(opts, { effectiveMaxContext: limit });
|
|
3450
|
+
if (error) {
|
|
3451
|
+
opts.renderer.write(`${color.red(error)}
|
|
3452
|
+
`);
|
|
3453
|
+
return { message: error };
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
const msg2 = `${color.green("Effective context window set:")} ${effective.toLocaleString()} tokens${persist ? " (persisted)" : ""}`;
|
|
3457
|
+
opts.renderer.write(`${msg2}
|
|
3458
|
+
`);
|
|
3459
|
+
return { message: msg2 };
|
|
3460
|
+
}
|
|
3461
|
+
if (trimmed.startsWith("thresholds ")) {
|
|
3462
|
+
const persist = hasPersistFlag(trimmed);
|
|
3463
|
+
const thresholdArgs = stripPersistFlag(trimmed.slice("thresholds ".length)).trim();
|
|
3464
|
+
const parts = thresholdArgs.split(/\s+/).filter(Boolean);
|
|
3465
|
+
if (parts.length !== 3) {
|
|
3466
|
+
const msg3 = "Usage: /context thresholds <warn> <soft> <hard> (examples: 60% 75% 90% or 0.6 0.75 0.9)";
|
|
3467
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3468
|
+
`);
|
|
3469
|
+
return { message: msg3 };
|
|
3470
|
+
}
|
|
3471
|
+
const thresholds = parts.map(parseThreshold);
|
|
3472
|
+
if (thresholds.some((v) => v === null)) {
|
|
3473
|
+
const msg3 = "Invalid thresholds. Use percentages (60%) or decimals between 0 and 1.";
|
|
3474
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3475
|
+
`);
|
|
3476
|
+
return { message: msg3 };
|
|
3477
|
+
}
|
|
3478
|
+
const [warn, soft, hard] = thresholds;
|
|
3479
|
+
if (!(warn < soft && soft < hard)) {
|
|
3480
|
+
const msg3 = "Invalid thresholds: require warn < soft < hard.";
|
|
3481
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3482
|
+
`);
|
|
3483
|
+
return { message: msg3 };
|
|
3484
|
+
}
|
|
3485
|
+
const base = readPolicy(ctx) ?? resolveContextWindowPolicy({});
|
|
3486
|
+
const policy2 = { ...base, thresholds: { warn, soft, hard } };
|
|
3487
|
+
ctx.meta["contextWindowMode"] = policy2.id;
|
|
3488
|
+
ctx.meta["contextWindowPolicy"] = policy2;
|
|
3489
|
+
if (persist) {
|
|
3490
|
+
const error = await persistContextConfig(opts, {
|
|
3491
|
+
warnThreshold: warn,
|
|
3492
|
+
softThreshold: soft,
|
|
3493
|
+
hardThreshold: hard
|
|
3494
|
+
});
|
|
3495
|
+
if (error) {
|
|
3496
|
+
opts.renderer.write(`${color.red(error)}
|
|
3497
|
+
`);
|
|
3498
|
+
return { message: error };
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
const msg2 = `${color.green("Context thresholds set:")} warn ${pct(warn)}, soft ${pct(soft)}, hard ${pct(hard)}${persist ? " (persisted)" : ""}`;
|
|
3502
|
+
opts.renderer.write(`${msg2}
|
|
3403
3503
|
`);
|
|
3404
3504
|
return { message: msg2 };
|
|
3405
3505
|
}
|
|
@@ -3433,6 +3533,7 @@ ${formatContextWindowModeList(active)}`;
|
|
|
3433
3533
|
` messages: ${messages.length} total (${countTurnPairs(messages)} user+assistant pairs)`,
|
|
3434
3534
|
` tokens (est): ${estimateTokens(messages).toLocaleString()} (chars / 4 estimate)`,
|
|
3435
3535
|
` mode: ${policy ? `${policy.id} (${policy.name})` : "balanced"}`,
|
|
3536
|
+
` limit: ${formatLimit(readEffectiveLimit(ctx, opts))}`,
|
|
3436
3537
|
` system prompt: ${ctx.systemPrompt.length} block${ctx.systemPrompt.length !== 1 ? "s" : ""}`,
|
|
3437
3538
|
` tools: ${countToolUses(messages)} calls made, ${countToolResults(messages)} results in history`,
|
|
3438
3539
|
` read files: ${ctx.readFiles.size} files`,
|
|
@@ -3459,6 +3560,68 @@ function readPolicy(ctx) {
|
|
|
3459
3560
|
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
3460
3561
|
return policy && typeof policy === "object" ? policy : null;
|
|
3461
3562
|
}
|
|
3563
|
+
function hasPersistFlag(input) {
|
|
3564
|
+
return /(?:^|\s)--persist(?:\s|$)/.test(input);
|
|
3565
|
+
}
|
|
3566
|
+
function stripPersistFlag(input) {
|
|
3567
|
+
return input.replace(/(?:^|\s)--persist(?:\s|$)/g, " ").trim();
|
|
3568
|
+
}
|
|
3569
|
+
async function persistContextConfig(opts, patch) {
|
|
3570
|
+
if (!opts.configStore || !opts.paths) return "Cannot persist context settings: config store not available.";
|
|
3571
|
+
let raw = "{}";
|
|
3572
|
+
try {
|
|
3573
|
+
raw = await fsp3.readFile(opts.paths.globalConfig, "utf8");
|
|
3574
|
+
} catch (err) {
|
|
3575
|
+
if (err.code !== "ENOENT") {
|
|
3576
|
+
return `Could not read ${opts.paths.globalConfig}: ${err.message}`;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
let parsed;
|
|
3580
|
+
try {
|
|
3581
|
+
parsed = JSON.parse(raw);
|
|
3582
|
+
} catch (err) {
|
|
3583
|
+
return `Config at ${opts.paths.globalConfig} is not valid JSON: ${err.message}`;
|
|
3584
|
+
}
|
|
3585
|
+
const current = opts.configStore.get();
|
|
3586
|
+
const context = {
|
|
3587
|
+
...current.context,
|
|
3588
|
+
...parsed.context ?? {},
|
|
3589
|
+
...patch
|
|
3590
|
+
};
|
|
3591
|
+
parsed.context = context;
|
|
3592
|
+
await atomicWrite(opts.paths.globalConfig, JSON.stringify(parsed, null, 2), { mode: 384 });
|
|
3593
|
+
opts.configStore.update({ context });
|
|
3594
|
+
return null;
|
|
3595
|
+
}
|
|
3596
|
+
function readEffectiveLimit(ctx, opts) {
|
|
3597
|
+
const live = opts.onContextLimit?.();
|
|
3598
|
+
if (typeof live === "number" && Number.isFinite(live) && live > 0) return live;
|
|
3599
|
+
const metaLimit = ctx.meta?.["effectiveMaxContext"];
|
|
3600
|
+
if (typeof metaLimit === "number" && Number.isFinite(metaLimit) && metaLimit > 0) return metaLimit;
|
|
3601
|
+
const providerLimit = ctx.provider?.capabilities?.maxContext;
|
|
3602
|
+
return typeof providerLimit === "number" && Number.isFinite(providerLimit) && providerLimit > 0 ? providerLimit : 0;
|
|
3603
|
+
}
|
|
3604
|
+
function parseTokenCount(raw) {
|
|
3605
|
+
const normalized = raw.trim().toLowerCase().replace(/,/g, "").replace(/_/g, "");
|
|
3606
|
+
const match = /^(\d+(?:\.\d+)?)([km])?$/.exec(normalized);
|
|
3607
|
+
if (!match) return null;
|
|
3608
|
+
const value = Number(match[1]);
|
|
3609
|
+
const unit = match[2];
|
|
3610
|
+
const scaled = unit === "m" ? value * 1e6 : unit === "k" ? value * 1e3 : value;
|
|
3611
|
+
const rounded = Math.floor(scaled);
|
|
3612
|
+
return Number.isFinite(rounded) && rounded > 0 ? rounded : null;
|
|
3613
|
+
}
|
|
3614
|
+
function parseThreshold(raw) {
|
|
3615
|
+
const s = raw.trim();
|
|
3616
|
+
const percent = s.endsWith("%");
|
|
3617
|
+
const n = Number((percent ? s.slice(0, -1) : s).trim());
|
|
3618
|
+
if (!Number.isFinite(n)) return null;
|
|
3619
|
+
const value = percent ? n / 100 : n;
|
|
3620
|
+
return value > 0 && value < 1 ? value : null;
|
|
3621
|
+
}
|
|
3622
|
+
function formatLimit(limit) {
|
|
3623
|
+
return limit > 0 ? `${limit.toLocaleString()} tokens` : "unknown";
|
|
3624
|
+
}
|
|
3462
3625
|
function pct(n) {
|
|
3463
3626
|
return `${Math.round(n * 100)}%`;
|
|
3464
3627
|
}
|
|
@@ -4867,12 +5030,20 @@ function parseMcpArgs(args) {
|
|
|
4867
5030
|
}
|
|
4868
5031
|
async function runMcpManagementCommand(parsed, deps) {
|
|
4869
5032
|
const { config, configPath: configPath2, mcpRegistry, allServerPresets } = deps;
|
|
4870
|
-
const
|
|
5033
|
+
const diskConfig = await readConfig(configPath2);
|
|
5034
|
+
const configured = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : config.mcpServers ?? {};
|
|
4871
5035
|
switch (parsed.action) {
|
|
4872
5036
|
case "list":
|
|
4873
5037
|
return renderList(configured, mcpRegistry, allServerPresets);
|
|
4874
5038
|
case "add":
|
|
4875
|
-
return runAdd(
|
|
5039
|
+
return runAdd(
|
|
5040
|
+
parsed.name,
|
|
5041
|
+
parsed.enable ?? false,
|
|
5042
|
+
configured,
|
|
5043
|
+
configPath2,
|
|
5044
|
+
mcpRegistry,
|
|
5045
|
+
allServerPresets
|
|
5046
|
+
);
|
|
4876
5047
|
case "remove":
|
|
4877
5048
|
return runRemove(parsed.name, configured, configPath2, mcpRegistry);
|
|
4878
5049
|
case "enable":
|
|
@@ -4916,34 +5087,46 @@ function renderList(configured, mcpRegistry, all) {
|
|
|
4916
5087
|
lines.push(color.dim(" /mcp restart <name> (runtime restart)"));
|
|
4917
5088
|
return lines.join("\n");
|
|
4918
5089
|
}
|
|
4919
|
-
async function runAdd(name, enable, configured, configPath2, all) {
|
|
5090
|
+
async function runAdd(name, enable, configured, configPath2, mcpRegistry, all) {
|
|
4920
5091
|
const preset = all[name];
|
|
4921
5092
|
if (!preset) {
|
|
4922
5093
|
const known = Object.keys(all).join(", ");
|
|
4923
5094
|
return `Unknown server "${name}". Available: ${known}`;
|
|
4924
5095
|
}
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
full2.mcpServers = {
|
|
4928
|
-
...full2.mcpServers ?? {},
|
|
4929
|
-
[name]: { ...preset, ...configured[name], enabled: enable }
|
|
4930
|
-
};
|
|
4931
|
-
await writeConfig(configPath2, full2);
|
|
4932
|
-
return `${color.green("Updated")} "${name}" (${enable ? "enabled" : "disabled"}). Config written.`;
|
|
4933
|
-
}
|
|
5096
|
+
const existing = configured[name];
|
|
5097
|
+
const nextCfg = existing ? { ...preset, ...existing, enabled: enable } : { ...preset, enabled: enable };
|
|
4934
5098
|
const full = await readConfig(configPath2);
|
|
4935
|
-
const mcpServers = {
|
|
5099
|
+
const mcpServers = {
|
|
5100
|
+
...isMcpServerRecord(full.mcpServers) ? full.mcpServers : {},
|
|
5101
|
+
[name]: nextCfg
|
|
5102
|
+
};
|
|
4936
5103
|
full.mcpServers = mcpServers;
|
|
4937
5104
|
await writeConfig(configPath2, full);
|
|
4938
|
-
|
|
4939
|
-
|
|
5105
|
+
if (!enable) {
|
|
5106
|
+
const verb = existing ? "Updated" : "Added (disabled \u2014 /mcp enable to start)";
|
|
5107
|
+
return `${color.green(verb)} "${name}" (${nextCfg.transport}). Config written to ${configPath2}.`;
|
|
5108
|
+
}
|
|
5109
|
+
try {
|
|
5110
|
+
if (mcpRegistry.list().some((server) => server.name === name)) {
|
|
5111
|
+
await mcpRegistry.restart(name);
|
|
5112
|
+
} else {
|
|
5113
|
+
await mcpRegistry.start(nextCfg);
|
|
5114
|
+
}
|
|
5115
|
+
const verb = existing ? "Updated and started" : "Enabled and started";
|
|
5116
|
+
return `${color.green(verb)} "${name}" (${nextCfg.transport}). Config written to ${configPath2}.`;
|
|
5117
|
+
} catch (err) {
|
|
5118
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5119
|
+
return `${color.yellow("Enabled")} "${name}" in config, but failed to start: ${message}`;
|
|
5120
|
+
}
|
|
4940
5121
|
}
|
|
4941
5122
|
async function runRemove(name, configured, configPath2, mcpRegistry) {
|
|
4942
5123
|
if (!configured[name]) return `Server "${name}" is not in config.`;
|
|
4943
5124
|
await mcpRegistry.stop(name).catch(() => {
|
|
4944
5125
|
});
|
|
4945
5126
|
const full = await readConfig(configPath2);
|
|
4946
|
-
const mcpServers = {
|
|
5127
|
+
const mcpServers = {
|
|
5128
|
+
...full.mcpServers ?? {}
|
|
5129
|
+
};
|
|
4947
5130
|
delete mcpServers[name];
|
|
4948
5131
|
full.mcpServers = mcpServers;
|
|
4949
5132
|
await writeConfig(configPath2, full);
|
|
@@ -4962,7 +5145,9 @@ async function runEnable(name, configured, configPath2, mcpRegistry) {
|
|
|
4962
5145
|
}
|
|
4963
5146
|
}
|
|
4964
5147
|
const full = await readConfig(configPath2);
|
|
4965
|
-
const mcpServers = {
|
|
5148
|
+
const mcpServers = {
|
|
5149
|
+
...full.mcpServers ?? {}
|
|
5150
|
+
};
|
|
4966
5151
|
mcpServers[name] = { ...mcpServers[name], enabled: true };
|
|
4967
5152
|
full.mcpServers = mcpServers;
|
|
4968
5153
|
await writeConfig(configPath2, full);
|
|
@@ -4979,7 +5164,9 @@ async function runDisable(name, configured, configPath2, mcpRegistry) {
|
|
|
4979
5164
|
await mcpRegistry.stop(name).catch(() => {
|
|
4980
5165
|
});
|
|
4981
5166
|
const full = await readConfig(configPath2);
|
|
4982
|
-
const mcpServers = {
|
|
5167
|
+
const mcpServers = {
|
|
5168
|
+
...full.mcpServers ?? {}
|
|
5169
|
+
};
|
|
4983
5170
|
mcpServers[name] = { ...mcpServers[name], enabled: false };
|
|
4984
5171
|
full.mcpServers = mcpServers;
|
|
4985
5172
|
await writeConfig(configPath2, full);
|
|
@@ -5020,6 +5207,9 @@ async function readConfig(path25) {
|
|
|
5020
5207
|
return {};
|
|
5021
5208
|
}
|
|
5022
5209
|
}
|
|
5210
|
+
function isMcpServerRecord(value) {
|
|
5211
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5212
|
+
}
|
|
5023
5213
|
async function writeConfig(path25, cfg) {
|
|
5024
5214
|
const raw = JSON.stringify(cfg, null, 2);
|
|
5025
5215
|
const tmp = path25 + ".tmp";
|
|
@@ -6340,12 +6530,12 @@ function buildYoloCommand(opts) {
|
|
|
6340
6530
|
help: [
|
|
6341
6531
|
"Usage:",
|
|
6342
6532
|
" /yolo Show current YOLO status",
|
|
6343
|
-
" /yolo on Enable YOLO mode (auto-approve
|
|
6533
|
+
" /yolo on Enable YOLO mode (auto-approve normal project work)",
|
|
6344
6534
|
" /yolo off Disable YOLO mode (restore permission prompts)",
|
|
6345
6535
|
" /yolo toggle Toggle YOLO mode",
|
|
6346
6536
|
"",
|
|
6347
|
-
"YOLO mode
|
|
6348
|
-
"
|
|
6537
|
+
"YOLO mode auto-approves normal in-project tool calls, including simple shell commands.",
|
|
6538
|
+
"Clearly destructive calls may still ask unless --yolo-destructive is enabled."
|
|
6349
6539
|
].join("\n"),
|
|
6350
6540
|
async run(args) {
|
|
6351
6541
|
const arg = args.trim().toLowerCase();
|
|
@@ -6356,7 +6546,7 @@ function buildYoloCommand(opts) {
|
|
|
6356
6546
|
}
|
|
6357
6547
|
if (!arg) {
|
|
6358
6548
|
const current = opts.onYolo();
|
|
6359
|
-
const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving
|
|
6549
|
+
const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving normal project work)")}` : `${color.green("OFF")} ${color.dim("(permission prompts active)")}`;
|
|
6360
6550
|
const msg2 = `YOLO mode: ${status}`;
|
|
6361
6551
|
opts.renderer.write(msg2);
|
|
6362
6552
|
return { message: msg2 };
|
|
@@ -6374,7 +6564,7 @@ function buildYoloCommand(opts) {
|
|
|
6374
6564
|
return { message: msg2 };
|
|
6375
6565
|
}
|
|
6376
6566
|
opts.onYolo(newState);
|
|
6377
|
-
const label = newState ? `${color.yellow("ENABLED")} \u2014
|
|
6567
|
+
const label = newState ? `${color.yellow("ENABLED")} \u2014 normal project tool calls will be auto-approved` : `${color.green("DISABLED")} \u2014 permission prompts are active`;
|
|
6378
6568
|
const msg = `YOLO mode: ${label}`;
|
|
6379
6569
|
opts.renderer.write(msg);
|
|
6380
6570
|
return { message: msg };
|
|
@@ -6581,7 +6771,7 @@ async function runLaunchPrompts(opts) {
|
|
|
6581
6771
|
yolo = yoloPinned;
|
|
6582
6772
|
} else {
|
|
6583
6773
|
const answer = (await reader.readLine(
|
|
6584
|
-
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve
|
|
6774
|
+
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve normal project work)")} ${color.dim("[Y/n/q]")} `
|
|
6585
6775
|
)).trim().toLowerCase();
|
|
6586
6776
|
if (answer === "q") {
|
|
6587
6777
|
renderer.write(color.dim(" Goodbye!\n"));
|
|
@@ -7138,11 +7328,11 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
7138
7328
|
var theme = { primary: color.amber };
|
|
7139
7329
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
|
|
7140
7330
|
try {
|
|
7141
|
-
const { atomicWrite:
|
|
7142
|
-
const
|
|
7331
|
+
const { atomicWrite: atomicWrite13 } = await import('@wrongstack/core');
|
|
7332
|
+
const fs25 = await import('fs/promises');
|
|
7143
7333
|
let existing = {};
|
|
7144
7334
|
try {
|
|
7145
|
-
const raw = await
|
|
7335
|
+
const raw = await fs25.readFile(configPath2, "utf8");
|
|
7146
7336
|
existing = JSON.parse(raw);
|
|
7147
7337
|
} catch {
|
|
7148
7338
|
}
|
|
@@ -7150,7 +7340,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
7150
7340
|
existing.provider = provider;
|
|
7151
7341
|
existing.model = model;
|
|
7152
7342
|
await backupCurrent(homeFn);
|
|
7153
|
-
await
|
|
7343
|
+
await atomicWrite13(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
7154
7344
|
try {
|
|
7155
7345
|
await appendHistory(
|
|
7156
7346
|
oldCfg,
|
|
@@ -7440,7 +7630,7 @@ var GROUPS = [
|
|
|
7440
7630
|
items: [
|
|
7441
7631
|
{ key: "/mode", blurb: "switch persona: code-reviewer, debugger, architect, tester, devops, \u2026" },
|
|
7442
7632
|
{ key: "/model", blurb: "two-step provider \u2192 model picker, hot-swap at runtime" },
|
|
7443
|
-
{ key: "/yolo on|off|toggle", blurb: "auto-approve
|
|
7633
|
+
{ key: "/yolo on|off|toggle", blurb: "auto-approve normal project work without restart" },
|
|
7444
7634
|
{ key: "/context mode frugal|balanced|deep|archival", blurb: "pick how aggressively history is trimmed" },
|
|
7445
7635
|
{ key: "/compact", blurb: "manually compact the in-flight context window" },
|
|
7446
7636
|
{ key: "/plan show|add|start|done", blurb: "strategic roadmap, survives /resume across sessions" }
|
|
@@ -7473,12 +7663,12 @@ function pickGroupIndex(opts) {
|
|
|
7473
7663
|
try {
|
|
7474
7664
|
let current = 0;
|
|
7475
7665
|
try {
|
|
7476
|
-
const parsed = Number.parseInt(
|
|
7666
|
+
const parsed = Number.parseInt(fs12.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
7477
7667
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
7478
7668
|
} catch {
|
|
7479
7669
|
}
|
|
7480
|
-
|
|
7481
|
-
|
|
7670
|
+
fs12.mkdirSync(path8.dirname(opts.cursorFile), { recursive: true });
|
|
7671
|
+
fs12.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
7482
7672
|
return current;
|
|
7483
7673
|
} catch {
|
|
7484
7674
|
}
|
|
@@ -9016,9 +9206,160 @@ var initCmd = async (_args, deps) => {
|
|
|
9016
9206
|
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
9017
9207
|
return 0;
|
|
9018
9208
|
};
|
|
9209
|
+
var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
|
|
9210
|
+
async evaluate() {
|
|
9211
|
+
return { permission: "auto", source: "default" };
|
|
9212
|
+
}
|
|
9213
|
+
};
|
|
9214
|
+
function parseToolsFlag(flags) {
|
|
9215
|
+
const raw = flags["tools"];
|
|
9216
|
+
if (typeof raw !== "string") return null;
|
|
9217
|
+
const set = new Set(
|
|
9218
|
+
raw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
9219
|
+
);
|
|
9220
|
+
return set.size > 0 ? set : null;
|
|
9221
|
+
}
|
|
9222
|
+
function makeServeContext(cwd, projectRoot, signal) {
|
|
9223
|
+
const provider = {
|
|
9224
|
+
id: "mcp-serve",
|
|
9225
|
+
capabilities: { maxContext: 0 },
|
|
9226
|
+
complete: async () => {
|
|
9227
|
+
throw new Error("no model provider in `mcp serve` mode");
|
|
9228
|
+
},
|
|
9229
|
+
stream: () => {
|
|
9230
|
+
throw new Error("no model provider in `mcp serve` mode");
|
|
9231
|
+
}
|
|
9232
|
+
};
|
|
9233
|
+
const session = { append: async () => {
|
|
9234
|
+
} };
|
|
9235
|
+
const tokenCounter = {
|
|
9236
|
+
account: () => {
|
|
9237
|
+
},
|
|
9238
|
+
total: () => ({ input: 0, output: 0 }),
|
|
9239
|
+
estimateCost: () => ({ total: 0 })
|
|
9240
|
+
};
|
|
9241
|
+
return new Context({
|
|
9242
|
+
systemPrompt: [],
|
|
9243
|
+
provider,
|
|
9244
|
+
session,
|
|
9245
|
+
signal,
|
|
9246
|
+
tokenCounter,
|
|
9247
|
+
cwd,
|
|
9248
|
+
projectRoot,
|
|
9249
|
+
model: "mcp-serve",
|
|
9250
|
+
tools: []
|
|
9251
|
+
});
|
|
9252
|
+
}
|
|
9253
|
+
async function selectExposedTools(registry, ctx, policy, whitelist) {
|
|
9254
|
+
const allowed = [];
|
|
9255
|
+
for (const tool of registry.list()) {
|
|
9256
|
+
if (whitelist && !whitelist.has(tool.name)) continue;
|
|
9257
|
+
const decision = await policy.evaluate(tool, {}, ctx);
|
|
9258
|
+
if (decision.permission === "auto") allowed.push(tool);
|
|
9259
|
+
}
|
|
9260
|
+
return allowed;
|
|
9261
|
+
}
|
|
9262
|
+
async function serveMcpStdio(deps) {
|
|
9263
|
+
const flags = deps.flags ?? {};
|
|
9264
|
+
const yolo = flags["yolo"] === true || flags["allow-all"] === true;
|
|
9265
|
+
const whitelist = parseToolsFlag(flags);
|
|
9266
|
+
const log = (m) => process.stderr.write(`${m}
|
|
9267
|
+
`);
|
|
9268
|
+
let registry = deps.toolRegistry;
|
|
9269
|
+
if (!registry) {
|
|
9270
|
+
registry = new ToolRegistry();
|
|
9271
|
+
registry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
9272
|
+
}
|
|
9273
|
+
const controller = new AbortController();
|
|
9274
|
+
const ctx = makeServeContext(deps.cwd, deps.projectRoot, controller.signal);
|
|
9275
|
+
const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
|
|
9276
|
+
const executor = new ToolExecutor(registry, {
|
|
9277
|
+
permissionPolicy,
|
|
9278
|
+
secretScrubber: new DefaultSecretScrubber(),
|
|
9279
|
+
perIterationOutputCapBytes: 1e6
|
|
9280
|
+
});
|
|
9281
|
+
const allowed = await selectExposedTools(registry, ctx, permissionPolicy, whitelist);
|
|
9282
|
+
const allowedNames = new Set(allowed.map((t) => t.name));
|
|
9283
|
+
if (allowed.length === 0) {
|
|
9284
|
+
log(
|
|
9285
|
+
"wrongstack MCP server: no tools to expose (all withheld by policy or filtered out). Pass --yolo to expose write/exec tools, or --tools <names> to whitelist."
|
|
9286
|
+
);
|
|
9287
|
+
}
|
|
9288
|
+
let counter = 0;
|
|
9289
|
+
const host = {
|
|
9290
|
+
listTools: () => allowed.map((t) => ({
|
|
9291
|
+
name: t.name,
|
|
9292
|
+
description: t.description,
|
|
9293
|
+
inputSchema: t.inputSchema ?? { type: "object" }
|
|
9294
|
+
})),
|
|
9295
|
+
callTool: async (name, callArgs) => {
|
|
9296
|
+
if (!allowedNames.has(name)) {
|
|
9297
|
+
return { content: `Tool "${name}" is not exposed by this server`, isError: true };
|
|
9298
|
+
}
|
|
9299
|
+
const use = {
|
|
9300
|
+
type: "tool_use",
|
|
9301
|
+
id: `srv_${++counter}`,
|
|
9302
|
+
name,
|
|
9303
|
+
input: callArgs
|
|
9304
|
+
};
|
|
9305
|
+
const batch = await executor.executeBatch([use], ctx, "sequential");
|
|
9306
|
+
const result = batch.outputs[0]?.result;
|
|
9307
|
+
if (!result || result.type === "tool_confirm_pending") {
|
|
9308
|
+
return {
|
|
9309
|
+
content: `Tool "${name}" requires interactive confirmation, which is unavailable over MCP`,
|
|
9310
|
+
isError: true
|
|
9311
|
+
};
|
|
9312
|
+
}
|
|
9313
|
+
return { content: result.content, isError: Boolean(result.is_error) };
|
|
9314
|
+
}
|
|
9315
|
+
};
|
|
9316
|
+
const server = new MCPServer({
|
|
9317
|
+
host,
|
|
9318
|
+
logger: { warn: (m) => log(`[mcp-serve] ${m}`) }
|
|
9319
|
+
});
|
|
9320
|
+
const mode = yolo ? "yolo: all tools" : "safe: read-only tools";
|
|
9321
|
+
if (flags["http"] === true || typeof flags["http"] === "string" || flags["port"] || flags["host"]) {
|
|
9322
|
+
const port = Number(flags["port"] ?? flags["http"] ?? 0) || 0;
|
|
9323
|
+
const httpHost = typeof flags["host"] === "string" ? flags["host"] : "127.0.0.1";
|
|
9324
|
+
const token = typeof flags["token"] === "string" ? flags["token"] : void 0;
|
|
9325
|
+
let handle2;
|
|
9326
|
+
try {
|
|
9327
|
+
handle2 = await serveHttp(server, {
|
|
9328
|
+
port,
|
|
9329
|
+
host: httpHost,
|
|
9330
|
+
token,
|
|
9331
|
+
logger: { warn: (m) => log(`[mcp-serve] ${m}`) }
|
|
9332
|
+
});
|
|
9333
|
+
} catch (err) {
|
|
9334
|
+
log(`wrongstack MCP server: ${err instanceof Error ? err.message : String(err)}`);
|
|
9335
|
+
return 1;
|
|
9336
|
+
}
|
|
9337
|
+
log(
|
|
9338
|
+
`wrongstack MCP server ready at ${handle2.url} \u2014 exposing ${allowed.length} tool(s) (${mode})${token ? " [token auth]" : ""}.`
|
|
9339
|
+
);
|
|
9340
|
+
await new Promise((resolve3) => {
|
|
9341
|
+
const stop = () => resolve3();
|
|
9342
|
+
process.once("SIGINT", stop);
|
|
9343
|
+
process.once("SIGTERM", stop);
|
|
9344
|
+
});
|
|
9345
|
+
await handle2.close();
|
|
9346
|
+
controller.abort();
|
|
9347
|
+
return 0;
|
|
9348
|
+
}
|
|
9349
|
+
log(`wrongstack MCP server ready on stdio \u2014 exposing ${allowed.length} tool(s) (${mode}).`);
|
|
9350
|
+
const handle = serveStdio(server);
|
|
9351
|
+
await handle.done;
|
|
9352
|
+
controller.abort();
|
|
9353
|
+
return 0;
|
|
9354
|
+
}
|
|
9355
|
+
|
|
9356
|
+
// src/subcommands/handlers/mcp.ts
|
|
9019
9357
|
var BUILT_IN_MCP = allServers();
|
|
9020
9358
|
var mcpCmd = async (args, deps) => {
|
|
9021
9359
|
const sub = args[0];
|
|
9360
|
+
if (sub === "serve") {
|
|
9361
|
+
return serveMcpStdio(deps);
|
|
9362
|
+
}
|
|
9022
9363
|
if (!sub || sub === "list") {
|
|
9023
9364
|
const servers = deps.config.mcpServers ?? {};
|
|
9024
9365
|
if (Object.keys(servers).length === 0) {
|
|
@@ -10266,10 +10607,10 @@ var auditCmd = async (args, deps) => {
|
|
|
10266
10607
|
return verify.ok ? 0 : 1;
|
|
10267
10608
|
};
|
|
10268
10609
|
async function listAudits(log, dir, deps) {
|
|
10269
|
-
const
|
|
10610
|
+
const fs25 = await import('fs/promises');
|
|
10270
10611
|
let entries;
|
|
10271
10612
|
try {
|
|
10272
|
-
entries = await
|
|
10613
|
+
entries = await fs25.readdir(dir);
|
|
10273
10614
|
} catch {
|
|
10274
10615
|
deps.renderer.write(
|
|
10275
10616
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -10356,7 +10697,15 @@ var helpCmd = async (_args, deps) => {
|
|
|
10356
10697
|
" wstack doctor Health checks",
|
|
10357
10698
|
" wstack export <id> [opts] Render a session",
|
|
10358
10699
|
" wstack usage Token + cost summary",
|
|
10359
|
-
" wstack version Print version"
|
|
10700
|
+
" wstack version Print version",
|
|
10701
|
+
"",
|
|
10702
|
+
color.bold("Common flags"),
|
|
10703
|
+
" --yolo Auto-approve normal in-project tool calls",
|
|
10704
|
+
" --yolo-destructive Also auto-approve clearly destructive YOLO-gated calls",
|
|
10705
|
+
" --force-all-yolo Deprecated alias for --yolo-destructive",
|
|
10706
|
+
" --tui / --no-tui Force or disable TUI mode",
|
|
10707
|
+
' --eternal "<mission>" Start an eternal-autonomy loop',
|
|
10708
|
+
" --no-hints Hide launch hints"
|
|
10360
10709
|
];
|
|
10361
10710
|
deps.renderer.write(lines.join("\n") + "\n");
|
|
10362
10711
|
return 0;
|
|
@@ -10529,7 +10878,8 @@ async function boot(argv) {
|
|
|
10529
10878
|
vault,
|
|
10530
10879
|
cwd,
|
|
10531
10880
|
projectRoot,
|
|
10532
|
-
userHome
|
|
10881
|
+
userHome,
|
|
10882
|
+
flags
|
|
10533
10883
|
});
|
|
10534
10884
|
await reader.close();
|
|
10535
10885
|
return code;
|
|
@@ -10647,6 +10997,20 @@ async function boot(argv) {
|
|
|
10647
10997
|
updateInfo
|
|
10648
10998
|
};
|
|
10649
10999
|
}
|
|
11000
|
+
var CONTEXT_OVERFLOW_RE = /context window|exceeds the context|too many tokens|context.*tokens/i;
|
|
11001
|
+
function contextOverflowHint(err) {
|
|
11002
|
+
const structured = err.code === ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW || err.code === ERROR_CODES.AGENT_CONTEXT_OVERFLOW;
|
|
11003
|
+
const textual = CONTEXT_OVERFLOW_RE.test(`${err.message}
|
|
11004
|
+
${err.describe()}`);
|
|
11005
|
+
if (!structured && !textual) return null;
|
|
11006
|
+
return [
|
|
11007
|
+
"Provider rejected the request as over its effective context window.",
|
|
11008
|
+
"If you use a custom baseUrl/proxy, the real limit may be lower than models.dev reports.",
|
|
11009
|
+
"Try: /context limit 220k",
|
|
11010
|
+
"Then, if needed: /context thresholds 50% 70% 85%",
|
|
11011
|
+
"Persistent config: set context.effectiveMaxContext."
|
|
11012
|
+
].join("\n");
|
|
11013
|
+
}
|
|
10650
11014
|
function fmtElapsed(ms) {
|
|
10651
11015
|
const s = Math.floor(ms / 1e3);
|
|
10652
11016
|
if (s < 60) return `${s}s`;
|
|
@@ -11238,6 +11602,8 @@ ${taskList}`;
|
|
|
11238
11602
|
if (err) {
|
|
11239
11603
|
const tag = err.recoverable ? " (recoverable)" : "";
|
|
11240
11604
|
opts.renderer.writeError(`Failed [${err.severity}]${tag}: ${err.describe()}`);
|
|
11605
|
+
const hint = contextOverflowHint(err);
|
|
11606
|
+
if (hint) opts.renderer.writeWarning(hint);
|
|
11241
11607
|
} else {
|
|
11242
11608
|
opts.renderer.writeError("Failed.");
|
|
11243
11609
|
}
|
|
@@ -11679,6 +12045,8 @@ async function execute(deps) {
|
|
|
11679
12045
|
if (err) {
|
|
11680
12046
|
const tag = err.recoverable ? " (recoverable)" : "";
|
|
11681
12047
|
renderer.writeError(`Failed [${err.severity}]${tag}: ${err.describe()}`);
|
|
12048
|
+
const hint = contextOverflowHint(err);
|
|
12049
|
+
if (hint) renderer.writeWarning(hint);
|
|
11682
12050
|
} else {
|
|
11683
12051
|
renderer.writeError("Failed.");
|
|
11684
12052
|
}
|
|
@@ -13305,6 +13673,10 @@ async function setupCompaction(params) {
|
|
|
13305
13673
|
providerId: config.provider ?? provider.id,
|
|
13306
13674
|
modelId: config.model ?? context.model
|
|
13307
13675
|
});
|
|
13676
|
+
const initialPolicy = resolveContextWindowPolicy(config.context);
|
|
13677
|
+
context.meta ??= {};
|
|
13678
|
+
context.meta["contextWindowMode"] = initialPolicy.id;
|
|
13679
|
+
context.meta["contextWindowPolicy"] = initialPolicy;
|
|
13308
13680
|
let autoCompactor;
|
|
13309
13681
|
if (config.context.autoCompact !== false && effectiveMaxContext > 0) {
|
|
13310
13682
|
const auditLevel = resolveAuditLevel(fullConfig ?? config);
|
|
@@ -13315,15 +13687,15 @@ async function setupCompaction(params) {
|
|
|
13315
13687
|
// Calibrated estimator: recordActualUsage() is called after each API
|
|
13316
13688
|
// response so this converges on real token counts for compaction decisions.
|
|
13317
13689
|
(ctx) => estimateRequestTokensCalibrated(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total,
|
|
13690
|
+
initialPolicy.thresholds,
|
|
13318
13691
|
{
|
|
13319
|
-
|
|
13320
|
-
soft: config.context.softThreshold,
|
|
13321
|
-
hard: config.context.hardThreshold
|
|
13322
|
-
},
|
|
13323
|
-
{
|
|
13324
|
-
aggressiveOn: "soft",
|
|
13692
|
+
aggressiveOn: initialPolicy.aggressiveOn,
|
|
13325
13693
|
failureMode: "throw_on_hard",
|
|
13326
13694
|
events,
|
|
13695
|
+
policyProvider: (ctx) => {
|
|
13696
|
+
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
13697
|
+
return policy && typeof policy === "object" ? policy : null;
|
|
13698
|
+
},
|
|
13327
13699
|
sessionBridge
|
|
13328
13700
|
}
|
|
13329
13701
|
);
|
|
@@ -13342,7 +13714,8 @@ function createAgent(params) {
|
|
|
13342
13714
|
confirmAwaiter: params.confirmAwaiter,
|
|
13343
13715
|
iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
|
|
13344
13716
|
perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
|
|
13345
|
-
tracer: params.tracer
|
|
13717
|
+
tracer: params.tracer,
|
|
13718
|
+
hookRunner: params.hookRunner
|
|
13346
13719
|
});
|
|
13347
13720
|
return new Agent({
|
|
13348
13721
|
container: params.container,
|
|
@@ -13360,6 +13733,139 @@ function createAgent(params) {
|
|
|
13360
13733
|
tracer: params.tracer
|
|
13361
13734
|
});
|
|
13362
13735
|
}
|
|
13736
|
+
function parseModelRef(ref) {
|
|
13737
|
+
const trimmed = ref.trim();
|
|
13738
|
+
const slash = trimmed.indexOf("/");
|
|
13739
|
+
if (slash !== -1) {
|
|
13740
|
+
return {
|
|
13741
|
+
provider: trimmed.slice(0, slash) || void 0,
|
|
13742
|
+
model: trimmed.slice(slash + 1).trim()
|
|
13743
|
+
};
|
|
13744
|
+
}
|
|
13745
|
+
const parts = trimmed.split(/\s+/);
|
|
13746
|
+
if (parts.length >= 2) {
|
|
13747
|
+
return { provider: parts[0], model: parts.slice(1).join(" ") };
|
|
13748
|
+
}
|
|
13749
|
+
return { model: trimmed };
|
|
13750
|
+
}
|
|
13751
|
+
function overloadStatus(err) {
|
|
13752
|
+
if (!(err instanceof ProviderError)) return null;
|
|
13753
|
+
const s = err.status;
|
|
13754
|
+
if (s === 429 || s === 529 || s >= 500) return s;
|
|
13755
|
+
return null;
|
|
13756
|
+
}
|
|
13757
|
+
function createFallbackModelExtension(deps) {
|
|
13758
|
+
const initial = deps.getConfig().fallbackModels ?? [];
|
|
13759
|
+
if (initial.length === 0) return null;
|
|
13760
|
+
let dirty = false;
|
|
13761
|
+
return {
|
|
13762
|
+
name: "fallback-model",
|
|
13763
|
+
beforeRun: (ctx) => {
|
|
13764
|
+
if (!dirty) return;
|
|
13765
|
+
const cfg = deps.getConfig();
|
|
13766
|
+
try {
|
|
13767
|
+
ctx.provider = deps.buildProvider(cfg.provider);
|
|
13768
|
+
ctx.model = cfg.model;
|
|
13769
|
+
deps.onModelSwitch?.(cfg.provider, cfg.model);
|
|
13770
|
+
} catch (err) {
|
|
13771
|
+
deps.logger.warn(
|
|
13772
|
+
`fallback-model: could not restore primary "${cfg.provider}/${cfg.model}": ${err instanceof Error ? err.message : String(err)}`
|
|
13773
|
+
);
|
|
13774
|
+
}
|
|
13775
|
+
dirty = false;
|
|
13776
|
+
},
|
|
13777
|
+
wrapProviderRunner: async (ctx, request, inner) => {
|
|
13778
|
+
try {
|
|
13779
|
+
return await inner(ctx, request);
|
|
13780
|
+
} catch (firstErr) {
|
|
13781
|
+
let lastErr = firstErr;
|
|
13782
|
+
const cfg = deps.getConfig();
|
|
13783
|
+
const chain = cfg.fallbackModels ?? [];
|
|
13784
|
+
for (const ref of chain) {
|
|
13785
|
+
const status = overloadStatus(lastErr);
|
|
13786
|
+
if (status === null) break;
|
|
13787
|
+
const parsed = parseModelRef(ref);
|
|
13788
|
+
if (!parsed.model) continue;
|
|
13789
|
+
const targetProviderId = parsed.provider ?? cfg.provider;
|
|
13790
|
+
const from = { providerId: ctx.provider.id, model: ctx.model };
|
|
13791
|
+
let nextProvider;
|
|
13792
|
+
try {
|
|
13793
|
+
nextProvider = deps.buildProvider(targetProviderId);
|
|
13794
|
+
} catch (err) {
|
|
13795
|
+
deps.logger.warn(
|
|
13796
|
+
`fallback-model: skipping "${ref}" \u2014 cannot build provider "${targetProviderId}": ${err instanceof Error ? err.message : String(err)}`
|
|
13797
|
+
);
|
|
13798
|
+
continue;
|
|
13799
|
+
}
|
|
13800
|
+
const providerSwitched = nextProvider.id !== from.providerId;
|
|
13801
|
+
ctx.provider = nextProvider;
|
|
13802
|
+
ctx.model = parsed.model;
|
|
13803
|
+
request.model = parsed.model;
|
|
13804
|
+
dirty = true;
|
|
13805
|
+
deps.onModelSwitch?.(targetProviderId, parsed.model);
|
|
13806
|
+
deps.events.emit("provider.fallback", {
|
|
13807
|
+
from,
|
|
13808
|
+
to: { providerId: nextProvider.id, model: parsed.model },
|
|
13809
|
+
status,
|
|
13810
|
+
providerSwitched
|
|
13811
|
+
});
|
|
13812
|
+
try {
|
|
13813
|
+
return await inner(ctx, request);
|
|
13814
|
+
} catch (err) {
|
|
13815
|
+
lastErr = err;
|
|
13816
|
+
}
|
|
13817
|
+
}
|
|
13818
|
+
throw lastErr;
|
|
13819
|
+
}
|
|
13820
|
+
}
|
|
13821
|
+
};
|
|
13822
|
+
}
|
|
13823
|
+
|
|
13824
|
+
// src/hooks-wiring.ts
|
|
13825
|
+
var HookBlockedError = class extends Error {
|
|
13826
|
+
constructor(reason) {
|
|
13827
|
+
super(`Prompt blocked by hook: ${reason}`);
|
|
13828
|
+
this.name = "HookBlockedError";
|
|
13829
|
+
}
|
|
13830
|
+
};
|
|
13831
|
+
function createUserPromptSubmitMiddleware(hookRunner) {
|
|
13832
|
+
return {
|
|
13833
|
+
name: "UserPromptSubmitHooks",
|
|
13834
|
+
handler: async (payload, next) => {
|
|
13835
|
+
const prompt = payload.text;
|
|
13836
|
+
if (prompt && hookRunner.has("UserPromptSubmit")) {
|
|
13837
|
+
const r = await hookRunner.userPromptSubmit(prompt, payload.ctx);
|
|
13838
|
+
if (r.block) throw new HookBlockedError(r.reason ?? "no reason given");
|
|
13839
|
+
if (r.additionalContext) {
|
|
13840
|
+
const block = { type: "text", text: r.additionalContext };
|
|
13841
|
+
payload.content = [...payload.content, block];
|
|
13842
|
+
payload.text = `${prompt}
|
|
13843
|
+
|
|
13844
|
+
${r.additionalContext}`;
|
|
13845
|
+
}
|
|
13846
|
+
}
|
|
13847
|
+
return next(payload);
|
|
13848
|
+
}
|
|
13849
|
+
};
|
|
13850
|
+
}
|
|
13851
|
+
function createLifecycleHooksExtension(hookRunner) {
|
|
13852
|
+
let started = false;
|
|
13853
|
+
return {
|
|
13854
|
+
name: "lifecycle-hooks",
|
|
13855
|
+
beforeRun: async (ctx) => {
|
|
13856
|
+
if (started) return;
|
|
13857
|
+
started = true;
|
|
13858
|
+
if (!hookRunner.has("SessionStart")) return;
|
|
13859
|
+
const r = await hookRunner.sessionStart(ctx);
|
|
13860
|
+
if (r.additionalContext) {
|
|
13861
|
+
ctx.systemPrompt.push({ type: "text", text: r.additionalContext });
|
|
13862
|
+
}
|
|
13863
|
+
},
|
|
13864
|
+
afterRun: async (ctx) => {
|
|
13865
|
+
if (hookRunner.has("Stop")) await hookRunner.stop(ctx);
|
|
13866
|
+
}
|
|
13867
|
+
};
|
|
13868
|
+
}
|
|
13363
13869
|
function setupMetrics(params) {
|
|
13364
13870
|
const { flags, wpaths, events, logger, config } = params;
|
|
13365
13871
|
let metricsSink;
|
|
@@ -13475,7 +13981,8 @@ async function setupPlugins(params) {
|
|
|
13475
13981
|
skillLoader,
|
|
13476
13982
|
configStore,
|
|
13477
13983
|
pipelines,
|
|
13478
|
-
paths
|
|
13984
|
+
paths,
|
|
13985
|
+
hookRegistry
|
|
13479
13986
|
} = params;
|
|
13480
13987
|
const builtinPlugins = [];
|
|
13481
13988
|
const disabledBuiltins = new Set(
|
|
@@ -13540,6 +14047,7 @@ async function setupPlugins(params) {
|
|
|
13540
14047
|
config: pluginConfig,
|
|
13541
14048
|
log,
|
|
13542
14049
|
extensions: agent.extensions,
|
|
14050
|
+
hookRegistry,
|
|
13543
14051
|
sessionWriter: {
|
|
13544
14052
|
transcriptPath: sessionWriter.transcriptPath,
|
|
13545
14053
|
append: (e) => sessionWriter.append(e)
|
|
@@ -13785,10 +14293,8 @@ async function launchEternalFromFlag(deps) {
|
|
|
13785
14293
|
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13786
14294
|
} : emptyGoal2(eternalFlag);
|
|
13787
14295
|
await saveGoal2(goalPath, next);
|
|
13788
|
-
const policy = deps.container.resolve(
|
|
13789
|
-
|
|
13790
|
-
);
|
|
13791
|
-
policy.setYolo(true);
|
|
14296
|
+
const policy = deps.container.resolve(TOKENS.PermissionPolicy);
|
|
14297
|
+
policy.setYolo?.(true);
|
|
13792
14298
|
deps.configRef.current = patchConfig(deps.configRef.current, { yolo: true });
|
|
13793
14299
|
const compactor = deps.container.resolve(TOKENS.Compactor);
|
|
13794
14300
|
const engine = new EternalAutonomyEngine({
|
|
@@ -13851,7 +14357,7 @@ async function main(argv) {
|
|
|
13851
14357
|
modelsRegistry,
|
|
13852
14358
|
permission: {
|
|
13853
14359
|
yolo: config.yolo,
|
|
13854
|
-
|
|
14360
|
+
yoloDestructive: flags["yolo-destructive"] === true || flags["force-all-yolo"] === true,
|
|
13855
14361
|
promptDelegate: makePromptDelegate(reader)
|
|
13856
14362
|
},
|
|
13857
14363
|
compactor: {
|
|
@@ -14121,6 +14627,19 @@ async function main(argv) {
|
|
|
14121
14627
|
});
|
|
14122
14628
|
});
|
|
14123
14629
|
const pipelines = setupPipelines({ events, logger });
|
|
14630
|
+
const hooksEnabled = flags["no-hooks"] !== true;
|
|
14631
|
+
const hookRegistry = new HookRegistry();
|
|
14632
|
+
if (hooksEnabled) hookRegistry.loadShellHooks(config.hooks);
|
|
14633
|
+
container.bind(TOKENS.HookRegistry, () => hookRegistry);
|
|
14634
|
+
const hookRunner = new HookRunner({
|
|
14635
|
+
registry: hookRegistry,
|
|
14636
|
+
logger,
|
|
14637
|
+
allowShell: hooksEnabled,
|
|
14638
|
+
sessionId: () => session.id
|
|
14639
|
+
});
|
|
14640
|
+
if (hooksEnabled) {
|
|
14641
|
+
pipelines.userInput.use(createUserPromptSubmitMiddleware(hookRunner));
|
|
14642
|
+
}
|
|
14124
14643
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
14125
14644
|
const compactionSetup = await setupCompaction({
|
|
14126
14645
|
compactor,
|
|
@@ -14167,8 +14686,12 @@ async function main(argv) {
|
|
|
14167
14686
|
pipelines,
|
|
14168
14687
|
context,
|
|
14169
14688
|
config,
|
|
14170
|
-
confirmAwaiter: makeConfirmAwaiter(reader)
|
|
14689
|
+
confirmAwaiter: makeConfirmAwaiter(reader),
|
|
14690
|
+
hookRunner
|
|
14171
14691
|
});
|
|
14692
|
+
if (hooksEnabled) {
|
|
14693
|
+
agent.extensions.register(createLifecycleHooksExtension(hookRunner));
|
|
14694
|
+
}
|
|
14172
14695
|
const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger });
|
|
14173
14696
|
if (config.features.mcp) {
|
|
14174
14697
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -14196,24 +14719,41 @@ async function main(argv) {
|
|
|
14196
14719
|
healthRegistry,
|
|
14197
14720
|
skillLoader: config.features.skills ? skillLoader : void 0,
|
|
14198
14721
|
configStore,
|
|
14199
|
-
paths: wpaths
|
|
14722
|
+
paths: wpaths,
|
|
14723
|
+
hookRegistry
|
|
14200
14724
|
});
|
|
14725
|
+
const resolveProviderCfg = (providerId) => {
|
|
14726
|
+
const savedCfg = config.providers?.[providerId];
|
|
14727
|
+
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
14728
|
+
const cfgWithType = {
|
|
14729
|
+
...savedCfg ?? { type: providerId, apiKey: config.apiKey, baseUrl: config.baseUrl },
|
|
14730
|
+
type: resolvedProviderId
|
|
14731
|
+
};
|
|
14732
|
+
return { resolvedProviderId, cfgWithType };
|
|
14733
|
+
};
|
|
14734
|
+
const buildProviderForId = (providerId) => {
|
|
14735
|
+
const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
|
|
14736
|
+
return config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
14737
|
+
};
|
|
14738
|
+
const refreshMaxContextFor = (providerId, modelId) => {
|
|
14739
|
+
const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
|
|
14740
|
+
void refreshMaxContext(resolvedProviderId, modelId, cfgWithType);
|
|
14741
|
+
};
|
|
14742
|
+
const fallbackExtension = createFallbackModelExtension({
|
|
14743
|
+
getConfig: () => config,
|
|
14744
|
+
buildProvider: buildProviderForId,
|
|
14745
|
+
onModelSwitch: refreshMaxContextFor,
|
|
14746
|
+
events,
|
|
14747
|
+
logger
|
|
14748
|
+
});
|
|
14749
|
+
if (fallbackExtension) agent.extensions.register(fallbackExtension);
|
|
14201
14750
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
14202
14751
|
try {
|
|
14203
|
-
|
|
14204
|
-
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
14205
|
-
const newCfg = savedCfg ?? {
|
|
14206
|
-
type: providerId,
|
|
14207
|
-
apiKey: config.apiKey,
|
|
14208
|
-
baseUrl: config.baseUrl
|
|
14209
|
-
};
|
|
14210
|
-
const cfgWithType = { ...newCfg, type: resolvedProviderId };
|
|
14211
|
-
const newProvider = config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
14212
|
-
context.provider = newProvider;
|
|
14752
|
+
context.provider = buildProviderForId(providerId);
|
|
14213
14753
|
context.model = modelId;
|
|
14214
14754
|
config = patchConfig(config, { provider: providerId, model: modelId });
|
|
14215
14755
|
configStore.update({ provider: providerId, model: modelId });
|
|
14216
|
-
|
|
14756
|
+
refreshMaxContextFor(providerId, modelId);
|
|
14217
14757
|
return null;
|
|
14218
14758
|
} catch (err) {
|
|
14219
14759
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -14820,6 +15360,21 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
14820
15360
|
}
|
|
14821
15361
|
return result.message;
|
|
14822
15362
|
},
|
|
15363
|
+
onContextLimit: (tokens) => {
|
|
15364
|
+
if (typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0) {
|
|
15365
|
+
effectiveMaxContext = tokens;
|
|
15366
|
+
context.provider.capabilities.maxContext = tokens;
|
|
15367
|
+
context.meta["effectiveMaxContext"] = tokens;
|
|
15368
|
+
autoCompactor?.setMaxContext(tokens);
|
|
15369
|
+
events.emit("ctx.max_context", {
|
|
15370
|
+
providerId: config.provider,
|
|
15371
|
+
modelId: context.model,
|
|
15372
|
+
maxContext: tokens
|
|
15373
|
+
});
|
|
15374
|
+
updateSpinnerContext();
|
|
15375
|
+
}
|
|
15376
|
+
return effectiveMaxContext;
|
|
15377
|
+
},
|
|
14823
15378
|
onMcp: async (args) => {
|
|
14824
15379
|
const parsed = parseMcpArgs(args);
|
|
14825
15380
|
if (!parsed) {
|
|
@@ -14838,11 +15393,11 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
14838
15393
|
onYolo: (setTo) => {
|
|
14839
15394
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
14840
15395
|
if (setTo !== void 0) {
|
|
14841
|
-
policy.setYolo(setTo);
|
|
15396
|
+
policy.setYolo?.(setTo);
|
|
14842
15397
|
config = patchConfig(config, { yolo: setTo });
|
|
14843
15398
|
return setTo;
|
|
14844
15399
|
}
|
|
14845
|
-
return policy.getYolo();
|
|
15400
|
+
return policy.getYolo?.() ?? config.yolo ?? false;
|
|
14846
15401
|
},
|
|
14847
15402
|
onNextPredict: (setTo) => {
|
|
14848
15403
|
if (setTo !== void 0) {
|
|
@@ -15076,7 +15631,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
15076
15631
|
setStatuslineHiddenItems,
|
|
15077
15632
|
getYolo: () => {
|
|
15078
15633
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
15079
|
-
return policy.getYolo();
|
|
15634
|
+
return policy.getYolo?.() ?? config.yolo ?? false;
|
|
15080
15635
|
},
|
|
15081
15636
|
getAutonomy: () => autonomyMode,
|
|
15082
15637
|
onAutonomy: (setTo) => {
|