@wrongstack/cli 0.109.1 → 0.141.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1241 -177
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
|
|
2
|
+
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens } from '@wrongstack/core';
|
|
3
3
|
import * as fsp4 from 'fs/promises';
|
|
4
4
|
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
5
|
-
import * as
|
|
5
|
+
import * as path9 from 'path';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os2 from 'os';
|
|
@@ -18,7 +18,7 @@ import { createDefaultContainer, routeImagesForModel, readClipboardImage } from
|
|
|
18
18
|
import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
20
|
import * as readline from 'readline';
|
|
21
|
-
import * as
|
|
21
|
+
import * as fs14 from 'fs';
|
|
22
22
|
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
23
23
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
24
24
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
@@ -386,7 +386,7 @@ async function findSpec(store, idOrTitle) {
|
|
|
386
386
|
async function gatherProjectContext2(projectRoot) {
|
|
387
387
|
const parts = [];
|
|
388
388
|
try {
|
|
389
|
-
const pkgPath =
|
|
389
|
+
const pkgPath = path9.join(projectRoot, "package.json");
|
|
390
390
|
const pkgRaw = await fsp4.readFile(pkgPath, "utf8");
|
|
391
391
|
const pkg = JSON.parse(pkgRaw);
|
|
392
392
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -402,13 +402,13 @@ async function gatherProjectContext2(projectRoot) {
|
|
|
402
402
|
} catch {
|
|
403
403
|
}
|
|
404
404
|
try {
|
|
405
|
-
const tsconfigPath =
|
|
405
|
+
const tsconfigPath = path9.join(projectRoot, "tsconfig.json");
|
|
406
406
|
await fsp4.access(tsconfigPath);
|
|
407
407
|
parts.push("Language: TypeScript");
|
|
408
408
|
} catch {
|
|
409
409
|
}
|
|
410
410
|
try {
|
|
411
|
-
const srcDir =
|
|
411
|
+
const srcDir = path9.join(projectRoot, "src");
|
|
412
412
|
const entries = await fsp4.readdir(srcDir, { withFileTypes: true });
|
|
413
413
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
414
414
|
if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -1602,7 +1602,7 @@ __export(update_check_exports, {
|
|
|
1602
1602
|
getUpdateNotification: () => getUpdateNotification
|
|
1603
1603
|
});
|
|
1604
1604
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
1605
|
-
return
|
|
1605
|
+
return path9.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
1606
1606
|
}
|
|
1607
1607
|
function currentVersion() {
|
|
1608
1608
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1639,7 +1639,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1639
1639
|
}
|
|
1640
1640
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1641
1641
|
try {
|
|
1642
|
-
const dir =
|
|
1642
|
+
const dir = path9.dirname(cachePath(homeFn));
|
|
1643
1643
|
await fsp4.mkdir(dir, { recursive: true });
|
|
1644
1644
|
await fsp4.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1645
1645
|
} catch {
|
|
@@ -1746,7 +1746,7 @@ async function runWebUI(opts) {
|
|
|
1746
1746
|
try {
|
|
1747
1747
|
const requireFromHere = createRequire(import.meta.url);
|
|
1748
1748
|
const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
|
|
1749
|
-
const distDir =
|
|
1749
|
+
const distDir = path9.resolve(path9.dirname(serverEntry), "..");
|
|
1750
1750
|
httpServer = createHttpServer({ host, distDir, wsPort });
|
|
1751
1751
|
const openUrl = `http://${host}:${httpPort}`;
|
|
1752
1752
|
httpServer?.listen(httpPort, host, () => {
|
|
@@ -1763,7 +1763,7 @@ async function runWebUI(opts) {
|
|
|
1763
1763
|
`[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}.`
|
|
1764
1764
|
);
|
|
1765
1765
|
}
|
|
1766
|
-
const registryBaseDir = opts.globalConfigPath ?
|
|
1766
|
+
const registryBaseDir = opts.globalConfigPath ? path9.dirname(opts.globalConfigPath) : void 0;
|
|
1767
1767
|
if (opts.projectRoot) {
|
|
1768
1768
|
void registerInstance(
|
|
1769
1769
|
{
|
|
@@ -1772,7 +1772,7 @@ async function runWebUI(opts) {
|
|
|
1772
1772
|
wsPort,
|
|
1773
1773
|
host,
|
|
1774
1774
|
projectRoot: opts.projectRoot,
|
|
1775
|
-
projectName:
|
|
1775
|
+
projectName: path9.basename(opts.projectRoot) || opts.projectRoot,
|
|
1776
1776
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1777
1777
|
url: `http://${host}:${httpPort}`
|
|
1778
1778
|
},
|
|
@@ -2083,8 +2083,7 @@ async function runWebUI(opts) {
|
|
|
2083
2083
|
ws.close();
|
|
2084
2084
|
}
|
|
2085
2085
|
clients.clear();
|
|
2086
|
-
void unregisterInstance(process.pid, registryBaseDir).catch(() => {
|
|
2087
|
-
});
|
|
2086
|
+
void unregisterInstance(process.pid, registryBaseDir).catch((err) => console.debug(`[webui-server] unregister failed: ${err}`));
|
|
2088
2087
|
httpServer?.close();
|
|
2089
2088
|
wss.close(() => {
|
|
2090
2089
|
console.log("[WebUI] Server stopped");
|
|
@@ -2160,6 +2159,15 @@ async function runWebUI(opts) {
|
|
|
2160
2159
|
await handleProviderRemove(ws, m.payload.providerId);
|
|
2161
2160
|
break;
|
|
2162
2161
|
}
|
|
2162
|
+
default:
|
|
2163
|
+
send(ws, {
|
|
2164
|
+
type: "error",
|
|
2165
|
+
payload: {
|
|
2166
|
+
phase: "ws.dispatch",
|
|
2167
|
+
message: `Unknown message type: ${String(msg.type)}`
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
break;
|
|
2163
2171
|
}
|
|
2164
2172
|
}
|
|
2165
2173
|
async function handleUserMessage(ws, _client, content) {
|
|
@@ -2400,7 +2408,7 @@ async function runWebUI(opts) {
|
|
|
2400
2408
|
}
|
|
2401
2409
|
}
|
|
2402
2410
|
function getVault() {
|
|
2403
|
-
const keyFile =
|
|
2411
|
+
const keyFile = path9.join(path9.dirname(opts.globalConfigPath ?? ""), ".key");
|
|
2404
2412
|
return new DefaultSecretVault({ keyFile });
|
|
2405
2413
|
}
|
|
2406
2414
|
async function loadSavedProviders() {
|
|
@@ -2701,10 +2709,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2701
2709
|
const name = declared.split("@")[0];
|
|
2702
2710
|
if (name) return name;
|
|
2703
2711
|
}
|
|
2704
|
-
if (await pathExists(
|
|
2705
|
-
if (await pathExists(
|
|
2706
|
-
if (await pathExists(
|
|
2707
|
-
if (await pathExists(
|
|
2712
|
+
if (await pathExists(path9.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2713
|
+
if (await pathExists(path9.join(root, "bun.lockb"))) return "bun";
|
|
2714
|
+
if (await pathExists(path9.join(root, "bun.lock"))) return "bun";
|
|
2715
|
+
if (await pathExists(path9.join(root, "yarn.lock"))) return "yarn";
|
|
2708
2716
|
return "npm";
|
|
2709
2717
|
}
|
|
2710
2718
|
function hasUsableScript(scripts, name) {
|
|
@@ -2725,7 +2733,7 @@ function parseMakeTargets(makefile) {
|
|
|
2725
2733
|
async function detectProjectFacts(root) {
|
|
2726
2734
|
const facts = { hints: [] };
|
|
2727
2735
|
try {
|
|
2728
|
-
const pkg = JSON.parse(await fsp4.readFile(
|
|
2736
|
+
const pkg = JSON.parse(await fsp4.readFile(path9.join(root, "package.json"), "utf8"));
|
|
2729
2737
|
const scripts = pkg.scripts ?? {};
|
|
2730
2738
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2731
2739
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2739,14 +2747,14 @@ async function detectProjectFacts(root) {
|
|
|
2739
2747
|
} catch {
|
|
2740
2748
|
}
|
|
2741
2749
|
try {
|
|
2742
|
-
if (!await pathExists(
|
|
2750
|
+
if (!await pathExists(path9.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2743
2751
|
facts.test ??= "pytest";
|
|
2744
2752
|
facts.lint ??= "ruff check .";
|
|
2745
2753
|
facts.hints.push("pyproject.toml");
|
|
2746
2754
|
} catch {
|
|
2747
2755
|
}
|
|
2748
2756
|
try {
|
|
2749
|
-
if (!await pathExists(
|
|
2757
|
+
if (!await pathExists(path9.join(root, "go.mod"))) throw new Error("not go");
|
|
2750
2758
|
facts.build ??= "go build ./...";
|
|
2751
2759
|
facts.test ??= "go test ./...";
|
|
2752
2760
|
facts.run ??= "go run .";
|
|
@@ -2754,7 +2762,7 @@ async function detectProjectFacts(root) {
|
|
|
2754
2762
|
} catch {
|
|
2755
2763
|
}
|
|
2756
2764
|
try {
|
|
2757
|
-
if (!await pathExists(
|
|
2765
|
+
if (!await pathExists(path9.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2758
2766
|
facts.build ??= "cargo build";
|
|
2759
2767
|
facts.test ??= "cargo test";
|
|
2760
2768
|
facts.lint ??= "cargo clippy";
|
|
@@ -2763,7 +2771,7 @@ async function detectProjectFacts(root) {
|
|
|
2763
2771
|
} catch {
|
|
2764
2772
|
}
|
|
2765
2773
|
try {
|
|
2766
|
-
const makefile = await fsp4.readFile(
|
|
2774
|
+
const makefile = await fsp4.readFile(path9.join(root, "Makefile"), "utf8");
|
|
2767
2775
|
const targets = parseMakeTargets(makefile);
|
|
2768
2776
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2769
2777
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -2873,20 +2881,7 @@ function countToolResults(messages) {
|
|
|
2873
2881
|
return count;
|
|
2874
2882
|
}
|
|
2875
2883
|
function estimateTokens(messages) {
|
|
2876
|
-
|
|
2877
|
-
for (const m of messages) {
|
|
2878
|
-
const content = m.content;
|
|
2879
|
-
if (typeof content === "string") {
|
|
2880
|
-
total += Math.ceil(content.length / 4);
|
|
2881
|
-
} else if (Array.isArray(content)) {
|
|
2882
|
-
for (const b of content) {
|
|
2883
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
2884
|
-
else if (b.type === "tool_use" || b.type === "tool_result")
|
|
2885
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
return total;
|
|
2884
|
+
return estimateMessageTokens(messages);
|
|
2890
2885
|
}
|
|
2891
2886
|
|
|
2892
2887
|
// src/slash-commands/auth.ts
|
|
@@ -3071,7 +3066,7 @@ function formatPhaseList(graph) {
|
|
|
3071
3066
|
}
|
|
3072
3067
|
async function gatherProjectContext(projectRoot) {
|
|
3073
3068
|
try {
|
|
3074
|
-
const raw = await fsp4.readFile(
|
|
3069
|
+
const raw = await fsp4.readFile(path9.join(projectRoot, "package.json"), "utf8");
|
|
3075
3070
|
const pkg = JSON.parse(raw);
|
|
3076
3071
|
const parts = [
|
|
3077
3072
|
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
@@ -3187,6 +3182,7 @@ function buildAutoPhaseCommand(opts) {
|
|
|
3187
3182
|
};
|
|
3188
3183
|
}
|
|
3189
3184
|
}
|
|
3185
|
+
return { message: `Unknown subcommand "${sub}". Run \`/autophase\` for usage.` };
|
|
3190
3186
|
}
|
|
3191
3187
|
};
|
|
3192
3188
|
}
|
|
@@ -3874,7 +3870,7 @@ ${formatContextWindowModeList(active)}`;
|
|
|
3874
3870
|
const lines = [
|
|
3875
3871
|
`${color.bold("Context Window")}`,
|
|
3876
3872
|
` messages: ${messages.length} total (${countTurnPairs(messages)} user+assistant pairs)`,
|
|
3877
|
-
` tokens (est): ${estimateTokens(messages).toLocaleString()} (chars
|
|
3873
|
+
` tokens (est): ${estimateTokens(messages).toLocaleString()} (\u2248 chars/3.5)`,
|
|
3878
3874
|
` mode: ${policy ? `${policy.id} (${policy.name})` : "balanced"}`,
|
|
3879
3875
|
` limit: ${formatLimit(readEffectiveLimit(ctx, opts))}`,
|
|
3880
3876
|
` system prompt: ${ctx.systemPrompt.length} block${ctx.systemPrompt.length !== 1 ? "s" : ""}`,
|
|
@@ -3993,14 +3989,59 @@ function buildStatsCommand(opts) {
|
|
|
3993
3989
|
}
|
|
3994
3990
|
};
|
|
3995
3991
|
}
|
|
3992
|
+
function resolvePersistPath(deps) {
|
|
3993
|
+
const scope = deps.configStore.get().configScope;
|
|
3994
|
+
if (scope === "project" && deps.inProjectConfigPath) {
|
|
3995
|
+
return deps.inProjectConfigPath;
|
|
3996
|
+
}
|
|
3997
|
+
return deps.globalConfigPath;
|
|
3998
|
+
}
|
|
3999
|
+
async function ensureProjectDir(filePath) {
|
|
4000
|
+
const dir = path9.dirname(filePath);
|
|
4001
|
+
try {
|
|
4002
|
+
await fsp4.mkdir(dir, { recursive: true });
|
|
4003
|
+
} catch {
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
|
|
4007
|
+
"provider",
|
|
4008
|
+
"model",
|
|
4009
|
+
"fallbackModels",
|
|
4010
|
+
"modelMatrix",
|
|
4011
|
+
"maxConcurrent",
|
|
4012
|
+
"autonomy",
|
|
4013
|
+
"hints",
|
|
4014
|
+
"nextPrediction",
|
|
4015
|
+
"debugStream",
|
|
4016
|
+
"configScope",
|
|
4017
|
+
"yolo",
|
|
4018
|
+
"features",
|
|
4019
|
+
"context",
|
|
4020
|
+
"log",
|
|
4021
|
+
"session",
|
|
4022
|
+
"indexing",
|
|
4023
|
+
"tools",
|
|
4024
|
+
"launch"
|
|
4025
|
+
]);
|
|
4026
|
+
function filterSafeForProject(cfg) {
|
|
4027
|
+
const out = {};
|
|
4028
|
+
for (const [key, value] of Object.entries(cfg)) {
|
|
4029
|
+
if (PROJECT_SAFE_FIELDS.has(key)) {
|
|
4030
|
+
out[key] = value;
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
return out;
|
|
4034
|
+
}
|
|
3996
4035
|
async function persistAutonomySetting(deps, mutator) {
|
|
4036
|
+
const targetPath = resolvePersistPath(deps);
|
|
4037
|
+
await ensureProjectDir(targetPath);
|
|
3997
4038
|
let raw;
|
|
3998
4039
|
let fileExists = true;
|
|
3999
4040
|
try {
|
|
4000
|
-
raw = await fsp4.readFile(
|
|
4041
|
+
raw = await fsp4.readFile(targetPath, "utf8");
|
|
4001
4042
|
} catch (err) {
|
|
4002
4043
|
if (err.code !== "ENOENT") {
|
|
4003
|
-
throw new Error(`Could not read ${
|
|
4044
|
+
throw new Error(`Could not read ${targetPath}: ${err.message}`);
|
|
4004
4045
|
}
|
|
4005
4046
|
fileExists = false;
|
|
4006
4047
|
raw = "{}";
|
|
@@ -4010,7 +4051,7 @@ async function persistAutonomySetting(deps, mutator) {
|
|
|
4010
4051
|
parsed = JSON.parse(raw);
|
|
4011
4052
|
} catch (err) {
|
|
4012
4053
|
if (fileExists) {
|
|
4013
|
-
throw new Error(`Config at ${
|
|
4054
|
+
throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
|
|
4014
4055
|
}
|
|
4015
4056
|
parsed = {};
|
|
4016
4057
|
}
|
|
@@ -4018,18 +4059,26 @@ async function persistAutonomySetting(deps, mutator) {
|
|
|
4018
4059
|
const autonomy = decrypted.autonomy ?? {};
|
|
4019
4060
|
mutator(autonomy);
|
|
4020
4061
|
decrypted.autonomy = autonomy;
|
|
4021
|
-
const
|
|
4022
|
-
|
|
4062
|
+
const newScope = decrypted.configScope;
|
|
4063
|
+
const actualTarget = newScope === "project" && deps.inProjectConfigPath ? deps.inProjectConfigPath : newScope === "global" ? deps.globalConfigPath : targetPath;
|
|
4064
|
+
if (actualTarget !== targetPath) {
|
|
4065
|
+
await ensureProjectDir(actualTarget);
|
|
4066
|
+
}
|
|
4067
|
+
const toWrite = actualTarget === deps.globalConfigPath ? decrypted : filterSafeForProject(decrypted);
|
|
4068
|
+
const encrypted = encryptConfigSecrets$1(toWrite, deps.vault);
|
|
4069
|
+
await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4023
4070
|
deps.configStore.update({ autonomy: decrypted.autonomy });
|
|
4024
4071
|
}
|
|
4025
4072
|
async function persistConfigSetting(deps, mutator) {
|
|
4073
|
+
const targetPath = resolvePersistPath(deps);
|
|
4074
|
+
await ensureProjectDir(targetPath);
|
|
4026
4075
|
let raw;
|
|
4027
4076
|
let fileExists = true;
|
|
4028
4077
|
try {
|
|
4029
|
-
raw = await fsp4.readFile(
|
|
4078
|
+
raw = await fsp4.readFile(targetPath, "utf8");
|
|
4030
4079
|
} catch (err) {
|
|
4031
4080
|
if (err.code !== "ENOENT") {
|
|
4032
|
-
throw new Error(`Could not read ${
|
|
4081
|
+
throw new Error(`Could not read ${targetPath}: ${err.message}`);
|
|
4033
4082
|
}
|
|
4034
4083
|
fileExists = false;
|
|
4035
4084
|
raw = "{}";
|
|
@@ -4039,14 +4088,20 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
4039
4088
|
parsed = JSON.parse(raw);
|
|
4040
4089
|
} catch (err) {
|
|
4041
4090
|
if (fileExists) {
|
|
4042
|
-
throw new Error(`Config at ${
|
|
4091
|
+
throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
|
|
4043
4092
|
}
|
|
4044
4093
|
parsed = {};
|
|
4045
4094
|
}
|
|
4046
4095
|
const decrypted = decryptConfigSecrets$1(parsed, deps.vault);
|
|
4047
4096
|
mutator(decrypted);
|
|
4048
|
-
const
|
|
4049
|
-
|
|
4097
|
+
const newScope = decrypted.configScope;
|
|
4098
|
+
const actualTarget = newScope === "project" && deps.inProjectConfigPath ? deps.inProjectConfigPath : newScope === "global" ? deps.globalConfigPath : targetPath;
|
|
4099
|
+
if (actualTarget !== targetPath) {
|
|
4100
|
+
await ensureProjectDir(actualTarget);
|
|
4101
|
+
}
|
|
4102
|
+
const toWrite = actualTarget === deps.globalConfigPath ? decrypted : filterSafeForProject(decrypted);
|
|
4103
|
+
const encrypted = encryptConfigSecrets$1(toWrite, deps.vault);
|
|
4104
|
+
await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4050
4105
|
deps.configStore.update(decrypted);
|
|
4051
4106
|
}
|
|
4052
4107
|
async function persistTelegramConfig(deps, mutator) {
|
|
@@ -4078,7 +4133,7 @@ async function persistTelegramConfig(deps, mutator) {
|
|
|
4078
4133
|
decrypted.extensions = extensions;
|
|
4079
4134
|
const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
|
|
4080
4135
|
await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4081
|
-
deps.configStore.update({ extensions
|
|
4136
|
+
deps.configStore.update({ extensions });
|
|
4082
4137
|
}
|
|
4083
4138
|
|
|
4084
4139
|
// src/slash-commands/enhance.ts
|
|
@@ -5546,6 +5601,7 @@ ${formatGoal(updated)}` };
|
|
|
5546
5601
|
} catch {
|
|
5547
5602
|
}
|
|
5548
5603
|
if (opts.onEternalStop) opts.onEternalStop();
|
|
5604
|
+
if (opts.onAutonomy) opts.onAutonomy("off");
|
|
5549
5605
|
const msg = `${color.amber("Goal cleared.")} Previous goal marked abandoned; eternal mode will stop.`;
|
|
5550
5606
|
opts.renderer.write(msg);
|
|
5551
5607
|
return { message: msg };
|
|
@@ -5690,8 +5746,8 @@ function buildInitCommand(opts) {
|
|
|
5690
5746
|
category: "Config",
|
|
5691
5747
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
5692
5748
|
async run(_args, ctx) {
|
|
5693
|
-
const dir =
|
|
5694
|
-
const file =
|
|
5749
|
+
const dir = path9.join(ctx.projectRoot, ".wrongstack");
|
|
5750
|
+
const file = path9.join(dir, "AGENTS.md");
|
|
5695
5751
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
5696
5752
|
const body = renderAgentsTemplate(detected);
|
|
5697
5753
|
await fsp4.mkdir(dir, { recursive: true });
|
|
@@ -5906,9 +5962,9 @@ function stateBadge(state) {
|
|
|
5906
5962
|
return color.dim(state);
|
|
5907
5963
|
}
|
|
5908
5964
|
}
|
|
5909
|
-
async function readConfig(
|
|
5965
|
+
async function readConfig(path27) {
|
|
5910
5966
|
try {
|
|
5911
|
-
return JSON.parse(await fsp4.readFile(
|
|
5967
|
+
return JSON.parse(await fsp4.readFile(path27, "utf8"));
|
|
5912
5968
|
} catch {
|
|
5913
5969
|
return {};
|
|
5914
5970
|
}
|
|
@@ -5916,11 +5972,11 @@ async function readConfig(path26) {
|
|
|
5916
5972
|
function isMcpServerRecord(value) {
|
|
5917
5973
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5918
5974
|
}
|
|
5919
|
-
async function writeConfig(
|
|
5975
|
+
async function writeConfig(path27, cfg) {
|
|
5920
5976
|
const raw = JSON.stringify(cfg, null, 2);
|
|
5921
|
-
const tmp =
|
|
5977
|
+
const tmp = path27 + ".tmp";
|
|
5922
5978
|
await fsp4.writeFile(tmp, raw, "utf8");
|
|
5923
|
-
await fsp4.rename(tmp,
|
|
5979
|
+
await fsp4.rename(tmp, path27);
|
|
5924
5980
|
}
|
|
5925
5981
|
|
|
5926
5982
|
// src/slash-commands/mcp.ts
|
|
@@ -7134,7 +7190,7 @@ function buildSetModelCommand(opts) {
|
|
|
7134
7190
|
for (const phase of MATRIX_PHASE_KEYS) {
|
|
7135
7191
|
const agents = AGENTS_BY_PHASE[phase];
|
|
7136
7192
|
if (agents && agents.length > 0) {
|
|
7137
|
-
picks.push(agents[0]
|
|
7193
|
+
picks.push(agents[0]?.config.role);
|
|
7138
7194
|
}
|
|
7139
7195
|
}
|
|
7140
7196
|
picks.push("security-scanner", "bug-hunter");
|
|
@@ -7379,6 +7435,130 @@ function buildSetModelCommand(opts) {
|
|
|
7379
7435
|
}
|
|
7380
7436
|
};
|
|
7381
7437
|
}
|
|
7438
|
+
function fmtTokens(n) {
|
|
7439
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
7440
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
7441
|
+
return String(n);
|
|
7442
|
+
}
|
|
7443
|
+
function fmtPrice(pricePer1k) {
|
|
7444
|
+
if (pricePer1k === void 0 || pricePer1k <= 0) return color.dim("\u2014");
|
|
7445
|
+
return `$${pricePer1k.toFixed(2)}/M tok`;
|
|
7446
|
+
}
|
|
7447
|
+
function contextBar(maxContext) {
|
|
7448
|
+
const emoji = maxContext > 2e5 ? "\u{1F7E2}" : maxContext > 128e3 ? "\u{1F7E1}" : "\u{1F534}";
|
|
7449
|
+
return `${emoji} ${fmtTokens(maxContext)}`;
|
|
7450
|
+
}
|
|
7451
|
+
function buildModelCapsCommand(opts) {
|
|
7452
|
+
return {
|
|
7453
|
+
name: "modelcaps",
|
|
7454
|
+
category: "Config",
|
|
7455
|
+
description: "List available models with capacities (context window, max output, pricing).",
|
|
7456
|
+
help: [
|
|
7457
|
+
"Usage:",
|
|
7458
|
+
" /modelcaps List all available models grouped by provider",
|
|
7459
|
+
" /modelcaps <provider> Show models for one provider only",
|
|
7460
|
+
" /modelcaps <fragment> Filter models by id fragment (case-insensitive)",
|
|
7461
|
+
" /modelcaps summary Show agent-type \u2192 model mapping matrix",
|
|
7462
|
+
"",
|
|
7463
|
+
"Capacities shown: context window, max output tokens, input/output pricing.",
|
|
7464
|
+
"\u25CF = API key present \xB7 \u25CB = no key (model listed but not usable)."
|
|
7465
|
+
].join("\n"),
|
|
7466
|
+
async run(args) {
|
|
7467
|
+
const trimmed = args.trim().toLowerCase();
|
|
7468
|
+
if (trimmed === "summary") {
|
|
7469
|
+
return {
|
|
7470
|
+
message: [
|
|
7471
|
+
`${color.bold("Agent-Type \u2192 Model Mapping")} ${color.dim("\u2014 use /setmodel")}`,
|
|
7472
|
+
"",
|
|
7473
|
+
`${color.dim("Run /setmodel to see the current model matrix and resolution chain.")}`,
|
|
7474
|
+
`${color.dim("Each agent role resolves its model via: role \u2192 phase \u2192 * \u2192 leader.")}`,
|
|
7475
|
+
"",
|
|
7476
|
+
`${color.dim("/setmodel \u2014 show leader + matrix + resolution summary")}`,
|
|
7477
|
+
`${color.dim("/setmodel resolve <role> \u2014 walk the resolution chain for one role")}`
|
|
7478
|
+
].join("\n")
|
|
7479
|
+
};
|
|
7480
|
+
}
|
|
7481
|
+
const cachePath2 = opts.paths?.modelsCache;
|
|
7482
|
+
if (!cachePath2) {
|
|
7483
|
+
return { message: `${color.red("Models cache path not available")}.` };
|
|
7484
|
+
}
|
|
7485
|
+
let providers;
|
|
7486
|
+
try {
|
|
7487
|
+
const raw = await fsp4.readFile(cachePath2, "utf8");
|
|
7488
|
+
const parsed = JSON.parse(raw);
|
|
7489
|
+
const payload = parsed.payload ?? parsed;
|
|
7490
|
+
providers = Object.entries(payload).map(([id, p]) => ({
|
|
7491
|
+
id: p.id ?? id,
|
|
7492
|
+
name: p.name ?? id,
|
|
7493
|
+
family: p.npm ?? id,
|
|
7494
|
+
models: Object.values(p.models ?? {}).map((m) => ({
|
|
7495
|
+
id: m.id,
|
|
7496
|
+
name: m.name,
|
|
7497
|
+
capabilities: {
|
|
7498
|
+
contextWindow: m.limit?.context,
|
|
7499
|
+
maxOutputTokens: m.limit?.output
|
|
7500
|
+
},
|
|
7501
|
+
pricing: m.cost
|
|
7502
|
+
}))
|
|
7503
|
+
}));
|
|
7504
|
+
} catch {
|
|
7505
|
+
return {
|
|
7506
|
+
message: [
|
|
7507
|
+
`${color.amber("Models cache not available")}.`,
|
|
7508
|
+
`${color.dim(`Expected at: ${cachePath2}`)}`,
|
|
7509
|
+
"",
|
|
7510
|
+
`${color.dim("Run wstack sync-models or wait for the next auto-sync.")}`
|
|
7511
|
+
].join("\n")
|
|
7512
|
+
};
|
|
7513
|
+
}
|
|
7514
|
+
const config = opts.configStore.get();
|
|
7515
|
+
const configProviders = config?.providers ?? {};
|
|
7516
|
+
function hasKey(providerId) {
|
|
7517
|
+
const pc = configProviders[providerId];
|
|
7518
|
+
if (!pc) return false;
|
|
7519
|
+
if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
|
|
7520
|
+
if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
|
|
7521
|
+
return false;
|
|
7522
|
+
}
|
|
7523
|
+
const lines = [
|
|
7524
|
+
`${color.bold("Available Models")} ${color.dim("\u2014 capacities + pricing")}`,
|
|
7525
|
+
""
|
|
7526
|
+
];
|
|
7527
|
+
let shown = 0;
|
|
7528
|
+
for (const prov of providers) {
|
|
7529
|
+
if (trimmed && !trimmed.includes("/") && !prov.id.toLowerCase().includes(trimmed) && !prov.name.toLowerCase().includes(trimmed)) {
|
|
7530
|
+
continue;
|
|
7531
|
+
}
|
|
7532
|
+
const keyed = hasKey(prov.id);
|
|
7533
|
+
const marker = keyed ? color.green("\u25CF") : color.dim("\u25CB");
|
|
7534
|
+
lines.push(` ${marker} ${color.bold(prov.id.padEnd(16))} ${color.dim(`(${prov.name})`)}`);
|
|
7535
|
+
const models = prov.models ?? [];
|
|
7536
|
+
if (models.length === 0) {
|
|
7537
|
+
lines.push(` ${color.dim("no models listed \u2014 any model id accepted")}`);
|
|
7538
|
+
}
|
|
7539
|
+
for (const m of models) {
|
|
7540
|
+
if (trimmed?.includes("/")) {
|
|
7541
|
+
const frag = trimmed.split("/").pop() ?? "";
|
|
7542
|
+
if (frag && !m.id.toLowerCase().includes(frag)) continue;
|
|
7543
|
+
}
|
|
7544
|
+
const cap = m.capabilities;
|
|
7545
|
+
const ctx = cap?.contextWindow ?? 0;
|
|
7546
|
+
const maxOut = cap?.maxOutputTokens ?? 0;
|
|
7547
|
+
lines.push(
|
|
7548
|
+
` ${color.cyan(m.id)} ${contextBar(ctx)}` + (maxOut > 0 ? ` ${color.dim("out")} ${fmtTokens(maxOut)}` : "") + ` ${color.dim("in")} ${fmtPrice(m.pricing?.input)} ${color.dim("out")} ${fmtPrice(m.pricing?.output)}`
|
|
7549
|
+
);
|
|
7550
|
+
shown++;
|
|
7551
|
+
}
|
|
7552
|
+
lines.push("");
|
|
7553
|
+
}
|
|
7554
|
+
if (shown === 0) {
|
|
7555
|
+
lines.push(` ${color.dim("No models matched. Try /modelcaps without a filter.")}`);
|
|
7556
|
+
}
|
|
7557
|
+
lines.push(color.dim(`${shown} model(s). \u25CF = key present \xB7 \u25CB = no key. Use /modelcaps summary for agent-type mapping.`));
|
|
7558
|
+
return { message: lines.join("\n") };
|
|
7559
|
+
}
|
|
7560
|
+
};
|
|
7561
|
+
}
|
|
7382
7562
|
var noOpVault4 = {
|
|
7383
7563
|
encrypt: (v) => v,
|
|
7384
7564
|
decrypt: (v) => v,
|
|
@@ -7396,6 +7576,8 @@ function buildSettingsCommand(opts) {
|
|
|
7396
7576
|
" /settings delay <seconds> Auto-proceed delay in auto mode (0 disables)",
|
|
7397
7577
|
" /settings mode <off|suggest|auto> Default autonomy mode at startup",
|
|
7398
7578
|
" /settings hints on|off Show or suppress rotating launch hints",
|
|
7579
|
+
" /settings debug-stream on|off Raw SSE hex-dump to stderr for debugging",
|
|
7580
|
+
" /settings config-scope global|project Save settings globally or per-project",
|
|
7399
7581
|
" /settings defaults Show built-in default values",
|
|
7400
7582
|
"",
|
|
7401
7583
|
"Settings are persisted to ~/.wrongstack/config.json."
|
|
@@ -7405,12 +7587,16 @@ function buildSettingsCommand(opts) {
|
|
|
7405
7587
|
const delay = autonomy?.autoProceedDelayMs ?? 45e3;
|
|
7406
7588
|
const mode = autonomy?.defaultMode ?? "off";
|
|
7407
7589
|
const hints = opts.configStore.get().hints !== false;
|
|
7590
|
+
const debugStream = opts.configStore.get().debugStream === true;
|
|
7591
|
+
const configScope = opts.configStore.get().configScope ?? "global";
|
|
7408
7592
|
return [
|
|
7409
7593
|
`${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
|
|
7410
7594
|
"",
|
|
7411
7595
|
` auto-proceed delay: ${color.cyan(formatDelay(delay))} ${color.dim("change: /settings delay <seconds>")}`,
|
|
7412
7596
|
` default autonomy mode: ${color.cyan(mode)} ${color.dim("change: /settings mode off|suggest|auto")}`,
|
|
7413
7597
|
` launch hints: ${hints ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hints on|off")}`,
|
|
7598
|
+
` debug stream: ${debugStream ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings debug-stream on|off")}`,
|
|
7599
|
+
` config scope: ${color.cyan(configScope)} ${color.dim("change: /settings config-scope global|project")}`,
|
|
7414
7600
|
"",
|
|
7415
7601
|
color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
|
|
7416
7602
|
].join("\n");
|
|
@@ -7449,6 +7635,7 @@ function buildSettingsCommand(opts) {
|
|
|
7449
7635
|
const persistDeps = {
|
|
7450
7636
|
configStore: opts.configStore,
|
|
7451
7637
|
globalConfigPath: opts.paths.globalConfig,
|
|
7638
|
+
inProjectConfigPath: opts.paths.inProjectConfig,
|
|
7452
7639
|
vault: noOpVault4
|
|
7453
7640
|
};
|
|
7454
7641
|
try {
|
|
@@ -7493,8 +7680,32 @@ function buildSettingsCommand(opts) {
|
|
|
7493
7680
|
});
|
|
7494
7681
|
return { message: `${color.green("\u2713")} launch hints \u2192 ${on ? color.cyan("on") : color.dim("off")}` };
|
|
7495
7682
|
}
|
|
7683
|
+
if (sub === "debug-stream") {
|
|
7684
|
+
const raw = (parts[1] ?? "").toLowerCase();
|
|
7685
|
+
if (!["on", "off"].includes(raw)) {
|
|
7686
|
+
return { message: `${color.amber("Usage:")} /settings debug-stream on|off` };
|
|
7687
|
+
}
|
|
7688
|
+
const on = raw === "on";
|
|
7689
|
+
const { setDebugStreamEnabled } = await import('@wrongstack/providers');
|
|
7690
|
+
setDebugStreamEnabled(on);
|
|
7691
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
7692
|
+
cfg.debugStream = on;
|
|
7693
|
+
});
|
|
7694
|
+
return { message: `${color.green("\u2713")} debug stream \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim("raw SSE hex-dump to stderr")}` };
|
|
7695
|
+
}
|
|
7696
|
+
if (sub === "config-scope") {
|
|
7697
|
+
const raw = (parts[1] ?? "").toLowerCase();
|
|
7698
|
+
if (!["global", "project"].includes(raw)) {
|
|
7699
|
+
return { message: `${color.amber("Usage:")} /settings config-scope global|project` };
|
|
7700
|
+
}
|
|
7701
|
+
await persistConfigSetting(persistDeps, (cfg) => {
|
|
7702
|
+
cfg.configScope = raw;
|
|
7703
|
+
});
|
|
7704
|
+
const label = raw === "project" ? `${color.cyan("project")} \u2014 settings saved to <project>/.wrongstack/config.json` : `${color.cyan("global")} \u2014 settings saved to ~/.wrongstack/config.json`;
|
|
7705
|
+
return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
|
|
7706
|
+
}
|
|
7496
7707
|
return {
|
|
7497
|
-
message: `${color.red("Unknown setting")} "${sub}". Try ${color.dim("/settings")}, ${color.dim("/settings delay <s>")}, ${color.dim("/settings mode <m>")},
|
|
7708
|
+
message: `${color.red("Unknown setting")} "${sub}". Try ${color.dim("/settings")}, ${color.dim("/settings delay <s>")}, ${color.dim("/settings mode <m>")}, ${color.dim("/settings hints on|off")}, ${color.dim("/settings debug-stream on|off")}, or ${color.dim("/settings config-scope global|project")}.`
|
|
7498
7709
|
};
|
|
7499
7710
|
} catch (err) {
|
|
7500
7711
|
return {
|
|
@@ -7745,7 +7956,7 @@ var DEFAULTS = {
|
|
|
7745
7956
|
cost: true
|
|
7746
7957
|
};
|
|
7747
7958
|
function resolveConfigPath() {
|
|
7748
|
-
return process.env[CONFIG_ENV] ??
|
|
7959
|
+
return process.env[CONFIG_ENV] ?? path9.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
7749
7960
|
}
|
|
7750
7961
|
async function loadStatuslineConfig() {
|
|
7751
7962
|
const p = resolveConfigPath();
|
|
@@ -7759,7 +7970,7 @@ async function loadStatuslineConfig() {
|
|
|
7759
7970
|
async function saveStatuslineConfig(cfg) {
|
|
7760
7971
|
const p = resolveConfigPath();
|
|
7761
7972
|
try {
|
|
7762
|
-
await fsp4.mkdir(
|
|
7973
|
+
await fsp4.mkdir(path9.dirname(p), { recursive: true });
|
|
7763
7974
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
7764
7975
|
} catch (err) {
|
|
7765
7976
|
throw new FsError({
|
|
@@ -8276,6 +8487,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
8276
8487
|
buildSettingsCommand(opts),
|
|
8277
8488
|
buildTelegramSetupCommand(opts),
|
|
8278
8489
|
buildSetModelCommand(opts),
|
|
8490
|
+
buildModelCapsCommand(opts),
|
|
8279
8491
|
buildModelsCommand(opts),
|
|
8280
8492
|
buildCollabCommand(opts),
|
|
8281
8493
|
buildStatuslineCommand({
|
|
@@ -8305,13 +8517,13 @@ var MANIFESTS = [
|
|
|
8305
8517
|
];
|
|
8306
8518
|
async function detectProjectKind(projectRoot) {
|
|
8307
8519
|
try {
|
|
8308
|
-
await fsp4.access(
|
|
8520
|
+
await fsp4.access(path9.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
8309
8521
|
return "initialized";
|
|
8310
8522
|
} catch {
|
|
8311
8523
|
}
|
|
8312
8524
|
for (const m of MANIFESTS) {
|
|
8313
8525
|
try {
|
|
8314
|
-
await fsp4.access(
|
|
8526
|
+
await fsp4.access(path9.join(projectRoot, m));
|
|
8315
8527
|
return "project";
|
|
8316
8528
|
} catch {
|
|
8317
8529
|
}
|
|
@@ -8319,8 +8531,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
8319
8531
|
return "empty";
|
|
8320
8532
|
}
|
|
8321
8533
|
async function scaffoldAgentsMd(projectRoot) {
|
|
8322
|
-
const dir =
|
|
8323
|
-
const file =
|
|
8534
|
+
const dir = path9.join(projectRoot, ".wrongstack");
|
|
8535
|
+
const file = path9.join(dir, "AGENTS.md");
|
|
8324
8536
|
const facts = await detectProjectFacts(projectRoot);
|
|
8325
8537
|
const body = renderAgentsTemplate(facts);
|
|
8326
8538
|
await fsp4.mkdir(dir, { recursive: true });
|
|
@@ -8333,7 +8545,7 @@ async function runProjectCheck(opts) {
|
|
|
8333
8545
|
if (kind === "initialized") {
|
|
8334
8546
|
renderer.write(
|
|
8335
8547
|
`
|
|
8336
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
8548
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path9.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
8337
8549
|
`
|
|
8338
8550
|
);
|
|
8339
8551
|
return true;
|
|
@@ -8364,7 +8576,7 @@ async function runProjectCheck(opts) {
|
|
|
8364
8576
|
}
|
|
8365
8577
|
return true;
|
|
8366
8578
|
}
|
|
8367
|
-
const gitDir =
|
|
8579
|
+
const gitDir = path9.join(projectRoot, ".git");
|
|
8368
8580
|
let hasGit = false;
|
|
8369
8581
|
try {
|
|
8370
8582
|
await fsp4.access(gitDir);
|
|
@@ -8558,7 +8770,7 @@ var ReadlineInputReader = class {
|
|
|
8558
8770
|
history = [];
|
|
8559
8771
|
pending = false;
|
|
8560
8772
|
constructor(opts = {}) {
|
|
8561
|
-
this.historyFile = opts.historyFile ??
|
|
8773
|
+
this.historyFile = opts.historyFile ?? path9.join(os2.homedir(), ".wrongstack", "history");
|
|
8562
8774
|
}
|
|
8563
8775
|
async loadHistory() {
|
|
8564
8776
|
try {
|
|
@@ -8570,7 +8782,7 @@ var ReadlineInputReader = class {
|
|
|
8570
8782
|
}
|
|
8571
8783
|
async saveHistory() {
|
|
8572
8784
|
try {
|
|
8573
|
-
await fsp4.mkdir(
|
|
8785
|
+
await fsp4.mkdir(path9.dirname(this.historyFile), { recursive: true });
|
|
8574
8786
|
await fsp4.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
8575
8787
|
} catch {
|
|
8576
8788
|
}
|
|
@@ -8608,7 +8820,10 @@ var ReadlineInputReader = class {
|
|
|
8608
8820
|
const fresh = this.ensure();
|
|
8609
8821
|
this.installPromptGuard(fresh);
|
|
8610
8822
|
return new Promise((resolve5) => {
|
|
8823
|
+
let settled = false;
|
|
8611
8824
|
const settle = (line) => {
|
|
8825
|
+
if (settled) return;
|
|
8826
|
+
settled = true;
|
|
8612
8827
|
setOutputLineGuard(null);
|
|
8613
8828
|
resolve5(line);
|
|
8614
8829
|
};
|
|
@@ -8620,6 +8835,7 @@ var ReadlineInputReader = class {
|
|
|
8620
8835
|
settle(line);
|
|
8621
8836
|
});
|
|
8622
8837
|
fresh.once("close", () => settle(""));
|
|
8838
|
+
fresh.on && fresh.on("error", (_e) => settle(""));
|
|
8623
8839
|
}).then((result) => {
|
|
8624
8840
|
this.rl?.close();
|
|
8625
8841
|
return result;
|
|
@@ -8859,20 +9075,20 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
8859
9075
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
8860
9076
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
8861
9077
|
}
|
|
8862
|
-
if (filename !==
|
|
9078
|
+
if (filename !== path9.basename(filename)) {
|
|
8863
9079
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
8864
9080
|
}
|
|
8865
9081
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
8866
9082
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
8867
9083
|
}
|
|
8868
|
-
const resolvedParent =
|
|
9084
|
+
const resolvedParent = path9.resolve(parentDir);
|
|
8869
9085
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
8870
9086
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
8871
9087
|
}
|
|
8872
9088
|
}
|
|
8873
9089
|
async function safeDelete(filePath) {
|
|
8874
|
-
const dir =
|
|
8875
|
-
const filename =
|
|
9090
|
+
const dir = path9.dirname(filePath);
|
|
9091
|
+
const filename = path9.basename(filePath);
|
|
8876
9092
|
try {
|
|
8877
9093
|
assertSafeToDelete(filename, dir);
|
|
8878
9094
|
await fsp4.unlink(filePath);
|
|
@@ -8917,16 +9133,16 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
8917
9133
|
}
|
|
8918
9134
|
var defaultHomeDir = () => os2__default.homedir();
|
|
8919
9135
|
function historyDir(homeFn = defaultHomeDir) {
|
|
8920
|
-
return
|
|
9136
|
+
return path9.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
8921
9137
|
}
|
|
8922
9138
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
8923
|
-
return
|
|
9139
|
+
return path9.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
8924
9140
|
}
|
|
8925
9141
|
function configPath(homeFn = defaultHomeDir) {
|
|
8926
|
-
return
|
|
9142
|
+
return path9.join(homeFn(), ".wrongstack", "config.json");
|
|
8927
9143
|
}
|
|
8928
9144
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
8929
|
-
return
|
|
9145
|
+
return path9.join(homeFn(), ".wrongstack", "config.json.last");
|
|
8930
9146
|
}
|
|
8931
9147
|
function entryId(ts) {
|
|
8932
9148
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -8981,17 +9197,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
8981
9197
|
}
|
|
8982
9198
|
if (content !== void 0) {
|
|
8983
9199
|
try {
|
|
8984
|
-
const bakPath =
|
|
9200
|
+
const bakPath = path9.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
8985
9201
|
await atomicWrite(bakPath, content);
|
|
8986
9202
|
} catch {
|
|
8987
9203
|
}
|
|
8988
9204
|
}
|
|
8989
9205
|
try {
|
|
8990
|
-
const dir =
|
|
9206
|
+
const dir = path9.join(homeFn(), ".wrongstack");
|
|
8991
9207
|
const files = await fsp4.readdir(dir);
|
|
8992
9208
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
8993
9209
|
for (const f of baks.slice(10)) {
|
|
8994
|
-
await safeDelete(
|
|
9210
|
+
await safeDelete(path9.join(dir, f));
|
|
8995
9211
|
}
|
|
8996
9212
|
} catch {
|
|
8997
9213
|
}
|
|
@@ -9009,7 +9225,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
9009
9225
|
};
|
|
9010
9226
|
try {
|
|
9011
9227
|
await fsp4.writeFile(
|
|
9012
|
-
|
|
9228
|
+
path9.join(historyDir(homeFn), `${id}.json`),
|
|
9013
9229
|
JSON.stringify(entry, null, 2),
|
|
9014
9230
|
"utf8"
|
|
9015
9231
|
);
|
|
@@ -9017,7 +9233,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
9017
9233
|
throw new FsError({
|
|
9018
9234
|
message: err instanceof Error ? err.message : String(err),
|
|
9019
9235
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
9020
|
-
path:
|
|
9236
|
+
path: path9.join(historyDir(homeFn), `${id}.json`),
|
|
9021
9237
|
cause: err
|
|
9022
9238
|
});
|
|
9023
9239
|
}
|
|
@@ -9032,7 +9248,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
9032
9248
|
}
|
|
9033
9249
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
9034
9250
|
try {
|
|
9035
|
-
const raw = await fsp4.readFile(
|
|
9251
|
+
const raw = await fsp4.readFile(path9.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
9036
9252
|
return JSON.parse(raw);
|
|
9037
9253
|
} catch {
|
|
9038
9254
|
return null;
|
|
@@ -9098,10 +9314,10 @@ var theme = { primary: color.amber };
|
|
|
9098
9314
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
|
|
9099
9315
|
try {
|
|
9100
9316
|
const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
|
|
9101
|
-
const
|
|
9317
|
+
const fs29 = await import('fs/promises');
|
|
9102
9318
|
let existing = {};
|
|
9103
9319
|
try {
|
|
9104
|
-
const raw = await
|
|
9320
|
+
const raw = await fs29.readFile(configPath2, "utf8");
|
|
9105
9321
|
existing = JSON.parse(raw);
|
|
9106
9322
|
} catch {
|
|
9107
9323
|
}
|
|
@@ -9437,12 +9653,12 @@ function pickGroupIndex(opts) {
|
|
|
9437
9653
|
try {
|
|
9438
9654
|
let current = 0;
|
|
9439
9655
|
try {
|
|
9440
|
-
const parsed = Number.parseInt(
|
|
9656
|
+
const parsed = Number.parseInt(fs14.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
9441
9657
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
9442
9658
|
} catch {
|
|
9443
9659
|
}
|
|
9444
|
-
|
|
9445
|
-
|
|
9660
|
+
fs14.mkdirSync(path9.dirname(opts.cursorFile), { recursive: true });
|
|
9661
|
+
fs14.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
9446
9662
|
return current;
|
|
9447
9663
|
} catch {
|
|
9448
9664
|
}
|
|
@@ -9778,14 +9994,14 @@ function summarize(value, name) {
|
|
|
9778
9994
|
if (typeof v === "object" && v !== null) {
|
|
9779
9995
|
const o = v;
|
|
9780
9996
|
if (name === "edit") {
|
|
9781
|
-
const
|
|
9997
|
+
const path27 = typeof o["path"] === "string" ? o["path"] : "";
|
|
9782
9998
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
9783
|
-
return `${
|
|
9999
|
+
return `${path27} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
9784
10000
|
}
|
|
9785
10001
|
if (name === "write") {
|
|
9786
|
-
const
|
|
10002
|
+
const path27 = typeof o["path"] === "string" ? o["path"] : "";
|
|
9787
10003
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
9788
|
-
return bytes !== void 0 ? `${
|
|
10004
|
+
return bytes !== void 0 ? `${path27} ${bytes}B` : path27;
|
|
9789
10005
|
}
|
|
9790
10006
|
if (typeof o["count"] === "number") {
|
|
9791
10007
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -10009,7 +10225,8 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
|
|
|
10009
10225
|
renderer.write(color.dim(" (no keys saved)\n"));
|
|
10010
10226
|
} else {
|
|
10011
10227
|
for (let i = 0; i < keys.length; i++) {
|
|
10012
|
-
|
|
10228
|
+
const key = keys[i];
|
|
10229
|
+
if (key) renderKeyLine(renderer, key, i + 1, active);
|
|
10013
10230
|
}
|
|
10014
10231
|
}
|
|
10015
10232
|
}
|
|
@@ -10101,7 +10318,7 @@ async function confirm(deps, question) {
|
|
|
10101
10318
|
return answer === "y" || answer === "yes";
|
|
10102
10319
|
}
|
|
10103
10320
|
function suggestLabel(usedLabels) {
|
|
10104
|
-
|
|
10321
|
+
const candidate = "default";
|
|
10105
10322
|
if (!usedLabels.has(candidate)) return candidate;
|
|
10106
10323
|
let n = 2;
|
|
10107
10324
|
while (usedLabels.has(`key${n}`)) n++;
|
|
@@ -10420,7 +10637,7 @@ ${color.amber("?")} ${providerId} > `
|
|
|
10420
10637
|
if (!raw || raw === "b" || raw === "back" || raw === "q" || raw === "quit") {
|
|
10421
10638
|
return;
|
|
10422
10639
|
}
|
|
10423
|
-
const [verb, argRaw] = raw.split(/\s+/, 2);
|
|
10640
|
+
const [verb = "", argRaw = ""] = raw.split(/\s+/, 2);
|
|
10424
10641
|
const arg = argRaw ? Number.parseInt(argRaw, 10) : Number.NaN;
|
|
10425
10642
|
const handled = await dispatchAction(verb, arg, providerId, keys, cfg, deps);
|
|
10426
10643
|
if (handled === "exit") return;
|
|
@@ -11002,7 +11219,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
11002
11219
|
}
|
|
11003
11220
|
try {
|
|
11004
11221
|
await fsp4.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
11005
|
-
const probe =
|
|
11222
|
+
const probe = path9.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
11006
11223
|
await fsp4.writeFile(probe, "");
|
|
11007
11224
|
await fsp4.unlink(probe);
|
|
11008
11225
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -11105,8 +11322,8 @@ var exportCmd = async (args, deps) => {
|
|
|
11105
11322
|
return 1;
|
|
11106
11323
|
}
|
|
11107
11324
|
if (output) {
|
|
11108
|
-
await fsp4.mkdir(
|
|
11109
|
-
await fsp4.writeFile(
|
|
11325
|
+
await fsp4.mkdir(path9.dirname(path9.resolve(deps.cwd, output)), { recursive: true });
|
|
11326
|
+
await fsp4.writeFile(path9.resolve(deps.cwd, output), rendered, "utf8");
|
|
11110
11327
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
11111
11328
|
`);
|
|
11112
11329
|
} else {
|
|
@@ -11179,8 +11396,8 @@ var initCmd = async (_args, deps) => {
|
|
|
11179
11396
|
const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
|
|
11180
11397
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
11181
11398
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
11182
|
-
await fsp4.mkdir(
|
|
11183
|
-
const agentsFile =
|
|
11399
|
+
await fsp4.mkdir(path9.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
11400
|
+
const agentsFile = path9.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
11184
11401
|
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
11185
11402
|
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
11186
11403
|
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
@@ -11637,7 +11854,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
11637
11854
|
return 0;
|
|
11638
11855
|
};
|
|
11639
11856
|
var projectsCmd = async (_args, deps) => {
|
|
11640
|
-
const projectsRoot =
|
|
11857
|
+
const projectsRoot = path9.join(deps.paths.globalRoot, "projects");
|
|
11641
11858
|
try {
|
|
11642
11859
|
const entries = await fsp4.readdir(projectsRoot);
|
|
11643
11860
|
if (entries.length === 0) {
|
|
@@ -11647,7 +11864,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
11647
11864
|
for (const hash of entries) {
|
|
11648
11865
|
try {
|
|
11649
11866
|
const meta = JSON.parse(
|
|
11650
|
-
await fsp4.readFile(
|
|
11867
|
+
await fsp4.readFile(path9.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
11651
11868
|
);
|
|
11652
11869
|
deps.renderer.write(
|
|
11653
11870
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -12026,7 +12243,7 @@ async function listFleetRuns(deps) {
|
|
|
12026
12243
|
}
|
|
12027
12244
|
const runs = [];
|
|
12028
12245
|
for (const id of entries) {
|
|
12029
|
-
const runDir =
|
|
12246
|
+
const runDir = path9.join(deps.paths.projectSessions, id);
|
|
12030
12247
|
let stat4;
|
|
12031
12248
|
try {
|
|
12032
12249
|
stat4 = await fsp4.stat(runDir);
|
|
@@ -12039,17 +12256,17 @@ async function listFleetRuns(deps) {
|
|
|
12039
12256
|
let subagentCount = 0;
|
|
12040
12257
|
let subagentsDir;
|
|
12041
12258
|
try {
|
|
12042
|
-
await fsp4.access(
|
|
12259
|
+
await fsp4.access(path9.join(runDir, "fleet.json"));
|
|
12043
12260
|
manifest = true;
|
|
12044
12261
|
} catch {
|
|
12045
12262
|
}
|
|
12046
12263
|
try {
|
|
12047
|
-
await fsp4.access(
|
|
12264
|
+
await fsp4.access(path9.join(runDir, "checkpoint.json"));
|
|
12048
12265
|
checkpoint = true;
|
|
12049
12266
|
} catch {
|
|
12050
12267
|
}
|
|
12051
12268
|
try {
|
|
12052
|
-
subagentsDir =
|
|
12269
|
+
subagentsDir = path9.join(runDir, "subagents");
|
|
12053
12270
|
const files = await fsp4.readdir(subagentsDir);
|
|
12054
12271
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
12055
12272
|
} catch {
|
|
@@ -12078,7 +12295,7 @@ async function listFleetRuns(deps) {
|
|
|
12078
12295
|
return 0;
|
|
12079
12296
|
}
|
|
12080
12297
|
async function showFleetRun(runId, deps) {
|
|
12081
|
-
const runDir =
|
|
12298
|
+
const runDir = path9.join(deps.paths.projectSessions, runId);
|
|
12082
12299
|
let stat4;
|
|
12083
12300
|
try {
|
|
12084
12301
|
stat4 = await fsp4.stat(runDir);
|
|
@@ -12095,7 +12312,7 @@ async function showFleetRun(runId, deps) {
|
|
|
12095
12312
|
deps.renderer.write(color.bold(`
|
|
12096
12313
|
Fleet Run: ${runId}
|
|
12097
12314
|
`) + "\n");
|
|
12098
|
-
const manifestPath =
|
|
12315
|
+
const manifestPath = path9.join(runDir, "fleet.json");
|
|
12099
12316
|
let manifestData = null;
|
|
12100
12317
|
try {
|
|
12101
12318
|
manifestData = await fsp4.readFile(manifestPath, "utf8");
|
|
@@ -12111,7 +12328,7 @@ Fleet Run: ${runId}
|
|
|
12111
12328
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
12112
12329
|
`);
|
|
12113
12330
|
}
|
|
12114
|
-
const checkpointPath =
|
|
12331
|
+
const checkpointPath = path9.join(runDir, "checkpoint.json");
|
|
12115
12332
|
let checkpointData = null;
|
|
12116
12333
|
try {
|
|
12117
12334
|
checkpointData = await fsp4.readFile(checkpointPath, "utf8");
|
|
@@ -12158,7 +12375,7 @@ Fleet Run: ${runId}
|
|
|
12158
12375
|
} catch {
|
|
12159
12376
|
}
|
|
12160
12377
|
}
|
|
12161
|
-
const subagentsDir =
|
|
12378
|
+
const subagentsDir = path9.join(runDir, "subagents");
|
|
12162
12379
|
let subagentFiles = [];
|
|
12163
12380
|
try {
|
|
12164
12381
|
subagentFiles = await fsp4.readdir(subagentsDir);
|
|
@@ -12170,7 +12387,7 @@ Fleet Run: ${runId}
|
|
|
12170
12387
|
Subagent transcripts (${subagentFiles.length}):
|
|
12171
12388
|
`);
|
|
12172
12389
|
for (const f of subagentFiles.sort()) {
|
|
12173
|
-
const filePath =
|
|
12390
|
+
const filePath = path9.join(subagentsDir, f);
|
|
12174
12391
|
let size;
|
|
12175
12392
|
try {
|
|
12176
12393
|
const s = await fsp4.stat(filePath);
|
|
@@ -12187,7 +12404,7 @@ Fleet Run: ${runId}
|
|
|
12187
12404
|
${color.dim("\u25CB")} No subagent transcripts
|
|
12188
12405
|
`);
|
|
12189
12406
|
}
|
|
12190
|
-
const sharedDir =
|
|
12407
|
+
const sharedDir = path9.join(runDir, "shared");
|
|
12191
12408
|
try {
|
|
12192
12409
|
const files = await fsp4.readdir(sharedDir);
|
|
12193
12410
|
deps.renderer.write(`
|
|
@@ -12354,7 +12571,7 @@ function findSessionId(args) {
|
|
|
12354
12571
|
var rewindCmd = async (args, deps) => {
|
|
12355
12572
|
const flags = parseRewindFlags(args);
|
|
12356
12573
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
12357
|
-
const sessionsDir =
|
|
12574
|
+
const sessionsDir = path9.join(wpaths.globalRoot, "sessions");
|
|
12358
12575
|
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
12359
12576
|
let sessionId = findSessionId(args);
|
|
12360
12577
|
if (!sessionId) {
|
|
@@ -12594,10 +12811,10 @@ var auditCmd = async (args, deps) => {
|
|
|
12594
12811
|
return verify.ok ? 0 : 1;
|
|
12595
12812
|
};
|
|
12596
12813
|
async function listAudits(log, dir, deps) {
|
|
12597
|
-
const
|
|
12814
|
+
const fs29 = await import('fs/promises');
|
|
12598
12815
|
let entries;
|
|
12599
12816
|
try {
|
|
12600
|
-
entries = await
|
|
12817
|
+
entries = await fs29.readdir(dir);
|
|
12601
12818
|
} catch {
|
|
12602
12819
|
deps.renderer.write(
|
|
12603
12820
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -12650,6 +12867,750 @@ var skillsCmd = async (_args, deps) => {
|
|
|
12650
12867
|
);
|
|
12651
12868
|
return 0;
|
|
12652
12869
|
};
|
|
12870
|
+
var MODEL_PROFILES = [
|
|
12871
|
+
{ provider: "anthropic", pattern: /claude-opus/i, family: "Claude Opus", strengths: ["reasoning", "planning"], bestFor: ["planning", "security", "debugging"], costTier: "premium", speedTier: "slow" },
|
|
12872
|
+
{ provider: "anthropic", pattern: /claude-sonnet/i, family: "Claude Sonnet", strengths: ["coding", "balanced"], bestFor: ["coding", "general"], costTier: "standard", speedTier: "fast" },
|
|
12873
|
+
{ provider: "anthropic", pattern: /claude-haiku/i, family: "Claude Haiku", strengths: ["speed"], bestFor: ["lightweight", "docs"], avoidFor: ["planning"], costTier: "budget", speedTier: "fast" },
|
|
12874
|
+
{ provider: "openai", pattern: /gpt-5|o3|o4/i, family: "GPT-5/o3/o4", strengths: ["reasoning", "coding"], bestFor: ["planning", "coding", "debugging"], costTier: "premium", speedTier: "normal" },
|
|
12875
|
+
{ provider: "openai", pattern: /gpt-4/i, family: "GPT-4", strengths: ["coding"], bestFor: ["coding", "docs"], costTier: "standard", speedTier: "fast" },
|
|
12876
|
+
{ provider: "openai", pattern: /gpt-4o-mini/i, family: "GPT-4o Mini", strengths: ["speed"], bestFor: ["lightweight", "docs"], avoidFor: ["planning"], costTier: "budget", speedTier: "fast" },
|
|
12877
|
+
{ provider: "google", pattern: /gemini-(?:2\.5|3)/i, family: "Gemini 2.5/3", strengths: ["context", "coding"], bestFor: ["coding", "data"], costTier: "standard", speedTier: "normal" },
|
|
12878
|
+
{ provider: "google", pattern: /gemini.*flash/i, family: "Gemini Flash", strengths: ["speed"], bestFor: ["lightweight", "docs"], avoidFor: ["planning"], costTier: "budget", speedTier: "fast" },
|
|
12879
|
+
{ provider: "deepseek", pattern: /deepseek/i, family: "DeepSeek", strengths: ["coding", "cost-effective"], bestFor: ["coding", "general"], costTier: "standard", speedTier: "normal" }
|
|
12880
|
+
];
|
|
12881
|
+
function fmtTokens2(n) {
|
|
12882
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
12883
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
12884
|
+
return String(n);
|
|
12885
|
+
}
|
|
12886
|
+
function fmtMs(ms) {
|
|
12887
|
+
if (ms >= 1e3) return `${(ms / 1e3).toFixed(1)}s`;
|
|
12888
|
+
return `${ms}ms`;
|
|
12889
|
+
}
|
|
12890
|
+
function fmtPrice2(usdPer1M) {
|
|
12891
|
+
if (usdPer1M === void 0) return color.dim("?");
|
|
12892
|
+
if (usdPer1M >= 10) return `$${usdPer1M.toFixed(1)}`;
|
|
12893
|
+
return `$${usdPer1M.toFixed(2)}`;
|
|
12894
|
+
}
|
|
12895
|
+
function checkMark(ok) {
|
|
12896
|
+
return ok ? color.green("\u2713") : color.red("\u2717");
|
|
12897
|
+
}
|
|
12898
|
+
function costLabel(tier) {
|
|
12899
|
+
switch (tier) {
|
|
12900
|
+
case "premium":
|
|
12901
|
+
return color.red("$$$");
|
|
12902
|
+
case "standard":
|
|
12903
|
+
return color.amber("$$");
|
|
12904
|
+
case "budget":
|
|
12905
|
+
return color.green("$");
|
|
12906
|
+
default:
|
|
12907
|
+
return color.dim("?");
|
|
12908
|
+
}
|
|
12909
|
+
}
|
|
12910
|
+
function speedLabel(tier) {
|
|
12911
|
+
switch (tier) {
|
|
12912
|
+
case "fast":
|
|
12913
|
+
return color.green("\u26A1");
|
|
12914
|
+
case "normal":
|
|
12915
|
+
return color.amber("\u2192");
|
|
12916
|
+
case "slow":
|
|
12917
|
+
return color.red("\u{1F422}");
|
|
12918
|
+
default:
|
|
12919
|
+
return color.dim("?");
|
|
12920
|
+
}
|
|
12921
|
+
}
|
|
12922
|
+
function scoreBar(score, max) {
|
|
12923
|
+
const pct2 = Math.min(1, Math.max(0, score / max));
|
|
12924
|
+
const filled = Math.round(pct2 * 10);
|
|
12925
|
+
const bar = color.green("\u2588".repeat(filled)) + color.dim("\u2591".repeat(10 - filled));
|
|
12926
|
+
return `${bar} ${score}/${max}`;
|
|
12927
|
+
}
|
|
12928
|
+
var ROLE_CATEGORY = {
|
|
12929
|
+
"security-scanner": "security",
|
|
12930
|
+
"security-reviewer": "security",
|
|
12931
|
+
"bug-hunter": "debugging",
|
|
12932
|
+
debugger: "debugging",
|
|
12933
|
+
tracer: "debugging",
|
|
12934
|
+
planner: "planning",
|
|
12935
|
+
architect: "planning",
|
|
12936
|
+
"refactor-planner": "planning",
|
|
12937
|
+
test: "testing",
|
|
12938
|
+
e2e: "testing",
|
|
12939
|
+
document: "docs",
|
|
12940
|
+
simplifier: "docs",
|
|
12941
|
+
"code-reviewer": "review",
|
|
12942
|
+
critic: "review",
|
|
12943
|
+
executor: "coding",
|
|
12944
|
+
refactor: "refactoring",
|
|
12945
|
+
migration: "coding",
|
|
12946
|
+
frontend: "frontend",
|
|
12947
|
+
backend: "backend",
|
|
12948
|
+
api: "backend",
|
|
12949
|
+
auth: "backend",
|
|
12950
|
+
designer: "frontend",
|
|
12951
|
+
analyst: "data",
|
|
12952
|
+
data: "data",
|
|
12953
|
+
database: "data",
|
|
12954
|
+
explore: "planning",
|
|
12955
|
+
search: "planning",
|
|
12956
|
+
researcher: "planning"
|
|
12957
|
+
};
|
|
12958
|
+
function findProfile(pid, mid) {
|
|
12959
|
+
for (const p of MODEL_PROFILES) {
|
|
12960
|
+
if (p.provider === pid && p.pattern.test(mid)) return p;
|
|
12961
|
+
}
|
|
12962
|
+
return void 0;
|
|
12963
|
+
}
|
|
12964
|
+
function scoreModel(pid, mid, category, ctxWindow) {
|
|
12965
|
+
const profile = findProfile(pid, mid);
|
|
12966
|
+
let score = 50;
|
|
12967
|
+
if (profile) {
|
|
12968
|
+
if (profile.bestFor.includes(category)) score += 35;
|
|
12969
|
+
if (profile.avoidFor?.includes(category)) score -= 50;
|
|
12970
|
+
if (category === "planning" && profile.costTier === "premium") score += 15;
|
|
12971
|
+
if (profile.speedTier === "slow" && category === "planning") score += 10;
|
|
12972
|
+
if (profile.costTier === "budget" && category !== "planning" && category !== "security") score += 10;
|
|
12973
|
+
}
|
|
12974
|
+
if (ctxWindow > 2e5) score += 10;
|
|
12975
|
+
else if (ctxWindow > 1e5) score += 5;
|
|
12976
|
+
else if (ctxWindow > 32e3) score += 2;
|
|
12977
|
+
return { score, profile };
|
|
12978
|
+
}
|
|
12979
|
+
function rankModels(providers, hasKey, category, limit) {
|
|
12980
|
+
const candidates = [];
|
|
12981
|
+
for (const prov of providers) {
|
|
12982
|
+
if (!hasKey(prov.id)) continue;
|
|
12983
|
+
for (const m of prov.models ?? []) {
|
|
12984
|
+
const ctxWindow = m.capabilities?.contextWindow ?? 0;
|
|
12985
|
+
const { score, profile } = scoreModel(prov.id, m.id, category, ctxWindow);
|
|
12986
|
+
if (score > 0) {
|
|
12987
|
+
candidates.push({
|
|
12988
|
+
provider: prov.id,
|
|
12989
|
+
model: m.id,
|
|
12990
|
+
profile,
|
|
12991
|
+
score,
|
|
12992
|
+
ctxWindow,
|
|
12993
|
+
maxOutput: m.capabilities?.maxOutputTokens ?? 0,
|
|
12994
|
+
inputPrice: m.pricing?.input,
|
|
12995
|
+
outputPrice: m.pricing?.output
|
|
12996
|
+
});
|
|
12997
|
+
}
|
|
12998
|
+
}
|
|
12999
|
+
}
|
|
13000
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
13001
|
+
return candidates.slice(0, limit);
|
|
13002
|
+
}
|
|
13003
|
+
var EVAL_TASKS = {
|
|
13004
|
+
coding: {
|
|
13005
|
+
label: "Code Generation",
|
|
13006
|
+
prompt: "Write a TypeScript function parseCSV(input: string): { headers: string[]; rows: string[][] } that handles quoted fields, escaped quotes, and empty lines. Return an error string on malformed input. Keep under 40 lines."
|
|
13007
|
+
},
|
|
13008
|
+
planning: {
|
|
13009
|
+
label: "Architecture Planning",
|
|
13010
|
+
prompt: "Design the folder structure and key interfaces for a monorepo CLI tool with slash commands, model routing, subagent spawning, and config persistence. List packages, their responsibilities, and the 5 most important TypeScript interfaces."
|
|
13011
|
+
},
|
|
13012
|
+
security: {
|
|
13013
|
+
label: "Vulnerability Detection",
|
|
13014
|
+
prompt: 'Review this code for security issues:\n```ts\napp.get("/api/user", (req, res) => {\n const id = req.query.id;\n const user = db.query("SELECT * FROM users WHERE id = " + id);\n res.json(user);\n});\n\napp.post("/api/run", (req, res) => {\n const { cmd } = req.body;\n exec("echo " + cmd, (err, stdout) => res.send(stdout));\n});\n```\nList every vulnerability, its severity (critical/high/medium), and the exact fix.'
|
|
13015
|
+
},
|
|
13016
|
+
debugging: {
|
|
13017
|
+
label: "Bug Diagnosis",
|
|
13018
|
+
prompt: 'This async function has 2 bugs. Find and fix both:\n```ts\nasync function processBatch(items: string[]) {\n const results = [];\n for (const item of items) {\n const result = await fetch("https://api.example.com/" + item);\n results.push(result);\n }\n return results.map(r => r.json());\n}\n```\nExplain what each bug is, why it fails, and write the corrected version.'
|
|
13019
|
+
},
|
|
13020
|
+
testing: {
|
|
13021
|
+
label: "Test Authoring",
|
|
13022
|
+
prompt: 'Write vitest test cases for this deepMerge function:\n```ts\nfunction deepMerge(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n const merged = { ...base };\n for (const [key, val] of Object.entries(overrides)) {\n if (val === null) { delete merged[key]; continue; }\n if (typeof val === "object" && !Array.isArray(val) && typeof merged[key] === "object" && !Array.isArray(merged[key])) {\n merged[key] = deepMerge(merged[key] as Record<string, unknown>, val as Record<string, unknown>);\n } else { merged[key] = val; }\n }\n return merged;\n}\n```\nCover: happy path, edge cases, and error conditions.'
|
|
13023
|
+
},
|
|
13024
|
+
docs: {
|
|
13025
|
+
label: "Documentation",
|
|
13026
|
+
prompt: "Write TSDoc comments for this RateLimiter interface. Include @param, @returns, @throws, and @example for each method:\n```ts\ninterface RateLimiter {\n tryAcquire(key: string, maxPerWindow: number, windowMs: number): Promise<boolean>;\n getRemaining(key: string): Promise<number>;\n reset(key: string): Promise<void>;\n}\n```"
|
|
13027
|
+
},
|
|
13028
|
+
review: {
|
|
13029
|
+
label: "Code Review",
|
|
13030
|
+
prompt: 'Review this PR change:\n```diff\n async function loadConfig(path: string) {\n- const raw = await fs.readFile(path, "utf8");\n- return JSON.parse(raw);\n+ const raw = await fs.readFile(path);\n+ const config = JSON.parse(raw);\n+ process.env.API_KEY = config.apiKey;\n+ return config;\n }\n```\nList issues by severity (blocking / should-fix / nit) and explain your reasoning.'
|
|
13031
|
+
},
|
|
13032
|
+
refactoring: {
|
|
13033
|
+
label: "Refactoring",
|
|
13034
|
+
prompt: 'Refactor this nested condition into a cleaner pattern:\n```ts\nfunction getDiscount(user: { type: string; years: number; coupon?: string }): number {\n if (user.type === "premium") {\n if (user.years > 5) {\n if (user.coupon === "BLACKFRIDAY") return 0.5;\n return 0.3;\n }\n return 0.2;\n }\n if (user.type === "standard") {\n if (user.years > 3) return 0.15;\n return 0.1;\n }\n return 0;\n}\n```\nShow your refactored code and explain why your approach is cleaner.'
|
|
13035
|
+
}
|
|
13036
|
+
};
|
|
13037
|
+
var EVAL_CATEGORIES = Object.keys(EVAL_TASKS);
|
|
13038
|
+
function roleCat(role) {
|
|
13039
|
+
return ROLE_CATEGORY[role] ?? "general";
|
|
13040
|
+
}
|
|
13041
|
+
function createProviderForId(providerId, cfg) {
|
|
13042
|
+
const savedCfg = cfg.providers?.[providerId];
|
|
13043
|
+
const resolvedProviderId = savedCfg?.type ?? providerId;
|
|
13044
|
+
const cfgWithType = Object.assign(
|
|
13045
|
+
{ type: resolvedProviderId },
|
|
13046
|
+
savedCfg ?? { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }
|
|
13047
|
+
);
|
|
13048
|
+
try {
|
|
13049
|
+
return makeProviderFromConfig(resolvedProviderId, cfgWithType);
|
|
13050
|
+
} catch {
|
|
13051
|
+
return void 0;
|
|
13052
|
+
}
|
|
13053
|
+
}
|
|
13054
|
+
async function rankResponses(provider, leaderModel, taskPrompt, responses) {
|
|
13055
|
+
const labelToIdx = /* @__PURE__ */ new Map();
|
|
13056
|
+
const responseBlock = responses.map((r, i) => {
|
|
13057
|
+
const label = String.fromCharCode(65 + i);
|
|
13058
|
+
labelToIdx.set(label, i);
|
|
13059
|
+
return `=== Response ${label} ===
|
|
13060
|
+
${r.text.slice(0, 800)}`;
|
|
13061
|
+
}).join("\n\n");
|
|
13062
|
+
const rankingPrompt = `Rank these responses from BEST (1) to WORST.
|
|
13063
|
+
|
|
13064
|
+
TASK:
|
|
13065
|
+
${taskPrompt.slice(0, 600)}
|
|
13066
|
+
|
|
13067
|
+
RESPONSES:
|
|
13068
|
+
${responseBlock}
|
|
13069
|
+
|
|
13070
|
+
Output ONLY a ranked list, one per line:
|
|
13071
|
+
1. Response X \u2014 brief reason
|
|
13072
|
+
2. Response Y \u2014 brief reason`;
|
|
13073
|
+
try {
|
|
13074
|
+
const resp = await provider.complete(
|
|
13075
|
+
{
|
|
13076
|
+
model: leaderModel,
|
|
13077
|
+
system: [{ type: "text", text: "You are an expert evaluator. Rank responses concisely. Output ONLY the ranked list." }],
|
|
13078
|
+
messages: [{ role: "user", content: [{ type: "text", text: rankingPrompt }] }],
|
|
13079
|
+
maxTokens: 400
|
|
13080
|
+
},
|
|
13081
|
+
{ signal: AbortSignal.timeout(3e4) }
|
|
13082
|
+
);
|
|
13083
|
+
const text = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
|
|
13084
|
+
const rankings = [];
|
|
13085
|
+
for (const line of text.split("\n")) {
|
|
13086
|
+
const m = line.match(/^\s*(\d+)[\.\)]\s*Response\s+([A-Z])/i);
|
|
13087
|
+
if (m) {
|
|
13088
|
+
const label = m[2].toUpperCase();
|
|
13089
|
+
const idx = labelToIdx.get(label);
|
|
13090
|
+
if (idx !== void 0 && !rankings.includes(idx)) {
|
|
13091
|
+
rankings.push(idx);
|
|
13092
|
+
}
|
|
13093
|
+
}
|
|
13094
|
+
}
|
|
13095
|
+
return rankings.length > 0 ? rankings : responses.map((_, i) => i);
|
|
13096
|
+
} catch {
|
|
13097
|
+
return responses.map((_, i) => i);
|
|
13098
|
+
}
|
|
13099
|
+
}
|
|
13100
|
+
async function readProviders(cachePath2) {
|
|
13101
|
+
if (!cachePath2) {
|
|
13102
|
+
return `${color.red("Models cache not available")}.`;
|
|
13103
|
+
}
|
|
13104
|
+
try {
|
|
13105
|
+
const raw = await fsp4.readFile(cachePath2, "utf8");
|
|
13106
|
+
const parsed = JSON.parse(raw);
|
|
13107
|
+
const payload = parsed.payload ?? parsed;
|
|
13108
|
+
return Object.entries(payload).map(([id, p]) => ({
|
|
13109
|
+
id: p.id ?? id,
|
|
13110
|
+
name: p.name ?? id,
|
|
13111
|
+
family: p.npm ?? id,
|
|
13112
|
+
models: Object.values(p.models ?? {}).map((m) => ({
|
|
13113
|
+
id: m.id,
|
|
13114
|
+
name: m.name,
|
|
13115
|
+
capabilities: {
|
|
13116
|
+
contextWindow: m.limit?.context,
|
|
13117
|
+
maxOutputTokens: m.limit?.output
|
|
13118
|
+
},
|
|
13119
|
+
pricing: m.cost
|
|
13120
|
+
}))
|
|
13121
|
+
}));
|
|
13122
|
+
} catch {
|
|
13123
|
+
return `${color.amber("Models cache not available")}. Run wstack sync-models.`;
|
|
13124
|
+
}
|
|
13125
|
+
}
|
|
13126
|
+
function checkHasKey(pid, config) {
|
|
13127
|
+
if (pid === config.provider && config.provider) return true;
|
|
13128
|
+
const pc = config.providers?.[pid];
|
|
13129
|
+
if (!pc) return false;
|
|
13130
|
+
if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
|
|
13131
|
+
if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
|
|
13132
|
+
return false;
|
|
13133
|
+
}
|
|
13134
|
+
var modeldiagCmd = async (args, deps) => {
|
|
13135
|
+
const sub = args[0]?.toLowerCase() || "full";
|
|
13136
|
+
const cacheResult = await readProviders(deps.paths.modelsCache);
|
|
13137
|
+
if (typeof cacheResult === "string") {
|
|
13138
|
+
deps.renderer.write(`${cacheResult}
|
|
13139
|
+
`);
|
|
13140
|
+
return cacheResult.includes(color.red("")) ? 1 : 0;
|
|
13141
|
+
}
|
|
13142
|
+
const providers = cacheResult;
|
|
13143
|
+
const config = deps.config;
|
|
13144
|
+
const modelMatrix = config.modelMatrix ?? {};
|
|
13145
|
+
function hasKey(pid) {
|
|
13146
|
+
return checkHasKey(pid, config);
|
|
13147
|
+
}
|
|
13148
|
+
function writeLine(line = "") {
|
|
13149
|
+
deps.renderer.write(`${line}
|
|
13150
|
+
`);
|
|
13151
|
+
}
|
|
13152
|
+
if (sub === "keys") {
|
|
13153
|
+
writeLine(`${color.bold("API Key Status")}`);
|
|
13154
|
+
writeLine();
|
|
13155
|
+
for (const prov of providers) {
|
|
13156
|
+
const k = hasKey(prov.id);
|
|
13157
|
+
writeLine(` ${checkMark(k)} ${color.bold(prov.id.padEnd(18))} ${color.dim(prov.name)}`);
|
|
13158
|
+
}
|
|
13159
|
+
writeLine();
|
|
13160
|
+
writeLine(`${color.dim(`Leader: ${config.provider}/${config.model}`)}`);
|
|
13161
|
+
return 0;
|
|
13162
|
+
}
|
|
13163
|
+
if (sub === "caps") {
|
|
13164
|
+
writeLine(`${color.bold("Model Capabilities")} ${color.dim("\u2014 matched to known profiles")}`);
|
|
13165
|
+
writeLine();
|
|
13166
|
+
for (const prov of providers) {
|
|
13167
|
+
if (!hasKey(prov.id)) continue;
|
|
13168
|
+
writeLine(` ${color.bold(prov.id)} ${color.dim(`(${prov.name})`)}`);
|
|
13169
|
+
const tiers = { premium: [], standard: [], budget: [], unknown: [] };
|
|
13170
|
+
for (const m of prov.models ?? []) {
|
|
13171
|
+
const profile = findProfile(prov.id, m.id);
|
|
13172
|
+
tiers[profile?.costTier ?? "unknown"].push(m);
|
|
13173
|
+
}
|
|
13174
|
+
for (const tier of ["premium", "standard", "budget", "unknown"]) {
|
|
13175
|
+
const tierModels = tiers[tier];
|
|
13176
|
+
if (tierModels.length === 0) continue;
|
|
13177
|
+
const label = tier === "unknown" ? color.dim("unmatched") : `${costLabel(tier)} ${tier}`;
|
|
13178
|
+
writeLine(` ${label}`);
|
|
13179
|
+
for (const m of tierModels) {
|
|
13180
|
+
const cap = m.capabilities;
|
|
13181
|
+
const ctx = cap?.contextWindow ?? 0;
|
|
13182
|
+
const maxOut = cap?.maxOutputTokens ?? 0;
|
|
13183
|
+
const profile = findProfile(prov.id, m.id);
|
|
13184
|
+
const family = profile ? `${speedLabel(profile.speedTier)} ${color.green(profile.family)}` : color.dim("no profile match");
|
|
13185
|
+
const pricing = m.pricing ? `${color.dim("in")}${fmtPrice2(m.pricing.input)} ${color.dim("out")}${fmtPrice2(m.pricing.output)}` : color.dim("pricing ?");
|
|
13186
|
+
writeLine(
|
|
13187
|
+
` ${color.cyan(m.id.padEnd(34))}${ctx > 0 ? `ctx ${fmtTokens2(ctx).padEnd(6)}` : color.dim("ctx ? ")}${maxOut > 0 ? `out ${fmtTokens2(maxOut).padEnd(6)}` : " "}${family} ${pricing}`
|
|
13188
|
+
);
|
|
13189
|
+
}
|
|
13190
|
+
}
|
|
13191
|
+
writeLine();
|
|
13192
|
+
}
|
|
13193
|
+
writeLine(color.dim("Prices in USD per 1M tokens (input/output). ctx = context window, out = max output."));
|
|
13194
|
+
return 0;
|
|
13195
|
+
}
|
|
13196
|
+
async function renderSuggest() {
|
|
13197
|
+
writeLine();
|
|
13198
|
+
writeLine(`${color.bold("Agent \u2192 Model Suggestions")} ${color.amber("(heuristic \u2014 untested)")}`);
|
|
13199
|
+
writeLine(color.dim('These are profile-based best guesses. Test them with wstack modeldiag bench <role> "<prompt>".'));
|
|
13200
|
+
writeLine();
|
|
13201
|
+
const keyedProviders = providers.filter((p) => hasKey(p.id));
|
|
13202
|
+
if (keyedProviders.length === 0) {
|
|
13203
|
+
writeLine(` ${color.amber("No providers have API keys configured. Add keys with wstack auth.")}`);
|
|
13204
|
+
} else {
|
|
13205
|
+
const roles = [
|
|
13206
|
+
"security-scanner",
|
|
13207
|
+
"bug-hunter",
|
|
13208
|
+
"planner",
|
|
13209
|
+
"architect",
|
|
13210
|
+
"refactor-planner",
|
|
13211
|
+
"test",
|
|
13212
|
+
"document",
|
|
13213
|
+
"code-reviewer",
|
|
13214
|
+
"executor",
|
|
13215
|
+
"debugger"
|
|
13216
|
+
];
|
|
13217
|
+
for (const role of roles) {
|
|
13218
|
+
if (modelMatrix[role]) {
|
|
13219
|
+
const entry = modelMatrix[role];
|
|
13220
|
+
const p = entry.provider ?? config.provider;
|
|
13221
|
+
writeLine(` ${color.dim(role.padEnd(20))} \u2192 ${color.cyan(`${p}/${entry.model}`)} ${color.dim("(user-configured)")}`);
|
|
13222
|
+
continue;
|
|
13223
|
+
}
|
|
13224
|
+
const cat = roleCat(role);
|
|
13225
|
+
const ranked = rankModels(providers, hasKey, cat, 3);
|
|
13226
|
+
if (ranked.length === 0) {
|
|
13227
|
+
writeLine(` ${color.dim(role.padEnd(20))} \u2192 ${color.dim("no candidates")}`);
|
|
13228
|
+
continue;
|
|
13229
|
+
}
|
|
13230
|
+
const best = ranked[0];
|
|
13231
|
+
const family = best.profile ? ` ${color.dim(`(${best.profile.family})`)}` : "";
|
|
13232
|
+
const bar = scoreBar(best.score, 110);
|
|
13233
|
+
writeLine(
|
|
13234
|
+
` ${color.amber(role.padEnd(20))} \u2192 ${color.cyan(`${best.provider}/${best.model}`)}${family}`
|
|
13235
|
+
);
|
|
13236
|
+
writeLine(` ${" ".repeat(22)} ${bar} ${color.dim(cat)}`);
|
|
13237
|
+
if (ranked.length > 1 && ranked[1].score >= best.score - 15) {
|
|
13238
|
+
for (const alt of ranked.slice(1)) {
|
|
13239
|
+
const af = alt.profile ? ` (${alt.profile.family})` : "";
|
|
13240
|
+
writeLine(` ${" ".repeat(22)} ${color.dim(`${alt.provider}/${alt.model}${af} score ${alt.score}`)}`);
|
|
13241
|
+
}
|
|
13242
|
+
}
|
|
13243
|
+
}
|
|
13244
|
+
writeLine();
|
|
13245
|
+
writeLine(` ${color.bold("leader".padEnd(20))} \u2192 ${color.cyan(`${config.provider}/${config.model}`)}`);
|
|
13246
|
+
}
|
|
13247
|
+
}
|
|
13248
|
+
if (sub === "suggest") {
|
|
13249
|
+
await renderSuggest();
|
|
13250
|
+
writeLine();
|
|
13251
|
+
writeLine(color.dim("Pin a suggestion: wstack setmodel set <role> <provider>/<model>"));
|
|
13252
|
+
writeLine(color.dim('Test candidates: wstack modeldiag bench <role> "<test prompt>"'));
|
|
13253
|
+
return 0;
|
|
13254
|
+
}
|
|
13255
|
+
if (sub === "test") {
|
|
13256
|
+
writeLine(`${color.bold("Connectivity Test")}`);
|
|
13257
|
+
writeLine();
|
|
13258
|
+
const keyed = providers.filter((p) => hasKey(p.id));
|
|
13259
|
+
if (keyed.length === 0) {
|
|
13260
|
+
writeLine(` ${color.amber("No providers have API keys. Add keys with wstack auth.")}`);
|
|
13261
|
+
return 0;
|
|
13262
|
+
}
|
|
13263
|
+
for (const prov of keyed) {
|
|
13264
|
+
writeLine(` ${color.cyan("\u27F3")} ${prov.id}... ${color.dim("(capability scan, no API call)")}`);
|
|
13265
|
+
const profile = findProfile(prov.id, config.model ?? "");
|
|
13266
|
+
const firstModel = prov.models?.[0]?.id ?? config.model ?? "?";
|
|
13267
|
+
const cap = prov.models?.[0]?.capabilities;
|
|
13268
|
+
const ctx = cap?.contextWindow ?? 0;
|
|
13269
|
+
writeLine(` ${checkMark(true)} provider: ${prov.id}`);
|
|
13270
|
+
writeLine(` ${checkMark(ctx > 0)} context: ${ctx > 0 ? fmtTokens2(ctx) : "unknown"}`);
|
|
13271
|
+
writeLine(` ${checkMark(!!profile)} profile: ${profile?.family ?? "no match"}`);
|
|
13272
|
+
writeLine(` model: ${color.cyan(firstModel)}`);
|
|
13273
|
+
writeLine();
|
|
13274
|
+
}
|
|
13275
|
+
writeLine(color.dim("Full API connectivity test requires an active session (costs tokens)."));
|
|
13276
|
+
writeLine(color.dim('Use wstack modeldiag bench <role> "<prompt>" to test models with real API calls.'));
|
|
13277
|
+
return 0;
|
|
13278
|
+
}
|
|
13279
|
+
if (sub === "bench") {
|
|
13280
|
+
const benchArgs = args.slice(1);
|
|
13281
|
+
if (benchArgs.length < 2) {
|
|
13282
|
+
writeLine(`${color.amber("Usage:")} wstack modeldiag bench <role> "<test prompt>" [--providers=p1,p2]`);
|
|
13283
|
+
writeLine();
|
|
13284
|
+
writeLine(color.dim('Example: wstack modeldiag bench verify "Write a function that checks if a string is a palindrome"'));
|
|
13285
|
+
writeLine(color.dim("Tests the top 5 candidate models for the role with your prompt and reports results."));
|
|
13286
|
+
writeLine(color.dim("Add --providers=anthropic,google to test across multiple providers."));
|
|
13287
|
+
return 0;
|
|
13288
|
+
}
|
|
13289
|
+
const providersEqIdx = benchArgs.findIndex((a) => a.startsWith("--providers="));
|
|
13290
|
+
let providerFilter;
|
|
13291
|
+
if (providersEqIdx >= 0) {
|
|
13292
|
+
const rawFilter = benchArgs[providersEqIdx]?.replace("--providers=", "").split(",");
|
|
13293
|
+
if (rawFilter) {
|
|
13294
|
+
providerFilter = rawFilter.map((s) => s.trim()).filter(Boolean);
|
|
13295
|
+
benchArgs.splice(providersEqIdx, 1);
|
|
13296
|
+
}
|
|
13297
|
+
}
|
|
13298
|
+
const benchRole = benchArgs[0];
|
|
13299
|
+
if (!benchRole) {
|
|
13300
|
+
writeLine(`${color.amber("No benchmark role specified")}. Usage: wstack diag bench <role> [prompt]`);
|
|
13301
|
+
return 1;
|
|
13302
|
+
}
|
|
13303
|
+
const benchPrompt = benchArgs.slice(1).join(" ");
|
|
13304
|
+
const cat = roleCat(benchRole);
|
|
13305
|
+
const candidates = rankModels(providers, hasKey, cat, 5);
|
|
13306
|
+
if (candidates.length === 0) {
|
|
13307
|
+
writeLine(`${color.amber("No candidate models found")} for role "${benchRole}" (category: ${cat}).`);
|
|
13308
|
+
return 0;
|
|
13309
|
+
}
|
|
13310
|
+
let targetCandidates;
|
|
13311
|
+
if (providerFilter && providerFilter.length > 0) {
|
|
13312
|
+
targetCandidates = candidates.filter((c) => providerFilter.includes(c.provider));
|
|
13313
|
+
if (targetCandidates.length === 0) {
|
|
13314
|
+
writeLine(`${color.amber("No candidates match the specified providers")}: ${providerFilter.join(", ")}`);
|
|
13315
|
+
writeLine(`Candidate providers: ${[...new Set(candidates.map((c) => c.provider))].join(", ")}`);
|
|
13316
|
+
return 0;
|
|
13317
|
+
}
|
|
13318
|
+
} else {
|
|
13319
|
+
targetCandidates = candidates;
|
|
13320
|
+
}
|
|
13321
|
+
writeLine(`${color.bold("Model Benchmark")} \u2014 ${color.amber(benchRole)} ${color.dim(`(category: ${cat})`)}`);
|
|
13322
|
+
writeLine(`${color.dim("Prompt:")} "${benchPrompt.slice(0, 120)}${benchPrompt.length > 120 ? "\u2026" : ""}"`);
|
|
13323
|
+
writeLine();
|
|
13324
|
+
writeLine(
|
|
13325
|
+
` ${color.dim("# model".padEnd(52))} ${color.dim("score".padEnd(12))} ${color.dim("latency".padEnd(10))} ${color.dim("tokens".padEnd(14))} ${color.dim("first line")}`
|
|
13326
|
+
);
|
|
13327
|
+
writeLine(` ${color.dim("\u2500".repeat(108))}`);
|
|
13328
|
+
const providerInstances = /* @__PURE__ */ new Map();
|
|
13329
|
+
for (const pid of [...new Set(targetCandidates.map((c) => c.provider))]) {
|
|
13330
|
+
const prov = createProviderForId(pid, config);
|
|
13331
|
+
if (prov) providerInstances.set(pid, prov);
|
|
13332
|
+
}
|
|
13333
|
+
let idx = 0;
|
|
13334
|
+
for (const c of targetCandidates.slice(0, 20)) {
|
|
13335
|
+
idx++;
|
|
13336
|
+
const label = `${idx}`.padStart(2);
|
|
13337
|
+
const modelKey = `${c.provider}/${c.model}`;
|
|
13338
|
+
const prov = providerInstances.get(c.provider);
|
|
13339
|
+
if (!prov) {
|
|
13340
|
+
writeLine(
|
|
13341
|
+
` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("NO PROVIDER")}`
|
|
13342
|
+
);
|
|
13343
|
+
continue;
|
|
13344
|
+
}
|
|
13345
|
+
try {
|
|
13346
|
+
const start = Date.now();
|
|
13347
|
+
const resp = await prov.complete(
|
|
13348
|
+
{
|
|
13349
|
+
model: c.model,
|
|
13350
|
+
messages: [{ role: "user", content: [{ type: "text", text: benchPrompt }] }],
|
|
13351
|
+
maxTokens: 256
|
|
13352
|
+
},
|
|
13353
|
+
{ signal: AbortSignal.timeout(3e4) }
|
|
13354
|
+
);
|
|
13355
|
+
const latency = Date.now() - start;
|
|
13356
|
+
const firstText = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
|
|
13357
|
+
const firstLineClean = firstText.replace(/\n/g, " ").slice(0, 80) || color.dim("(empty)");
|
|
13358
|
+
const provColor = c.provider === config.provider ? color.green : color.cyan;
|
|
13359
|
+
const usage = resp.usage;
|
|
13360
|
+
writeLine(
|
|
13361
|
+
` ${label} ${provColor(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.amber(fmtMs(latency).padEnd(8))} ${color.dim(`in${usage?.input ?? "?"}/out${usage?.output ?? "?"}`.padEnd(12))} ${firstLineClean}`
|
|
13362
|
+
);
|
|
13363
|
+
} catch (err) {
|
|
13364
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13365
|
+
writeLine(
|
|
13366
|
+
` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("FAILED")} ${color.dim(errMsg.slice(0, 40))}`
|
|
13367
|
+
);
|
|
13368
|
+
}
|
|
13369
|
+
}
|
|
13370
|
+
const testedProviders = [...new Set(targetCandidates.map((c) => c.provider))];
|
|
13371
|
+
writeLine();
|
|
13372
|
+
writeLine(
|
|
13373
|
+
color.dim(`Tested ${idx} model(s) across ${testedProviders.length} provider(s): ${testedProviders.join(", ")}.`)
|
|
13374
|
+
);
|
|
13375
|
+
writeLine(color.dim("Pin the best: wstack setmodel set <role> <provider>/<model>"));
|
|
13376
|
+
return 0;
|
|
13377
|
+
}
|
|
13378
|
+
if (sub === "eval" || sub === "evall") {
|
|
13379
|
+
const evalArgs = args.slice(1);
|
|
13380
|
+
const providersEq = evalArgs.find((a) => a.startsWith("--providers="));
|
|
13381
|
+
const providerFilter = providersEq ? providersEq.replace("--providers=", "").split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
13382
|
+
const maxEq = evalArgs.find((a) => a.startsWith("--max="));
|
|
13383
|
+
const maxModels = maxEq ? Math.max(1, parseInt(maxEq.replace("--max=", ""), 10) || 2) : 2;
|
|
13384
|
+
const quick = evalArgs.includes("--quick");
|
|
13385
|
+
const modelsPerCat = quick ? 1 : maxModels;
|
|
13386
|
+
const roleFilter = evalArgs.find((a) => !a.startsWith("--"));
|
|
13387
|
+
const targetCategories = roleFilter ? EVAL_CATEGORIES.includes(roleCat(roleFilter)) ? [roleCat(roleFilter)] : [] : EVAL_CATEGORIES;
|
|
13388
|
+
if (targetCategories.length === 0 && roleFilter) {
|
|
13389
|
+
writeLine(`${color.amber("Unknown role/category")}: "${roleFilter}". Try: ${EVAL_CATEGORIES.join(", ")}`);
|
|
13390
|
+
return 1;
|
|
13391
|
+
}
|
|
13392
|
+
const keyedProviderIds2 = providers.filter((p) => hasKey(p.id)).map((p) => p.id);
|
|
13393
|
+
let targetProviderIds;
|
|
13394
|
+
if (providerFilter && providerFilter.length > 0) {
|
|
13395
|
+
const unknown = providerFilter.filter((pid) => !keyedProviderIds2.includes(pid));
|
|
13396
|
+
targetProviderIds = providerFilter.filter((pid) => keyedProviderIds2.includes(pid));
|
|
13397
|
+
if (targetProviderIds.length === 0) {
|
|
13398
|
+
const noKeyMsg = unknown.length > 0 ? `None of the specified providers (${unknown.join(", ")}) have API keys. Add keys with wstack auth.` : "None of the specified providers have API keys configured.";
|
|
13399
|
+
writeLine(`${color.amber(noKeyMsg)}`);
|
|
13400
|
+
return 0;
|
|
13401
|
+
}
|
|
13402
|
+
} else if (keyedProviderIds2.length === 0) {
|
|
13403
|
+
writeLine(`${color.amber("No providers have API keys. Add keys with wstack auth.")}`);
|
|
13404
|
+
return 0;
|
|
13405
|
+
} else if (keyedProviderIds2.length === 1) {
|
|
13406
|
+
targetProviderIds = keyedProviderIds2;
|
|
13407
|
+
} else {
|
|
13408
|
+
const providerList = keyedProviderIds2.map((pid, i) => {
|
|
13409
|
+
const info = providers.find((p) => p.id === pid);
|
|
13410
|
+
return ` ${color.cyan(String(i + 1))}) ${color.bold(pid.padEnd(16))} ${color.dim(info?.name ?? "")}`;
|
|
13411
|
+
}).join("\n");
|
|
13412
|
+
deps.renderer.write(`
|
|
13413
|
+
${color.bold("Select providers to evaluate")}
|
|
13414
|
+
|
|
13415
|
+
${providerList}
|
|
13416
|
+
|
|
13417
|
+
${color.dim('Enter numbers or provider IDs (comma-separated, or "all"):')}
|
|
13418
|
+
`);
|
|
13419
|
+
const input = await deps.reader.readLine(" > ");
|
|
13420
|
+
const selected = input.trim().toLowerCase();
|
|
13421
|
+
if (selected === "" || selected === "all") {
|
|
13422
|
+
targetProviderIds = keyedProviderIds2;
|
|
13423
|
+
} else {
|
|
13424
|
+
targetProviderIds = [];
|
|
13425
|
+
for (const part of selected.split(",").map((s) => s.trim())) {
|
|
13426
|
+
const idx = parseInt(part, 10);
|
|
13427
|
+
if (idx >= 1 && idx <= keyedProviderIds2.length) {
|
|
13428
|
+
const pid = keyedProviderIds2[idx - 1];
|
|
13429
|
+
if (!targetProviderIds.includes(pid)) targetProviderIds.push(pid);
|
|
13430
|
+
} else if (keyedProviderIds2.includes(part)) {
|
|
13431
|
+
if (!targetProviderIds.includes(part)) targetProviderIds.push(part);
|
|
13432
|
+
}
|
|
13433
|
+
}
|
|
13434
|
+
}
|
|
13435
|
+
if (targetProviderIds.length === 0) {
|
|
13436
|
+
writeLine(color.dim("No providers selected."));
|
|
13437
|
+
return 0;
|
|
13438
|
+
}
|
|
13439
|
+
}
|
|
13440
|
+
const leaderModel = config.model ?? "unknown";
|
|
13441
|
+
const unknownProviders = providerFilter ? providerFilter.filter((pid) => !keyedProviderIds2.includes(pid)) : [];
|
|
13442
|
+
const warningLine = unknownProviders.length > 0 ? ` ${color.amber("\u26A0 skipped (no key):")} ${unknownProviders.join(", ")}
|
|
13443
|
+
` : "";
|
|
13444
|
+
writeLine(`${color.bold("Model Competency Evaluation")}`);
|
|
13445
|
+
writeLine(color.dim(`Providers: ${targetProviderIds.join(", ")} | ${targetCategories.length} cats | ${modelsPerCat} model(s)/cat/provider`));
|
|
13446
|
+
writeLine(warningLine);
|
|
13447
|
+
writeLine(color.dim(`Leader (ranker): ${config.provider}/${leaderModel}`));
|
|
13448
|
+
writeLine();
|
|
13449
|
+
const collected = /* @__PURE__ */ new Map();
|
|
13450
|
+
let total = 0;
|
|
13451
|
+
let ok = 0;
|
|
13452
|
+
for (const pid of targetProviderIds) {
|
|
13453
|
+
const prov = createProviderForId(pid, config);
|
|
13454
|
+
if (!prov) {
|
|
13455
|
+
writeLine(color.dim(` \u2298 ${pid}: provider unavailable, skipping`));
|
|
13456
|
+
continue;
|
|
13457
|
+
}
|
|
13458
|
+
for (const cat of targetCategories) {
|
|
13459
|
+
const task = EVAL_TASKS[cat];
|
|
13460
|
+
if (!task) continue;
|
|
13461
|
+
const candidates = rankModels(providers, hasKey, cat, modelsPerCat).filter((c) => c.provider === pid);
|
|
13462
|
+
if (candidates.length === 0) continue;
|
|
13463
|
+
if (!collected.has(cat)) collected.set(cat, /* @__PURE__ */ new Map());
|
|
13464
|
+
for (const c of candidates) {
|
|
13465
|
+
total++;
|
|
13466
|
+
const modelKey = `${pid}/${c.model}`;
|
|
13467
|
+
try {
|
|
13468
|
+
const start = Date.now();
|
|
13469
|
+
const resp = await prov.complete(
|
|
13470
|
+
{
|
|
13471
|
+
model: c.model,
|
|
13472
|
+
system: [{ type: "text", text: "Be thorough and correct." }],
|
|
13473
|
+
messages: [{ role: "user", content: [{ type: "text", text: task.prompt }] }],
|
|
13474
|
+
maxTokens: 1024
|
|
13475
|
+
},
|
|
13476
|
+
{ signal: AbortSignal.timeout(45e3) }
|
|
13477
|
+
);
|
|
13478
|
+
const respText = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
|
|
13479
|
+
const respUsage = resp.usage;
|
|
13480
|
+
collected.get(cat).set(modelKey, {
|
|
13481
|
+
model: modelKey,
|
|
13482
|
+
latency: Date.now() - start,
|
|
13483
|
+
tokens: (respUsage?.input ?? 0) + (respUsage?.output ?? 0),
|
|
13484
|
+
text: respText
|
|
13485
|
+
});
|
|
13486
|
+
ok++;
|
|
13487
|
+
} catch {
|
|
13488
|
+
collected.get(cat).set(modelKey, {
|
|
13489
|
+
model: modelKey,
|
|
13490
|
+
latency: -1,
|
|
13491
|
+
tokens: 0,
|
|
13492
|
+
text: ""
|
|
13493
|
+
});
|
|
13494
|
+
}
|
|
13495
|
+
}
|
|
13496
|
+
}
|
|
13497
|
+
}
|
|
13498
|
+
writeLine(`${color.dim(`Phase 1: ${ok}/${total} calls succeeded`)}`);
|
|
13499
|
+
writeLine();
|
|
13500
|
+
if (collected.size === 0) {
|
|
13501
|
+
writeLine(color.amber("No responses collected. Check provider configuration."));
|
|
13502
|
+
return 0;
|
|
13503
|
+
}
|
|
13504
|
+
const leaderProvider = createProviderForId(config.provider, config);
|
|
13505
|
+
if (leaderProvider) {
|
|
13506
|
+
writeLine(`${color.bold("Phase 2")} \u2014 ${color.dim("leader ranks responses")}`);
|
|
13507
|
+
writeLine();
|
|
13508
|
+
}
|
|
13509
|
+
const rankings = /* @__PURE__ */ new Map();
|
|
13510
|
+
for (const [cat, responses] of collected) {
|
|
13511
|
+
const valid = Array.from(responses.values()).filter((r) => r.latency >= 0);
|
|
13512
|
+
if (valid.length < 2) {
|
|
13513
|
+
if (valid.length === 1) {
|
|
13514
|
+
const m = valid[0].model;
|
|
13515
|
+
if (!rankings.has(m)) rankings.set(m, /* @__PURE__ */ new Map());
|
|
13516
|
+
rankings.get(m).set(cat, { rank: 1, total: 1 });
|
|
13517
|
+
}
|
|
13518
|
+
continue;
|
|
13519
|
+
}
|
|
13520
|
+
const task = EVAL_TASKS[cat];
|
|
13521
|
+
if (leaderProvider) {
|
|
13522
|
+
const ranked = await rankResponses(leaderProvider, leaderModel, task.prompt, valid);
|
|
13523
|
+
for (let i = 0; i < valid.length; i++) {
|
|
13524
|
+
const m = valid[ranked[i] ?? i].model;
|
|
13525
|
+
if (!rankings.has(m)) rankings.set(m, /* @__PURE__ */ new Map());
|
|
13526
|
+
rankings.get(m).set(cat, { rank: i + 1, total: valid.length });
|
|
13527
|
+
}
|
|
13528
|
+
} else {
|
|
13529
|
+
for (const r of valid) {
|
|
13530
|
+
if (!rankings.has(r.model)) rankings.set(r.model, /* @__PURE__ */ new Map());
|
|
13531
|
+
rankings.get(r.model).set(cat, { rank: 1, total: valid.length });
|
|
13532
|
+
}
|
|
13533
|
+
}
|
|
13534
|
+
}
|
|
13535
|
+
writeLine(`${color.bold("Competency Report")}`);
|
|
13536
|
+
writeLine();
|
|
13537
|
+
const allModels = [.../* @__PURE__ */ new Set([...rankings.keys()])].sort();
|
|
13538
|
+
const catList = [...collected.keys()];
|
|
13539
|
+
const modelColWidth = Math.max(24, ...allModels.map((m) => m.length)) + 2;
|
|
13540
|
+
const cw = 12;
|
|
13541
|
+
writeLine(
|
|
13542
|
+
` ${color.dim("model".padEnd(modelColWidth))}` + catList.map((c) => color.dim((EVAL_TASKS[c]?.label ?? c).slice(0, cw).padEnd(cw + 2))).join("")
|
|
13543
|
+
);
|
|
13544
|
+
writeLine(` ${color.dim("\u2500".repeat(modelColWidth + catList.length * (cw + 2)))}`);
|
|
13545
|
+
for (const model of allModels) {
|
|
13546
|
+
const mr = rankings.get(model);
|
|
13547
|
+
const provFromModel = model.split("/")[0] ?? "";
|
|
13548
|
+
const modelColor = provFromModel === config.provider ? color.cyan : color.green;
|
|
13549
|
+
let row = ` ${modelColor(model.padEnd(modelColWidth))}`;
|
|
13550
|
+
for (const cat of catList) {
|
|
13551
|
+
const e = mr.get(cat);
|
|
13552
|
+
if (e) {
|
|
13553
|
+
const pct2 = Math.round((1 - (e.rank - 1) / Math.max(1, e.total - 1)) * 100);
|
|
13554
|
+
const pc = pct2 >= 80 ? color.green : pct2 >= 50 ? color.amber : color.red;
|
|
13555
|
+
row += `${pc(`#${e.rank} ${pct2}%`.padEnd(cw + 2))}`;
|
|
13556
|
+
} else {
|
|
13557
|
+
row += color.dim("\u2014".padEnd(cw + 2));
|
|
13558
|
+
}
|
|
13559
|
+
}
|
|
13560
|
+
writeLine(row);
|
|
13561
|
+
}
|
|
13562
|
+
writeLine();
|
|
13563
|
+
writeLine(color.dim("#1 100% = best in category. \u2014 = not tested."));
|
|
13564
|
+
writeLine();
|
|
13565
|
+
writeLine(color.dim("Pin: wstack setmodel set <role> <provider>/<model>"));
|
|
13566
|
+
writeLine(color.dim("Full: wstack modeldiag eval Providers: wstack modeldiag eval --providers=id1,id2"));
|
|
13567
|
+
writeLine(color.dim("Max: wstack modeldiag eval --max=3 Quick: wstack modeldiag eval --quick"));
|
|
13568
|
+
return 0;
|
|
13569
|
+
}
|
|
13570
|
+
writeLine(`${color.bold("API Key Status")}`);
|
|
13571
|
+
writeLine();
|
|
13572
|
+
for (const prov of providers) {
|
|
13573
|
+
const k = hasKey(prov.id);
|
|
13574
|
+
writeLine(` ${checkMark(k)} ${color.bold(prov.id.padEnd(18))} ${color.dim(prov.name)}`);
|
|
13575
|
+
}
|
|
13576
|
+
writeLine();
|
|
13577
|
+
writeLine(`${color.dim(`Leader: ${config.provider}/${config.model}`)}`);
|
|
13578
|
+
writeLine();
|
|
13579
|
+
writeLine(`${color.bold("Model Capabilities")} ${color.dim("\u2014 matched to known profiles")}`);
|
|
13580
|
+
writeLine();
|
|
13581
|
+
for (const prov of providers) {
|
|
13582
|
+
if (!hasKey(prov.id)) continue;
|
|
13583
|
+
writeLine(` ${color.bold(prov.id)} ${color.dim(`(${prov.name})`)}`);
|
|
13584
|
+
const tiers = { premium: [], standard: [], budget: [], unknown: [] };
|
|
13585
|
+
for (const m of prov.models ?? []) {
|
|
13586
|
+
const profile = findProfile(prov.id, m.id);
|
|
13587
|
+
tiers[profile?.costTier ?? "unknown"].push(m);
|
|
13588
|
+
}
|
|
13589
|
+
for (const tier of ["premium", "standard", "budget", "unknown"]) {
|
|
13590
|
+
const tierModels = tiers[tier];
|
|
13591
|
+
if (tierModels.length === 0) continue;
|
|
13592
|
+
const label = tier === "unknown" ? color.dim("unmatched") : `${costLabel(tier)} ${tier}`;
|
|
13593
|
+
writeLine(` ${label}`);
|
|
13594
|
+
for (const m of tierModels) {
|
|
13595
|
+
const cap = m.capabilities;
|
|
13596
|
+
const ctx = cap?.contextWindow ?? 0;
|
|
13597
|
+
const maxOut = cap?.maxOutputTokens ?? 0;
|
|
13598
|
+
const profile = findProfile(prov.id, m.id);
|
|
13599
|
+
const family = profile ? `${speedLabel(profile.speedTier)} ${color.green(profile.family)}` : color.dim("no profile match");
|
|
13600
|
+
const pricing = m.pricing ? `${color.dim("in")}${fmtPrice2(m.pricing.input)} ${color.dim("out")}${fmtPrice2(m.pricing.output)}` : color.dim("pricing ?");
|
|
13601
|
+
writeLine(
|
|
13602
|
+
` ${color.cyan(m.id.padEnd(34))}${ctx > 0 ? `ctx ${fmtTokens2(ctx).padEnd(6)}` : color.dim("ctx ? ")}${maxOut > 0 ? `out ${fmtTokens2(maxOut).padEnd(6)}` : " "}${family} ${pricing}`
|
|
13603
|
+
);
|
|
13604
|
+
}
|
|
13605
|
+
}
|
|
13606
|
+
writeLine();
|
|
13607
|
+
}
|
|
13608
|
+
await renderSuggest();
|
|
13609
|
+
writeLine();
|
|
13610
|
+
writeLine(color.dim("Pin a suggestion: wstack setmodel set <role> <provider>/<model>"));
|
|
13611
|
+
writeLine(color.dim('Test candidates: wstack modeldiag bench <role> "<test prompt>"'));
|
|
13612
|
+
return 0;
|
|
13613
|
+
};
|
|
12653
13614
|
var versionCmd = async (_args, deps) => {
|
|
12654
13615
|
deps.renderer.write(
|
|
12655
13616
|
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os2.platform()})
|
|
@@ -12729,7 +13690,8 @@ var subcommands = {
|
|
|
12729
13690
|
usage: usageCmd,
|
|
12730
13691
|
version: versionCmd,
|
|
12731
13692
|
help: helpCmd,
|
|
12732
|
-
projects: projectsCmd
|
|
13693
|
+
projects: projectsCmd,
|
|
13694
|
+
modeldiag: modeldiagCmd
|
|
12733
13695
|
};
|
|
12734
13696
|
|
|
12735
13697
|
// src/utils.ts
|
|
@@ -12752,22 +13714,22 @@ function fmtDuration(ms) {
|
|
|
12752
13714
|
const remMin = m - h * 60;
|
|
12753
13715
|
return `${h}h${remMin}m`;
|
|
12754
13716
|
}
|
|
12755
|
-
function fmtTaskResultLine(r,
|
|
13717
|
+
function fmtTaskResultLine(r, color58) {
|
|
12756
13718
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
12757
13719
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
12758
13720
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
12759
13721
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
12760
|
-
const errKindChip = errKind ?
|
|
12761
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
13722
|
+
const errKindChip = errKind ? color58.dim(` [${errKind}]`) : "";
|
|
13723
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color58.dim(errTail)}` : "";
|
|
12762
13724
|
switch (r.status) {
|
|
12763
13725
|
case "success":
|
|
12764
|
-
return { mark:
|
|
13726
|
+
return { mark: color58.green("\u2713"), stats, tail: "" };
|
|
12765
13727
|
case "timeout":
|
|
12766
|
-
return { mark:
|
|
13728
|
+
return { mark: color58.yellow("\u23F1"), stats: `${color58.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
12767
13729
|
case "stopped":
|
|
12768
|
-
return { mark:
|
|
13730
|
+
return { mark: color58.dim("\u2298"), stats: `${color58.dim("stopped")} ${stats}`, tail: errSnip };
|
|
12769
13731
|
case "failed":
|
|
12770
|
-
return { mark:
|
|
13732
|
+
return { mark: color58.red("\u2717"), stats: `${color58.red("failed")} ${stats}`, tail: errSnip };
|
|
12771
13733
|
}
|
|
12772
13734
|
}
|
|
12773
13735
|
|
|
@@ -12785,7 +13747,7 @@ function resolveBundledSkillsDir() {
|
|
|
12785
13747
|
try {
|
|
12786
13748
|
const req2 = createRequire(import.meta.url);
|
|
12787
13749
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
12788
|
-
return
|
|
13750
|
+
return path9.join(path9.dirname(corePkg), "skills");
|
|
12789
13751
|
} catch {
|
|
12790
13752
|
return void 0;
|
|
12791
13753
|
}
|
|
@@ -13035,7 +13997,7 @@ async function boot(argv) {
|
|
|
13035
13997
|
} catch {
|
|
13036
13998
|
}
|
|
13037
13999
|
printLaunchHints(renderer, flags, {
|
|
13038
|
-
cursorFile:
|
|
14000
|
+
cursorFile: path9.join(wpaths.cacheDir, "hint-cursor")
|
|
13039
14001
|
});
|
|
13040
14002
|
}
|
|
13041
14003
|
return {
|
|
@@ -13056,7 +14018,7 @@ async function boot(argv) {
|
|
|
13056
14018
|
}
|
|
13057
14019
|
async function checkGitInCwd(opts) {
|
|
13058
14020
|
const { cwd, renderer, reader } = opts;
|
|
13059
|
-
const cwdGit =
|
|
14021
|
+
const cwdGit = path9.join(cwd, ".git");
|
|
13060
14022
|
let hasCwdGit = false;
|
|
13061
14023
|
try {
|
|
13062
14024
|
await fsp4.access(cwdGit);
|
|
@@ -13097,10 +14059,10 @@ async function checkGitInCwd(opts) {
|
|
|
13097
14059
|
}
|
|
13098
14060
|
}
|
|
13099
14061
|
}
|
|
13100
|
-
const parentDir =
|
|
14062
|
+
const parentDir = path9.dirname(cwd);
|
|
13101
14063
|
if (parentDir !== cwd) {
|
|
13102
14064
|
try {
|
|
13103
|
-
await fsp4.access(
|
|
14065
|
+
await fsp4.access(path9.join(parentDir, ".git"));
|
|
13104
14066
|
renderer.write(
|
|
13105
14067
|
` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
|
|
13106
14068
|
`
|
|
@@ -13477,6 +14439,28 @@ async function runRepl(opts) {
|
|
|
13477
14439
|
`
|
|
13478
14440
|
);
|
|
13479
14441
|
}
|
|
14442
|
+
if (engine.currentState === "stopped") {
|
|
14443
|
+
const goal = await loadGoalSafe(opts);
|
|
14444
|
+
if (goal?.goalState === "completed") {
|
|
14445
|
+
const u = goal.journal.length > 0 ? summarizeUsage(goal) : null;
|
|
14446
|
+
const costLine = u && u.iterationsWithUsage > 0 ? color.dim(` \u2014 ${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out \xB7 ${goal.iterations} iterations`) : goal.iterations > 0 ? color.dim(` \u2014 ${goal.iterations} iterations`) : "";
|
|
14447
|
+
opts.renderer.write(
|
|
14448
|
+
color.green(`
|
|
14449
|
+
\u{1F3AF} Goal completed!${costLine}
|
|
14450
|
+
|
|
14451
|
+
`) + color.dim(" Goal cleared. Use /goal set <mission> to create a new goal.\n")
|
|
14452
|
+
);
|
|
14453
|
+
if (opts.projectRoot) {
|
|
14454
|
+
try {
|
|
14455
|
+
const { unlink: unlink4 } = await import('fs/promises');
|
|
14456
|
+
await unlink4(goalFilePath(opts.projectRoot));
|
|
14457
|
+
} catch {
|
|
14458
|
+
}
|
|
14459
|
+
}
|
|
14460
|
+
}
|
|
14461
|
+
opts.onAutonomy?.("off");
|
|
14462
|
+
continue;
|
|
14463
|
+
}
|
|
13480
14464
|
} catch (err) {
|
|
13481
14465
|
opts.renderer.writeError(
|
|
13482
14466
|
`[eternal] ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -13523,6 +14507,28 @@ async function runRepl(opts) {
|
|
|
13523
14507
|
`
|
|
13524
14508
|
);
|
|
13525
14509
|
}
|
|
14510
|
+
if (engine.currentState === "stopped") {
|
|
14511
|
+
const goal = await loadGoalSafe(opts);
|
|
14512
|
+
if (goal?.goalState === "completed") {
|
|
14513
|
+
const u = goal.journal.length > 0 ? summarizeUsage(goal) : null;
|
|
14514
|
+
const costLine = u && u.iterationsWithUsage > 0 ? color.dim(` \u2014 ${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out \xB7 ${goal.iterations} iterations`) : goal.iterations > 0 ? color.dim(` \u2014 ${goal.iterations} iterations`) : "";
|
|
14515
|
+
opts.renderer.write(
|
|
14516
|
+
color.green(`
|
|
14517
|
+
\u{1F3AF} Goal completed!${costLine}
|
|
14518
|
+
|
|
14519
|
+
`) + color.dim(" Goal cleared. Use /goal set <mission> to create a new goal.\n")
|
|
14520
|
+
);
|
|
14521
|
+
if (opts.projectRoot) {
|
|
14522
|
+
try {
|
|
14523
|
+
const { unlink: unlink4 } = await import('fs/promises');
|
|
14524
|
+
await unlink4(goalFilePath(opts.projectRoot));
|
|
14525
|
+
} catch {
|
|
14526
|
+
}
|
|
14527
|
+
}
|
|
14528
|
+
}
|
|
14529
|
+
opts.onAutonomy?.("off");
|
|
14530
|
+
continue;
|
|
14531
|
+
}
|
|
13526
14532
|
} catch (err) {
|
|
13527
14533
|
opts.renderer.writeError(
|
|
13528
14534
|
`[parallel] ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14296,7 +15302,10 @@ async function execute(deps) {
|
|
|
14296
15302
|
logLevel: cfg.log?.level ?? "info",
|
|
14297
15303
|
auditLevel: cfg.session?.auditLevel ?? "standard",
|
|
14298
15304
|
indexOnStart: cfg.indexing?.onSessionStart !== false,
|
|
14299
|
-
maxIterations: cfg.tools?.maxIterations ?? 500
|
|
15305
|
+
maxIterations: cfg.tools?.maxIterations ?? 500,
|
|
15306
|
+
debugStream: cfg.debugStream ?? false,
|
|
15307
|
+
configScope: cfg.configScope ?? "global",
|
|
15308
|
+
enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4
|
|
14300
15309
|
};
|
|
14301
15310
|
},
|
|
14302
15311
|
async saveSettings(s) {
|
|
@@ -14305,6 +15314,7 @@ async function execute(deps) {
|
|
|
14305
15314
|
{
|
|
14306
15315
|
configStore,
|
|
14307
15316
|
globalConfigPath: wpaths.globalConfig,
|
|
15317
|
+
inProjectConfigPath: wpaths.inProjectConfig,
|
|
14308
15318
|
vault: { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false }
|
|
14309
15319
|
},
|
|
14310
15320
|
(autonomy) => {
|
|
@@ -14318,8 +15328,10 @@ async function execute(deps) {
|
|
|
14318
15328
|
a["confirmExit"] = s.confirmExit ?? true;
|
|
14319
15329
|
}
|
|
14320
15330
|
);
|
|
14321
|
-
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0) {
|
|
14322
|
-
const
|
|
15331
|
+
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
|
|
15332
|
+
const configScope = s.configScope ?? (configStore.get().configScope ?? "global");
|
|
15333
|
+
const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
|
|
15334
|
+
const raw = await fsp4.readFile(targetPath, "utf8").catch(() => "{}");
|
|
14323
15335
|
const parsed = JSON.parse(raw);
|
|
14324
15336
|
const vault = { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false };
|
|
14325
15337
|
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
@@ -14361,8 +15373,25 @@ async function execute(deps) {
|
|
|
14361
15373
|
tools.maxIterations = s.maxIterations;
|
|
14362
15374
|
decrypted.tools = tools;
|
|
14363
15375
|
}
|
|
14364
|
-
|
|
14365
|
-
|
|
15376
|
+
if (s.debugStream !== void 0) {
|
|
15377
|
+
decrypted.debugStream = s.debugStream;
|
|
15378
|
+
const { setDebugStreamEnabled } = await import('@wrongstack/providers');
|
|
15379
|
+
setDebugStreamEnabled(s.debugStream);
|
|
15380
|
+
}
|
|
15381
|
+
if (s.configScope !== void 0) {
|
|
15382
|
+
decrypted.configScope = s.configScope;
|
|
15383
|
+
}
|
|
15384
|
+
if (s.enhanceDelayMs !== void 0) {
|
|
15385
|
+
const autonomy = decrypted.autonomy ?? {};
|
|
15386
|
+
autonomy.enhanceDelayMs = s.enhanceDelayMs;
|
|
15387
|
+
decrypted.autonomy = autonomy;
|
|
15388
|
+
}
|
|
15389
|
+
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
15390
|
+
const encrypted = encryptConfigSecrets$1(toWrite, vault);
|
|
15391
|
+
if (targetPath !== wpaths.globalConfig) {
|
|
15392
|
+
await fsp4.mkdir(path9.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
|
|
15393
|
+
}
|
|
15394
|
+
await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
14366
15395
|
configStore.update({
|
|
14367
15396
|
...s.nextPrediction !== void 0 ? { nextPrediction: s.nextPrediction } : {},
|
|
14368
15397
|
...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 ? { features: decrypted.features } : {},
|
|
@@ -14370,7 +15399,10 @@ async function execute(deps) {
|
|
|
14370
15399
|
...s.logLevel !== void 0 ? { log: decrypted.log } : {},
|
|
14371
15400
|
...s.auditLevel !== void 0 ? { session: decrypted.session } : {},
|
|
14372
15401
|
...s.indexOnStart !== void 0 ? { indexing: decrypted.indexing } : {},
|
|
14373
|
-
...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {}
|
|
15402
|
+
...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {},
|
|
15403
|
+
...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
|
|
15404
|
+
...s.configScope !== void 0 ? { configScope: s.configScope } : {},
|
|
15405
|
+
...s.enhanceDelayMs !== void 0 ? { autonomy: { ...configStore.get().autonomy ?? {}, enhanceDelayMs: s.enhanceDelayMs } } : {}
|
|
14374
15406
|
});
|
|
14375
15407
|
}
|
|
14376
15408
|
if (s.streamFleet !== void 0) {
|
|
@@ -14445,6 +15477,18 @@ async function execute(deps) {
|
|
|
14445
15477
|
getModeLabel: () => {
|
|
14446
15478
|
const metaMode = context.meta?.["mode"];
|
|
14447
15479
|
return typeof metaMode === "string" ? metaMode : modeId ?? "default";
|
|
15480
|
+
},
|
|
15481
|
+
registerDebugStreamCallback: (cb) => {
|
|
15482
|
+
void import('@wrongstack/providers').then(({ setDebugStreamCallback }) => setDebugStreamCallback(cb)).catch(
|
|
15483
|
+
(err) => console.error("[execution] failed to register debug stream callback:", err)
|
|
15484
|
+
);
|
|
15485
|
+
},
|
|
15486
|
+
restoreDebugStreamCallback: () => {
|
|
15487
|
+
void import('@wrongstack/providers').then(
|
|
15488
|
+
({ setDebugStreamCallback, defaultDebugStreamCallback }) => setDebugStreamCallback(defaultDebugStreamCallback)
|
|
15489
|
+
).catch(
|
|
15490
|
+
(err) => console.error("[execution] failed to restore debug stream callback:", err)
|
|
15491
|
+
);
|
|
14448
15492
|
}
|
|
14449
15493
|
});
|
|
14450
15494
|
} finally {
|
|
@@ -14475,7 +15519,7 @@ async function execute(deps) {
|
|
|
14475
15519
|
supportsVision,
|
|
14476
15520
|
attachments,
|
|
14477
15521
|
effectiveMaxContext,
|
|
14478
|
-
projectName:
|
|
15522
|
+
projectName: path9.basename(projectRoot) || void 0,
|
|
14479
15523
|
projectRoot,
|
|
14480
15524
|
getAutonomy,
|
|
14481
15525
|
onAutonomy,
|
|
@@ -14490,7 +15534,7 @@ async function execute(deps) {
|
|
|
14490
15534
|
onAgentIterationComplete: director ? (tokens) => director.setLeaderContextPressure(tokens) : void 0
|
|
14491
15535
|
});
|
|
14492
15536
|
} finally {
|
|
14493
|
-
await webuiPromise.catch(() =>
|
|
15537
|
+
await webuiPromise.catch((err) => console.debug(`[execution] webui shutdown failed: ${err}`));
|
|
14494
15538
|
}
|
|
14495
15539
|
} else {
|
|
14496
15540
|
code = await runRepl({
|
|
@@ -14503,7 +15547,7 @@ async function execute(deps) {
|
|
|
14503
15547
|
supportsVision,
|
|
14504
15548
|
attachments,
|
|
14505
15549
|
effectiveMaxContext,
|
|
14506
|
-
projectName:
|
|
15550
|
+
projectName: path9.basename(projectRoot) || void 0,
|
|
14507
15551
|
getAutonomy,
|
|
14508
15552
|
onAutonomy,
|
|
14509
15553
|
getNextPredict,
|
|
@@ -14635,7 +15679,7 @@ var MultiAgentHost = class {
|
|
|
14635
15679
|
doneCondition: { type: "all_tasks_done" },
|
|
14636
15680
|
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
14637
15681
|
};
|
|
14638
|
-
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ?
|
|
15682
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path9.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
14639
15683
|
this.director = new Director({
|
|
14640
15684
|
config: coordinatorConfig,
|
|
14641
15685
|
manifestPath: this.opts.manifestPath,
|
|
@@ -15104,16 +16148,16 @@ var MultiAgentHost = class {
|
|
|
15104
16148
|
if (this.director) return this.director;
|
|
15105
16149
|
this.opts.directorMode = true;
|
|
15106
16150
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
15107
|
-
this.opts.manifestPath =
|
|
16151
|
+
this.opts.manifestPath = path9.join(this.opts.fleetRoot, "fleet.json");
|
|
15108
16152
|
}
|
|
15109
16153
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
15110
|
-
this.opts.sharedScratchpadPath =
|
|
16154
|
+
this.opts.sharedScratchpadPath = path9.join(this.opts.fleetRoot, "shared");
|
|
15111
16155
|
}
|
|
15112
16156
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
15113
|
-
this.opts.sessionsRoot =
|
|
16157
|
+
this.opts.sessionsRoot = path9.join(this.opts.fleetRoot, "subagents");
|
|
15114
16158
|
}
|
|
15115
16159
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
15116
|
-
this.opts.stateCheckpointPath =
|
|
16160
|
+
this.opts.stateCheckpointPath = path9.join(this.opts.fleetRoot, "director-state.json");
|
|
15117
16161
|
}
|
|
15118
16162
|
await this.ensureDirector();
|
|
15119
16163
|
return this.director ?? null;
|
|
@@ -15285,11 +16329,11 @@ var SessionStats = class {
|
|
|
15285
16329
|
if (e.name === "bash") this.bashCommands++;
|
|
15286
16330
|
else if (e.name === "fetch") this.fetches++;
|
|
15287
16331
|
if (!e.ok) return;
|
|
15288
|
-
const
|
|
15289
|
-
if (e.name === "read" &&
|
|
15290
|
-
else if (e.name === "edit" &&
|
|
15291
|
-
else if (e.name === "write" &&
|
|
15292
|
-
this.writtenPaths.add(
|
|
16332
|
+
const path27 = typeof input?.path === "string" ? input.path : void 0;
|
|
16333
|
+
if (e.name === "read" && path27) this.readPaths.add(path27);
|
|
16334
|
+
else if (e.name === "edit" && path27) this.editedPaths.add(path27);
|
|
16335
|
+
else if (e.name === "write" && path27) {
|
|
16336
|
+
this.writtenPaths.add(path27);
|
|
15293
16337
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
15294
16338
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
15295
16339
|
}
|
|
@@ -15894,7 +16938,12 @@ async function setupCompaction(params) {
|
|
|
15894
16938
|
effectiveMaxContext,
|
|
15895
16939
|
// Calibrated estimator: recordActualUsage() is called after each API
|
|
15896
16940
|
// response so this converges on real token counts for compaction decisions.
|
|
15897
|
-
(ctx) => estimateRequestTokensCalibrated(
|
|
16941
|
+
(ctx) => estimateRequestTokensCalibrated(
|
|
16942
|
+
ctx.messages,
|
|
16943
|
+
ctx.systemPrompt,
|
|
16944
|
+
ctx.tools ?? [],
|
|
16945
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
16946
|
+
).total,
|
|
15898
16947
|
initialPolicy.thresholds,
|
|
15899
16948
|
{
|
|
15900
16949
|
aggressiveOn: initialPolicy.aggressiveOn,
|
|
@@ -15956,9 +17005,13 @@ function parseModelRef(ref) {
|
|
|
15956
17005
|
}
|
|
15957
17006
|
return { model: trimmed };
|
|
15958
17007
|
}
|
|
15959
|
-
function
|
|
17008
|
+
function shouldFallback(err) {
|
|
17009
|
+
if (err instanceof StreamHangError) {
|
|
17010
|
+
return 599;
|
|
17011
|
+
}
|
|
15960
17012
|
if (!(err instanceof ProviderError)) return null;
|
|
15961
17013
|
const s = err.status;
|
|
17014
|
+
if (s === 0) return s;
|
|
15962
17015
|
if (s === 429 || s === 529 || s >= 500) return s;
|
|
15963
17016
|
return null;
|
|
15964
17017
|
}
|
|
@@ -15990,7 +17043,7 @@ function createFallbackModelExtension(deps) {
|
|
|
15990
17043
|
const cfg = deps.getConfig();
|
|
15991
17044
|
const chain = cfg.fallbackModels ?? [];
|
|
15992
17045
|
for (const ref of chain) {
|
|
15993
|
-
const status =
|
|
17046
|
+
const status = shouldFallback(lastErr);
|
|
15994
17047
|
if (status === null) break;
|
|
15995
17048
|
const parsed = parseModelRef(ref);
|
|
15996
17049
|
if (!parsed.model) continue;
|
|
@@ -16107,7 +17160,7 @@ function setupMetrics(params) {
|
|
|
16107
17160
|
const dumpMetrics = () => {
|
|
16108
17161
|
if (!metricsSink) return;
|
|
16109
17162
|
try {
|
|
16110
|
-
const out =
|
|
17163
|
+
const out = path9.join(wpaths.projectSessions, "metrics.json");
|
|
16111
17164
|
const snap = metricsSink.snapshot();
|
|
16112
17165
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
16113
17166
|
} catch {
|
|
@@ -16182,7 +17235,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
16182
17235
|
if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
|
|
16183
17236
|
const fp = payload.toolUse.input?.file_path;
|
|
16184
17237
|
if (typeof fp === "string" && fp.length > 0) {
|
|
16185
|
-
const abs =
|
|
17238
|
+
const abs = path9.resolve(payload.ctx.cwd, fp);
|
|
16186
17239
|
if (isIndexableFile(abs)) {
|
|
16187
17240
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
16188
17241
|
}
|
|
@@ -16197,11 +17250,11 @@ async function setupCodebaseIndexing(deps) {
|
|
|
16197
17250
|
let watcher;
|
|
16198
17251
|
if (idx.watchExternal) {
|
|
16199
17252
|
try {
|
|
16200
|
-
watcher =
|
|
17253
|
+
watcher = fs14.watch(projectRoot, { recursive: true }, (_event, filename) => {
|
|
16201
17254
|
if (!filename) return;
|
|
16202
17255
|
const rel = filename.toString();
|
|
16203
17256
|
if (isIgnored(rel)) return;
|
|
16204
|
-
const abs =
|
|
17257
|
+
const abs = path9.resolve(projectRoot, rel);
|
|
16205
17258
|
if (!isIndexableFile(abs)) return;
|
|
16206
17259
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
16207
17260
|
});
|
|
@@ -16448,7 +17501,7 @@ async function setupSession(params) {
|
|
|
16448
17501
|
} = params;
|
|
16449
17502
|
sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
|
|
16450
17503
|
if (count > 0) renderer.writeInfo(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
|
|
16451
|
-
}).catch(() =>
|
|
17504
|
+
}).catch((err) => console.debug(`[session] prune failed: ${err}`));
|
|
16452
17505
|
let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
|
|
16453
17506
|
const recoveryLock = new RecoveryLock({ dir: wpaths.projectSessions, sessionStore });
|
|
16454
17507
|
if (!resumeId && !flags["no-recovery"]) {
|
|
@@ -16490,9 +17543,9 @@ async function setupSession(params) {
|
|
|
16490
17543
|
const sessionRef = { current: session };
|
|
16491
17544
|
await recoveryLock.write(session?.id).catch(() => void 0);
|
|
16492
17545
|
const attachments = new DefaultAttachmentStore({
|
|
16493
|
-
spoolDir:
|
|
17546
|
+
spoolDir: path9.join(wpaths.projectSessions, session?.id, "attachments")
|
|
16494
17547
|
});
|
|
16495
|
-
const queueStore = new QueueStore({ dir:
|
|
17548
|
+
const queueStore = new QueueStore({ dir: path9.join(wpaths.projectSessions, session?.id) });
|
|
16496
17549
|
const ctxSignal = new AbortController().signal;
|
|
16497
17550
|
const context = new Context({
|
|
16498
17551
|
systemPrompt,
|
|
@@ -16505,7 +17558,7 @@ async function setupSession(params) {
|
|
|
16505
17558
|
model: config.model
|
|
16506
17559
|
});
|
|
16507
17560
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
16508
|
-
const todosCheckpointPath =
|
|
17561
|
+
const todosCheckpointPath = path9.join(wpaths.projectSessions, `${session?.id}.todos.json`);
|
|
16509
17562
|
if (resumeId) {
|
|
16510
17563
|
try {
|
|
16511
17564
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -16523,15 +17576,15 @@ async function setupSession(params) {
|
|
|
16523
17576
|
todosCheckpointPath,
|
|
16524
17577
|
session?.id
|
|
16525
17578
|
);
|
|
16526
|
-
const planPath =
|
|
17579
|
+
const planPath = path9.join(wpaths.projectSessions, `${session?.id}.plan.json`);
|
|
16527
17580
|
context.state.setMeta("plan.path", planPath);
|
|
16528
|
-
const taskPath =
|
|
17581
|
+
const taskPath = path9.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
|
|
16529
17582
|
context.state.setMeta("task.path", taskPath);
|
|
16530
17583
|
let dirState;
|
|
16531
17584
|
if (resumeId) {
|
|
16532
17585
|
try {
|
|
16533
|
-
const fleetRoot =
|
|
16534
|
-
dirState = await loadDirectorState(
|
|
17586
|
+
const fleetRoot = path9.join(wpaths.projectSessions, session?.id);
|
|
17587
|
+
dirState = await loadDirectorState(path9.join(fleetRoot, "director-state.json"));
|
|
16535
17588
|
if (dirState) {
|
|
16536
17589
|
const tCounts = {};
|
|
16537
17590
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -16571,7 +17624,7 @@ function resolveBundledSkillsDir2() {
|
|
|
16571
17624
|
try {
|
|
16572
17625
|
const req2 = createRequire(import.meta.url);
|
|
16573
17626
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
16574
|
-
return
|
|
17627
|
+
return path9.join(path9.dirname(corePkg), "skills");
|
|
16575
17628
|
} catch {
|
|
16576
17629
|
return void 0;
|
|
16577
17630
|
}
|
|
@@ -16682,6 +17735,9 @@ async function main(argv) {
|
|
|
16682
17735
|
updateInfo
|
|
16683
17736
|
} = ctx;
|
|
16684
17737
|
updateInfo = await printUpdateNotice(updateInfo);
|
|
17738
|
+
const { setDebugStreamEnabled, setDebugStreamCallback, defaultDebugStreamCallback } = await import('@wrongstack/providers');
|
|
17739
|
+
if (config.debugStream) setDebugStreamEnabled(true);
|
|
17740
|
+
setDebugStreamCallback(defaultDebugStreamCallback);
|
|
16685
17741
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
16686
17742
|
const events = new EventBus();
|
|
16687
17743
|
events.setLogger(logger);
|
|
@@ -16768,7 +17824,7 @@ async function main(argv) {
|
|
|
16768
17824
|
modeId,
|
|
16769
17825
|
modePrompt,
|
|
16770
17826
|
modelCapabilities,
|
|
16771
|
-
planPath: () => sessionRef.current ?
|
|
17827
|
+
planPath: () => sessionRef.current ? path9.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
|
|
16772
17828
|
contributors: [
|
|
16773
17829
|
// Injects the ETERNAL AUTONOMY block when the user has activated
|
|
16774
17830
|
// a long-running autonomy engine. Without this, the per-iteration
|
|
@@ -17158,12 +18214,12 @@ async function main(argv) {
|
|
|
17158
18214
|
}
|
|
17159
18215
|
}
|
|
17160
18216
|
};
|
|
17161
|
-
const fleetRoot = directorMode ?
|
|
17162
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
17163
|
-
const sharedScratchpadPath = directorMode ?
|
|
17164
|
-
const subagentSessionsRoot = directorMode ?
|
|
17165
|
-
const stateCheckpointPath = directorMode ?
|
|
17166
|
-
const fleetRootForPromotion =
|
|
18217
|
+
const fleetRoot = directorMode ? path9.join(wpaths.projectSessions, session.id) : void 0;
|
|
18218
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path9.join(expectDefined(fleetRoot), "fleet.json") : void 0;
|
|
18219
|
+
const sharedScratchpadPath = directorMode ? path9.join(expectDefined(fleetRoot), "shared") : void 0;
|
|
18220
|
+
const subagentSessionsRoot = directorMode ? path9.join(expectDefined(fleetRoot), "subagents") : void 0;
|
|
18221
|
+
const stateCheckpointPath = directorMode ? path9.join(expectDefined(fleetRoot), "director-state.json") : void 0;
|
|
18222
|
+
const fleetRootForPromotion = path9.join(wpaths.projectSessions, session.id);
|
|
17167
18223
|
const brainQueue = new BrainDecisionQueue(events);
|
|
17168
18224
|
const brain = new ObservableBrainArbiter(
|
|
17169
18225
|
new HumanEscalatingBrainArbiter(new DefaultBrainArbiter(), brainQueue),
|
|
@@ -17301,6 +18357,13 @@ async function main(argv) {
|
|
|
17301
18357
|
enhanceController,
|
|
17302
18358
|
llmProvider: provider,
|
|
17303
18359
|
llmModel: config.model,
|
|
18360
|
+
createProvider: (pid) => {
|
|
18361
|
+
try {
|
|
18362
|
+
return buildProviderForId(pid);
|
|
18363
|
+
} catch {
|
|
18364
|
+
return void 0;
|
|
18365
|
+
}
|
|
18366
|
+
},
|
|
17304
18367
|
statuslineConfig: statuslineConfigDeps,
|
|
17305
18368
|
statuslineHiddenItems: [...currentHiddenItems],
|
|
17306
18369
|
setStatuslineHiddenItems,
|
|
@@ -17521,7 +18584,7 @@ async function main(argv) {
|
|
|
17521
18584
|
return director.spawn(cfg);
|
|
17522
18585
|
},
|
|
17523
18586
|
onFleetLog: async (subagentId, mode) => {
|
|
17524
|
-
const subagentsRoot =
|
|
18587
|
+
const subagentsRoot = path9.join(fleetRootForPromotion, "subagents");
|
|
17525
18588
|
let runDirs;
|
|
17526
18589
|
try {
|
|
17527
18590
|
runDirs = await fsp4.readdir(subagentsRoot);
|
|
@@ -17530,7 +18593,7 @@ async function main(argv) {
|
|
|
17530
18593
|
}
|
|
17531
18594
|
const found = [];
|
|
17532
18595
|
for (const runId of runDirs) {
|
|
17533
|
-
const runDir =
|
|
18596
|
+
const runDir = path9.join(subagentsRoot, runId);
|
|
17534
18597
|
let files;
|
|
17535
18598
|
try {
|
|
17536
18599
|
files = await fsp4.readdir(runDir);
|
|
@@ -17539,7 +18602,7 @@ async function main(argv) {
|
|
|
17539
18602
|
}
|
|
17540
18603
|
for (const f of files) {
|
|
17541
18604
|
if (!f.endsWith(".jsonl")) continue;
|
|
17542
|
-
const full =
|
|
18605
|
+
const full = path9.join(runDir, f);
|
|
17543
18606
|
try {
|
|
17544
18607
|
const stat4 = await fsp4.stat(full);
|
|
17545
18608
|
found.push({
|
|
@@ -17640,7 +18703,7 @@ async function main(argv) {
|
|
|
17640
18703
|
}
|
|
17641
18704
|
const dir = await multiAgentHost.ensureDirector();
|
|
17642
18705
|
if (!dir) return "Director is not available.";
|
|
17643
|
-
const dirStatePath =
|
|
18706
|
+
const dirStatePath = path9.join(fleetRootForPromotion, "director-state.json");
|
|
17644
18707
|
const prior = await loadDirectorState(dirStatePath);
|
|
17645
18708
|
if (!prior) {
|
|
17646
18709
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -17709,9 +18772,9 @@ async function main(argv) {
|
|
|
17709
18772
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
17710
18773
|
toolRegistry.register(tool);
|
|
17711
18774
|
}
|
|
17712
|
-
const mp =
|
|
17713
|
-
const sp =
|
|
17714
|
-
const ss =
|
|
18775
|
+
const mp = path9.join(fleetRootForPromotion, "fleet.json");
|
|
18776
|
+
const sp = path9.join(fleetRootForPromotion, "shared");
|
|
18777
|
+
const ss = path9.join(fleetRootForPromotion, "subagents");
|
|
17715
18778
|
const lines = [
|
|
17716
18779
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
17717
18780
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -17861,6 +18924,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
17861
18924
|
message: `\u26A0 ${color.yellow(`${lines.length} uncommitted change${lines.length > 1 ? "s" : ""}`)} \u2014 session ended without commit`
|
|
17862
18925
|
};
|
|
17863
18926
|
}
|
|
18927
|
+
return void 0;
|
|
17864
18928
|
},
|
|
17865
18929
|
onClear: () => {
|
|
17866
18930
|
if (flags.tui && !flags["no-tui"]) return;
|