@wrongstack/cli 0.119.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 CHANGED
@@ -1,5 +1,5 @@
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, 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 } 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
5
  import * as path9 from 'path';
@@ -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) {
@@ -2873,20 +2881,7 @@ function countToolResults(messages) {
2873
2881
  return count;
2874
2882
  }
2875
2883
  function estimateTokens(messages) {
2876
- let total = 0;
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
@@ -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 / 4 estimate)`,
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" : ""}`,
@@ -4137,7 +4133,7 @@ async function persistTelegramConfig(deps, mutator) {
4137
4133
  decrypted.extensions = extensions;
4138
4134
  const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
4139
4135
  await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
4140
- deps.configStore.update({ extensions: decrypted.extensions });
4136
+ deps.configStore.update({ extensions });
4141
4137
  }
4142
4138
 
4143
4139
  // src/slash-commands/enhance.ts
@@ -5605,6 +5601,7 @@ ${formatGoal(updated)}` };
5605
5601
  } catch {
5606
5602
  }
5607
5603
  if (opts.onEternalStop) opts.onEternalStop();
5604
+ if (opts.onAutonomy) opts.onAutonomy("off");
5608
5605
  const msg = `${color.amber("Goal cleared.")} Previous goal marked abandoned; eternal mode will stop.`;
5609
5606
  opts.renderer.write(msg);
5610
5607
  return { message: msg };
@@ -8823,7 +8820,10 @@ var ReadlineInputReader = class {
8823
8820
  const fresh = this.ensure();
8824
8821
  this.installPromptGuard(fresh);
8825
8822
  return new Promise((resolve5) => {
8823
+ let settled = false;
8826
8824
  const settle = (line) => {
8825
+ if (settled) return;
8826
+ settled = true;
8827
8827
  setOutputLineGuard(null);
8828
8828
  resolve5(line);
8829
8829
  };
@@ -8835,6 +8835,7 @@ var ReadlineInputReader = class {
8835
8835
  settle(line);
8836
8836
  });
8837
8837
  fresh.once("close", () => settle(""));
8838
+ fresh.on && fresh.on("error", (_e) => settle(""));
8838
8839
  }).then((result) => {
8839
8840
  this.rl?.close();
8840
8841
  return result;
@@ -13040,10 +13041,10 @@ function roleCat(role) {
13040
13041
  function createProviderForId(providerId, cfg) {
13041
13042
  const savedCfg = cfg.providers?.[providerId];
13042
13043
  const resolvedProviderId = savedCfg?.type ?? providerId;
13043
- const cfgWithType = {
13044
- ...savedCfg ?? { type: providerId, apiKey: cfg.apiKey, baseUrl: cfg.baseUrl },
13045
- type: resolvedProviderId
13046
- };
13044
+ const cfgWithType = Object.assign(
13045
+ { type: resolvedProviderId },
13046
+ savedCfg ?? { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }
13047
+ );
13047
13048
  try {
13048
13049
  return makeProviderFromConfig(resolvedProviderId, cfgWithType);
13049
13050
  } catch {
@@ -13288,10 +13289,17 @@ var modeldiagCmd = async (args, deps) => {
13288
13289
  const providersEqIdx = benchArgs.findIndex((a) => a.startsWith("--providers="));
13289
13290
  let providerFilter;
13290
13291
  if (providersEqIdx >= 0) {
13291
- providerFilter = benchArgs[providersEqIdx].replace("--providers=", "").split(",").map((s) => s.trim()).filter(Boolean);
13292
- benchArgs.splice(providersEqIdx, 1);
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
+ }
13293
13297
  }
13294
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
+ }
13295
13303
  const benchPrompt = benchArgs.slice(1).join(" ");
13296
13304
  const cat = roleCat(benchRole);
13297
13305
  const candidates = rankModels(providers, hasKey, cat, 5);
@@ -14431,6 +14439,28 @@ async function runRepl(opts) {
14431
14439
  `
14432
14440
  );
