@wrongstack/cli 0.54.1 → 0.66.13
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 +868 -163
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -2,29 +2,30 @@
|
|
|
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, resolveSessionLoggingConfig, createSessionEventBridge, 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, resolveAuditLevel, 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';
|
|
9
9
|
import * as crypto2 from 'crypto';
|
|
10
10
|
import { randomUUID } from 'crypto';
|
|
11
|
+
import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
|
|
11
12
|
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
12
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
13
|
-
import { MCPRegistry } from '@wrongstack/mcp';
|
|
14
|
+
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
14
15
|
import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
|
|
15
16
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
16
17
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
17
18
|
import { fileURLToPath } from 'url';
|
|
18
19
|
import * as readline from 'readline';
|
|
19
|
-
import * as
|
|
20
|
+
import * as fs12 from 'fs';
|
|
20
21
|
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
21
22
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
22
23
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
23
24
|
import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
|
|
24
25
|
import { spawn } from 'child_process';
|
|
25
26
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
26
|
-
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
27
27
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
28
|
+
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
28
29
|
|
|
29
30
|
var __defProp = Object.defineProperty;
|
|
30
31
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -1694,13 +1695,63 @@ __export(webui_server_exports, {
|
|
|
1694
1695
|
runWebUI: () => runWebUI
|
|
1695
1696
|
});
|
|
1696
1697
|
async function runWebUI(opts) {
|
|
1697
|
-
const
|
|
1698
|
+
const host = "127.0.0.1";
|
|
1699
|
+
const requestedWsPort = opts.port ?? 3457;
|
|
1700
|
+
const requestedHttpPort = opts.httpPort ?? 3456;
|
|
1701
|
+
const strictPort = process.env["WEBUI_STRICT_PORT"] === "1" || process.env["WEBUI_STRICT_PORT"] === "true";
|
|
1702
|
+
let httpPort = requestedHttpPort;
|
|
1703
|
+
let wsPort = requestedWsPort;
|
|
1704
|
+
if (!strictPort) {
|
|
1705
|
+
httpPort = await findFreePort(host, requestedHttpPort);
|
|
1706
|
+
wsPort = await findFreePort(host, requestedWsPort, { exclude: /* @__PURE__ */ new Set([httpPort]) });
|
|
1707
|
+
}
|
|
1708
|
+
const port = wsPort;
|
|
1709
|
+
const rateLimitMax = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "0", 10);
|
|
1698
1710
|
const clients = /* @__PURE__ */ new Map();
|
|
1711
|
+
const pendingConfirms = /* @__PURE__ */ new Map();
|
|
1699
1712
|
const secretScrubber = new DefaultSecretScrubber();
|
|
1700
1713
|
let abortController = null;
|
|
1701
1714
|
const authToken = crypto2.randomBytes(16).toString("hex");
|
|
1702
|
-
const wss = new WebSocketServer({ port, host
|
|
1703
|
-
console.log(`[WebUI] WebSocket server starting on ws
|
|
1715
|
+
const wss = new WebSocketServer({ port, host, maxPayload: 1 * 1024 * 1024 });
|
|
1716
|
+
console.log(`[WebUI] WebSocket server starting on ws://${host}:${port}`);
|
|
1717
|
+
let httpServer = null;
|
|
1718
|
+
try {
|
|
1719
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
1720
|
+
const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
|
|
1721
|
+
const distDir = path8.resolve(path8.dirname(serverEntry), "..");
|
|
1722
|
+
httpServer = createHttpServer({ host, distDir, wsPort });
|
|
1723
|
+
const openUrl = `http://${host}:${httpPort}`;
|
|
1724
|
+
httpServer.listen(httpPort, host, () => {
|
|
1725
|
+
console.log(
|
|
1726
|
+
`
|
|
1727
|
+
\u25B8 WebUI ready \u2014 open \x1B[1m${openUrl}\x1B[0m in your browser
|
|
1728
|
+
(same agent as this terminal \xB7 ws:${wsPort})
|
|
1729
|
+
`
|
|
1730
|
+
);
|
|
1731
|
+
if (opts.open) openBrowser(openUrl);
|
|
1732
|
+
});
|
|
1733
|
+
} catch (err) {
|
|
1734
|
+
console.warn(
|
|
1735
|
+
`[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`): ${err instanceof Error ? err.message : String(err)}. WS bridge still active on ws://${host}:${wsPort}.`
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
const registryBaseDir = opts.globalConfigPath ? path8.dirname(opts.globalConfigPath) : void 0;
|
|
1739
|
+
if (opts.projectRoot) {
|
|
1740
|
+
void registerInstance(
|
|
1741
|
+
{
|
|
1742
|
+
pid: process.pid,
|
|
1743
|
+
httpPort,
|
|
1744
|
+
wsPort,
|
|
1745
|
+
host,
|
|
1746
|
+
projectRoot: opts.projectRoot,
|
|
1747
|
+
projectName: path8.basename(opts.projectRoot) || opts.projectRoot,
|
|
1748
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1749
|
+
url: `http://${host}:${httpPort}`
|
|
1750
|
+
},
|
|
1751
|
+
registryBaseDir
|
|
1752
|
+
).catch(() => {
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1704
1755
|
const eventUnsubscribers = [];
|
|
1705
1756
|
function setupEvents() {
|
|
1706
1757
|
for (const unsub of eventUnsubscribers) unsub();
|
|
@@ -1794,6 +1845,88 @@ async function runWebUI(opts) {
|
|
|
1794
1845
|
});
|
|
1795
1846
|
})
|
|
1796
1847
|
);
|
|
1848
|
+
eventUnsubscribers.push(
|
|
1849
|
+
opts.events.on("tool.confirm_needed", (e) => {
|
|
1850
|
+
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
1851
|
+
pendingConfirms.set(id, e.resolve);
|
|
1852
|
+
broadcast({
|
|
1853
|
+
type: "tool.confirm_needed",
|
|
1854
|
+
payload: {
|
|
1855
|
+
id,
|
|
1856
|
+
toolName: e.tool?.name ?? "unknown",
|
|
1857
|
+
input: secretScrubber.scrubObject(e.input),
|
|
1858
|
+
suggestedPattern: e.suggestedPattern
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
})
|
|
1862
|
+
);
|
|
1863
|
+
const forwardSubagent = (kind, payload) => broadcast({ type: "subagent.event", payload: { kind, ...payload } });
|
|
1864
|
+
eventUnsubscribers.push(
|
|
1865
|
+
opts.events.on(
|
|
1866
|
+
"subagent.spawned",
|
|
1867
|
+
(e) => forwardSubagent("spawned", {
|
|
1868
|
+
subagentId: e.subagentId,
|
|
1869
|
+
taskId: e.taskId,
|
|
1870
|
+
name: e.name,
|
|
1871
|
+
provider: e.provider,
|
|
1872
|
+
model: e.model,
|
|
1873
|
+
description: e.description
|
|
1874
|
+
})
|
|
1875
|
+
),
|
|
1876
|
+
opts.events.on(
|
|
1877
|
+
"subagent.task_started",
|
|
1878
|
+
(e) => forwardSubagent("task_started", {
|
|
1879
|
+
subagentId: e.subagentId,
|
|
1880
|
+
taskId: e.taskId,
|
|
1881
|
+
description: e.description
|
|
1882
|
+
})
|
|
1883
|
+
),
|
|
1884
|
+
opts.events.on(
|
|
1885
|
+
"subagent.tool_executed",
|
|
1886
|
+
(e) => forwardSubagent("tool_executed", {
|
|
1887
|
+
subagentId: e.subagentId,
|
|
1888
|
+
toolName: e.name,
|
|
1889
|
+
durationMs: e.durationMs,
|
|
1890
|
+
ok: e.ok
|
|
1891
|
+
})
|
|
1892
|
+
),
|
|
1893
|
+
opts.events.on(
|
|
1894
|
+
"subagent.iteration_summary",
|
|
1895
|
+
(e) => forwardSubagent("iteration_summary", {
|
|
1896
|
+
subagentId: e.subagentId,
|
|
1897
|
+
iteration: e.iteration,
|
|
1898
|
+
toolCalls: e.toolCalls,
|
|
1899
|
+
costUsd: e.costUsd,
|
|
1900
|
+
currentTool: e.currentTool
|
|
1901
|
+
})
|
|
1902
|
+
),
|
|
1903
|
+
opts.events.on(
|
|
1904
|
+
"subagent.budget_extended",
|
|
1905
|
+
(e) => forwardSubagent("budget_extended", {
|
|
1906
|
+
subagentId: e.subagentId,
|
|
1907
|
+
totalExtensions: e.totalExtensions
|
|
1908
|
+
})
|
|
1909
|
+
),
|
|
1910
|
+
opts.events.on(
|
|
1911
|
+
"subagent.ctx_pct",
|
|
1912
|
+
(e) => forwardSubagent("ctx_pct", {
|
|
1913
|
+
subagentId: e.subagentId,
|
|
1914
|
+
load: e.load,
|
|
1915
|
+
tokens: e.tokens,
|
|
1916
|
+
maxContext: e.maxContext
|
|
1917
|
+
})
|
|
1918
|
+
),
|
|
1919
|
+
opts.events.on(
|
|
1920
|
+
"subagent.task_completed",
|
|
1921
|
+
(e) => forwardSubagent("task_completed", {
|
|
1922
|
+
subagentId: e.subagentId,
|
|
1923
|
+
status: e.status,
|
|
1924
|
+
iterations: e.iterations,
|
|
1925
|
+
toolCalls: e.toolCalls,
|
|
1926
|
+
error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
|
|
1927
|
+
})
|
|
1928
|
+
)
|
|
1929
|
+
);
|
|
1797
1930
|
if (opts.subscribeEternalIteration) {
|
|
1798
1931
|
eventUnsubscribers.push(
|
|
1799
1932
|
opts.subscribeEternalIteration((entry) => {
|
|
@@ -1814,10 +1947,11 @@ async function runWebUI(opts) {
|
|
|
1814
1947
|
);
|
|
1815
1948
|
}
|
|
1816
1949
|
}
|
|
1817
|
-
return new Promise((
|
|
1950
|
+
return new Promise((resolve4) => {
|
|
1818
1951
|
wss.on("listening", () => {
|
|
1819
|
-
console.log(`[WebUI] WebSocket server running on ws
|
|
1952
|
+
console.log(`[WebUI] WebSocket server running on ws://${host}:${port}`);
|
|
1820
1953
|
setupEvents();
|
|
1954
|
+
opts.onListening?.({ httpPort, wsPort, host });
|
|
1821
1955
|
});
|
|
1822
1956
|
wss.on("connection", (ws, req2) => {
|
|
1823
1957
|
const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
@@ -1870,17 +2004,19 @@ async function runWebUI(opts) {
|
|
|
1870
2004
|
let msgCount = 0;
|
|
1871
2005
|
let windowResetAt = Date.now() + 6e4;
|
|
1872
2006
|
ws.on("message", async (data) => {
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2007
|
+
if (rateLimitMax > 0) {
|
|
2008
|
+
const now = Date.now();
|
|
2009
|
+
if (now > windowResetAt) {
|
|
2010
|
+
msgCount = 0;
|
|
2011
|
+
windowResetAt = now + 6e4;
|
|
2012
|
+
}
|
|
2013
|
+
if (++msgCount > rateLimitMax) {
|
|
2014
|
+
send(ws, {
|
|
2015
|
+
type: "error",
|
|
2016
|
+
payload: { phase: "rate_limit", message: "Too many messages. Please wait." }
|
|
2017
|
+
});
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
1884
2020
|
}
|
|
1885
2021
|
try {
|
|
1886
2022
|
const msg = JSON.parse(data.toString());
|
|
@@ -1892,6 +2028,12 @@ async function runWebUI(opts) {
|
|
|
1892
2028
|
ws.on("close", () => {
|
|
1893
2029
|
console.log("[WebUI] Client disconnected");
|
|
1894
2030
|
clients.delete(ws);
|
|
2031
|
+
if (clients.size === 0 && pendingConfirms.size > 0) {
|
|
2032
|
+
for (const [id, resolve5] of pendingConfirms) {
|
|
2033
|
+
resolve5("no");
|
|
2034
|
+
pendingConfirms.delete(id);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
1895
2037
|
});
|
|
1896
2038
|
send(ws, {
|
|
1897
2039
|
type: "session.start",
|
|
@@ -1913,9 +2055,12 @@ async function runWebUI(opts) {
|
|
|
1913
2055
|
ws.close();
|
|
1914
2056
|
}
|
|
1915
2057
|
clients.clear();
|
|
2058
|
+
void unregisterInstance(process.pid, registryBaseDir).catch(() => {
|
|
2059
|
+
});
|
|
2060
|
+
httpServer?.close();
|
|
1916
2061
|
wss.close(() => {
|
|
1917
2062
|
console.log("[WebUI] Server stopped");
|
|
1918
|
-
|
|
2063
|
+
resolve4();
|
|
1919
2064
|
});
|
|
1920
2065
|
}
|
|
1921
2066
|
process.on("SIGINT", shutdown);
|
|
@@ -1940,6 +2085,15 @@ async function runWebUI(opts) {
|
|
|
1940
2085
|
case "ping":
|
|
1941
2086
|
send(ws, { type: "pong", payload: {} });
|
|
1942
2087
|
break;
|
|
2088
|
+
case "tool.confirm_result": {
|
|
2089
|
+
const { id, decision } = msg.payload;
|
|
2090
|
+
const resolve4 = pendingConfirms.get(id);
|
|
2091
|
+
if (resolve4) {
|
|
2092
|
+
pendingConfirms.delete(id);
|
|
2093
|
+
resolve4(decision);
|
|
2094
|
+
}
|
|
2095
|
+
break;
|
|
2096
|
+
}
|
|
1943
2097
|
case "providers.list":
|
|
1944
2098
|
await handleProvidersList(ws);
|
|
1945
2099
|
break;
|
|
@@ -2345,29 +2499,43 @@ async function resolveRuntimeMaxContext(input) {
|
|
|
2345
2499
|
const providerConfig = input.runtimeProviderConfig ?? input.config.providers?.[input.providerId];
|
|
2346
2500
|
const providerOverride = positiveNumber(readConfiguredMaxContext(providerConfig));
|
|
2347
2501
|
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
|
-
|
|
2502
|
+
const catalogId = providerConfig?.type && providerConfig.type !== input.providerId ? providerConfig.type : input.providerId;
|
|
2503
|
+
if (input.modelsRegistry) {
|
|
2504
|
+
const topLevelBaseUrlApplies = input.providerId === input.config.provider;
|
|
2505
|
+
const configuredBaseUrl = providerConfig?.baseUrl ?? (topLevelBaseUrlApplies ? input.config.baseUrl : void 0);
|
|
2506
|
+
let divergesFromCatalog = false;
|
|
2507
|
+
if (configuredBaseUrl) {
|
|
2508
|
+
const resolved = await safeGetProvider(input.modelsRegistry, catalogId);
|
|
2509
|
+
divergesFromCatalog = normalizeBaseUrl(configuredBaseUrl) !== normalizeBaseUrl(resolved?.apiBase);
|
|
2510
|
+
}
|
|
2511
|
+
if (!divergesFromCatalog) {
|
|
2512
|
+
const mergedModels = mergeCustomModelDefs(providerConfig?.customModels, input.config.models);
|
|
2513
|
+
const caps = await capabilitiesFor(
|
|
2514
|
+
input.modelsRegistry,
|
|
2515
|
+
catalogId,
|
|
2516
|
+
input.modelId,
|
|
2517
|
+
mergedModels
|
|
2518
|
+
).catch(() => void 0);
|
|
2519
|
+
const catalogMax = positiveNumber(caps?.maxContext);
|
|
2520
|
+
if (catalogMax) return catalogMax;
|
|
2521
|
+
const directModel = await input.modelsRegistry.getModel(catalogId, input.modelId).catch(() => void 0);
|
|
2522
|
+
const directMax = positiveNumber(directModel?.capabilities.maxContext);
|
|
2523
|
+
if (directMax) return directMax;
|
|
2524
|
+
}
|
|
2368
2525
|
}
|
|
2369
2526
|
return positiveNumber(input.provider.capabilities.maxContext) ?? 0;
|
|
2370
2527
|
}
|
|
2528
|
+
async function safeGetProvider(registry, id) {
|
|
2529
|
+
try {
|
|
2530
|
+
return await registry.getProvider(id);
|
|
2531
|
+
} catch {
|
|
2532
|
+
return void 0;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
function normalizeBaseUrl(url) {
|
|
2536
|
+
if (!url) return "";
|
|
2537
|
+
return url.trim().toLowerCase().replace(/\/+$/, "");
|
|
2538
|
+
}
|
|
2371
2539
|
function readConfiguredMaxContext(providerConfig) {
|
|
2372
2540
|
if (!providerConfig || typeof providerConfig !== "object") return void 0;
|
|
2373
2541
|
const capabilities = providerConfig.capabilities;
|
|
@@ -2381,6 +2549,8 @@ function positiveNumber(value) {
|
|
|
2381
2549
|
// src/arg-parser.ts
|
|
2382
2550
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
2383
2551
|
"yolo",
|
|
2552
|
+
"yolo-destructive",
|
|
2553
|
+
"confirm-destructive",
|
|
2384
2554
|
"force-all-yolo",
|
|
2385
2555
|
"verbose",
|
|
2386
2556
|
"trace",
|
|
@@ -2399,6 +2569,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
2399
2569
|
"prompt",
|
|
2400
2570
|
"metrics",
|
|
2401
2571
|
"webui",
|
|
2572
|
+
"open",
|
|
2402
2573
|
"no-check",
|
|
2403
2574
|
"no-models-refresh",
|
|
2404
2575
|
"director",
|
|
@@ -2407,7 +2578,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
2407
2578
|
"autonomy",
|
|
2408
2579
|
"eternal",
|
|
2409
2580
|
"no-hints",
|
|
2410
|
-
"hints"
|
|
2581
|
+
"hints",
|
|
2582
|
+
"no-hooks"
|
|
2411
2583
|
]);
|
|
2412
2584
|
function parseArgs(argv) {
|
|
2413
2585
|
const flags = {};
|
|
@@ -2735,10 +2907,12 @@ function buildAutonomyCommand(opts) {
|
|
|
2735
2907
|
" auto \u2014 After each turn, agent picks the best next step and continues.",
|
|
2736
2908
|
" Runs indefinitely until you press Esc or Ctrl+C.",
|
|
2737
2909
|
" eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
|
|
2738
|
-
" Force-enables YOLO
|
|
2910
|
+
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
2911
|
+
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
2739
2912
|
" parallel \u2014 Fan-out 4\u20138 subagents per tick. Each tick decomposes the goal,",
|
|
2740
2913
|
" spawns N agents, awaits results, aggregates. Requires /goal.",
|
|
2741
|
-
" Force-enables YOLO
|
|
2914
|
+
" Force-enables regular YOLO; destructive-gated calls still use",
|
|
2915
|
+
" the permission flow. Runs until /autonomy stop or Ctrl+C twice.",
|
|
2742
2916
|
"",
|
|
2743
2917
|
"Eternal stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
2744
2918
|
"Stage shown in real-time. Use /goal pause to pause, /goal resume to continue.",
|
|
@@ -2896,7 +3070,7 @@ function buildAutonomyCommand(opts) {
|
|
|
2896
3070
|
opts.onEternalStart(newMode);
|
|
2897
3071
|
const modeLabel = newMode === "eternal-parallel" ? `${color.magenta("PARALLEL")} mode` : `${color.red("ETERNAL")} mode`;
|
|
2898
3072
|
const msg2 = `Autonomy mode: ${modeLabel} \u2014 engine launching against goal: ${color.bold(goal.goal)}
|
|
2899
|
-
${color.dim("YOLO
|
|
3073
|
+
${color.dim("Regular YOLO enabled; destructive-gated calls still use the permission flow. Use /autonomy stop to end. Journal at /goal journal.")}`;
|
|
2900
3074
|
opts.renderer.write(msg2);
|
|
2901
3075
|
return { message: msg2 };
|
|
2902
3076
|
}
|
|
@@ -3373,6 +3547,11 @@ function buildContextCommand(opts) {
|
|
|
3373
3547
|
" /context Show counts: messages, est. tokens, tool calls, todos, read files.",
|
|
3374
3548
|
" /context detail As above, plus model, cwd, projectRoot, and the file list.",
|
|
3375
3549
|
" /context repair Repair orphan tool_use/tool_result blocks after manual compaction.",
|
|
3550
|
+
" /context limit Show effective context window for this session.",
|
|
3551
|
+
" /context limit <tokens> Set effective context window for this session (e.g. 220k).",
|
|
3552
|
+
" /context limit <tokens> --persist Persist effective context window to config.",
|
|
3553
|
+
" /context thresholds <warn> <soft> <hard> Set compaction thresholds (percent or decimal).",
|
|
3554
|
+
" /context thresholds <warn> <soft> <hard> --persist Persist thresholds to config.",
|
|
3376
3555
|
" /context mode List context-window modes.",
|
|
3377
3556
|
" /context mode <id> Switch context-window mode for this session."
|
|
3378
3557
|
].join("\n"),
|
|
@@ -3400,6 +3579,83 @@ ${formatContextWindowModeList(active)}`;
|
|
|
3400
3579
|
` empty msgs: removed ${repaired.report.removedMessages}`
|
|
3401
3580
|
].join("\n") : "Context repair: no orphan tool_use/tool_result blocks found.";
|
|
3402
3581
|
opts.renderer.write(`${msg2}
|
|
3582
|
+
`);
|
|
3583
|
+
return { message: msg2 };
|
|
3584
|
+
}
|
|
3585
|
+
if (trimmed === "limit") {
|
|
3586
|
+
const limit = readEffectiveLimit(ctx, opts);
|
|
3587
|
+
const msg2 = limit > 0 ? `Effective context window: ${limit.toLocaleString()} tokens` : "Effective context window: unknown (auto-compaction may be disabled).";
|
|
3588
|
+
opts.renderer.write(`${msg2}
|
|
3589
|
+
`);
|
|
3590
|
+
return { message: msg2 };
|
|
3591
|
+
}
|
|
3592
|
+
if (trimmed.startsWith("limit ")) {
|
|
3593
|
+
const persist = hasPersistFlag(trimmed);
|
|
3594
|
+
const raw = stripPersistFlag(trimmed.slice("limit ".length)).trim();
|
|
3595
|
+
const limit = parseTokenCount(raw);
|
|
3596
|
+
if (!limit) {
|
|
3597
|
+
const msg3 = `Invalid context limit "${raw}". Use a positive token count, e.g. 220k or 220000.`;
|
|
3598
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3599
|
+
`);
|
|
3600
|
+
return { message: msg3 };
|
|
3601
|
+
}
|
|
3602
|
+
ctx.meta["effectiveMaxContext"] = limit;
|
|
3603
|
+
const effective = opts.onContextLimit?.(limit) ?? limit;
|
|
3604
|
+
if (persist) {
|
|
3605
|
+
const error = await persistContextConfig(opts, { effectiveMaxContext: limit });
|
|
3606
|
+
if (error) {
|
|
3607
|
+
opts.renderer.write(`${color.red(error)}
|
|
3608
|
+
`);
|
|
3609
|
+
return { message: error };
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
const msg2 = `${color.green("Effective context window set:")} ${effective.toLocaleString()} tokens${persist ? " (persisted)" : ""}`;
|
|
3613
|
+
opts.renderer.write(`${msg2}
|
|
3614
|
+
`);
|
|
3615
|
+
return { message: msg2 };
|
|
3616
|
+
}
|
|
3617
|
+
if (trimmed.startsWith("thresholds ")) {
|
|
3618
|
+
const persist = hasPersistFlag(trimmed);
|
|
3619
|
+
const thresholdArgs = stripPersistFlag(trimmed.slice("thresholds ".length)).trim();
|
|
3620
|
+
const parts = thresholdArgs.split(/\s+/).filter(Boolean);
|
|
3621
|
+
if (parts.length !== 3) {
|
|
3622
|
+
const msg3 = "Usage: /context thresholds <warn> <soft> <hard> (examples: 60% 75% 90% or 0.6 0.75 0.9)";
|
|
3623
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3624
|
+
`);
|
|
3625
|
+
return { message: msg3 };
|
|
3626
|
+
}
|
|
3627
|
+
const thresholds = parts.map(parseThreshold);
|
|
3628
|
+
if (thresholds.some((v) => v === null)) {
|
|
3629
|
+
const msg3 = "Invalid thresholds. Use percentages (60%) or decimals between 0 and 1.";
|
|
3630
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3631
|
+
`);
|
|
3632
|
+
return { message: msg3 };
|
|
3633
|
+
}
|
|
3634
|
+
const [warn, soft, hard] = thresholds;
|
|
3635
|
+
if (!(warn < soft && soft < hard)) {
|
|
3636
|
+
const msg3 = "Invalid thresholds: require warn < soft < hard.";
|
|
3637
|
+
opts.renderer.write(`${color.red(msg3)}
|
|
3638
|
+
`);
|
|
3639
|
+
return { message: msg3 };
|
|
3640
|
+
}
|
|
3641
|
+
const base = readPolicy(ctx) ?? resolveContextWindowPolicy({});
|
|
3642
|
+
const policy2 = { ...base, thresholds: { warn, soft, hard } };
|
|
3643
|
+
ctx.meta["contextWindowMode"] = policy2.id;
|
|
3644
|
+
ctx.meta["contextWindowPolicy"] = policy2;
|
|
3645
|
+
if (persist) {
|
|
3646
|
+
const error = await persistContextConfig(opts, {
|
|
3647
|
+
warnThreshold: warn,
|
|
3648
|
+
softThreshold: soft,
|
|
3649
|
+
hardThreshold: hard
|
|
3650
|
+
});
|
|
3651
|
+
if (error) {
|
|
3652
|
+
opts.renderer.write(`${color.red(error)}
|
|
3653
|
+
`);
|
|
3654
|
+
return { message: error };
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
const msg2 = `${color.green("Context thresholds set:")} warn ${pct(warn)}, soft ${pct(soft)}, hard ${pct(hard)}${persist ? " (persisted)" : ""}`;
|
|
3658
|
+
opts.renderer.write(`${msg2}
|
|
3403
3659
|
`);
|
|
3404
3660
|
return { message: msg2 };
|
|
3405
3661
|
}
|
|
@@ -3433,6 +3689,7 @@ ${formatContextWindowModeList(active)}`;
|
|
|
3433
3689
|
` messages: ${messages.length} total (${countTurnPairs(messages)} user+assistant pairs)`,
|
|
3434
3690
|
` tokens (est): ${estimateTokens(messages).toLocaleString()} (chars / 4 estimate)`,
|
|
3435
3691
|
` mode: ${policy ? `${policy.id} (${policy.name})` : "balanced"}`,
|
|
3692
|
+
` limit: ${formatLimit(readEffectiveLimit(ctx, opts))}`,
|
|
3436
3693
|
` system prompt: ${ctx.systemPrompt.length} block${ctx.systemPrompt.length !== 1 ? "s" : ""}`,
|
|
3437
3694
|
` tools: ${countToolUses(messages)} calls made, ${countToolResults(messages)} results in history`,
|
|
3438
3695
|
` read files: ${ctx.readFiles.size} files`,
|
|
@@ -3459,6 +3716,68 @@ function readPolicy(ctx) {
|
|
|
3459
3716
|
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
3460
3717
|
return policy && typeof policy === "object" ? policy : null;
|
|
3461
3718
|
}
|
|
3719
|
+
function hasPersistFlag(input) {
|
|
3720
|
+
return /(?:^|\s)--persist(?:\s|$)/.test(input);
|
|
3721
|
+
}
|
|
3722
|
+
function stripPersistFlag(input) {
|
|
3723
|
+
return input.replace(/(?:^|\s)--persist(?:\s|$)/g, " ").trim();
|
|
3724
|
+
}
|
|
3725
|
+
async function persistContextConfig(opts, patch) {
|
|
3726
|
+
if (!opts.configStore || !opts.paths) return "Cannot persist context settings: config store not available.";
|
|
3727
|
+
let raw = "{}";
|
|
3728
|
+
try {
|
|
3729
|
+
raw = await fsp3.readFile(opts.paths.globalConfig, "utf8");
|
|
3730
|
+
} catch (err) {
|
|
3731
|
+
if (err.code !== "ENOENT") {
|
|
3732
|
+
return `Could not read ${opts.paths.globalConfig}: ${err.message}`;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
let parsed;
|
|
3736
|
+
try {
|
|
3737
|
+
parsed = JSON.parse(raw);
|
|
3738
|
+
} catch (err) {
|
|
3739
|
+
return `Config at ${opts.paths.globalConfig} is not valid JSON: ${err.message}`;
|
|
3740
|
+
}
|
|
3741
|
+
const current = opts.configStore.get();
|
|
3742
|
+
const context = {
|
|
3743
|
+
...current.context,
|
|
3744
|
+
...parsed.context ?? {},
|
|
3745
|
+
...patch
|
|
3746
|
+
};
|
|
3747
|
+
parsed.context = context;
|
|
3748
|
+
await atomicWrite(opts.paths.globalConfig, JSON.stringify(parsed, null, 2), { mode: 384 });
|
|
3749
|
+
opts.configStore.update({ context });
|
|
3750
|
+
return null;
|
|
3751
|
+
}
|
|
3752
|
+
function readEffectiveLimit(ctx, opts) {
|
|
3753
|
+
const live = opts.onContextLimit?.();
|
|
3754
|
+
if (typeof live === "number" && Number.isFinite(live) && live > 0) return live;
|
|
3755
|
+
const metaLimit = ctx.meta?.["effectiveMaxContext"];
|
|
3756
|
+
if (typeof metaLimit === "number" && Number.isFinite(metaLimit) && metaLimit > 0) return metaLimit;
|
|
3757
|
+
const providerLimit = ctx.provider?.capabilities?.maxContext;
|
|
3758
|
+
return typeof providerLimit === "number" && Number.isFinite(providerLimit) && providerLimit > 0 ? providerLimit : 0;
|
|
3759
|
+
}
|
|
3760
|
+
function parseTokenCount(raw) {
|
|
3761
|
+
const normalized = raw.trim().toLowerCase().replace(/,/g, "").replace(/_/g, "");
|
|
3762
|
+
const match = /^(\d+(?:\.\d+)?)([km])?$/.exec(normalized);
|
|
3763
|
+
if (!match) return null;
|
|
3764
|
+
const value = Number(match[1]);
|
|
3765
|
+
const unit = match[2];
|
|
3766
|
+
const scaled = unit === "m" ? value * 1e6 : unit === "k" ? value * 1e3 : value;
|
|
3767
|
+
const rounded = Math.floor(scaled);
|
|
3768
|
+
return Number.isFinite(rounded) && rounded > 0 ? rounded : null;
|
|
3769
|
+
}
|
|
3770
|
+
function parseThreshold(raw) {
|
|
3771
|
+
const s = raw.trim();
|
|
3772
|
+
const percent = s.endsWith("%");
|
|
3773
|
+
const n = Number((percent ? s.slice(0, -1) : s).trim());
|
|
3774
|
+
if (!Number.isFinite(n)) return null;
|
|
3775
|
+
const value = percent ? n / 100 : n;
|
|
3776
|
+
return value > 0 && value < 1 ? value : null;
|
|
3777
|
+
}
|
|
3778
|
+
function formatLimit(limit) {
|
|
3779
|
+
return limit > 0 ? `${limit.toLocaleString()} tokens` : "unknown";
|
|
3780
|
+
}
|
|
3462
3781
|
function pct(n) {
|
|
3463
3782
|
return `${Math.round(n * 100)}%`;
|
|
3464
3783
|
}
|
|
@@ -4867,12 +5186,20 @@ function parseMcpArgs(args) {
|
|
|
4867
5186
|
}
|
|
4868
5187
|
async function runMcpManagementCommand(parsed, deps) {
|
|
4869
5188
|
const { config, configPath: configPath2, mcpRegistry, allServerPresets } = deps;
|
|
4870
|
-
const
|
|
5189
|
+
const diskConfig = await readConfig(configPath2);
|
|
5190
|
+
const configured = isMcpServerRecord(diskConfig.mcpServers) ? diskConfig.mcpServers : config.mcpServers ?? {};
|
|
4871
5191
|
switch (parsed.action) {
|
|
4872
5192
|
case "list":
|
|
4873
5193
|
return renderList(configured, mcpRegistry, allServerPresets);
|
|
4874
5194
|
case "add":
|
|
4875
|
-
return runAdd(
|
|
5195
|
+
return runAdd(
|
|
5196
|
+
parsed.name,
|
|
5197
|
+
parsed.enable ?? false,
|
|
5198
|
+
configured,
|
|
5199
|
+
configPath2,
|
|
5200
|
+
mcpRegistry,
|
|
5201
|
+
allServerPresets
|
|
5202
|
+
);
|
|
4876
5203
|
case "remove":
|
|
4877
5204
|
return runRemove(parsed.name, configured, configPath2, mcpRegistry);
|
|
4878
5205
|
case "enable":
|
|
@@ -4916,34 +5243,46 @@ function renderList(configured, mcpRegistry, all) {
|
|
|
4916
5243
|
lines.push(color.dim(" /mcp restart <name> (runtime restart)"));
|
|
4917
5244
|
return lines.join("\n");
|
|
4918
5245
|
}
|
|
4919
|
-
async function runAdd(name, enable, configured, configPath2, all) {
|
|
5246
|
+
async function runAdd(name, enable, configured, configPath2, mcpRegistry, all) {
|
|
4920
5247
|
const preset = all[name];
|
|
4921
5248
|
if (!preset) {
|
|
4922
5249
|
const known = Object.keys(all).join(", ");
|
|
4923
5250
|
return `Unknown server "${name}". Available: ${known}`;
|
|
4924
5251
|
}
|
|
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
|
-
}
|
|
5252
|
+
const existing = configured[name];
|
|
5253
|
+
const nextCfg = existing ? { ...preset, ...existing, enabled: enable } : { ...preset, enabled: enable };
|
|
4934
5254
|
const full = await readConfig(configPath2);
|
|
4935
|
-
const mcpServers = {
|
|
5255
|
+
const mcpServers = {
|
|
5256
|
+
...isMcpServerRecord(full.mcpServers) ? full.mcpServers : {},
|
|
5257
|
+
[name]: nextCfg
|
|
5258
|
+
};
|
|
4936
5259
|
full.mcpServers = mcpServers;
|
|
4937
5260
|
await writeConfig(configPath2, full);
|
|
4938
|
-
|
|
4939
|
-
|
|
5261
|
+
if (!enable) {
|
|
5262
|
+
const verb = existing ? "Updated" : "Added (disabled \u2014 /mcp enable to start)";
|
|
5263
|
+
return `${color.green(verb)} "${name}" (${nextCfg.transport}). Config written to ${configPath2}.`;
|
|
5264
|
+
}
|
|
5265
|
+
try {
|
|
5266
|
+
if (mcpRegistry.list().some((server) => server.name === name)) {
|
|
5267
|
+
await mcpRegistry.restart(name);
|
|
5268
|
+
} else {
|
|
5269
|
+
await mcpRegistry.start(nextCfg);
|
|
5270
|
+
}
|
|
5271
|
+
const verb = existing ? "Updated and started" : "Enabled and started";
|
|
5272
|
+
return `${color.green(verb)} "${name}" (${nextCfg.transport}). Config written to ${configPath2}.`;
|
|
5273
|
+
} catch (err) {
|
|
5274
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5275
|
+
return `${color.yellow("Enabled")} "${name}" in config, but failed to start: ${message}`;
|
|
5276
|
+
}
|
|
4940
5277
|
}
|
|
4941
5278
|
async function runRemove(name, configured, configPath2, mcpRegistry) {
|
|
4942
5279
|
if (!configured[name]) return `Server "${name}" is not in config.`;
|
|
4943
5280
|
await mcpRegistry.stop(name).catch(() => {
|
|
4944
5281
|
});
|
|
4945
5282
|
const full = await readConfig(configPath2);
|
|
4946
|
-
const mcpServers = {
|
|
5283
|
+
const mcpServers = {
|
|
5284
|
+
...full.mcpServers ?? {}
|
|
5285
|
+
};
|
|
4947
5286
|
delete mcpServers[name];
|
|
4948
5287
|
full.mcpServers = mcpServers;
|
|
4949
5288
|
await writeConfig(configPath2, full);
|
|
@@ -4962,7 +5301,9 @@ async function runEnable(name, configured, configPath2, mcpRegistry) {
|
|
|
4962
5301
|
}
|
|
4963
5302
|
}
|
|
4964
5303
|
const full = await readConfig(configPath2);
|
|
4965
|
-
const mcpServers = {
|
|
5304
|
+
const mcpServers = {
|
|
5305
|
+
...full.mcpServers ?? {}
|
|
5306
|
+
};
|
|
4966
5307
|
mcpServers[name] = { ...mcpServers[name], enabled: true };
|
|
4967
5308
|
full.mcpServers = mcpServers;
|
|
4968
5309
|
await writeConfig(configPath2, full);
|
|
@@ -4979,7 +5320,9 @@ async function runDisable(name, configured, configPath2, mcpRegistry) {
|
|
|
4979
5320
|
await mcpRegistry.stop(name).catch(() => {
|
|
4980
5321
|
});
|
|
4981
5322
|
const full = await readConfig(configPath2);
|
|
4982
|
-
const mcpServers = {
|
|
5323
|
+
const mcpServers = {
|
|
5324
|
+
...full.mcpServers ?? {}
|
|
5325
|
+
};
|
|
4983
5326
|
mcpServers[name] = { ...mcpServers[name], enabled: false };
|
|
4984
5327
|
full.mcpServers = mcpServers;
|
|
4985
5328
|
await writeConfig(configPath2, full);
|
|
@@ -5020,6 +5363,9 @@ async function readConfig(path25) {
|
|
|
5020
5363
|
return {};
|
|
5021
5364
|
}
|
|
5022
5365
|
}
|
|
5366
|
+
function isMcpServerRecord(value) {
|
|
5367
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5368
|
+
}
|
|
5023
5369
|
async function writeConfig(path25, cfg) {
|
|
5024
5370
|
const raw = JSON.stringify(cfg, null, 2);
|
|
5025
5371
|
const tmp = path25 + ".tmp";
|
|
@@ -6339,13 +6685,13 @@ function buildYoloCommand(opts) {
|
|
|
6339
6685
|
description: "Toggle or query YOLO (auto-approve) mode.",
|
|
6340
6686
|
help: [
|
|
6341
6687
|
"Usage:",
|
|
6342
|
-
" /yolo
|
|
6343
|
-
" /yolo on
|
|
6344
|
-
" /yolo off
|
|
6345
|
-
" /yolo
|
|
6688
|
+
" /yolo Show current YOLO status",
|
|
6689
|
+
" /yolo on Enable YOLO mode (auto-approve all tool calls)",
|
|
6690
|
+
" /yolo off Disable YOLO mode (restore permission prompts)",
|
|
6691
|
+
" /yolo destructive Toggle destructive confirmation gate (YOLO mode only)",
|
|
6346
6692
|
"",
|
|
6347
|
-
"YOLO mode
|
|
6348
|
-
"Use
|
|
6693
|
+
"YOLO mode auto-approves everything, including destructive calls.",
|
|
6694
|
+
"Use /yolo destructive to re-enable confirmation for risky operations."
|
|
6349
6695
|
].join("\n"),
|
|
6350
6696
|
async run(args) {
|
|
6351
6697
|
const arg = args.trim().toLowerCase();
|
|
@@ -6356,7 +6702,7 @@ function buildYoloCommand(opts) {
|
|
|
6356
6702
|
}
|
|
6357
6703
|
if (!arg) {
|
|
6358
6704
|
const current = opts.onYolo();
|
|
6359
|
-
const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving
|
|
6705
|
+
const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving normal project work)")}` : `${color.green("OFF")} ${color.dim("(permission prompts active)")}`;
|
|
6360
6706
|
const msg2 = `YOLO mode: ${status}`;
|
|
6361
6707
|
opts.renderer.write(msg2);
|
|
6362
6708
|
return { message: msg2 };
|
|
@@ -6374,7 +6720,7 @@ function buildYoloCommand(opts) {
|
|
|
6374
6720
|
return { message: msg2 };
|
|
6375
6721
|
}
|
|
6376
6722
|
opts.onYolo(newState);
|
|
6377
|
-
const label = newState ? `${color.yellow("ENABLED")} \u2014
|
|
6723
|
+
const label = newState ? `${color.yellow("ENABLED")} \u2014 normal project tool calls will be auto-approved` : `${color.green("DISABLED")} \u2014 permission prompts are active`;
|
|
6378
6724
|
const msg = `YOLO mode: ${label}`;
|
|
6379
6725
|
opts.renderer.write(msg);
|
|
6380
6726
|
return { message: msg };
|
|
@@ -6527,10 +6873,10 @@ async function runProjectCheck(opts) {
|
|
|
6527
6873
|
if (answer2 === "y" || answer2 === "yes") {
|
|
6528
6874
|
try {
|
|
6529
6875
|
const { spawn: spawn3 } = await import('child_process');
|
|
6530
|
-
await new Promise((
|
|
6876
|
+
await new Promise((resolve4, reject) => {
|
|
6531
6877
|
const child = spawn3("git", ["init"], { cwd: projectRoot });
|
|
6532
6878
|
child.on("error", reject);
|
|
6533
|
-
child.on("close", (code) => code === 0 ?
|
|
6879
|
+
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
|
|
6534
6880
|
});
|
|
6535
6881
|
renderer.write(` ${color.green("\u2713")} Git repository initialized
|
|
6536
6882
|
`);
|
|
@@ -6581,7 +6927,7 @@ async function runLaunchPrompts(opts) {
|
|
|
6581
6927
|
yolo = yoloPinned;
|
|
6582
6928
|
} else {
|
|
6583
6929
|
const answer = (await reader.readLine(
|
|
6584
|
-
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve
|
|
6930
|
+
` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve normal project work)")} ${color.dim("[Y/n/q]")} `
|
|
6585
6931
|
)).trim().toLowerCase();
|
|
6586
6932
|
if (answer === "q") {
|
|
6587
6933
|
renderer.write(color.dim(" Goodbye!\n"));
|
|
@@ -6674,7 +7020,7 @@ var ReadlineInputReader = class {
|
|
|
6674
7020
|
async readLine(prompt) {
|
|
6675
7021
|
if (this.history.length === 0) await this.loadHistory();
|
|
6676
7022
|
while (this.pending) {
|
|
6677
|
-
await new Promise((
|
|
7023
|
+
await new Promise((resolve4) => setTimeout(resolve4, 50));
|
|
6678
7024
|
}
|
|
6679
7025
|
this.pending = true;
|
|
6680
7026
|
try {
|
|
@@ -6684,15 +7030,15 @@ var ReadlineInputReader = class {
|
|
|
6684
7030
|
this.rl = void 0;
|
|
6685
7031
|
}
|
|
6686
7032
|
const fresh = this.ensure();
|
|
6687
|
-
return new Promise((
|
|
7033
|
+
return new Promise((resolve4) => {
|
|
6688
7034
|
fresh.question(prompt ?? "> ", (line) => {
|
|
6689
7035
|
if (line.trim()) {
|
|
6690
7036
|
this.history.push(line);
|
|
6691
7037
|
void this.saveHistory();
|
|
6692
7038
|
}
|
|
6693
|
-
|
|
7039
|
+
resolve4(line);
|
|
6694
7040
|
});
|
|
6695
|
-
fresh.once("close", () =>
|
|
7041
|
+
fresh.once("close", () => resolve4(""));
|
|
6696
7042
|
}).then((result) => {
|
|
6697
7043
|
this.rl?.close();
|
|
6698
7044
|
this.rl = void 0;
|
|
@@ -6704,7 +7050,7 @@ var ReadlineInputReader = class {
|
|
|
6704
7050
|
}
|
|
6705
7051
|
async readKey(prompt, options) {
|
|
6706
7052
|
writeOut(prompt);
|
|
6707
|
-
return new Promise((
|
|
7053
|
+
return new Promise((resolve4) => {
|
|
6708
7054
|
const stdin = process.stdin;
|
|
6709
7055
|
const wasRaw = stdin.isRaw;
|
|
6710
7056
|
const wasPaused = stdin.isPaused();
|
|
@@ -6715,7 +7061,7 @@ var ReadlineInputReader = class {
|
|
|
6715
7061
|
if (key === "") {
|
|
6716
7062
|
cleanup();
|
|
6717
7063
|
writeOut("\n");
|
|
6718
|
-
|
|
7064
|
+
resolve4("");
|
|
6719
7065
|
return;
|
|
6720
7066
|
}
|
|
6721
7067
|
const opt = options.find(
|
|
@@ -6725,12 +7071,12 @@ var ReadlineInputReader = class {
|
|
|
6725
7071
|
cleanup();
|
|
6726
7072
|
writeOut(`${opt.key}
|
|
6727
7073
|
`);
|
|
6728
|
-
|
|
7074
|
+
resolve4(opt.value);
|
|
6729
7075
|
}
|
|
6730
7076
|
};
|
|
6731
7077
|
const onClose = () => {
|
|
6732
7078
|
cleanup();
|
|
6733
|
-
|
|
7079
|
+
resolve4("");
|
|
6734
7080
|
};
|
|
6735
7081
|
const cleanup = () => {
|
|
6736
7082
|
stdin.off("data", onData);
|
|
@@ -6758,7 +7104,7 @@ var ReadlineInputReader = class {
|
|
|
6758
7104
|
this.rl?.close();
|
|
6759
7105
|
this.rl = void 0;
|
|
6760
7106
|
writeOut(prompt);
|
|
6761
|
-
return new Promise((
|
|
7107
|
+
return new Promise((resolve4) => {
|
|
6762
7108
|
let buf = "";
|
|
6763
7109
|
const wasRaw = stdin.isRaw;
|
|
6764
7110
|
setRawMode(stdin, true);
|
|
@@ -6776,7 +7122,7 @@ var ReadlineInputReader = class {
|
|
|
6776
7122
|
cleanup();
|
|
6777
7123
|
writeOut(` ${dim(`[${buf.length} chars]`)}
|
|
6778
7124
|
`);
|
|
6779
|
-
|
|
7125
|
+
resolve4(buf);
|
|
6780
7126
|
return;
|
|
6781
7127
|
}
|
|
6782
7128
|
if (ch === "") {
|
|
@@ -7138,11 +7484,11 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
7138
7484
|
var theme = { primary: color.amber };
|
|
7139
7485
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
|
|
7140
7486
|
try {
|
|
7141
|
-
const { atomicWrite:
|
|
7142
|
-
const
|
|
7487
|
+
const { atomicWrite: atomicWrite13 } = await import('@wrongstack/core');
|
|
7488
|
+
const fs25 = await import('fs/promises');
|
|
7143
7489
|
let existing = {};
|
|
7144
7490
|
try {
|
|
7145
|
-
const raw = await
|
|
7491
|
+
const raw = await fs25.readFile(configPath2, "utf8");
|
|
7146
7492
|
existing = JSON.parse(raw);
|
|
7147
7493
|
} catch {
|
|
7148
7494
|
}
|
|
@@ -7150,7 +7496,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
7150
7496
|
existing.provider = provider;
|
|
7151
7497
|
existing.model = model;
|
|
7152
7498
|
await backupCurrent(homeFn);
|
|
7153
|
-
await
|
|
7499
|
+
await atomicWrite13(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
7154
7500
|
try {
|
|
7155
7501
|
await appendHistory(
|
|
7156
7502
|
oldCfg,
|
|
@@ -7440,7 +7786,7 @@ var GROUPS = [
|
|
|
7440
7786
|
items: [
|
|
7441
7787
|
{ key: "/mode", blurb: "switch persona: code-reviewer, debugger, architect, tester, devops, \u2026" },
|
|
7442
7788
|
{ key: "/model", blurb: "two-step provider \u2192 model picker, hot-swap at runtime" },
|
|
7443
|
-
{ key: "/yolo on|off|toggle", blurb: "auto-approve
|
|
7789
|
+
{ key: "/yolo on|off|toggle", blurb: "auto-approve normal project work without restart" },
|
|
7444
7790
|
{ key: "/context mode frugal|balanced|deep|archival", blurb: "pick how aggressively history is trimmed" },
|
|
7445
7791
|
{ key: "/compact", blurb: "manually compact the in-flight context window" },
|
|
7446
7792
|
{ key: "/plan show|add|start|done", blurb: "strategic roadmap, survives /resume across sessions" }
|
|
@@ -7473,12 +7819,12 @@ function pickGroupIndex(opts) {
|
|
|
7473
7819
|
try {
|
|
7474
7820
|
let current = 0;
|
|
7475
7821
|
try {
|
|
7476
|
-
const parsed = Number.parseInt(
|
|
7822
|
+
const parsed = Number.parseInt(fs12.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
7477
7823
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
7478
7824
|
} catch {
|
|
7479
7825
|
}
|
|
7480
|
-
|
|
7481
|
-
|
|
7826
|
+
fs12.mkdirSync(path8.dirname(opts.cursorFile), { recursive: true });
|
|
7827
|
+
fs12.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
7482
7828
|
return current;
|
|
7483
7829
|
} catch {
|
|
7484
7830
|
}
|
|
@@ -8680,8 +9026,9 @@ var updateCmd = async (args, deps) => {
|
|
|
8680
9026
|
deps.renderer.write(`Updating wrongstack from v${info.current} to v${info.latest}...
|
|
8681
9027
|
`);
|
|
8682
9028
|
try {
|
|
8683
|
-
const result = await new Promise((
|
|
8684
|
-
const
|
|
9029
|
+
const result = await new Promise((resolve4, reject) => {
|
|
9030
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
9031
|
+
const child = spawn(npmCommand, ["install", "-g", "wrongstack@latest"], {
|
|
8685
9032
|
cwd,
|
|
8686
9033
|
stdio: "pipe"
|
|
8687
9034
|
});
|
|
@@ -8690,7 +9037,7 @@ var updateCmd = async (args, deps) => {
|
|
|
8690
9037
|
_stderr += d;
|
|
8691
9038
|
});
|
|
8692
9039
|
child.on("error", reject);
|
|
8693
|
-
child.on("close", (code) =>
|
|
9040
|
+
child.on("close", (code) => resolve4({ code: code ?? 0 }));
|
|
8694
9041
|
});
|
|
8695
9042
|
if (result.code === 0) {
|
|
8696
9043
|
deps.renderer.write(`
|
|
@@ -9016,9 +9363,160 @@ var initCmd = async (_args, deps) => {
|
|
|
9016
9363
|
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
9017
9364
|
return 0;
|
|
9018
9365
|
};
|
|
9366
|
+
var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
|
|
9367
|
+
async evaluate() {
|
|
9368
|
+
return { permission: "auto", source: "default" };
|
|
9369
|
+
}
|
|
9370
|
+
};
|
|
9371
|
+
function parseToolsFlag(flags) {
|
|
9372
|
+
const raw = flags["tools"];
|
|
9373
|
+
if (typeof raw !== "string") return null;
|
|
9374
|
+
const set = new Set(
|
|
9375
|
+
raw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
9376
|
+
);
|
|
9377
|
+
return set.size > 0 ? set : null;
|
|
9378
|
+
}
|
|
9379
|
+
function makeServeContext(cwd, projectRoot, signal) {
|
|
9380
|
+
const provider = {
|
|
9381
|
+
id: "mcp-serve",
|
|
9382
|
+
capabilities: { maxContext: 0 },
|
|
9383
|
+
complete: async () => {
|
|
9384
|
+
throw new Error("no model provider in `mcp serve` mode");
|
|
9385
|
+
},
|
|
9386
|
+
stream: () => {
|
|
9387
|
+
throw new Error("no model provider in `mcp serve` mode");
|
|
9388
|
+
}
|
|
9389
|
+
};
|
|
9390
|
+
const session = { append: async () => {
|
|
9391
|
+
} };
|
|
9392
|
+
const tokenCounter = {
|
|
9393
|
+
account: () => {
|
|
9394
|
+
},
|
|
9395
|
+
total: () => ({ input: 0, output: 0 }),
|
|
9396
|
+
estimateCost: () => ({ total: 0 })
|
|
9397
|
+
};
|
|
9398
|
+
return new Context({
|
|
9399
|
+
systemPrompt: [],
|
|
9400
|
+
provider,
|
|
9401
|
+
session,
|
|
9402
|
+
signal,
|
|
9403
|
+
tokenCounter,
|
|
9404
|
+
cwd,
|
|
9405
|
+
projectRoot,
|
|
9406
|
+
model: "mcp-serve",
|
|
9407
|
+
tools: []
|
|
9408
|
+
});
|
|
9409
|
+
}
|
|
9410
|
+
async function selectExposedTools(registry, ctx, policy, whitelist) {
|
|
9411
|
+
const allowed = [];
|
|
9412
|
+
for (const tool of registry.list()) {
|
|
9413
|
+
if (whitelist && !whitelist.has(tool.name)) continue;
|
|
9414
|
+
const decision = await policy.evaluate(tool, {}, ctx);
|
|
9415
|
+
if (decision.permission === "auto") allowed.push(tool);
|
|
9416
|
+
}
|
|
9417
|
+
return allowed;
|
|
9418
|
+
}
|
|
9419
|
+
async function serveMcpStdio(deps) {
|
|
9420
|
+
const flags = deps.flags ?? {};
|
|
9421
|
+
const yolo = flags["yolo"] === true || flags["allow-all"] === true;
|
|
9422
|
+
const whitelist = parseToolsFlag(flags);
|
|
9423
|
+
const log = (m) => process.stderr.write(`${m}
|
|
9424
|
+
`);
|
|
9425
|
+
let registry = deps.toolRegistry;
|
|
9426
|
+
if (!registry) {
|
|
9427
|
+
registry = new ToolRegistry();
|
|
9428
|
+
registry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
|
|
9429
|
+
}
|
|
9430
|
+
const controller = new AbortController();
|
|
9431
|
+
const ctx = makeServeContext(deps.cwd, deps.projectRoot, controller.signal);
|
|
9432
|
+
const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
|
|
9433
|
+
const executor = new ToolExecutor(registry, {
|
|
9434
|
+
permissionPolicy,
|
|
9435
|
+
secretScrubber: new DefaultSecretScrubber(),
|
|
9436
|
+
perIterationOutputCapBytes: 1e6
|
|
9437
|
+
});
|
|
9438
|
+
const allowed = await selectExposedTools(registry, ctx, permissionPolicy, whitelist);
|
|
9439
|
+
const allowedNames = new Set(allowed.map((t) => t.name));
|
|
9440
|
+
if (allowed.length === 0) {
|
|
9441
|
+
log(
|
|
9442
|
+
"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."
|
|
9443
|
+
);
|
|
9444
|
+
}
|
|
9445
|
+
let counter = 0;
|
|
9446
|
+
const host = {
|
|
9447
|
+
listTools: () => allowed.map((t) => ({
|
|
9448
|
+
name: t.name,
|
|
9449
|
+
description: t.description,
|
|
9450
|
+
inputSchema: t.inputSchema ?? { type: "object" }
|
|
9451
|
+
})),
|
|
9452
|
+
callTool: async (name, callArgs) => {
|
|
9453
|
+
if (!allowedNames.has(name)) {
|
|
9454
|
+
return { content: `Tool "${name}" is not exposed by this server`, isError: true };
|
|
9455
|
+
}
|
|
9456
|
+
const use = {
|
|
9457
|
+
type: "tool_use",
|
|
9458
|
+
id: `srv_${++counter}`,
|
|
9459
|
+
name,
|
|
9460
|
+
input: callArgs
|
|
9461
|
+
};
|
|
9462
|
+
const batch = await executor.executeBatch([use], ctx, "sequential");
|
|
9463
|
+
const result = batch.outputs[0]?.result;
|
|
9464
|
+
if (!result || result.type === "tool_confirm_pending") {
|
|
9465
|
+
return {
|
|
9466
|
+
content: `Tool "${name}" requires interactive confirmation, which is unavailable over MCP`,
|
|
9467
|
+
isError: true
|
|
9468
|
+
};
|
|
9469
|
+
}
|
|
9470
|
+
return { content: result.content, isError: Boolean(result.is_error) };
|
|
9471
|
+
}
|
|
9472
|
+
};
|
|
9473
|
+
const server = new MCPServer({
|
|
9474
|
+
host,
|
|
9475
|
+
logger: { warn: (m) => log(`[mcp-serve] ${m}`) }
|
|
9476
|
+
});
|
|
9477
|
+
const mode = yolo ? "yolo: all tools" : "safe: read-only tools";
|
|
9478
|
+
if (flags["http"] === true || typeof flags["http"] === "string" || flags["port"] || flags["host"]) {
|
|
9479
|
+
const port = Number(flags["port"] ?? flags["http"] ?? 0) || 0;
|
|
9480
|
+
const httpHost = typeof flags["host"] === "string" ? flags["host"] : "127.0.0.1";
|
|
9481
|
+
const token = typeof flags["token"] === "string" ? flags["token"] : void 0;
|
|
9482
|
+
let handle2;
|
|
9483
|
+
try {
|
|
9484
|
+
handle2 = await serveHttp(server, {
|
|
9485
|
+
port,
|
|
9486
|
+
host: httpHost,
|
|
9487
|
+
token,
|
|
9488
|
+
logger: { warn: (m) => log(`[mcp-serve] ${m}`) }
|
|
9489
|
+
});
|
|
9490
|
+
} catch (err) {
|
|
9491
|
+
log(`wrongstack MCP server: ${err instanceof Error ? err.message : String(err)}`);
|
|
9492
|
+
return 1;
|
|
9493
|
+
}
|
|
9494
|
+
log(
|
|
9495
|
+
`wrongstack MCP server ready at ${handle2.url} \u2014 exposing ${allowed.length} tool(s) (${mode})${token ? " [token auth]" : ""}.`
|
|
9496
|
+
);
|
|
9497
|
+
await new Promise((resolve4) => {
|
|
9498
|
+
const stop = () => resolve4();
|
|
9499
|
+
process.once("SIGINT", stop);
|
|
9500
|
+
process.once("SIGTERM", stop);
|
|
9501
|
+
});
|
|
9502
|
+
await handle2.close();
|
|
9503
|
+
controller.abort();
|
|
9504
|
+
return 0;
|
|
9505
|
+
}
|
|
9506
|
+
log(`wrongstack MCP server ready on stdio \u2014 exposing ${allowed.length} tool(s) (${mode}).`);
|
|
9507
|
+
const handle = serveStdio(server);
|
|
9508
|
+
await handle.done;
|
|
9509
|
+
controller.abort();
|
|
9510
|
+
return 0;
|
|
9511
|
+
}
|
|
9512
|
+
|
|
9513
|
+
// src/subcommands/handlers/mcp.ts
|
|
9019
9514
|
var BUILT_IN_MCP = allServers();
|
|
9020
9515
|
var mcpCmd = async (args, deps) => {
|
|
9021
9516
|
const sub = args[0];
|
|
9517
|
+
if (sub === "serve") {
|
|
9518
|
+
return serveMcpStdio(deps);
|
|
9519
|
+
}
|
|
9022
9520
|
if (!sub || sub === "list") {
|
|
9023
9521
|
const servers = deps.config.mcpServers ?? {};
|
|
9024
9522
|
if (Object.keys(servers).length === 0) {
|
|
@@ -10266,10 +10764,10 @@ var auditCmd = async (args, deps) => {
|
|
|
10266
10764
|
return verify.ok ? 0 : 1;
|
|
10267
10765
|
};
|
|
10268
10766
|
async function listAudits(log, dir, deps) {
|
|
10269
|
-
const
|
|
10767
|
+
const fs25 = await import('fs/promises');
|
|
10270
10768
|
let entries;
|
|
10271
10769
|
try {
|
|
10272
|
-
entries = await
|
|
10770
|
+
entries = await fs25.readdir(dir);
|
|
10273
10771
|
} catch {
|
|
10274
10772
|
deps.renderer.write(
|
|
10275
10773
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -10356,7 +10854,18 @@ var helpCmd = async (_args, deps) => {
|
|
|
10356
10854
|
" wstack doctor Health checks",
|
|
10357
10855
|
" wstack export <id> [opts] Render a session",
|
|
10358
10856
|
" wstack usage Token + cost summary",
|
|
10359
|
-
" wstack version Print version"
|
|
10857
|
+
" wstack version Print version",
|
|
10858
|
+
"",
|
|
10859
|
+
color.bold("Common flags"),
|
|
10860
|
+
" --yolo Auto-approve all tool calls (including destructive)",
|
|
10861
|
+
" --confirm-destructive In YOLO mode, still prompt for destructive operations",
|
|
10862
|
+
" --yolo-destructive Deprecated \u2014 YOLO now auto-approves everything by default",
|
|
10863
|
+
" --tui / --no-tui Force or disable TUI mode",
|
|
10864
|
+
" --webui [--port <n>] [--open] Serve the browser UI + WS bridge (prints a URL,",
|
|
10865
|
+
" --open pops the browser; shares this terminal's",
|
|
10866
|
+
" agent; auto-picks free ports)",
|
|
10867
|
+
' --eternal "<mission>" Start an eternal-autonomy loop',
|
|
10868
|
+
" --no-hints Hide launch hints"
|
|
10360
10869
|
];
|
|
10361
10870
|
deps.renderer.write(lines.join("\n") + "\n");
|
|
10362
10871
|
return 0;
|
|
@@ -10529,7 +11038,8 @@ async function boot(argv) {
|
|
|
10529
11038
|
vault,
|
|
10530
11039
|
cwd,
|
|
10531
11040
|
projectRoot,
|
|
10532
|
-
userHome
|
|
11041
|
+
userHome,
|
|
11042
|
+
flags
|
|
10533
11043
|
});
|
|
10534
11044
|
await reader.close();
|
|
10535
11045
|
return code;
|
|
@@ -10582,6 +11092,10 @@ async function boot(argv) {
|
|
|
10582
11092
|
return 2;
|
|
10583
11093
|
}
|
|
10584
11094
|
}
|
|
11095
|
+
if (flags["webui"]) {
|
|
11096
|
+
flags["tui"] = false;
|
|
11097
|
+
flags["no-tui"] = true;
|
|
11098
|
+
}
|
|
10585
11099
|
if (isInteractiveTTY) {
|
|
10586
11100
|
let modePinned;
|
|
10587
11101
|
if (flags["no-tui"]) modePinned = "repl";
|
|
@@ -10647,6 +11161,20 @@ async function boot(argv) {
|
|
|
10647
11161
|
updateInfo
|
|
10648
11162
|
};
|
|
10649
11163
|
}
|
|
11164
|
+
var CONTEXT_OVERFLOW_RE = /context window|exceeds the context|too many tokens|context.*tokens/i;
|
|
11165
|
+
function contextOverflowHint(err) {
|
|
11166
|
+
const structured = err.code === ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW || err.code === ERROR_CODES.AGENT_CONTEXT_OVERFLOW;
|
|
11167
|
+
const textual = CONTEXT_OVERFLOW_RE.test(`${err.message}
|
|
11168
|
+
${err.describe()}`);
|
|
11169
|
+
if (!structured && !textual) return null;
|
|
11170
|
+
return [
|
|
11171
|
+
"Provider rejected the request as over its effective context window.",
|
|
11172
|
+
"If you use a custom baseUrl/proxy, the real limit may be lower than models.dev reports.",
|
|
11173
|
+
"Try: /context limit 220k",
|
|
11174
|
+
"Then, if needed: /context thresholds 50% 70% 85%",
|
|
11175
|
+
"Persistent config: set context.effectiveMaxContext."
|
|
11176
|
+
].join("\n");
|
|
11177
|
+
}
|
|
10650
11178
|
function fmtElapsed(ms) {
|
|
10651
11179
|
const s = Math.floor(ms / 1e3);
|
|
10652
11180
|
if (s < 60) return `${s}s`;
|
|
@@ -11006,7 +11534,7 @@ async function runRepl(opts) {
|
|
|
11006
11534
|
`[eternal] ${err instanceof Error ? err.message : String(err)}`
|
|
11007
11535
|
);
|
|
11008
11536
|
}
|
|
11009
|
-
await new Promise((
|
|
11537
|
+
await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
11010
11538
|
continue;
|
|
11011
11539
|
}
|
|
11012
11540
|
} else if (opts.getAutonomy?.() === "eternal-parallel") {
|
|
@@ -11052,7 +11580,7 @@ async function runRepl(opts) {
|
|
|
11052
11580
|
`[parallel] ${err instanceof Error ? err.message : String(err)}`
|
|
11053
11581
|
);
|
|
11054
11582
|
}
|
|
11055
|
-
await new Promise((
|
|
11583
|
+
await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
11056
11584
|
continue;
|
|
11057
11585
|
}
|
|
11058
11586
|
}
|
|
@@ -11238,6 +11766,8 @@ ${taskList}`;
|
|
|
11238
11766
|
if (err) {
|
|
11239
11767
|
const tag = err.recoverable ? " (recoverable)" : "";
|
|
11240
11768
|
opts.renderer.writeError(`Failed [${err.severity}]${tag}: ${err.describe()}`);
|
|
11769
|
+
const hint = contextOverflowHint(err);
|
|
11770
|
+
if (hint) opts.renderer.writeWarning(hint);
|
|
11241
11771
|
} else {
|
|
11242
11772
|
opts.renderer.writeError("Failed.");
|
|
11243
11773
|
}
|
|
@@ -11679,6 +12209,8 @@ async function execute(deps) {
|
|
|
11679
12209
|
if (err) {
|
|
11680
12210
|
const tag = err.recoverable ? " (recoverable)" : "";
|
|
11681
12211
|
renderer.writeError(`Failed [${err.severity}]${tag}: ${err.describe()}`);
|
|
12212
|
+
const hint = contextOverflowHint(err);
|
|
12213
|
+
if (hint) renderer.writeWarning(hint);
|
|
11682
12214
|
} else {
|
|
11683
12215
|
renderer.writeError("Failed.");
|
|
11684
12216
|
}
|
|
@@ -11698,7 +12230,7 @@ async function execute(deps) {
|
|
|
11698
12230
|
) + "\n"
|
|
11699
12231
|
);
|
|
11700
12232
|
}
|
|
11701
|
-
} else if (flags.tui && !flags["no-tui"]) {
|
|
12233
|
+
} else if (flags.tui && !flags["no-tui"] && !flags.webui) {
|
|
11702
12234
|
agent.disableInteractiveConfirmation();
|
|
11703
12235
|
const { runTui } = await import('@wrongstack/tui');
|
|
11704
12236
|
renderer.setSilent(true);
|
|
@@ -11887,12 +12419,15 @@ async function execute(deps) {
|
|
|
11887
12419
|
renderer.setSilent(false);
|
|
11888
12420
|
}
|
|
11889
12421
|
} else if (flags.webui) {
|
|
12422
|
+
agent.disableInteractiveConfirmation();
|
|
11890
12423
|
const { runWebUI: runWebUI2 } = await Promise.resolve().then(() => (init_webui_server(), webui_server_exports));
|
|
11891
12424
|
const webuiPromise = runWebUI2({
|
|
11892
12425
|
agent,
|
|
11893
12426
|
events,
|
|
11894
12427
|
session,
|
|
11895
12428
|
port: Number.parseInt(String(flags.port ?? "3457"), 10),
|
|
12429
|
+
projectRoot,
|
|
12430
|
+
open: !!flags.open,
|
|
11896
12431
|
modelsRegistry,
|
|
11897
12432
|
globalConfigPath: wpaths.globalConfig,
|
|
11898
12433
|
subscribeEternalIteration
|
|
@@ -12823,7 +13358,7 @@ function samplePaths(set) {
|
|
|
12823
13358
|
}
|
|
12824
13359
|
var WORKTREE_PHASE_CONCURRENCY = 4;
|
|
12825
13360
|
function gitText(args, cwd) {
|
|
12826
|
-
return new Promise((
|
|
13361
|
+
return new Promise((resolve4) => {
|
|
12827
13362
|
let out = "";
|
|
12828
13363
|
const child = spawn("git", args, {
|
|
12829
13364
|
cwd,
|
|
@@ -12836,8 +13371,8 @@ function gitText(args, cwd) {
|
|
|
12836
13371
|
child.stderr?.on("data", (c) => {
|
|
12837
13372
|
out += c.toString();
|
|
12838
13373
|
});
|
|
12839
|
-
child.on("error", () =>
|
|
12840
|
-
child.on("close", (code) =>
|
|
13374
|
+
child.on("error", () => resolve4({ code: 1, out }));
|
|
13375
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
|
|
12841
13376
|
});
|
|
12842
13377
|
}
|
|
12843
13378
|
async function isGitRepo(cwd) {
|
|
@@ -12845,7 +13380,7 @@ async function isGitRepo(cwd) {
|
|
|
12845
13380
|
return code === 0 && out.trim() === "true";
|
|
12846
13381
|
}
|
|
12847
13382
|
function runCmd(cmd, args, cwd, shell = false) {
|
|
12848
|
-
return new Promise((
|
|
13383
|
+
return new Promise((resolve4) => {
|
|
12849
13384
|
let out = "";
|
|
12850
13385
|
const child = spawn(cmd, args, {
|
|
12851
13386
|
cwd,
|
|
@@ -12859,8 +13394,8 @@ function runCmd(cmd, args, cwd, shell = false) {
|
|
|
12859
13394
|
child.stderr?.on("data", (c) => {
|
|
12860
13395
|
out += c.toString();
|
|
12861
13396
|
});
|
|
12862
|
-
child.on("error", (e) =>
|
|
12863
|
-
child.on("close", (code) =>
|
|
13397
|
+
child.on("error", (e) => resolve4({ code: 1, out: `${out}${String(e)}` }));
|
|
13398
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
|
|
12864
13399
|
});
|
|
12865
13400
|
}
|
|
12866
13401
|
function detectPackageManager2(root) {
|
|
@@ -13253,13 +13788,6 @@ function renderProgress3(ratio, width) {
|
|
|
13253
13788
|
const capped = Math.min(width, filled);
|
|
13254
13789
|
return FILLED2.repeat(capped) + EMPTY2.repeat(width - capped);
|
|
13255
13790
|
}
|
|
13256
|
-
var createSessionEventBridge = (_writer, level) => ({
|
|
13257
|
-
append: async (_e) => {
|
|
13258
|
-
},
|
|
13259
|
-
level: level ?? "standard",
|
|
13260
|
-
allows: () => true
|
|
13261
|
-
});
|
|
13262
|
-
var resolveAuditLevel = (cfg) => cfg?.session?.auditLevel ?? "standard";
|
|
13263
13791
|
function setupPipelines(params) {
|
|
13264
13792
|
const { events, logger } = params;
|
|
13265
13793
|
const pipelines = createDefaultPipelines();
|
|
@@ -13305,6 +13833,10 @@ async function setupCompaction(params) {
|
|
|
13305
13833
|
providerId: config.provider ?? provider.id,
|
|
13306
13834
|
modelId: config.model ?? context.model
|
|
13307
13835
|
});
|
|
13836
|
+
const initialPolicy = resolveContextWindowPolicy(config.context);
|
|
13837
|
+
context.meta ??= {};
|
|
13838
|
+
context.meta["contextWindowMode"] = initialPolicy.id;
|
|
13839
|
+
context.meta["contextWindowPolicy"] = initialPolicy;
|
|
13308
13840
|
let autoCompactor;
|
|
13309
13841
|
if (config.context.autoCompact !== false && effectiveMaxContext > 0) {
|
|
13310
13842
|
const auditLevel = resolveAuditLevel(fullConfig ?? config);
|
|
@@ -13315,15 +13847,15 @@ async function setupCompaction(params) {
|
|
|
13315
13847
|
// Calibrated estimator: recordActualUsage() is called after each API
|
|
13316
13848
|
// response so this converges on real token counts for compaction decisions.
|
|
13317
13849
|
(ctx) => estimateRequestTokensCalibrated(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total,
|
|
13850
|
+
initialPolicy.thresholds,
|
|
13318
13851
|
{
|
|
13319
|
-
|
|
13320
|
-
soft: config.context.softThreshold,
|
|
13321
|
-
hard: config.context.hardThreshold
|
|
13322
|
-
},
|
|
13323
|
-
{
|
|
13324
|
-
aggressiveOn: "soft",
|
|
13852
|
+
aggressiveOn: initialPolicy.aggressiveOn,
|
|
13325
13853
|
failureMode: "throw_on_hard",
|
|
13326
13854
|
events,
|
|
13855
|
+
policyProvider: (ctx) => {
|
|
13856
|
+
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
13857
|
+
return policy && typeof policy === "object" ? policy : null;
|
|
13858
|
+
},
|
|
13327
13859
|
sessionBridge
|
|
13328
13860
|
}
|
|
13329
13861
|
);
|
|
@@ -13342,7 +13874,8 @@ function createAgent(params) {
|
|
|
13342
13874
|
confirmAwaiter: params.confirmAwaiter,
|
|
13343
13875
|
iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
|
|
13344
13876
|
perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
|
|
13345
|
-
tracer: params.tracer
|
|
13877
|
+
tracer: params.tracer,
|
|
13878
|
+
hookRunner: params.hookRunner
|
|
13346
13879
|
});
|
|
13347
13880
|
return new Agent({
|
|
13348
13881
|
container: params.container,
|
|
@@ -13360,6 +13893,139 @@ function createAgent(params) {
|
|
|
13360
13893
|
tracer: params.tracer
|
|
13361
13894
|
});
|
|
13362
13895
|
}
|
|
13896
|
+
function parseModelRef(ref) {
|
|
13897
|
+
const trimmed = ref.trim();
|
|
13898
|
+
const slash = trimmed.indexOf("/");
|
|
13899
|
+
if (slash !== -1) {
|
|
13900
|
+
return {
|
|
13901
|
+
provider: trimmed.slice(0, slash) || void 0,
|
|
13902
|
+
model: trimmed.slice(slash + 1).trim()
|
|
13903
|
+
};
|
|
13904
|
+
}
|
|
13905
|
+
const parts = trimmed.split(/\s+/);
|
|
13906
|
+
if (parts.length >= 2) {
|
|
13907
|
+
return { provider: parts[0], model: parts.slice(1).join(" ") };
|
|
13908
|
+
}
|
|
13909
|
+
return { model: trimmed };
|
|
13910
|
+
}
|
|
13911
|
+
function overloadStatus(err) {
|
|
13912
|
+
if (!(err instanceof ProviderError)) return null;
|
|
13913
|
+
const s = err.status;
|
|
13914
|
+
if (s === 429 || s === 529 || s >= 500) return s;
|
|
13915
|
+
return null;
|
|
13916
|
+
}
|
|
13917
|
+
function createFallbackModelExtension(deps) {
|
|
13918
|
+
const initial = deps.getConfig().fallbackModels ?? [];
|
|
13919
|
+
if (initial.length === 0) return null;
|
|
13920
|
+
let dirty = false;
|
|
13921
|
+
return {
|
|
13922
|
+
name: "fallback-model",
|
|
13923
|
+
beforeRun: (ctx) => {
|
|
13924
|
+
if (!dirty) return;
|
|
13925
|
+
const cfg = deps.getConfig();
|
|
13926
|
+
try {
|
|
13927
|
+
ctx.provider = deps.buildProvider(cfg.provider);
|
|
13928
|
+
ctx.model = cfg.model;
|
|
13929
|
+
deps.onModelSwitch?.(cfg.provider, cfg.model);
|
|
13930
|
+
} catch (err) {
|
|
13931
|
+
deps.logger.warn(
|
|
13932
|
+
`fallback-model: could not restore primary "${cfg.provider}/${cfg.model}": ${err instanceof Error ? err.message : String(err)}`
|
|
13933
|
+
);
|
|
13934
|
+
}
|
|
13935
|
+
dirty = false;
|
|
13936
|
+
},
|
|
13937
|
+
wrapProviderRunner: async (ctx, request, inner) => {
|
|
13938
|
+
try {
|
|
13939
|
+
return await inner(ctx, request);
|
|
13940
|
+
} catch (firstErr) {
|
|
13941
|
+
let lastErr = firstErr;
|
|
13942
|
+
const cfg = deps.getConfig();
|
|
13943
|
+
const chain = cfg.fallbackModels ?? [];
|
|
13944
|
+
for (const ref of chain) {
|
|
13945
|
+
const status = overloadStatus(lastErr);
|
|
13946
|
+
if (status === null) break;
|
|
13947
|
+
const parsed = parseModelRef(ref);
|
|
13948
|
+
if (!parsed.model) continue;
|
|
13949
|
+
const targetProviderId = parsed.provider ?? cfg.provider;
|
|
13950
|
+
const from = { providerId: ctx.provider.id, model: ctx.model };
|
|
13951
|
+
let nextProvider;
|
|
13952
|
+
try {
|
|
13953
|
+
nextProvider = deps.buildProvider(targetProviderId);
|
|
13954
|
+
} catch (err) {
|
|
13955
|
+
deps.logger.warn(
|
|
13956
|
+
`fallback-model: skipping "${ref}" \u2014 cannot build provider "${targetProviderId}": ${err instanceof Error ? err.message : String(err)}`
|
|
13957
|
+
);
|
|
13958
|
+
continue;
|
|
13959
|
+
}
|
|
13960
|
+
const providerSwitched = nextProvider.id !== from.providerId;
|
|
13961
|
+
ctx.provider = nextProvider;
|
|
13962
|
+
ctx.model = parsed.model;
|
|
13963
|
+
request.model = parsed.model;
|
|
13964
|
+
dirty = true;
|
|
13965
|
+
deps.onModelSwitch?.(targetProviderId, parsed.model);
|
|
13966
|
+
deps.events.emit("provider.fallback", {
|
|
13967
|
+
from,
|
|
13968
|
+
to: { providerId: nextProvider.id, model: parsed.model },
|
|
13969
|
+
status,
|
|
13970
|
+
providerSwitched
|
|
13971
|
+
});
|
|
13972
|
+
try {
|
|
13973
|
+
return await inner(ctx, request);
|
|
13974
|
+
} catch (err) {
|
|
13975
|
+
lastErr = err;
|
|
13976
|
+
}
|
|
13977
|
+
}
|
|
13978
|
+
throw lastErr;
|
|
13979
|
+
}
|
|
13980
|
+
}
|
|
13981
|
+
};
|
|
13982
|
+
}
|
|
13983
|
+
|
|
13984
|
+
// src/hooks-wiring.ts
|
|
13985
|
+
var HookBlockedError = class extends Error {
|
|
13986
|
+
constructor(reason) {
|
|
13987
|
+
super(`Prompt blocked by hook: ${reason}`);
|
|
13988
|
+
this.name = "HookBlockedError";
|
|
13989
|
+
}
|
|
13990
|
+
};
|
|
13991
|
+
function createUserPromptSubmitMiddleware(hookRunner) {
|
|
13992
|
+
return {
|
|
13993
|
+
name: "UserPromptSubmitHooks",
|
|
13994
|
+
handler: async (payload, next) => {
|
|
13995
|
+
const prompt = payload.text;
|
|
13996
|
+
if (prompt && hookRunner.has("UserPromptSubmit")) {
|
|
13997
|
+
const r = await hookRunner.userPromptSubmit(prompt, payload.ctx);
|
|
13998
|
+
if (r.block) throw new HookBlockedError(r.reason ?? "no reason given");
|
|
13999
|
+
if (r.additionalContext) {
|
|
14000
|
+
const block = { type: "text", text: r.additionalContext };
|
|
14001
|
+
payload.content = [...payload.content, block];
|
|
14002
|
+
payload.text = `${prompt}
|
|
14003
|
+
|
|
14004
|
+
${r.additionalContext}`;
|
|
14005
|
+
}
|
|
14006
|
+
}
|
|
14007
|
+
return next(payload);
|
|
14008
|
+
}
|
|
14009
|
+
};
|
|
14010
|
+
}
|
|
14011
|
+
function createLifecycleHooksExtension(hookRunner) {
|
|
14012
|
+
let started = false;
|
|
14013
|
+
return {
|
|
14014
|
+
name: "lifecycle-hooks",
|
|
14015
|
+
beforeRun: async (ctx) => {
|
|
14016
|
+
if (started) return;
|
|
14017
|
+
started = true;
|
|
14018
|
+
if (!hookRunner.has("SessionStart")) return;
|
|
14019
|
+
const r = await hookRunner.sessionStart(ctx);
|
|
14020
|
+
if (r.additionalContext) {
|
|
14021
|
+
ctx.systemPrompt.push({ type: "text", text: r.additionalContext });
|
|
14022
|
+
}
|
|
14023
|
+
},
|
|
14024
|
+
afterRun: async (ctx) => {
|
|
14025
|
+
if (hookRunner.has("Stop")) await hookRunner.stop(ctx);
|
|
14026
|
+
}
|
|
14027
|
+
};
|
|
14028
|
+
}
|
|
13363
14029
|
function setupMetrics(params) {
|
|
13364
14030
|
const { flags, wpaths, events, logger, config } = params;
|
|
13365
14031
|
let metricsSink;
|
|
@@ -13475,7 +14141,8 @@ async function setupPlugins(params) {
|
|
|
13475
14141
|
skillLoader,
|
|
13476
14142
|
configStore,
|
|
13477
14143
|
pipelines,
|
|
13478
|
-
paths
|
|
14144
|
+
paths,
|
|
14145
|
+
hookRegistry
|
|
13479
14146
|
} = params;
|
|
13480
14147
|
const builtinPlugins = [];
|
|
13481
14148
|
const disabledBuiltins = new Set(
|
|
@@ -13540,6 +14207,7 @@ async function setupPlugins(params) {
|
|
|
13540
14207
|
config: pluginConfig,
|
|
13541
14208
|
log,
|
|
13542
14209
|
extensions: agent.extensions,
|
|
14210
|
+
hookRegistry,
|
|
13543
14211
|
sessionWriter: {
|
|
13544
14212
|
transcriptPath: sessionWriter.transcriptPath,
|
|
13545
14213
|
append: (e) => sessionWriter.append(e)
|
|
@@ -13785,10 +14453,8 @@ async function launchEternalFromFlag(deps) {
|
|
|
13785
14453
|
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13786
14454
|
} : emptyGoal2(eternalFlag);
|
|
13787
14455
|
await saveGoal2(goalPath, next);
|
|
13788
|
-
const policy = deps.container.resolve(
|
|
13789
|
-
|
|
13790
|
-
);
|
|
13791
|
-
policy.setYolo(true);
|
|
14456
|
+
const policy = deps.container.resolve(TOKENS.PermissionPolicy);
|
|
14457
|
+
policy.setYolo?.(true);
|
|
13792
14458
|
deps.configRef.current = patchConfig(deps.configRef.current, { yolo: true });
|
|
13793
14459
|
const compactor = deps.container.resolve(TOKENS.Compactor);
|
|
13794
14460
|
const engine = new EternalAutonomyEngine({
|
|
@@ -13810,21 +14476,6 @@ async function launchEternalFromFlag(deps) {
|
|
|
13810
14476
|
}
|
|
13811
14477
|
|
|
13812
14478
|
// src/index.ts
|
|
13813
|
-
var createSessionEventBridge2 = (_writer, level, _opts) => ({
|
|
13814
|
-
append: async (_e) => {
|
|
13815
|
-
},
|
|
13816
|
-
level: level ?? "standard",
|
|
13817
|
-
allows: () => true
|
|
13818
|
-
});
|
|
13819
|
-
var resolveAuditLevel2 = (cfg) => cfg?.session?.auditLevel ?? "standard";
|
|
13820
|
-
var resolveSessionLoggingConfig = (cfg) => ({
|
|
13821
|
-
auditLevel: resolveAuditLevel2(cfg),
|
|
13822
|
-
sampling: {
|
|
13823
|
-
toolProgress: {
|
|
13824
|
-
sampleRate: cfg?.session?.sampling?.toolProgress?.sampleRate ?? 8
|
|
13825
|
-
}
|
|
13826
|
-
}
|
|
13827
|
-
});
|
|
13828
14479
|
async function main(argv) {
|
|
13829
14480
|
const ctx = await boot(argv);
|
|
13830
14481
|
if (typeof ctx === "number") return ctx;
|
|
@@ -13851,7 +14502,8 @@ async function main(argv) {
|
|
|
13851
14502
|
modelsRegistry,
|
|
13852
14503
|
permission: {
|
|
13853
14504
|
yolo: config.yolo,
|
|
13854
|
-
|
|
14505
|
+
yoloDestructive: flags["yolo-destructive"] === true || flags["force-all-yolo"] === true,
|
|
14506
|
+
confirmDestructive: flags["confirm-destructive"] === true,
|
|
13855
14507
|
promptDelegate: makePromptDelegate(reader)
|
|
13856
14508
|
},
|
|
13857
14509
|
compactor: {
|
|
@@ -14042,9 +14694,13 @@ async function main(argv) {
|
|
|
14042
14694
|
const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
|
|
14043
14695
|
const priorFleetState = sessResult.priorFleetState;
|
|
14044
14696
|
const sessionConfig = resolveSessionLoggingConfig(config);
|
|
14045
|
-
const sessionBridge =
|
|
14697
|
+
const sessionBridge = createSessionEventBridge(
|
|
14046
14698
|
session,
|
|
14047
|
-
sessionConfig.auditLevel
|
|
14699
|
+
sessionConfig.auditLevel,
|
|
14700
|
+
{
|
|
14701
|
+
sampling: sessionConfig.sampling
|
|
14702
|
+
}
|
|
14703
|
+
);
|
|
14048
14704
|
const stats = new SessionStats(events, tokenCounter);
|
|
14049
14705
|
const errorRing = [];
|
|
14050
14706
|
events.on("error", (e) => {
|
|
@@ -14121,6 +14777,19 @@ async function main(argv) {
|
|
|
14121
14777
|
});
|
|
14122
14778
|
});
|
|
14123
14779
|
const pipelines = setupPipelines({ events, logger });
|
|
14780
|
+
const hooksEnabled = flags["no-hooks"] !== true;
|
|
14781
|
+
const hookRegistry = new HookRegistry();
|
|
14782
|
+
if (hooksEnabled) hookRegistry.loadShellHooks(config.hooks);
|
|
14783
|
+
container.bind(TOKENS.HookRegistry, () => hookRegistry);
|
|
14784
|
+
const hookRunner = new HookRunner({
|
|
14785
|
+
registry: hookRegistry,
|
|
14786
|
+
logger,
|
|
14787
|
+
allowShell: hooksEnabled,
|
|
14788
|
+
sessionId: () => session.id
|
|
14789
|
+
});
|
|
14790
|
+
if (hooksEnabled) {
|
|
14791
|
+
pipelines.userInput.use(createUserPromptSubmitMiddleware(hookRunner));
|
|
14792
|
+
}
|
|
14124
14793
|
const compactor = container.resolve(TOKENS.Compactor);
|
|
14125
14794
|
const compactionSetup = await setupCompaction({
|
|
14126
14795
|
compactor,
|
|
@@ -14167,8 +14836,12 @@ async function main(argv) {
|
|
|
14167
14836
|
pipelines,
|
|
14168
14837
|
context,
|
|
14169
14838
|
config,
|
|
14170
|
-
confirmAwaiter: makeConfirmAwaiter(reader)
|
|
14839
|
+
confirmAwaiter: makeConfirmAwaiter(reader),
|
|
14840
|
+
hookRunner
|
|
14171
14841
|
});
|
|
14842
|
+
if (hooksEnabled) {
|
|
14843
|
+
agent.extensions.register(createLifecycleHooksExtension(hookRunner));
|
|
14844
|
+
}
|
|
14172
14845
|
const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger });
|
|
14173
14846
|
if (config.features.mcp) {
|
|
14174
14847
|
for (const cfg of Object.values(config.mcpServers ?? {})) {
|
|
@@ -14196,24 +14869,41 @@ async function main(argv) {
|
|
|
14196
14869
|
healthRegistry,
|
|
14197
14870
|
skillLoader: config.features.skills ? skillLoader : void 0,
|
|
14198
14871
|
configStore,
|
|
14199
|
-
paths: wpaths
|
|
14872
|
+
paths: wpaths,
|
|
14873
|
+
hookRegistry
|
|
14874
|
+
});
|
|
14875
|
+
const resolveProviderCfg = (providerId) => {
|
|
14876
|
+
const savedCfg = config.providers?.[providerId];
|
|
14877
|
+
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
14878
|
+
const cfgWithType = {
|
|
14879
|
+
...savedCfg ?? { type: providerId, apiKey: config.apiKey, baseUrl: config.baseUrl },
|
|
14880
|
+
type: resolvedProviderId
|
|
14881
|
+
};
|
|
14882
|
+
return { resolvedProviderId, cfgWithType };
|
|
14883
|
+
};
|
|
14884
|
+
const buildProviderForId = (providerId) => {
|
|
14885
|
+
const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
|
|
14886
|
+
return config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
14887
|
+
};
|
|
14888
|
+
const refreshMaxContextFor = (providerId, modelId) => {
|
|
14889
|
+
const { resolvedProviderId, cfgWithType } = resolveProviderCfg(providerId);
|
|
14890
|
+
void refreshMaxContext(resolvedProviderId, modelId, cfgWithType);
|
|
14891
|
+
};
|
|
14892
|
+
const fallbackExtension = createFallbackModelExtension({
|
|
14893
|
+
getConfig: () => config,
|
|
14894
|
+
buildProvider: buildProviderForId,
|
|
14895
|
+
onModelSwitch: refreshMaxContextFor,
|
|
14896
|
+
events,
|
|
14897
|
+
logger
|
|
14200
14898
|
});
|
|
14899
|
+
if (fallbackExtension) agent.extensions.register(fallbackExtension);
|
|
14201
14900
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
14202
14901
|
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;
|
|
14902
|
+
context.provider = buildProviderForId(providerId);
|
|
14213
14903
|
context.model = modelId;
|
|
14214
14904
|
config = patchConfig(config, { provider: providerId, model: modelId });
|
|
14215
14905
|
configStore.update({ provider: providerId, model: modelId });
|
|
14216
|
-
|
|
14906
|
+
refreshMaxContextFor(providerId, modelId);
|
|
14217
14907
|
return null;
|
|
14218
14908
|
} catch (err) {
|
|
14219
14909
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -14820,6 +15510,21 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
14820
15510
|
}
|
|
14821
15511
|
return result.message;
|
|
14822
15512
|
},
|
|
15513
|
+
onContextLimit: (tokens) => {
|
|
15514
|
+
if (typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0) {
|
|
15515
|
+
effectiveMaxContext = tokens;
|
|
15516
|
+
context.provider.capabilities.maxContext = tokens;
|
|
15517
|
+
context.meta["effectiveMaxContext"] = tokens;
|
|
15518
|
+
autoCompactor?.setMaxContext(tokens);
|
|
15519
|
+
events.emit("ctx.max_context", {
|
|
15520
|
+
providerId: config.provider,
|
|
15521
|
+
modelId: context.model,
|
|
15522
|
+
maxContext: tokens
|
|
15523
|
+
});
|
|
15524
|
+
updateSpinnerContext();
|
|
15525
|
+
}
|
|
15526
|
+
return effectiveMaxContext;
|
|
15527
|
+
},
|
|
14823
15528
|
onMcp: async (args) => {
|
|
14824
15529
|
const parsed = parseMcpArgs(args);
|
|
14825
15530
|
if (!parsed) {
|
|
@@ -14838,11 +15543,11 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
14838
15543
|
onYolo: (setTo) => {
|
|
14839
15544
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
14840
15545
|
if (setTo !== void 0) {
|
|
14841
|
-
policy.setYolo(setTo);
|
|
15546
|
+
policy.setYolo?.(setTo);
|
|
14842
15547
|
config = patchConfig(config, { yolo: setTo });
|
|
14843
15548
|
return setTo;
|
|
14844
15549
|
}
|
|
14845
|
-
return policy.getYolo();
|
|
15550
|
+
return policy.getYolo?.() ?? config.yolo ?? false;
|
|
14846
15551
|
},
|
|
14847
15552
|
onNextPredict: (setTo) => {
|
|
14848
15553
|
if (setTo !== void 0) {
|
|
@@ -14905,7 +15610,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
14905
15610
|
const { spawn: spawn3 } = await import('child_process');
|
|
14906
15611
|
const cwd2 = projectRoot;
|
|
14907
15612
|
const statusResult = await new Promise(
|
|
14908
|
-
(
|
|
15613
|
+
(resolve4, reject) => {
|
|
14909
15614
|
const child = spawn3("git", ["status", "--porcelain"], {
|
|
14910
15615
|
cwd: cwd2,
|
|
14911
15616
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -14915,7 +15620,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
14915
15620
|
stdout += d;
|
|
14916
15621
|
});
|
|
14917
15622
|
child.on("error", reject);
|
|
14918
|
-
child.on("close", (code) =>
|
|
15623
|
+
child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
|
|
14919
15624
|
}
|
|
14920
15625
|
);
|
|
14921
15626
|
if (statusResult.stdout.trim().length > 0) {
|
|
@@ -15076,7 +15781,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
15076
15781
|
setStatuslineHiddenItems,
|
|
15077
15782
|
getYolo: () => {
|
|
15078
15783
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
15079
|
-
return policy.getYolo();
|
|
15784
|
+
return policy.getYolo?.() ?? config.yolo ?? false;
|
|
15080
15785
|
},
|
|
15081
15786
|
getAutonomy: () => autonomyMode,
|
|
15082
15787
|
onAutonomy: (setTo) => {
|