@wrongstack/cli 0.298.1 → 0.298.3

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 CHANGED
@@ -75,3 +75,54 @@ pnpm run sync:models -- --diff # what we override
75
75
  Then edit `providers.json` and commit. Keep it **small and curated** — it is an override layer,
76
76
  not a mirror of models.dev. Once models.dev catches up, drop the now-redundant override (`--diff`
77
77
  flags those).
78
+
79
+ ## Overlay vs. per-user `customModels` — when to use which
80
+
81
+ There are **two** override layers in WrongStack, for two different audiences:
82
+
83
+ | Layer | Location | Audience | Purpose |
84
+ |---|---|---|---|
85
+ | **Curated overlay** | `packages/cli/data/providers.json` (this file) | **All users, repo-wide** | Fix upstream errors, add models/providers models.dev doesn't list, remove bad entries (`_removeProviders` / `_removeModels` magic keys). Ships with every release. |
86
+ | **`customModels` config** | `~/.wrongstack/profiles/<name>/config.json` → `providers.<id>.customModels` | **One user, one profile** | Per-user model visibility + per-model metadata overrides (limits, cost, modalities, capability flags) via the WebUI ModelListEditor or TUI `/auth` panel. |
87
+
88
+ **Decision rule:** if the fix benefits every user of the product (an upstream data error, a brand-new
89
+ model id), it belongs in the **overlay** — commit it here. If it's personal tuning (my context limit,
90
+ my custom endpoint's pricing), it belongs in the user's **`customModels`** config — never here.
91
+
92
+ Both layers deep-merge one level into `limit` / `cost` / `modalities` objects, and both follow the
93
+ same precedence contract at resolution time:
94
+
95
+ ```
96
+ top-level config.models > providers.<id>.customModels > overlay > models.dev catalog > wire-family defaults
97
+ ```
98
+
99
+ The top-level `config.models` record overrides provider-local entries for the same model id
100
+ (`mergeCustomModelDefs` in `packages/core/src/utils/merge-custom-models.ts`). Inline
101
+ models.dev-style objects inside `providers.<id>.models` are normalized into `customModels` at
102
+ config load — they are not a separate runtime layer.
103
+
104
+ ## Catalog cache & boot refresh
105
+
106
+ The live models.dev catalog is fetched at boot and cached:
107
+
108
+ - **Cache:** `~/.wrongstack/cache/models.dev.json` (fetched at startup; `--no-models-refresh`
109
+ skips the network fetch and uses the stale cache).
110
+ - **Overlay cache:** `~/.wrongstack/cache/models-overlay.json` (fetched from GitHub raw; the
111
+ bundled `providers.json` is the offline floor).
112
+ - Resolution: `merged = mergeModelsPayload(cachedModelsDev, overlay)` — the overlay always wins.
113
+
114
+ If models.dev is unreachable and no cache exists, a non-empty overlay still drives the catalog
115
+ on its own. Users never edit the cache files — they are regenerated on boot.
116
+
117
+ ## Modalities format (important)
118
+
119
+ `modalities.input` and `modalities.output` are **plain string arrays**, not objects:
120
+
121
+ ```jsonc
122
+ "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
123
+ ```
124
+
125
+ So `modalities.input[0] === "text"` (a string). The known values are `"text" | "image" | "audio" |
126
+ "video" | "pdf"` (see `MODELS_DEV_MODALITY_VALUES` in `packages/core/src/models/models-dev-schema.ts`).
127
+ New upstream values are tolerated at parse time — the schema validates "array of non-empty strings",
128
+ not a closed enum.
@@ -13,10 +13,10 @@ import {
13
13
  runOAuthLoginKind,
14
14
  runOAuthLoginMenu,
15
15
  validateFamily
16
- } from "./chunk-I2BM6FK6.js";
16
+ } from "./chunk-L5CE52XW.js";
17
17
  import {
18
18
  parseAuthFlags
19
- } from "./chunk-JBSQ7ONN.js";
19
+ } from "./chunk-ZNZJ34RF.js";
20
20
  import "./chunk-66T43Q4Y.js";
21
21
  import "./chunk-GO3TJICK.js";