14433
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
+ }
14434
14464
  } catch (err) {
14435
14465
  opts.renderer.writeError(
14436
14466
  `[eternal] ${err instanceof Error ? err.message : String(err)}`
@@ -14477,6 +14507,28 @@ async function runRepl(opts) {
14477
14507
  `
14478
14508
  );
14479
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
+ }
14480
14532
  } catch (err) {
14481
14533
  opts.renderer.writeError(
14482
14534
  `[parallel] ${err instanceof Error ? err.message : String(err)}`
@@ -15337,8 +15389,7 @@ async function execute(deps) {
15337
15389
  const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
15338
15390
  const encrypted = encryptConfigSecrets$1(toWrite, vault);
15339
15391
  if (targetPath !== wpaths.globalConfig) {
15340
- await fsp4.mkdir(path9.dirname(targetPath), { recursive: true }).catch(() => {
15341
- });
15392
+ await fsp4.mkdir(path9.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
15342
15393
  }
15343
15394
  await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
15344
15395
  configStore.update({
@@ -15428,13 +15479,15 @@ async function execute(deps) {
15428
15479
  return typeof metaMode === "string" ? metaMode : modeId ?? "default";
15429
15480
  },
15430
15481
  registerDebugStreamCallback: (cb) => {
15431
- void import('@wrongstack/providers').then(
15432
- ({ setDebugStreamCallback }) => setDebugStreamCallback(cb)
15482
+ void import('@wrongstack/providers').then(({ setDebugStreamCallback }) => setDebugStreamCallback(cb)).catch(
15483
+ (err) => console.error("[execution] failed to register debug stream callback:", err)
15433
15484
  );
15434
15485
  },
15435
15486
  restoreDebugStreamCallback: () => {
15436
15487
  void import('@wrongstack/providers').then(
15437
15488
  ({ setDebugStreamCallback, defaultDebugStreamCallback }) => setDebugStreamCallback(defaultDebugStreamCallback)
15489
+ ).catch(
15490
+ (err) => console.error("[execution] failed to restore debug stream callback:", err)
15438
15491
  );
15439
15492
  }
15440
15493
  });
@@ -15481,7 +15534,7 @@ async function execute(deps) {
15481
15534
  onAgentIterationComplete: director ? (tokens) => director.setLeaderContextPressure(tokens) : void 0
15482
15535
  });
15483
15536
  } finally {
15484
- await webuiPromise.catch(() => void 0);
15537
+ await webuiPromise.catch((err) => console.debug(`[execution] webui shutdown failed: ${err}`));
15485
15538
  }
15486
15539
  } else {
15487
15540
  code = await runRepl({
@@ -16885,7 +16938,12 @@ async function setupCompaction(params) {
16885
16938
  effectiveMaxContext,
16886
16939
  // Calibrated estimator: recordActualUsage() is called after each API
16887
16940
  // response so this converges on real token counts for compaction decisions.
16888
- (ctx) => estimateRequestTokensCalibrated(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total,
16941
+ (ctx) => estimateRequestTokensCalibrated(
16942
+ ctx.messages,
16943
+ ctx.systemPrompt,
16944
+ ctx.tools ?? [],
16945
+ `${ctx.provider?.id ?? "unknown"}/${ctx.model}`
16946
+ ).total,
16889
16947
  initialPolicy.thresholds,
16890
16948
  {
16891
16949
  aggressiveOn: initialPolicy.aggressiveOn,
@@ -17443,7 +17501,7 @@ async function setupSession(params) {
17443
17501
  } = params;
17444
17502
  sessionStore.prune(DEFAULT_SESSION_PRUNE_DAYS).then((count) => {
17445
17503
  if (count > 0) renderer.writeInfo(`Pruned ${count} old session${count === 1 ? "" : "s"}.`);
17446
- }).catch(() => void 0);
17504
+ }).catch((err) => console.debug(`[session] prune failed: ${err}`));
17447
17505
  let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
17448
17506
  const recoveryLock = new RecoveryLock({ dir: wpaths.projectSessions, sessionStore });
17449
17507
  if (!resumeId && !flags["no-recovery"]) {
@@ -18866,6 +18924,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
18866
18924
  message: `\u26A0 ${color.yellow(`${lines.length} uncommitted change${lines.length > 1 ? "s" : ""}`)} \u2014 session ended without commit`
18867
18925
  };
18868
18926
  }
18927
+ return void 0;
18869
18928
  },
18870
18929
  onClear: () => {
18871
18930
  if (flags.tui && !flags["no-tui"]) return;