@wrongstack/cli 0.9.20 → 0.10.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/data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Curated model-catalog overlay (`providers.json`)
2
+
3
+ `providers.json` is a **curated override layer** that WrongStack deep-merges **on top of**
4
+ the live `https://models.dev/api.json` catalog. models.dev stays the base/primary source; this
5
+ file lets us **add** providers/models it doesn't carry and **fix** fields it gets wrong (a missing
6
+ model, a stale context limit, etc.) without waiting for an upstream fix or a release.
7
+
8
+ At runtime the registry resolves:
9
+
10
+ ```
11
+ merged = mergeModelsPayload(modelsDev, providers.json) // overlay wins
12
+ ```
13
+
14
+ It is loaded from (first non-empty wins):
15
+ 1. this file fetched from our GitHub raw URL (so it can refresh between releases), then
16
+ 2. this file bundled in the installed package (offline floor).
17
+
18
+ If models.dev is completely unreachable and there's no cache, a **non-empty** overlay still drives
19
+ the catalog on its own. An empty `{}` overlay is a safe no-op.
20
+
21
+ ## Shape
22
+
23
+ Same schema as `models.dev/api.json` — a map keyed by provider id. You only include the fields you
24
+ want to add or override; everything else falls through to the base. (JSON has no comments, hence
25
+ this README.)
26
+
27
+ ```jsonc
28
+ {
29
+ // Override just one field on an existing model — here, fix a context window.
30
+ "deepseek": {
31
+ "models": {
32
+ "deepseek-v4-pro": { "limit": { "context": 128000 } }
33
+ }
34
+ },
35
+
36
+ // Add a provider models.dev doesn't list at all.
37
+ "myco": {
38
+ "id": "myco",
39
+ "name": "My Co",
40
+ "npm": "@ai-sdk/openai-compatible", // determines the wire family
41
+ "api": "https://api.myco.example/v1",
42
+ "env": ["MYCO_API_KEY"],
43
+ "models": {
44
+ "myco-large": {
45
+ "id": "myco-large",
46
+ "name": "MyCo Large",
47
+ "tool_call": true,
48
+ "modalities": { "input": ["text"], "output": ["text"] },
49
+ "limit": { "context": 200000, "output": 16000 },
50
+ "cost": { "input": 0.5, "output": 1.5 }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Merge rules
58
+
59
+ - Provider in both → overlay scalar fields (`name`, `npm`, `api`, `env`, `doc`) override the base;
60
+ `models` are merged by id.
61
+ - Model in both → `{ ...baseModel, ...overlayModel }`, with the nested `limit` / `cost` /
62
+ `modalities` objects merged one level deeper — so `{"limit":{"context":…}}` overrides only the
63
+ context and keeps the base's `limit.output`.
64
+ - Anything only in the overlay is added.
65
+
66
+ ## Editing / refreshing
67
+
68
+ Use the helper to seed and sanity-check entries against upstream:
69
+
70
+ ```bash
71
+ pnpm run sync:models -- --extract deepseek:deepseek-v4-pro # print a paste-ready overlay snippet
72
+ pnpm run sync:models -- --diff # what we override vs upstream + drift
73
+ ```
74
+
75
+ Then edit `providers.json` and commit. Keep it **small and curated** — it is an override layer,
76
+ not a mirror of models.dev. Once models.dev catches up, drop the now-redundant override (`--diff`
77
+ flags those).
@@ -0,0 +1 @@
1
+ {}
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { MCPRegistry } from '@wrongstack/mcp';
13
13
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
14
14
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
15
15
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
16
+ import { fileURLToPath } from 'url';
16
17
  import * as readline from 'readline';
17
18
  import * as fs10 from 'fs';
18
19
  import { writeFileSync } from 'fs';
@@ -2353,6 +2354,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
2353
2354
  "recover",
2354
2355
  "no-alt-screen",
2355
2356
  "alt-screen",
2357
+ "mouse",
2356
2358
  "output-json",
2357
2359
  "prompt",
2358
2360
  "metrics",
@@ -9727,6 +9729,14 @@ function fmtTaskResultLine(r, color41) {
9727
9729
 
9728
9730
  // src/boot.ts
9729
9731
  init_update_check();
9732
+ var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
9733
+ function resolveBundledOverlayFile() {
9734
+ try {
9735
+ return fileURLToPath(new URL("../data/providers.json", import.meta.url));
9736
+ } catch {
9737
+ return void 0;
9738
+ }
9739
+ }
9730
9740
  function resolveBundledSkillsDir() {
9731
9741
  try {
9732
9742
  const req2 = createRequire(import.meta.url);
@@ -9742,6 +9752,10 @@ async function boot(argv) {
9742
9752
  flags["resume"] = positional[1];
9743
9753
  positional.splice(0, 2);
9744
9754
  }
9755
+ if (positional.length === 0) {
9756
+ if (flags["help"] === true) positional.push("help");
9757
+ else if (flags["version"] === true) positional.push("version");
9758
+ }
9745
9759
  let bootResult;
9746
9760
  try {
9747
9761
  bootResult = await bootConfig(flags);
@@ -9758,7 +9772,12 @@ async function boot(argv) {
9758
9772
  const reader = new ReadlineInputReader({ historyFile: wpaths.historyFile });
9759
9773
  const modelsRegistry = new DefaultModelsRegistry({
9760
9774
  cacheFile: wpaths.modelsCache,
9761
- ttlSeconds: 24 * 3600
9775
+ ttlSeconds: 24 * 3600,
9776
+ // Curated overlay merged on top of models.dev: fetched from GitHub raw for
9777
+ // freshness, with the bundled file as the offline floor.
9778
+ overlayUrl: GITHUB_PROVIDERS_OVERLAY_URL,
9779
+ overlayFile: resolveBundledOverlayFile(),
9780
+ overlayCacheFile: wpaths.modelsOverlayCache
9762
9781
  });
9763
9782
  let updateInfo;
9764
9783
  if (!flags["no-check"] && !process.env["WRONGSTACK_NO_CHECK"]) {
@@ -10911,9 +10930,15 @@ async function execute(deps) {
10911
10930
  // Default OFF so the terminal's native scrollback works for chat
10912
10931
  // history out of the box (mouse wheel / Shift+PgUp). Users who hit
10913
10932
  // resize/overlay-leak artifacts can opt back into alt-screen with
10914
- // `--alt-screen` or `/altscreen on`. `--no-alt-screen` still wins
10915
- // when both are passed.
10933
+ // `--alt-screen`. `--no-alt-screen` still wins when both are passed.
10916
10934
  altScreen: flags["alt-screen"] === true && flags["no-alt-screen"] !== true,
10935
+ // Full mouse mode: clickable pickers + in-app wheel scroll. Opt-in
10936
+ // via --mouse. The TUI combines altScreen || mouse to determine
10937
+ // whether to use the managed viewport (ScrollableHistory + in-app
10938
+ // scroll); --mouse alone enables that without requiring --alt-screen.
10939
+ // Mouse mode takes ownership of the terminal's mouse, suspending
10940
+ // native scroll/copy until `/mouse off`.
10941
+ mouse: flags.mouse === true,
10917
10942
  director,
10918
10943
  fleetRoster,
10919
10944
  onAfterExit: () => {
@@ -11208,7 +11233,11 @@ var MultiAgentHost = class {
11208
11233
  makeSubagentFactory(config) {
11209
11234
  return async (subCfg) => {
11210
11235
  const events = new EventBus();
11211
- const provider = await this.buildSubagentProvider(config, subCfg.provider);
11236
+ const provider = await this.buildSubagentProvider(
11237
+ config,
11238
+ subCfg.provider,
11239
+ subCfg.model ?? config.model
11240
+ );
11212
11241
  const subCwd = subCfg.cwd ?? this.deps.cwd;
11213
11242
  const baseSystem = await this.deps.systemPromptBuilder.build({
11214
11243
  cwd: subCwd,
@@ -11346,17 +11375,28 @@ var MultiAgentHost = class {
11346
11375
  * not configured (so a typo doesn't crash the whole run — we just
11347
11376
  * use the leader and the calling code can decide to error later).
11348
11377
  */
11349
- async buildSubagentProvider(config, overrideId) {
11378
+ async buildSubagentProvider(config, overrideId, model) {
11350
11379
  const providerId = overrideId && config.providers?.[overrideId] ? overrideId : config.provider;
11351
11380
  const newCfg = config.providers?.[providerId] ?? {
11352
11381
  type: providerId,
11353
11382
  apiKey: config.apiKey,
11354
11383
  baseUrl: config.baseUrl
11355
11384
  };
11356
- return makeProviderFromConfig(providerId, {
11385
+ const provider = makeProviderFromConfig(providerId, {
11357
11386
  ...newCfg,
11358
11387
  type: providerId
11359
11388
  });
11389
+ if (this.deps.modelsRegistry) {
11390
+ const resolvedModel = model ?? config.model;
11391
+ const caps = await capabilitiesFor(
11392
+ this.deps.modelsRegistry,
11393
+ providerId,
11394
+ resolvedModel
11395
+ ).catch(() => void 0);
11396
+ const mc = caps?.maxContext ?? config.context?.effectiveMaxContext ?? provider.capabilities.maxContext;
11397
+ if (mc && mc > 0) provider.capabilities.maxContext = mc;
11398
+ }
11399
+ return provider;
11360
11400
  }
11361
11401
  async spawnACP(subagentId, task, config) {
11362
11402
  const taskId = randomUUID();
@@ -12138,6 +12178,13 @@ function renderProgress3(ratio, width) {
12138
12178
  const capped = Math.min(width, filled);
12139
12179
  return FILLED2.repeat(capped) + EMPTY2.repeat(width - capped);
12140
12180
  }
12181
+ var createSessionEventBridge = (_writer, level) => ({
12182
+ append: async (_e) => {
12183
+ },
12184
+ level: level ?? "standard",
12185
+ allows: () => true
12186
+ });
12187
+ var resolveAuditLevel = (cfg) => cfg?.session?.auditLevel ?? "standard";
12141
12188
  function setupPipelines(params) {
12142
12189
  const { events, logger } = params;
12143
12190
  const pipelines = createDefaultPipelines();
@@ -12164,11 +12211,13 @@ function setupPipelines(params) {
12164
12211
  return pipelines;
12165
12212
  }
12166
12213
  async function setupCompaction(params) {
12167
- const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
12214
+ const { compactor, events, modelsRegistry, context, config, provider, pipelines, fullConfig, sessionWriter, sessionBridge: providedBridge } = params;
12168
12215
  const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
12169
12216
  const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
12170
12217
  let autoCompactor;
12171
12218
  if (config.context.autoCompact !== false) {
12219
+ const auditLevel = resolveAuditLevel(fullConfig ?? config);
12220
+ const sessionBridge = providedBridge ?? createSessionEventBridge(sessionWriter, auditLevel);
12172
12221
  autoCompactor = new AutoCompactionMiddleware(
12173
12222
  compactor,
12174
12223
  effectiveMaxContext,
@@ -12180,7 +12229,12 @@ async function setupCompaction(params) {
12180
12229
  soft: config.context.softThreshold,
12181
12230
  hard: config.context.hardThreshold
12182
12231
  },
12183
- { aggressiveOn: "soft", failureMode: "throw_on_hard", events }
12232
+ {
12233
+ aggressiveOn: "soft",
12234
+ failureMode: "throw_on_hard",
12235
+ events,
12236
+ sessionBridge
12237
+ }
12184
12238
  );
12185
12239
  pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
12186
12240
  }
@@ -12553,6 +12607,21 @@ async function setupSession(params) {
12553
12607
  }
12554
12608
 
12555
12609
  // src/index.ts
12610
+ var createSessionEventBridge2 = (_writer, level, _opts) => ({
12611
+ append: async (_e) => {
12612
+ },
12613
+ level: level ?? "standard",
12614
+ allows: () => true
12615
+ });
12616
+ var resolveAuditLevel2 = (cfg) => cfg?.session?.auditLevel ?? "standard";
12617
+ var resolveSessionLoggingConfig = (cfg) => ({
12618
+ auditLevel: resolveAuditLevel2(cfg),
12619
+ sampling: {
12620
+ toolProgress: {
12621
+ sampleRate: cfg?.session?.sampling?.toolProgress?.sampleRate ?? 8
12622
+ }
12623
+ }
12624
+ });
12556
12625
  function resolveBundledSkillsDir2() {
12557
12626
  try {
12558
12627
  const req2 = createRequire(import.meta.url);
@@ -12782,18 +12851,99 @@ async function main(argv) {
12782
12851
  const planPath = sessResult.planPath;
12783
12852
  const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
12784
12853
  const priorFleetState = sessResult.priorFleetState;
12854
+ const sessionConfig = resolveSessionLoggingConfig(config);
12855
+ const sessionBridge = createSessionEventBridge2(
12856
+ session,
12857
+ sessionConfig.auditLevel);
12785
12858
  const stats = new SessionStats(events, tokenCounter);
12786
12859
  const errorRing = [];
12787
12860
  events.on("error", (e) => {
12788
12861
  const err = e.err;
12789
12862
  const code = err && typeof err === "object" && "code" in err && typeof err.code === "string" ? err.code : "UNKNOWN";
12790
12863
  const message = e.err instanceof Error ? e.err.message : String(e.err);
12791
- errorRing.push({ ts: (/* @__PURE__ */ new Date()).toISOString(), phase: e.phase, code, message });
12864
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
12865
+ errorRing.push({ ts, phase: e.phase, code, message });
12792
12866
  if (errorRing.length > 5) errorRing.shift();
12867
+ sessionBridge.append({
12868
+ type: "error",
12869
+ ts,
12870
+ message,
12871
+ phase: e.phase
12872
+ }).catch(() => {
12873
+ });
12874
+ });
12875
+ events.on("tool.started", (e) => {
12876
+ sessionBridge.append({
12877
+ type: "tool_call_start",
12878
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12879
+ name: e.name,
12880
+ id: e.id,
12881
+ input: e.input
12882
+ }).catch(() => {
12883
+ });
12884
+ });
12885
+ events.on("tool.executed", (e) => {
12886
+ sessionBridge.append({
12887
+ type: "tool_call_end",
12888
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12889
+ name: e.name,
12890
+ id: e.id ?? "",
12891
+ durationMs: e.durationMs,
12892
+ outputSize: e.outputBytes ?? 0,
12893
+ ok: e.ok,
12894
+ outputBytes: e.outputBytes,
12895
+ outputTokens: e.outputTokens,
12896
+ outputLines: e.outputLines
12897
+ }).catch(() => {
12898
+ });
12899
+ });
12900
+ events.on("tool.progress", (e) => {
12901
+ sessionBridge.append({
12902
+ type: "tool_progress",
12903
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12904
+ name: e.name,
12905
+ id: e.id,
12906
+ event: { type: e.event.type, text: e.event.text, data: e.event.data }
12907
+ }).catch(() => {
12908
+ });
12909
+ });
12910
+ events.on("provider.retry", (e) => {
12911
+ sessionBridge.append({
12912
+ type: "provider_retry",
12913
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12914
+ providerId: e.providerId,
12915
+ attempt: e.attempt,
12916
+ delayMs: e.delayMs,
12917
+ status: e.status,
12918
+ description: e.description
12919
+ }).catch(() => {
12920
+ });
12921
+ });
12922
+ events.on("provider.error", (e) => {
12923
+ sessionBridge.append({
12924
+ type: "provider_error",
12925
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12926
+ providerId: e.providerId,
12927
+ status: e.status,
12928
+ description: e.description,
12929
+ retryable: e.retryable
12930
+ }).catch(() => {
12931
+ });
12793
12932
  });
12794
12933
  const pipelines = setupPipelines({ events, logger });
12795
12934
  const compactor = container.resolve(TOKENS.Compactor);
12796
- const { effectiveMaxContext, autoCompactor } = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
12935
+ const { effectiveMaxContext, autoCompactor } = await setupCompaction({
12936
+ compactor,
12937
+ events,
12938
+ modelsRegistry,
12939
+ context,
12940
+ config,
12941
+ provider,
12942
+ pipelines,
12943
+ fullConfig: config,
12944
+ sessionBridge
12945
+ // share the same bridge for consistent audit logging (compaction + errors + future)
12946
+ });
12797
12947
  const refreshMaxContext = async (providerId, modelId) => {
12798
12948
  if (!autoCompactor) return;
12799
12949
  const cap = await capabilitiesFor(modelsRegistry, providerId, modelId).catch(() => void 0);
@@ -12890,6 +13040,7 @@ async function main(argv) {
12890
13040
  toolRegistry,
12891
13041
  providerRegistry,
12892
13042
  configStore,
13043
+ modelsRegistry,
12893
13044
  events,
12894
13045
  systemPromptBuilder: promptBuilder,
12895
13046
  session,