22
22
  import {
@@ -501,4 +501,4 @@ async function runAuthRemove(deps, providerId) {
501
501
  export {
502
502
  authCmd
503
503
  };
504
- //# sourceMappingURL=auth-NLLGEYQR.js.map
504
+ //# sourceMappingURL=auth-G5YKKSRH.js.map
@@ -1,3 +1,4 @@
1
+ import type { ProviderModelStatusTracker } from '@wrongstack/core/coordination';
1
2
  import type { SubagentConfig, SubagentError, TaskResult } from '@wrongstack/core/types';
2
3
  /**
3
4
  * Resolve the fallback-model chain used when spawning the chimera-review
@@ -19,8 +20,14 @@ export declare function __resetReviewerRoundRobinCursor(value?: number): void;
19
20
  * Builds the pool from the configured primary + fallback chain, then picks
20
21
  * the next entry via round-robin. When the pool has <=1 usable entry the
21
22
  * original primary/fallbacks are returned unchanged.
23
+ *
24
+ * When a {@link ProviderModelStatusTracker} is supplied, blocked entries
25
+ * (waiting-room / token-reset-limit room) are filtered from both the pool
26
+ * and the round-robin pick so a 429-stricken model is never re-spawned on
27
+ * a concurrent reviewer turn. Without the tracker, the legacy pre-waiting-
28
+ * room behavior is preserved.
22
29
  */
23
- export declare function assignReviewerModelsRoundRobin(provider: string, model: string, fallbackModels: readonly string[]): {
30
+ export declare function assignReviewerModelsRoundRobin(provider: string, model: string, fallbackModels: readonly string[], statusTracker?: ProviderModelStatusTracker | undefined): {
24
31
  provider: string;
25
32
  model: string;
26
33
  fallbackModels: string[];
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LOCAL_LLM_PRESETS,
3
3
  runLiveProviderPicker
4
- } from "./chunk-JBSQ7ONN.js";
4
+ } from "./chunk-ZNZJ34RF.js";
5
5
  import {
6
6
  openBrowser,
7
7
  runCodexOAuthLogin,
@@ -1327,4 +1327,4 @@ export {
1327
1327
  addKeyForProvider,
1328
1328
  runAuthLocal
1329
1329
  };
1330
- //# sourceMappingURL=chunk-I2BM6FK6.js.map
1330
+ //# sourceMappingURL=chunk-L5CE52XW.js.map
@@ -1020,4 +1020,4 @@ export {
1020
1020
  runLiveProviderPicker,
1021
1021
  runPicker
1022
1022
  };
1023
- //# sourceMappingURL=chunk-JBSQ7ONN.js.map
1023
+ //# sourceMappingURL=chunk-ZNZJ34RF.js.map
@@ -80,11 +80,11 @@ import {
80
80
  runClaudeOAuthLogin,
81
81
  runCopilotOAuthLogin,
82
82
  validateFamily
83
- } from "./chunk-I2BM6FK6.js";
83
+ } from "./chunk-L5CE52XW.js";
84
84
  import {
85
85
  LOCAL_LLM_PRESETS,
86
86
  parseSpawnFlags
87
- } from "./chunk-JBSQ7ONN.js";
87
+ } from "./chunk-ZNZJ34RF.js";
88
88
  import {
89
89
  buildPickableProviders
90
90
  } from "./chunk-66T43Q4Y.js";
@@ -381,6 +381,187 @@ function createAuthPanelHost(deps) {
381
381
  return true;
382
382
  });
383
383
  },
384
+ editModelDetails(providerId, modelId, io) {
385
+ return runFlow(async () => {
386
+ const providers = await loadProviders();
387
+ const cfg = providers[providerId];
388
+ if (!cfg) {
389
+ io.onLog(`\u2717 Provider "${providerId}" no longer in config.`);
390
+ return false;
391
+ }
392
+ const catalogModel = await deps.modelsRegistry.getModel(
393
+ cfg.type && cfg.type !== providerId ? cfg.type : providerId,
394
+ modelId
395
+ ).catch(() => void 0);
396
+ const existing = cfg.customModels?.[modelId];
397
+ const currentMd = existing?.modelsDev ?? {};
398
+ io.onLog(
399
+ catalogModel ? `Catalog reference: ctx=${catalogModel.capabilities.maxContext ?? "?"}, out=${catalogModel.capabilities.maxOutput ?? "?"}` : `(no catalog entry for ${modelId})`
400
+ );
401
+ const name = (await io.prompt(
402
+ `Name (current: ${currentMd["name"] ?? modelId})`,
403
+ { secret: false }
404
+ )).trim();
405
+ const ctxRaw = (await io.prompt(
406
+ `Context window (current: ${currentMd.limit?.["context"] ?? "?"}, catalog: ${catalogModel?.capabilities.maxContext ?? "?"})`,
407
+ { secret: false }
408
+ )).trim();
409
+ const outRaw = (await io.prompt(
410
+ `Max output (current: ${existing?.maxOutput ?? currentMd.limit?.["output"] ?? "?"}, catalog: ${catalogModel?.capabilities.maxOutput ?? "?"})`,
411
+ { secret: false }
412
+ )).trim();
413
+ const costInRaw = (await io.prompt(
414
+ `Cost input $/1M (current: ${currentMd.cost?.["input"] ?? "?"}, catalog: ${catalogModel?.cost?.input ?? "?"})`,
415
+ { secret: false }
416
+ )).trim();
417
+ const costOutRaw = (await io.prompt(
418
+ `Cost output $/1M (current: ${currentMd.cost?.["output"] ?? "?"}, catalog: ${catalogModel?.cost?.output ?? "?"})`,
419
+ { secret: false }
420
+ )).trim();
421
+ const modelsDev = {};
422
+ if (name) modelsDev["name"] = name;
423
+ const limit = {};
424
+ if (ctxRaw) {
425
+ const n = Number(ctxRaw);
426
+ if (!Number.isNaN(n) && n >= 0) limit["context"] = n;
427
+ }
428
+ if (outRaw) {
429
+ const n = Number(outRaw);
430
+ if (!Number.isNaN(n) && n >= 0) limit["output"] = n;
431
+ }
432
+ if (Object.keys(limit).length > 0) modelsDev["limit"] = limit;
433
+ const cost = {};
434
+ if (costInRaw) {
435
+ const n = Number(costInRaw);
436
+ if (!Number.isNaN(n) && n >= 0) cost["input"] = n;
437
+ }
438
+ if (costOutRaw) {
439
+ const n = Number(costOutRaw);
440
+ if (!Number.isNaN(n) && n >= 0) cost["output"] = n;
441
+ }
442
+ if (Object.keys(cost).length > 0) modelsDev["cost"] = cost;
443
+ if (Object.keys(modelsDev).length === 0) {
444
+ io.onLog("(no changes \u2014 nothing entered)");
445
+ return true;
446
+ }
447
+ const err = await mutate((all) => {
448
+ const p = all[providerId];
449
+ if (!p) return `Provider "${providerId}" no longer in config.`;
450
+ if (!p.customModels) p.customModels = {};
451
+ const existingEntry = p.customModels[modelId] ?? {};
452
+ p.customModels[modelId] = {
453
+ ...existingEntry,
454
+ modelsDev: {
455
+ ...existingEntry.modelsDev ?? {},
456
+ ...modelsDev,
457
+ // Deep-merge limit/cost so partial overrides don't wipe sub-fields
458
+ ...limit || existingEntry.modelsDev ? {
459
+ limit: {
460
+ ...existingEntry.modelsDev?.["limit"] ?? {},
461
+ ...limit
462
+ }
463
+ } : {},
464
+ ...cost || existingEntry.modelsDev?.["cost"] ? {
465
+ cost: {
466
+ ...existingEntry.modelsDev?.["cost"] ?? {},
467
+ ...cost
468
+ }
469
+ } : {}
470
+ }
471
+ };
472
+ return null;
473
+ });
474
+ if (err) {
475
+ io.onLog(`\u2717 ${err}`);
476
+ return false;
477
+ }
478
+ io.onLog(`\u2713 ${modelId} updated`);
479
+ return true;
480
+ });
481
+ },
482
+ addModel(providerId, io, opts) {
483
+ return runFlow(async () => {
484
+ const providers = await loadProviders();
485
+ const cfg = providers[providerId];
486
+ if (!cfg) {
487
+ io.onLog(`\u2713 Provider "${providerId}" no longer in config.`);
488
+ return false;
489
+ }
490
+ const modelId = (await io.prompt("Model id", { secret: false })).trim();
491
+ if (!modelId) {
492
+ io.onLog("\u2717 Model id is required.");
493
+ return false;
494
+ }
495
+ if (opts?.fromCatalog) {
496
+ const catalogModel = await deps.modelsRegistry.getModel(
497
+ cfg.type && cfg.type !== providerId ? cfg.type : providerId,
498
+ modelId
499
+ ).catch(() => void 0);
500
+ if (catalogModel) {
501
+ io.onLog(
502
+ `Found in catalog: ctx=${catalogModel.capabilities.maxContext}, out=${catalogModel.capabilities.maxOutput}`
503
+ );
504
+ } else {
505
+ io.onLog(`(not found in catalog \u2014 entering as custom)`);
506
+ }
507
+ }
508
+ const err = await mutate((all) => {
509
+ const p = all[providerId];
510
+ if (!p) return `Provider "${providerId}" no longer in config.`;
511
+ if (!p.models) p.models = [];
512
+ if (!p.models.includes(modelId)) p.models.push(modelId);
513
+ return null;
514
+ });
515
+ if (err) {
516
+ io.onLog(`\u2717 ${err}`);
517
+ return false;
518
+ }
519
+ io.onLog(`\u2713 Added model "${modelId}" to ${providerId}`);
520
+ return true;
521
+ });
522
+ },
523
+ removeModel(providerId, modelId) {
524
+ return (async () => {
525
+ const err = await mutate((all) => {
526
+ const p = all[providerId];
527
+ if (!p) return `Provider "${providerId}" no longer in config.`;
528
+ if (p.models) {
529
+ p.models = p.models.filter((m) => m !== modelId);
530
+ if (p.models.length === 0) delete p.models;
531
+ }
532
+ if (p.customModels && modelId in p.customModels) {
533
+ delete p.customModels[modelId];
534
+ if (Object.keys(p.customModels).length === 0) delete p.customModels;
535
+ }
536
+ return null;
537
+ });
538
+ return err;
539
+ })();
540
+ },
541
+ resetModelToCatalog(providerId, modelId) {
542
+ return (async () => {
543
+ const providers = await loadProviders();
544
+ const cfg = providers[providerId];
545
+ if (!cfg) return `Provider "${providerId}" no longer in config.`;
546
+ const catalogModel = await deps.modelsRegistry.getModel(
547
+ cfg.type && cfg.type !== providerId ? cfg.type : providerId,
548
+ modelId
549
+ ).catch(() => void 0);
550
+ if (!catalogModel) {
551
+ return `Model "${modelId}" not found in catalog \u2014 cannot reset.`;
552
+ }
553
+ const err = await mutate((all) => {
554
+ const p = all[providerId];
555
+ if (!p) return `Provider "${providerId}" no longer in config.`;
556
+ if (p.customModels && modelId in p.customModels) {
557
+ delete p.customModels[modelId];
558
+ if (Object.keys(p.customModels).length === 0) delete p.customModels;
559
+ }
560
+ return null;
561
+ });
562
+ return err;
563
+ })();
564
+ },
384
565
  addCatalogProvider(catalogId, io) {
385
566
  return runFlow(async () => {
386
567
  const catalog = await deps.modelsRegistry.listProviders();
@@ -2842,7 +3023,7 @@ import {
2842
3023
  } from "@wrongstack/acp";
2843
3024
  import { ToolValidationError } from "@wrongstack/core/types";
2844
3025
  function buildAcpSubagentRunner(subagentId) {
2845
- let cmd = ACP_AGENT_COMMANDS[subagentId];
3026
+ let cmd = Object.prototype.hasOwnProperty.call(ACP_AGENT_COMMANDS, subagentId) ? ACP_AGENT_COMMANDS[subagentId] : void 0;
2846
3027
  if (!cmd) {
2847
3028
  const desc = findAgentDescriptor(subagentId);
2848
3029
  if (desc) {
@@ -3182,6 +3363,75 @@ import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
3182
3363
  import { EventBus } from "@wrongstack/core/kernel";
3183
3364
  import { AutoApprovePermissionPolicy } from "@wrongstack/core/security";
3184
3365
 
3366
+ // src/fleet/host-context.ts
3367
+ import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
3368
+ import { getSageRetrieval } from "@wrongstack/sage";
3369
+ async function resolveHostSubagentSkillContent(deps, roster, subCfg) {
3370
+ const rosterSkillNames = subCfg.role ? roster[subCfg.role]?.skillNames : void 0;
3371
+ const skillNames = [...new Set(subCfg.skillNames ?? rosterSkillNames ?? [])];
3372
+ const directContent = subCfg.skillContent?.trim();
3373
+ if (skillNames.length === 0 || !deps.skillLoader) return directContent ?? "";
3374
+ const resolved = [];
3375
+ let usedChars = 0;
3376
+ const maxChars = 16e3;
3377
+ const maxCharsPerSkill = 4e3;
3378
+ for (const skillName of skillNames) {
3379
+ try {
3380
+ const manifest = await deps.skillLoader.find(skillName);
3381
+ if (!manifest) continue;
3382
+ const body = (await deps.skillLoader.readSaveBody(skillName)).trim();
3383
+ if (!body) continue;
3384
+ const entry = `## Skill: ${skillName}
3385
+
3386
+ ${body.slice(0, maxCharsPerSkill)}`;
3387
+ if (usedChars + entry.length > maxChars) {
3388
+ console.warn(
3389
+ `[MultiAgentHost] resolveSubagentSkillContent: budget (${maxChars}) exhausted after ${resolved.length} skill(s); dropping "${skillName}" and remaining skills`
3390
+ );
3391
+ break;
3392
+ }
3393
+ resolved.push(entry);
3394
+ usedChars += entry.length;
3395
+ } catch {
3396
+ }
3397
+ }
3398
+ const sections = [
3399
+ directContent,
3400
+ resolved.length > 0 ? `# Role-prioritized skills
3401
+
3402
+ Apply these skills first for this assignment.
3403
+
3404
+ ${resolved.join("\n\n---\n\n")}` : void 0
3405
+ ].filter((section) => Boolean(section));
3406
+ return sections.join("\n\n");
3407
+ }
3408
+ async function retrieveHostSubagentMemory(deps, getLeaderMode, subCfg, taskContext) {
3409
+ const memoryPort = deps.container.safeResolve(TOKENS2.MemoryStore);
3410
+ const memory = memoryPort ? getSageRetrieval(memoryPort) : void 0;
3411
+ if (!memory?.retrieveForAudience) return [];
3412
+ const contextualTaskType = typeof taskContext?.["taskType"] === "string" ? taskContext["taskType"] : void 0;
3413
+ try {
3414
+ const taskType = subCfg.memoryContext?.taskType ?? contextualTaskType;
3415
+ const mode = subCfg.memoryContext?.mode ?? getLeaderMode?.();
3416
+ const matches = await memory.retrieveForAudience(
3417
+ {
3418
+ ...subCfg.role !== void 0 ? { role: subCfg.role } : {},
3419
+ ...taskType !== void 0 ? { taskType } : {},
3420
+ ...mode !== void 0 ? { mode } : {}
3421
+ },
3422
+ 20
3423
+ );
3424
+ await memory.recordInjection?.(
3425
+ matches.map((item) => item.id),
3426
+ "subagent_audience",
3427
+ deps.session.id
3428
+ );
3429
+ return matches.map((item) => item.text);
3430
+ } catch {
3431
+ return [];
3432
+ }
3433
+ }
3434
+
3185
3435
  // src/fleet/host-event-bridge.ts
3186
3436
  import * as path4 from "node:path";
3187
3437
  var BRIDGE_TEXT_CAP = 360;
@@ -3352,106 +3602,6 @@ function installSubagentEventBridge(opts) {
3352
3602
  };
3353
3603
  }
3354
3604
 
3355
- // src/fleet/host-session-writer.ts
3356
- function createParentSubagentSessionWriter(parentSession) {
3357
- return {
3358
- id: parentSession.id,
3359
- transcriptPath: parentSession.transcriptPath,
3360
- get pendingToolUses() {
3361
- return [];
3362
- },
3363
- append: (event) => parentSession.append({ ...event }),
3364
- appendBatch: (events) => parentSession.appendBatch(events.map((event) => ({ ...event }))),
3365
- flush: () => parentSession.flush(),
3366
- close: async () => {
3367
- },
3368
- recordFileChange: () => {
3369
- },
3370
- recordSideEffect: () => {
3371
- },
3372
- writeCheckpoint: async () => {
3373
- },
3374
- writeFileSnapshot: async () => {
3375
- },
3376
- truncateToCheckpoint: async () => 0,
3377
- clearSession: async () => {
3378
- },
3379
- writeInFlightMarker: async () => {
3380
- },
3381
- clearInFlightMarker: async () => {
3382
- }
3383
- };
3384
- }
3385
-
3386
- // src/fleet/host-context.ts
3387
- import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
3388
- import { getSageRetrieval } from "@wrongstack/sage";
3389
- async function resolveHostSubagentSkillContent(deps, roster, subCfg) {
3390
- const rosterSkillNames = subCfg.role ? roster[subCfg.role]?.skillNames : void 0;
3391
- const skillNames = [...new Set(subCfg.skillNames ?? rosterSkillNames ?? [])];
3392
- const directContent = subCfg.skillContent?.trim();
3393
- if (skillNames.length === 0 || !deps.skillLoader) return directContent ?? "";
3394
- const resolved = [];
3395
- let usedChars = 0;
3396
- const maxChars = 16e3;
3397
- const maxCharsPerSkill = 4e3;
3398
- for (const skillName of skillNames) {
3399
- try {
3400
- const manifest = await deps.skillLoader.find(skillName);
3401
- if (!manifest) continue;
3402
- const body = (await deps.skillLoader.readSaveBody(skillName)).trim();
3403
- if (!body) continue;
3404
- const entry = `## Skill: ${skillName}
3405
-
3406
- ${body.slice(0, maxCharsPerSkill)}`;
3407
- if (usedChars + entry.length > maxChars) {
3408
- console.warn(
3409
- `[MultiAgentHost] resolveSubagentSkillContent: budget (${maxChars}) exhausted after ${resolved.length} skill(s); dropping "${skillName}" and remaining skills`
3410
- );
3411
- break;
3412
- }
3413
- resolved.push(entry);
3414
- usedChars += entry.length;
3415
- } catch {
3416
- }
3417
- }
3418
- const sections = [
3419
- directContent,
3420
- resolved.length > 0 ? `# Role-prioritized skills
3421
-
3422
- Apply these skills first for this assignment.
3423
-
3424
- ${resolved.join("\n\n---\n\n")}` : void 0
3425
- ].filter((section) => Boolean(section));
3426
- return sections.join("\n\n");
3427
- }
3428
- async function retrieveHostSubagentMemory(deps, getLeaderMode, subCfg, taskContext) {
3429
- const memoryPort = deps.container.safeResolve(TOKENS2.MemoryStore);
3430
- const memory = memoryPort ? getSageRetrieval(memoryPort) : void 0;
3431
- if (!memory?.retrieveForAudience) return [];
3432
- const contextualTaskType = typeof taskContext?.["taskType"] === "string" ? taskContext["taskType"] : void 0;
3433
- try {
3434
- const taskType = subCfg.memoryContext?.taskType ?? contextualTaskType;
3435
- const mode = subCfg.memoryContext?.mode ?? getLeaderMode?.();
3436
- const matches = await memory.retrieveForAudience(
3437
- {
3438
- ...subCfg.role !== void 0 ? { role: subCfg.role } : {},
3439
- ...taskType !== void 0 ? { taskType } : {},
3440
- ...mode !== void 0 ? { mode } : {}
3441
- },
3442
- 20
3443
- );
3444
- await memory.recordInjection?.(
3445
- matches.map((item) => item.id),
3446
- "subagent_audience",
3447
- deps.session.id
3448
- );
3449
- return matches.map((item) => item.text);
3450
- } catch {
3451
- return [];
3452
- }
3453
- }
3454
-
3455
3605
  // src/fleet/host-provider.ts
3456
3606
  import { makeProviderFromConfig, withCatalogCapabilities } from "@wrongstack/providers";
3457
3607
  async function buildHostSubagentProvider(deps, config, overrideId, model) {
@@ -3532,6 +3682,37 @@ function resolveHostSubagentModelSelection(liveConfig, effectiveCfg, matrixTarge
3532
3682
  };
3533
3683
  }
3534
3684
 
3685
+ // src/fleet/host-session-writer.ts
3686
+ function createParentSubagentSessionWriter(parentSession) {
3687
+ return {
3688
+ id: parentSession.id,
3689
+ transcriptPath: parentSession.transcriptPath,
3690
+ get pendingToolUses() {
3691
+ return [];
3692
+ },
3693
+ append: (event) => parentSession.append({ ...event }),
3694
+ appendBatch: (events) => parentSession.appendBatch(events.map((event) => ({ ...event }))),
3695
+ flush: () => parentSession.flush(),
3696
+ close: async () => {
3697
+ },
3698
+ recordFileChange: () => {
3699
+ },
3700
+ recordSideEffect: () => {
3701
+ },
3702
+ writeCheckpoint: async () => {
3703
+ },
3704
+ writeFileSnapshot: async () => {
3705
+ },
3706
+ truncateToCheckpoint: async () => 0,
3707
+ clearSession: async () => {
3708
+ },
3709
+ writeInFlightMarker: async () => {
3710
+ },
3711
+ clearInFlightMarker: async () => {
3712
+ }
3713
+ };
3714
+ }
3715
+
3535
3716
  // src/fleet/host-subagent-factory.ts
3536
3717
  function createHostSubagentFactory(config, host) {
3537
3718
  return async (subCfg, task) => {
@@ -3544,7 +3725,14 @@ function createHostSubagentFactory(config, host) {
3544
3725
  const effectiveCfg = projectCfg ? applyProjectAgentConfig(subCfg, projectCfg, {
3545
3726
  protectSystemRole: isSystemRole
3546
3727
  }) : subCfg;
3547
- const matrixTarget = effectiveCfg.model ? void 0 : resolveSubagentModelTarget(liveConfig, effectiveCfg.role);
3728
+ const matrixTarget = effectiveCfg.model ? void 0 : resolveSubagentModelTarget(liveConfig, effectiveCfg.role, {
3729
+ // Thread the shared tracker so the resolved matrix target skips
3730
+ // (provider, model) pairs currently in the waiting room. Without
3731
+ // this, the subagent factory would seed its own primary from a
3732
+ // doomed model the leader just 429-stricken, and the fallback
3733
+ // extension would have to spend a turn rotating away.
3734
+ ...host.opts.statusTracker ? { statusTracker: host.opts.statusTracker } : {}
3735
+ });
3548
3736
  const modelSelection = resolveHostSubagentModelSelection(
3549
3737
  liveConfig,
3550
3738
  effectiveCfg,
@@ -3573,8 +3761,7 @@ function createHostSubagentFactory(config, host) {
3573
3761
  providerError = err;
3574
3762
  }
3575
3763
  }
3576
- if (!provider)
3577
- throw providerError ?? new Error("No permitted provider/model could be built.");
3764
+ if (!provider) throw providerError ?? new Error("No permitted provider/model could be built.");
3578
3765
  let subReasoningConfig = await resolveHostSubagentReasoningConfig(
3579
3766
  host.deps,
3580
3767
  effProvider,
@@ -29806,7 +29993,7 @@ async function runInteractive(cliCtx) {
29806
29993
  onEvent: evOn
29807
29994
  });
29808
29995
  const savedProviderCfg = config.providers?.[config.provider];
29809
- const { execute } = await import("./execution-HJ643JGZ.js");
29996
+ const { execute } = await import("./execution-BY556FCF.js");
29810
29997
  const stopHeapWatchdog = startSharedHeapWatchdog({
29811
29998
  collectStats: () => {
29812
29999
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -30038,4 +30225,4 @@ export {
30038
30225
  CLI_VERSION,
30039
30226
  runInteractive
30040
30227
  };
30041
- //# sourceMappingURL=cli-main-7QLL2PY6.js.map
30228
+ //# sourceMappingURL=cli-main-ZDZMCVLM.js.map
@@ -1289,6 +1289,29 @@ import { decryptConfigSecrets, encryptConfigSecrets, noOpVault } from "@wrongsta
1289
1289
  import { normalizeTokenSavingTier, resolveFleetChatVerbosity } from "@wrongstack/core/types";
1290
1290
  import { atomicWrite } from "@wrongstack/core/utils";
1291
1291
  import { getProcessRegistry } from "@wrongstack/tools";
1292
+ var PANEL_IDS_CLI = [
1293
+ "projectPicker",
1294
+ "fleet",
1295
+ "agents",
1296
+ "worktree",
1297
+ "plan",
1298
+ "todos",
1299
+ "queue",
1300
+ "processList",
1301
+ "goal",
1302
+ "sessions",
1303
+ "coordinator",
1304
+ "kanban",
1305
+ "connections"
1306
+ ];
1307
+ function coercePanelPositionMap(v) {
1308
+ const out = {};
1309
+ for (const id of PANEL_IDS_CLI) {
1310
+ const value = v?.[id];
1311
+ out[id] = value === "sidebar" ? "sidebar" : "bottom";
1312
+ }
1313
+ return out;
1314
+ }
1292
1315
  function coerceAgentSwarmMode(v) {
1293
1316
  if (v === true || v === void 0) return "bottom";
1294
1317
  if (v === false) return "off";
@@ -1365,6 +1388,16 @@ function createSettingsAdapter(ctx) {
1365
1388
  breakerAutoKillResetMs: cfg.circuitBreaker?.autoKillResetMs ?? 6e4,
1366
1389
  showModelReasoning: autonomy?.showModelReasoning ?? true,
1367
1390
  showAgentSwarmPanel: coerceAgentSwarmMode(autonomy?.showAgentSwarmPanel),
1391
+ // Migrate the legacy `autonomy.showAgentSwarmPanel: 'sidebar'` into
1392
+ // the new per-panel `panelPositions.fleet` map at the read boundary
1393
+ // so users with old configs (no `panelPositions` key on disk) get
1394
+ // their sidebar routing. Only migrate when the per-panel key is
1395
+ // UNDEFINED — an explicit `panelPositions.fleet: 'bottom'` must
1396
+ // NOT be reverted to `'sidebar'`.
1397
+ panelPositions: coercePanelPositionMap({
1398
+ ...autonomy?.panelPositions,
1399
+ ...coerceAgentSwarmMode(autonomy?.showAgentSwarmPanel) === "sidebar" && autonomy?.panelPositions?.fleet === void 0 ? { fleet: "sidebar" } : {}
1400
+ }),
1368
1401
  showSageMemoryInject: autonomy?.showSageMemoryInject ?? false,
1369
1402
  readSymbols: autonomy?.readAdvancedMode ?? false,
1370
1403
  sageMemoryInjectThreshold: cfg.Sage?.inject ? cfg.Sage.inject?.relationFloor : void 0
@@ -1372,7 +1405,7 @@ function createSettingsAdapter(ctx) {
1372
1405
  }
1373
1406
  async function saveSettings(s) {
1374
1407
  try {
1375
- if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0) {
1408
+ if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.showAgentSwarmPanel !== void 0 || s.panelPositions !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0) {
1376
1409
  const cfg = configStore.get();
1377
1410
  const activeProfileName = cfg.activeProfile ?? "default";
1378
1411
  const persistDeps = {
@@ -1421,6 +1454,7 @@ function createSettingsAdapter(ctx) {
1421
1454
  if (s.showModelReasoning !== void 0) autonomy.showModelReasoning = s.showModelReasoning;
1422
1455
  if (s.showAgentSwarmPanel !== void 0)
1423
1456
  autonomy.showAgentSwarmPanel = s.showAgentSwarmPanel;
1457
+ if (s.panelPositions !== void 0) autonomy.panelPositions = s.panelPositions;
1424
1458
  if (s.showSageMemoryInject !== void 0)
1425
1459
  autonomy.showSageMemoryInject = s.showSageMemoryInject;
1426
1460
  if (s.readSymbols !== void 0) autonomy.readAdvancedMode = s.readSymbols;
@@ -1517,8 +1551,24 @@ function createSettingsAdapter(ctx) {
1517
1551
  decrypted,
1518
1552
  targetPath
1519
1553
  );
1554
+ let mergedToWrite = decrypted;
1555
+ if (actualTarget !== targetPath) {
1556
+ try {
1557
+ const destRaw = await fs3.readFile(actualTarget, "utf8");
1558
+ const destParsed = JSON.parse(destRaw);
1559
+ const destDecrypted = decryptConfigSecrets(destParsed, noOpVault);
1560
+ mergedToWrite = { ...destDecrypted, ...decrypted };
1561
+ } catch (err) {
1562
+ if (err.code !== "ENOENT") {
1563
+ throw new Error(
1564
+ `Failed to read destination config at ${actualTarget}: ${err instanceof Error ? err.message : String(err)}`,
1565
+ { cause: err }
1566
+ );
1567
+ }
1568
+ }
1569
+ }
1520
1570
  const isProjectTarget = actualTarget === wpaths.inProjectConfig;
1521
- const toWrite = isProjectTarget ? filterSafeForProject(decrypted) : decrypted;
1571
+ const toWrite = isProjectTarget ? filterSafeForProject(mergedToWrite) : mergedToWrite;
1522
1572
  const encrypted = encryptConfigSecrets(toWrite, noOpVault);
1523
1573
  await fs3.mkdir(path7.dirname(actualTarget), { recursive: true });
1524
1574
  await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -1773,8 +1823,8 @@ var reviewerRoundRobinCursor = 0;
1773
1823
  function __resetReviewerRoundRobinCursor(value = 0) {
1774
1824
  reviewerRoundRobinCursor = value;
1775
1825
  }
1776
- function assignReviewerModelsRoundRobin(provider, model, fallbackModels) {
1777
- const pool = buildReviewerModelPool(provider, model, fallbackModels);
1826
+ function assignReviewerModelsRoundRobin(provider, model, fallbackModels, statusTracker) {
1827
+ const pool = buildReviewerModelPool(provider, model, fallbackModels, statusTracker);
1778
1828
  if (pool.length <= 1) {
1779
1829
  return {
1780
1830
  provider,
@@ -1786,7 +1836,8 @@ function assignReviewerModelsRoundRobin(provider, model, fallbackModels) {
1786
1836
  pool,
1787
1837
  reviewerRoundRobinCursor,
1788
1838
  provider,
1789
- model
1839
+ model,
1840
+ statusTracker
1790
1841
  );
1791
1842
  reviewerRoundRobinCursor = assignment.nextCursor;
1792
1843
  return {
@@ -2810,6 +2861,7 @@ function installChimeraReviewHandler({
2810
2861
  agent,
2811
2862
  config,
2812
2863
  projectDir,
2864
+ statusTracker,
2813
2865
  persistReview = persistChimeraReview,
2814
2866
  setPendingWork
2815
2867
  }) {
@@ -2876,7 +2928,12 @@ function installChimeraReviewHandler({
2876
2928
  const baseProvider = rawProvider || tProvider || config.provider;
2877
2929
  const baseModel = rawModel || tModel || config.model;
2878
2930
  const baseFallbacks = p.reviewFallbackModels ? [...p.reviewFallbackModels] : resolveReviewerFallbackModels(void 0);
2879
- const assigned = assignReviewerModelsRoundRobin(baseProvider, baseModel, baseFallbacks);
2931
+ const assigned = assignReviewerModelsRoundRobin(
2932
+ baseProvider,
2933
+ baseModel,
2934
+ baseFallbacks,
2935
+ statusTracker
2936
+ );
2880
2937
  const ladder = buildReviewerAttemptLadder({
2881
2938
  assigned,
2882
2939
  profileChain: effectiveFallbackChain(config),
@@ -3016,7 +3073,20 @@ ${reviewBody}`;
3016
3073
  audience: "leaders",
3017
3074
  subject,
3018
3075
  body,
3019
- priority: "normal"
3076
+ priority: "normal",
3077
+ // Session-affinity stamp: the recipient's leader filter
3078
+ // uses this to drop the message for any leader whose current
3079
+ // session id does NOT match the originating session of the
3080
+ // review (`reviewSessionId` is captured above from the
3081
+ // agent's active run / session id). Without this token the
3082
+ // project-wide mailbox would deliver every chimera result to
3083
+ // every leader, inviting them to act on reports that belong
3084
+ // to a different session.
3085
+ sessionAffinity: {
3086
+ sessionId: reviewSessionId,
3087
+ reportId,
3088
+ kind: "chimera.review"
3089
+ }
3020
3090
  });
3021
3091
  if (!mailMsg?.id) throw new Error("mailbox.send returned no message id");
3022
3092
  reviewMailMessageId = mailMsg.id;
@@ -4446,6 +4516,11 @@ async function execute(deps) {
4446
4516
  agent,
4447
4517
  config,
4448
4518
  projectDir: wpaths.projectDir,
4519
+ // Thread the shared tracker so the round-robin Chimera reviewer picks
4520
+ // skip (provider, model) pairs currently in the waiting room. Without
4521
+ // this, a 429-stricken model is re-spawned on every concurrent reviewer
4522
+ // turn and burns the whole chain instead of staying quarantined.
4523
+ statusTracker,
4449
4524
  setPendingWork: (work) => {
4450
4525
  pendingChimeraWork = work;
4451
4526
  }
@@ -4867,4 +4942,4 @@ export {
4867
4942
  execute,
4868
4943
  resolveReviewerFallbackModels
4869
4944
  };
4870
- //# sourceMappingURL=execution-HJ643JGZ.js.map
4945
+ //# sourceMappingURL=execution-BY556FCF.js.map
@@ -1,3 +1,4 @@
1
+ import type { ProviderModelStatusTracker } from '@wrongstack/core/coordination';
1
2
  import { type ChimeraReviewCompletePayload } from '@wrongstack/core/plugin';
2
3
  import type { ExecuteDeps } from './execute-deps.js';
3
4
  type Director = NonNullable<ExecuteDeps['fleet']['director']>;
@@ -27,9 +28,18 @@ export type InstallChimeraReviewHandlerOptions = {
27
28
  agent: Agent;
28
29
  config: Config;
29
30
  projectDir: string;
31
+ /**
32
+ * Shared provider/model status tracker. When supplied, the round-robin
33
+ * Chimera reviewer spawn skips (provider, model) pairs currently in the
34
+ * waiting room (`state: 'blocked'`) so a 429-stricken model is never
35
+ * re-spawned on a concurrent reviewer turn. The tracker is the same
36
+ * singleton the leader's `/provider-status` reads from; see
37
+ * `packages/core/src/coordination/provider-status-tracker.ts`.
38
+ */
39
+ statusTracker?: ProviderModelStatusTracker | undefined;
30
40
  persistReview?: ((payload: ChimeraReviewCompletePayload, projectDir: string) => Promise<void>) | undefined;
31
41
  setPendingWork: (work: PendingChimeraWork) => void;
32
42
  };
33
- export declare function installChimeraReviewHandler({ events, director, session, mailbox, agent, config, projectDir, persistReview, setPendingWork, }: InstallChimeraReviewHandlerOptions): void;
43
+ export declare function installChimeraReviewHandler({ events, director, session, mailbox, agent, config, projectDir, statusTracker, persistReview, setPendingWork, }: InstallChimeraReviewHandlerOptions): void;
34
44
  export {};
35
45
  //# sourceMappingURL=execution-chimera-review.d.ts.map
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  parseArgs,
37
37
  runPicker,
38
38
  saveToGlobalConfig
39
- } from "./chunk-JBSQ7ONN.js";
39
+ } from "./chunk-ZNZJ34RF.js";
40
40
  import {
41
41
  isKeylessLocalProvider,
42
42
  visibleModelIds
@@ -1497,7 +1497,7 @@ function compactSingleLine(text) {
1497
1497
  var loaders = {
1498
1498
  acp: async () => (await import("./acp-S4LZQB66.js")).acpCmd,
1499
1499
  init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
1500
- auth: async () => (await import("./auth-NLLGEYQR.js")).authCmd,
1500
+ auth: async () => (await import("./auth-G5YKKSRH.js")).authCmd,
1501
1501
  update: async () => (await import("./update-MV6TUACD.js")).updateCmd,
1502
1502
  sessions: async () => (await import("./sessions-config-NLCMK3BY.js")).sessionsCmd,
1503
1503
  config: async () => (await import("./sessions-config-NLCMK3BY.js")).configCmd,
@@ -3149,7 +3149,7 @@ async function initializeCli(argv) {
3149
3149
  async function main(argv) {
3150
3150
  const cliCtx = await initializeCli(argv);
3151
3151
  if (typeof cliCtx === "number") return cliCtx;
3152
- const { runInteractive } = await import("./cli-main-7QLL2PY6.js");
3152
+ const { runInteractive } = await import("./cli-main-ZDZMCVLM.js");
3153
3153
  return runInteractive(cliCtx);
3154
3154
  }
3155
3155
 
@@ -66,6 +66,14 @@ export interface LiveSettingsInput {
66
66
  /** Agent swarm panel placement: 'bottom' (lower region), 'sidebar' (right sidebar), or 'off' (hidden).
67
67
  * Backward-compat: legacy boolean values are coerced by the TUI settings adapter. Default: 'bottom'. */
68
68
  showAgentSwarmPanel?: 'bottom' | 'sidebar' | 'off' | boolean | undefined;
69
+ /**
70
+ * Per-panel position map (one entry per F-key panel id). Each value is
71
+ * 'bottom' (F-key behavior) or 'sidebar' (right-sidebar twin). Persisted
72
+ * as `autonomy.panelPositions` in the project/profile config. The TUI
73
+ * auto-save hook passes a full PanelPositionMap (13 entries); the adapter
74
+ * stores it as-is. Default: every panel 'bottom' when unset.
75
+ */
76
+ panelPositions?: Readonly<Record<string, 'bottom' | 'sidebar'>> | undefined;
69
77
  /** Show SAGE Memory Inject blocks in tool results. Default: false. */
70
78
  showSageMemoryInject?: boolean | undefined;
71
79
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/cli",
3
- "version": "0.298.1",
3
+ "version": "0.298.3",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack CLI — terminal AI coding agent with provider catalog from models.dev. Provides `wrongstack` and `wstack` binaries.",
6
6
  "keywords": [
@@ -42,29 +42,29 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "ws": "^8.21.1",
45
- "@wrongstack/acp": "0.298.1",
46
- "@wrongstack/mcp": "0.298.1",
47
- "@wrongstack/bench": "0.298.1",
48
- "@wrongstack/kanban": "0.298.1",
49
- "@wrongstack/providers": "0.298.1",
50
- "@wrongstack/plug-lsp": "0.298.1",
51
- "@wrongstack/runtime": "0.298.1",
52
- "@wrongstack/core": "0.298.1",
53
- "@wrongstack/plugins": "0.298.1",
54
- "@wrongstack/sdd": "0.298.1",
55
- "@wrongstack/security-scanner": "0.298.1",
56
- "@wrongstack/simpleui": "0.298.1",
57
- "@wrongstack/techstack": "0.298.1",
58
- "@wrongstack/sage": "0.298.1",
59
- "@wrongstack/telegram": "0.298.1",
60
- "@wrongstack/webui": "0.298.1",
61
- "@wrongstack/webui-server": "0.298.1",
62
- "@wrongstack/tools": "0.298.1",
63
- "@wrongstack/tui": "0.298.1",
64
- "@wrongstack/webui-hq": "0.298.1"
45
+ "@wrongstack/acp": "0.298.3",
46
+ "@wrongstack/mcp": "0.298.3",
47
+ "@wrongstack/plug-lsp": "0.298.3",
48
+ "@wrongstack/core": "0.298.3",
49
+ "@wrongstack/bench": "0.298.3",
50
+ "@wrongstack/kanban": "0.298.3",
51
+ "@wrongstack/sdd": "0.298.3",
52
+ "@wrongstack/runtime": "0.298.3",
53
+ "@wrongstack/security-scanner": "0.298.3",
54
+ "@wrongstack/simpleui": "0.298.3",
55
+ "@wrongstack/providers": "0.298.3",
56
+ "@wrongstack/plugins": "0.298.3",
57
+ "@wrongstack/telegram": "0.298.3",
58
+ "@wrongstack/sage": "0.298.3",
59
+ "@wrongstack/tui": "0.298.3",
60
+ "@wrongstack/techstack": "0.298.3",
61
+ "@wrongstack/tools": "0.298.3",
62
+ "@wrongstack/webui": "0.298.3",
63
+ "@wrongstack/webui-server": "0.298.3",
64
+ "@wrongstack/webui-hq": "0.298.3"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@wrongstack/desktop": "0.298.1"
67
+ "@wrongstack/desktop": "0.298.3"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/node": "^26.1.2",