@wrongstack/cli 1.0.4 → 1.0.7

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.
Files changed (44) hide show
  1. package/dist/{acp-AB54FWM2.js → acp-A5EQEXQO.js} +69 -11
  2. package/dist/{auth-WDMSNZ3S.js → auth-3K6EN5JP.js} +3 -3
  3. package/dist/boot/container-wiring.d.ts +2 -0
  4. package/dist/boot/short-circuit-desktop.d.ts +1 -0
  5. package/dist/{chunk-VUVOMXWP.js → chunk-3VXYY5JC.js} +8 -1
  6. package/dist/{chunk-U6PGWDMZ.js → chunk-4OLAZIIN.js} +2 -2
  7. package/dist/{chunk-I7V2OYVH.js → chunk-5EA6OTLJ.js} +3 -6
  8. package/dist/{chunk-O4A3HHKD.js → chunk-672UXYL3.js} +2 -2
  9. package/dist/{chunk-ONOT5OJ2.js → chunk-FSZ5QU3M.js} +2 -2
  10. package/dist/{chunk-KNMIBKLV.js → chunk-JY52HWL6.js} +47 -1
  11. package/dist/{chunk-BIWE22O5.js → chunk-RLCKKLJA.js} +8 -8
  12. package/dist/{chunk-VFCJOWJB.js → chunk-RMQNCHGO.js} +8 -7
  13. package/dist/{chunk-3I5ZJXM5.js → chunk-XIZZ2EXG.js} +161 -62
  14. package/dist/{cli-context-WFC3BNGM.js → cli-context-TIAP6YQK.js} +95 -15
  15. package/dist/cli-context.d.ts +2 -0
  16. package/dist/{cli-main-GU3YZLMO.js → cli-main-AKJHDSHX.js} +1111 -721
  17. package/dist/config-doctor.d.ts +1 -1
  18. package/dist/execute-deps.d.ts +3 -1
  19. package/dist/{execution-RAZDDQFC.js → execution-FJE27VQ6.js} +105 -28
  20. package/dist/{hq-5XHEIIH6.js → hq-G5PCP325.js} +8 -6
  21. package/dist/hq-command-controller.d.ts +15 -0
  22. package/dist/hq-server/auth-state.d.ts +6 -0
  23. package/dist/hq-server/auth.d.ts +10 -1
  24. package/dist/hq-server/routes/auth/common.d.ts +34 -0
  25. package/dist/hq-server/routes/auth/password-routes.d.ts +4 -2
  26. package/dist/hq-server/types.d.ts +18 -0
  27. package/dist/{hq-server-CNUTBWTK.js → hq-server-G3VVVMIG.js} +3 -3
  28. package/dist/index.js +8 -5
  29. package/dist/input-reader.d.ts +2 -2
  30. package/dist/{mcp-HOMLC4WE.js → mcp-FX7TKPTV.js} +10 -8
  31. package/dist/mcp-serve.d.ts +31 -0
  32. package/dist/{modeldiag-46CMWUNM.js → modeldiag-CJSFHZ3J.js} +55 -31
  33. package/dist/permission-prompt-mirror.d.ts +70 -0
  34. package/dist/permission-prompt.d.ts +10 -1
  35. package/dist/{providers-models-6MOGAOVK.js → providers-models-LFNZ5WI7.js} +2 -2
  36. package/dist/{short-circuit-flags-YDMLAQ5R.js → short-circuit-flags-RAT6UZVI.js} +2 -2
  37. package/dist/slash-commands/subagent-models.d.ts +4 -0
  38. package/dist/subagent-models/panel-service.d.ts +28 -0
  39. package/dist/subcommands/handlers/modeldiag-test.d.ts +7 -0
  40. package/dist/{subcommands-7N4RIK6H.js → subcommands-VFVUTYAV.js} +2 -2
  41. package/dist/webui-server/prefs-seeding.d.ts +8 -0
  42. package/dist/{webui-server-OBV5JYJV.js → webui-server-3NYXBZHX.js} +36 -12
  43. package/dist/wiring/hq-telemetry.d.ts +7 -0
  44. package/package.json +28 -28
@@ -47,7 +47,7 @@ export interface PluginSchemaInfo {
47
47
  * every theme change), and a phantom `agents` entry (that field is nested
48
48
  * under `acp`, never top-level) waved a stray top-level `agents` block
49
49
  * through. Add a field to `Config` and `tsc` now names it here. */
50
- declare const KNOWN_TOP_LEVEL_KEYS: readonly ['version', 'activeProfile', 'provider', 'model', 'apiKey', 'baseUrl', 'maxConcurrent', 'uiLocale', 'themePreset', 'providers', 'models', 'modelMatrix', 'modelTiers', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'context', 'tools', 'mcpServers', 'acp', 'fallbackModels', 'fallbackBridge', 'fallbackProfiles', 'fallbackProfile', 'fallbackAuto', 'fallbackMaxLastResortCandidates', 'fallbackStickiness', 'fallbackGateSeconds', 'hooks', 'plugins', 'pluginManager', 'log', 'features', 'Sage', 'skills', 'yolo', 'nextPrediction', 'cwd', 'autonomy', 'hints', 'debugStream', 'configScope', 'indexing', 'circuitBreaker', 'adaptiveConcurrency', 'launch', 'session', 'chronicle', 'modelRuntime', 'systemPrompt', 'hq', 'fleet', 'brain', 'sync', 'cloudSync', 'git', 'extensions'];
50
+ declare const KNOWN_TOP_LEVEL_KEYS: readonly ['version', 'activeProfile', 'provider', 'model', 'apiKey', 'baseUrl', 'maxConcurrent', 'uiLocale', 'themePreset', 'providers', 'models', 'modelMatrix', 'modelTiers', 'favoriteModels', 'disabledModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'context', 'tools', 'mcpServers', 'acp', 'fallbackModels', 'fallbackBridge', 'fallbackProfiles', 'fallbackProfile', 'fallbackAuto', 'fallbackMaxLastResortCandidates', 'fallbackStickiness', 'fallbackGateSeconds', 'hooks', 'plugins', 'pluginManager', 'log', 'features', 'Sage', 'skills', 'yolo', 'nextPrediction', 'cwd', 'autonomy', 'hints', 'debugStream', 'configScope', 'indexing', 'circuitBreaker', 'adaptiveConcurrency', 'launch', 'session', 'chronicle', 'modelRuntime', 'systemPrompt', 'hq', 'fleet', 'brain', 'sync', 'cloudSync', 'git', 'extensions'];
51
51
  /** Compile-time `never` assertion: instantiating it with a non-empty union
52
52
  * fails and the TS error text spells out the offending key names. */
53
53
  type AssertNever<T extends never> = T;
@@ -14,6 +14,7 @@ import type { BrainAutoRisk } from '@wrongstack/core/execution';
14
14
  import type { JournalEntry } from '@wrongstack/core/goal';
15
15
  import type { EventBus } from '@wrongstack/core/kernel';
16
16
  import type { SlashCommandRegistry } from '@wrongstack/core/registry';
17
+ import type { StatuslineDensities, StatuslineLines } from '@wrongstack/core/statusline';
17
18
  import type { QueueStore } from '@wrongstack/core/storage';
18
19
  import type { AttachmentStore, AutonomyStage, Config, ConfigStore, MemoryPort, Message, ModelsRegistry, ModeStore, PromptLoader, ProviderConfig, ResolvedProvider, SessionEvent, SessionStore, SessionWriter, SkillLoader, TokenCounter } from '@wrongstack/core/types';
19
20
  import type { WstackPaths } from '@wrongstack/core/utils';
@@ -25,7 +26,6 @@ import type { ReadlineInputReader } from './input-reader.js';
25
26
  import type { LiveSettingsInput } from './live-settings-input.js';
26
27
  import type { TerminalRenderer } from './renderer.js';
27
28
  import type { AutonomyMode } from './services/autonomy-mode.js';
28
- import type { StatuslineDensities, StatuslineLines } from '@wrongstack/core/statusline';
29
29
  import type { StatuslineConfigKey } from './services/statusline-config.js';
30
30
  import type { SessionStats } from './session-stats.js';
31
31
  import type { UpdateInfo } from './update-check.js';
@@ -163,6 +163,8 @@ interface ProviderDeps {
163
163
  tools?: boolean | undefined;
164
164
  vision?: boolean | undefined;
165
165
  reasoning?: boolean | undefined;
166
+ /** Documented reasoning-effort levels (drives the picker strip). */
167
+ effortLevels?: readonly string[] | undefined;
166
168
  maxContext?: number | undefined;
167
169
  maxOutput?: number | undefined;
168
170
  inputCost?: number | undefined;
@@ -212,7 +212,7 @@ async function runWebUIDispatch(ctx) {
212
212
  const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
213
213
  agent.disableInteractiveConfirmation();
214
214
  renderer.setSilent(true);
215
- const { runWebUI } = await import("./webui-server-OBV5JYJV.js");
215
+ const { runWebUI } = await import("./webui-server-3NYXBZHX.js");
216
216
  const flagValue = (names) => {
217
217
  for (const name of names) {
218
218
  if (!Object.hasOwn(flags, name)) continue;
@@ -1255,10 +1255,13 @@ async function onSDDOutput(output) {
1255
1255
 
1256
1256
  // src/boot/tui-session-resume.ts
1257
1257
  import * as path7 from "node:path";
1258
- import { restoreSessionSubagentPolicy } from "@wrongstack/core/coordination";
1258
+ import {
1259
+ restoreSessionSubagentModelPlan,
1260
+ restoreSessionSubagentPolicy
1261
+ } from "@wrongstack/core/coordination";
1259
1262
  import { attachTodosCheckpoint, loadTodosCheckpoint } from "@wrongstack/core/storage";
1260
1263
  import { projectLastRequestTokens } from "@wrongstack/core/types/session-timeline";
1261
- import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
1264
+ import { sessionScopedPath as sessionScopedPath2, toErrorMessage } from "@wrongstack/core/utils";
1262
1265
  var RESERVATION_WINDOW_MS = 6e4;
1263
1266
  var RESERVATION_RENEW_MS = 2e4;
1264
1267
  function messageText(content) {
@@ -1288,7 +1291,7 @@ async function resumeSession(ctx, sessionId) {
1288
1291
  };
1289
1292
  const warnings = [];
1290
1293
  const warn = (label, err) => {
1291
- warnings.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);
1294
+ warnings.push(`${label}: ${toErrorMessage(err)}`);
1292
1295
  };
1293
1296
  let stage = "start";
1294
1297
  const setStage = (next) => {
@@ -1309,7 +1312,7 @@ async function resumeSession(ctx, sessionId) {
1309
1312
  throw new TypeError("@wrongstack/tui does not export replaySessionMessages");
1310
1313
  }
1311
1314
  } catch (err) {
1312
- return fail(stage, err instanceof Error ? err.message : String(err));
1315
+ return fail(stage, toErrorMessage(err));
1313
1316
  }
1314
1317
  setStage("resolve_id");
1315
1318
  let canonicalSessionId = sessionId;
@@ -1323,11 +1326,11 @@ async function resumeSession(ctx, sessionId) {
1323
1326
  level: "error",
1324
1327
  event: "execution.resume_id_resolve_failed",
1325
1328
  sessionId,
1326
- message: err instanceof Error ? err.message : String(err),
1329
+ message: toErrorMessage(err),
1327
1330
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1328
1331
  })
1329
1332
  );
1330
- return fail(stage, err instanceof Error ? err.message : String(err));
1333
+ return fail(stage, toErrorMessage(err));
1331
1334
  }
1332
1335
  const previousSessionId = agent.ctx.session?.id;
1333
1336
  let hydrated;
@@ -1369,7 +1372,7 @@ async function resumeSession(ctx, sessionId) {
1369
1372
  level: "warn",
1370
1373
  event: "execution.resume_reservation_renew_failed",
1371
1374
  sessionId: canonicalSessionId,
1372
- message: err instanceof Error ? err.message : String(err),
1375
+ message: toErrorMessage(err),
1373
1376
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1374
1377
  })
1375
1378
  );
@@ -1425,6 +1428,7 @@ async function resumeSession(ctx, sessionId) {
1425
1428
  try {
1426
1429
  agent.ctx.state.replaceMessages(resumed.data.messages);
1427
1430
  restoreSessionSubagentPolicy(agent.ctx, resumed.data.events, resumed.data.subagentsAllowed);
1431
+ restoreSessionSubagentModelPlan(agent.ctx, resumed.data.events);
1428
1432
  } catch (err) {
1429
1433
  agent.ctx.session = oldWriter;
1430
1434
  agent.ctx.state.replaceMessages(oldMessages);
@@ -1441,7 +1445,7 @@ async function resumeSession(ctx, sessionId) {
1441
1445
  JSON.stringify({
1442
1446
  level: "error",
1443
1447
  event: "execution.resume_journal_flush_failed",
1444
- message: err instanceof Error ? err.message : String(err),
1448
+ message: toErrorMessage(err),
1445
1449
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1446
1450
  })
1447
1451
  );
@@ -1498,7 +1502,7 @@ async function resumeSession(ctx, sessionId) {
1498
1502
  event: "execution.resume_model_restore_failed",
1499
1503
  provider: targetProviderId,
1500
1504
  model: targetModel,
1501
- message: err instanceof Error ? err.message : String(err),
1505
+ message: toErrorMessage(err),
1502
1506
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1503
1507
  })
1504
1508
  );
@@ -1526,7 +1530,7 @@ async function resumeSession(ctx, sessionId) {
1526
1530
  JSON.stringify({
1527
1531
  level: "error",
1528
1532
  event: "execution.session_end_append_failed",
1529
- message: err instanceof Error ? err.message : String(err),
1533
+ message: toErrorMessage(err),
1530
1534
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1531
1535
  })
1532
1536
  );
@@ -1539,7 +1543,7 @@ async function resumeSession(ctx, sessionId) {
1539
1543
  JSON.stringify({
1540
1544
  level: "error",
1541
1545
  event: "execution.session_close_failed",
1542
- message: err instanceof Error ? err.message : String(err),
1546
+ message: toErrorMessage(err),
1543
1547
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1544
1548
  })
1545
1549
  );
@@ -1580,7 +1584,7 @@ async function resumeSession(ctx, sessionId) {
1580
1584
  level: "error",
1581
1585
  event: "execution.resume_identity_rollback_failed",
1582
1586
  sessionId: previousSessionId,
1583
- message: rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr),
1587
+ message: toErrorMessage(rollbackErr),
1584
1588
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1585
1589
  })
1586
1590
  );
@@ -1602,7 +1606,7 @@ async function resumeSession(ctx, sessionId) {
1602
1606
  JSON.stringify({
1603
1607
  level: "error",
1604
1608
  event: "execution.resume_post_swap_rollback_failed",
1605
- message: rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr),
1609
+ message: toErrorMessage(rollbackErr),
1606
1610
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1607
1611
  })
1608
1612
  );
@@ -1613,7 +1617,7 @@ async function resumeSession(ctx, sessionId) {
1613
1617
  JSON.stringify({
1614
1618
  level: "error",
1615
1619
  event: "execution.resume_opened_writer_close_failed",
1616
- message: closeErr instanceof Error ? closeErr.message : String(closeErr),
1620
+ message: toErrorMessage(closeErr),
1617
1621
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1618
1622
  })
1619
1623
  );
@@ -1631,7 +1635,7 @@ async function resumeSession(ctx, sessionId) {
1631
1635
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1632
1636
  })
1633
1637
  );
1634
- const message = err instanceof Error ? err.message : String(err);
1638
+ const message = toErrorMessage(err);
1635
1639
  if (err instanceof Error && err.name === "SessionOwnershipConflictError") {
1636
1640
  return fail(stage, message);
1637
1641
  }
@@ -1670,7 +1674,7 @@ async function resumeSession(ctx, sessionId) {
1670
1674
  level: "error",
1671
1675
  event: "execution.resume_readonly_render_failed",
1672
1676
  sessionId: canonicalSessionId,
1673
- message: renderErr instanceof Error ? renderErr.message : String(renderErr),
1677
+ message: toErrorMessage(renderErr),
1674
1678
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1675
1679
  })
1676
1680
  );
@@ -3203,6 +3207,75 @@ var FleetStatusLine = class {
3203
3207
  }
3204
3208
  };
3205
3209
 
3210
+ // src/subagent-models/panel-service.ts
3211
+ import {
3212
+ emptySubagentModelPlan,
3213
+ formatSubagentSlot,
3214
+ getSessionSubagentModelPlan,
3215
+ setSessionSubagentModelPlan,
3216
+ subagentSlotOccupancy
3217
+ } from "@wrongstack/core/coordination";
3218
+ function currentPlan(sessionId) {
3219
+ return getSessionSubagentModelPlan(sessionId) ?? emptySubagentModelPlan();
3220
+ }
3221
+ function createSubagentModelsPanelHost(deps) {
3222
+ const write = async (mutate) => {
3223
+ const ctx = deps.getContext();
3224
+ if (!ctx?.session) return "No active session \u2014 the plan cannot be saved.";
3225
+ const plan = structuredClone(currentPlan(ctx.session.id));
3226
+ mutate(plan);
3227
+ try {
3228
+ await setSessionSubagentModelPlan(ctx, plan);
3229
+ return null;
3230
+ } catch (err) {
3231
+ return err instanceof Error ? err.message : String(err);
3232
+ }
3233
+ };
3234
+ return {
3235
+ snapshot() {
3236
+ const sessionId = deps.getContext()?.session?.id;
3237
+ const plan = currentPlan(sessionId);
3238
+ const occupancy = subagentSlotOccupancy(sessionId);
3239
+ const session = deps.getSessionTarget?.();
3240
+ return {
3241
+ enabled: plan.enabled,
3242
+ lock: plan.lock,
3243
+ followSessionModel: plan.followSessionModel === true,
3244
+ sessionTarget: session?.provider ? `${session.provider}/${session.model ?? "?"}` : session?.model ?? "",
3245
+ lanes: plan.slots.map((slot, i) => ({
3246
+ target: formatSubagentSlot(slot),
3247
+ busy: occupancy[i]?.subagentIds.length ?? 0,
3248
+ ...slot.label ? { label: slot.label } : {}
3249
+ })),
3250
+ roles: Object.entries(plan.roles ?? {}).map(([role, slot]) => ({ role, target: formatSubagentSlot(slot) })).sort((a, b) => a.role.localeCompare(b.role))
3251
+ };
3252
+ },
3253
+ setLane(index, target) {
3254
+ return write((plan) => {
3255
+ if (index < 0 || index >= plan.slots.length) return;
3256
+ const slot = {};
3257
+ if (target.provider) slot.provider = target.provider;
3258
+ if (target.model) slot.model = target.model;
3259
+ if (target.tier) slot.tier = target.tier;
3260
+ if (target.fallbackProfile) slot.fallbackProfile = target.fallbackProfile;
3261
+ if (target.label) slot.label = target.label;
3262
+ plan.slots[index] = slot;
3263
+ });
3264
+ },
3265
+ clearLane(index) {
3266
+ return write((plan) => {
3267
+ if (index < 0 || index >= plan.slots.length) return;
3268
+ plan.slots[index] = {};
3269
+ });
3270
+ },
3271
+ toggle(field) {
3272
+ return write((plan) => {
3273
+ plan[field] = plan[field] !== true;
3274
+ });
3275
+ }
3276
+ };
3277
+ }
3278
+
3206
3279
  // src/chimera-work-registry.ts
3207
3280
  function createChimeraWorkRegistry() {
3208
3281
  const pending = /* @__PURE__ */ new Set();
@@ -4592,7 +4665,7 @@ import {
4592
4665
  color as color11,
4593
4666
  estimateRequestTokensCalibrated as estimateRequestTokensCalibrated2,
4594
4667
  hasOpenTodos as hasOpenTodos2,
4595
- toErrorMessage as toErrorMessage3
4668
+ toErrorMessage as toErrorMessage4
4596
4669
  } from "@wrongstack/core/utils";
4597
4670
  import { readClipboardImage, routeImagesForModel } from "@wrongstack/runtime";
4598
4671
  import { createAutoProceedLoopGuard } from "@wrongstack/tools/auto-proceed-loop-guard";
@@ -4820,11 +4893,11 @@ function registerReplClient(opts) {
4820
4893
 
4821
4894
  // src/repl-eternal-loop.ts
4822
4895
  import { goalFilePath as goalFilePath2, summarizeUsage } from "@wrongstack/core/goal";
4823
- import { color as color8, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
4896
+ import { color as color8, toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
4824
4897
 
4825
4898
  // src/repl-goal-banner.ts
4826
4899
  import { goalFilePath, loadGoal } from "@wrongstack/core/goal";
4827
- import { color as color7, expectDefined, toErrorMessage } from "@wrongstack/core/utils";
4900
+ import { color as color7, expectDefined, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
4828
4901
  async function loadGoalSafe(opts) {
4829
4902
  if (!opts.projectRoot) return null;
4830
4903
  try {
@@ -4858,7 +4931,7 @@ async function renderGoalBanner(opts) {
4858
4931
  try {
4859
4932
  await opts.slashRegistry.dispatch("/autonomy eternal", opts.agent.ctx);
4860
4933
  } catch (err) {
4861
- opts.renderer.writeError(`Auto-resume failed: ${toErrorMessage(err)}`);
4934
+ opts.renderer.writeError(`Auto-resume failed: ${toErrorMessage2(err)}`);
4862
4935
  }
4863
4936
  } else {
4864
4937
  opts.renderer.write(
@@ -4934,7 +5007,7 @@ async function runReplEternalLoop(opts, onInterruptsReset) {
4934
5007
  return true;
4935
5008
  }
4936
5009
  } catch (err) {
4937
- opts.renderer.writeError(`[eternal] ${toErrorMessage2(err)}`);
5010
+ opts.renderer.writeError(`[eternal] ${toErrorMessage3(err)}`);
4938
5011
  }
4939
5012
  await new Promise((resolve4) => setTimeout(resolve4, 250));
4940
5013
  return true;
@@ -5005,7 +5078,7 @@ async function runReplEternalLoop(opts, onInterruptsReset) {
5005
5078
  return true;
5006
5079
  }
5007
5080
  } catch (err) {
5008
- opts.renderer.writeError(`[parallel] ${toErrorMessage2(err)}`);
5081
+ opts.renderer.writeError(`[parallel] ${toErrorMessage3(err)}`);
5009
5082
  }
5010
5083
  await new Promise((resolve4) => setTimeout(resolve4, 250));
5011
5084
  return true;
@@ -5245,7 +5318,7 @@ ${lines.join("\n")}
5245
5318
  if (res?.message) opts.renderer.write(`${res.message}
5246
5319
  `);
5247
5320
  } catch (err) {
5248
- opts.renderer.writeError(toErrorMessage3(err));
5321
+ opts.renderer.writeError(toErrorMessage4(err));
5249
5322
  }
5250
5323
  continue;
5251
5324
  }
@@ -5337,7 +5410,7 @@ ${color11.dim(taskList2)}
5337
5410
  }
5338
5411
  }
5339
5412
  } catch (err) {
5340
- opts.renderer.writeError(toErrorMessage3(err));
5413
+ opts.renderer.writeError(toErrorMessage4(err));
5341
5414
  }
5342
5415
  continue;
5343
5416
  }
@@ -5616,7 +5689,7 @@ ${color11.dim(lines)}
5616
5689
  }
5617
5690
  }
5618
5691
  } catch (err) {
5619
- opts.renderer.writeError(toErrorMessage3(err));
5692
+ opts.renderer.writeError(toErrorMessage4(err));
5620
5693
  } finally {
5621
5694
  activeCtrl = void 0;
5622
5695
  }
@@ -5642,7 +5715,7 @@ async function pasteClipboardImage(builder, opts) {
5642
5715
  opts.renderer.write(color11.dim(` \u21B3 ${placeholder} (PNG ${kb}KB)
5643
5716
  `));
5644
5717
  } catch (err) {
5645
- opts.renderer.writeError(`Clipboard image error: ${toErrorMessage3(err)}`);
5718
+ opts.renderer.writeError(`Clipboard image error: ${toErrorMessage4(err)}`);
5646
5719
  }
5647
5720
  }
5648
5721
 
@@ -6554,6 +6627,10 @@ async function execute(deps) {
6554
6627
  getBrainData,
6555
6628
  onBrainRiskLevel,
6556
6629
  brainPanelHost: brainRuntime ? createBrainPanelHost({ brainRuntime }) : void 0,
6630
+ subagentModelsHost: createSubagentModelsPanelHost({
6631
+ getContext: () => agent.ctx,
6632
+ getSessionTarget: () => ({ provider: config.provider, model: config.model })
6633
+ }),
6557
6634
  getShadowData,
6558
6635
  onShadowStart,
6559
6636
  onShadowStop,
@@ -6840,4 +6917,4 @@ export {
6840
6917
  execute,
6841
6918
  resolveReviewerFallbackModels
6842
6919
  };
6843
- //# sourceMappingURL=execution-RAZDDQFC.js.map
6920
+ //# sourceMappingURL=execution-FJE27VQ6.js.map
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  normalizeHqPublicOrigin,
9
9
  resolveAuditActor
10
- } from "./chunk-I7V2OYVH.js";
10
+ } from "./chunk-5EA6OTLJ.js";
11
11
  import {
12
12
  parseHqIpAllowlist
13
13
  } from "./chunk-Q7E3BPDU.js";
@@ -62,7 +62,7 @@ var hqCmd = async (args, deps) => {
62
62
  return 1;
63
63
  };
64
64
  async function startServer(deps) {
65
- const { startHqServer } = await import("./hq-server-CNUTBWTK.js");
65
+ const { startHqServer } = await import("./hq-server-G3VVVMIG.js");
66
66
  const dataDir = resolveDataDir(deps);
67
67
  const flags = deps.flags ?? {};
68
68
  const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
@@ -357,7 +357,9 @@ async function computeAuditHashField(dataDir, warn) {
357
357
  return hash !== void 0 ? { contentHash: hash } : {};
358
358
  }
359
359
  var TOKEN_CAPABILITIES = {
360
- browser: ["control.enqueue"],
360
+ // `control.approve` is grantable but never default — see
361
+ // DEFAULT_TOKEN_CAPABILITIES below.
362
+ browser: ["control.enqueue", "control.approve"],
361
363
  client: ["telemetry.publish", "control.execute"]
362
364
  };
363
365
  var DEFAULT_TOKEN_CAPABILITIES = {
@@ -727,7 +729,7 @@ function printHelp(deps) {
727
729
  `
728
730
  );
729
731
  deps.renderer.write(
730
- ` --capabilities <csv> Token grants (browser: control.enqueue; client: telemetry.publish,control.execute).
732
+ ` --capabilities <csv> Token grants (browser: control.enqueue,control.approve; client: telemetry.publish,control.execute).
731
733
  `
732
734
  );
733
735
  deps.renderer.write(
@@ -785,7 +787,7 @@ function printTokenHelp(deps) {
785
787
  `
786
788
  );
787
789
  deps.renderer.write(
788
- ` --capabilities <csv> Comma-separated capability grants (browser: control.enqueue; client: telemetry.publish,control.execute).
790
+ ` --capabilities <csv> Comma-separated capability grants (browser: control.enqueue,control.approve; client: telemetry.publish,control.execute).
789
791
  `
790
792
  );
791
793
  deps.renderer.write(
@@ -827,4 +829,4 @@ export {
827
829
  hqCmd,
828
830
  resolveAuditActor
829
831
  };
830
- //# sourceMappingURL=hq-5XHEIIH6.js.map
832
+ //# sourceMappingURL=hq-G5PCP325.js.map
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import type { RemoteMailbox } from '@wrongstack/core/coordination';
19
19
  import type { HqPublisherCommandResult } from '@wrongstack/core/hq';
20
+ import type { UserInputResponse } from '@wrongstack/core/types';
20
21
  /**
21
22
  * Mutable holder for the command dispatch targets. Each field is populated
22
23
  * lazily as the corresponding subsystem comes online (director on
@@ -68,6 +69,20 @@ export interface HqCommandController {
68
69
  ownsSession?: ((sessionId: string) => boolean) | undefined;
69
70
  /** Whether raw shell execution is explicitly opted-in by the operator. */
70
71
  allowRunCommand: () => boolean;
72
+ /**
73
+ * Answer a permission prompt that is live on this host.
74
+ *
75
+ * Returns false when the prompt is no longer pending — it was answered at
76
+ * the keyboard, aborted, or timed out. HQ mirrors prompts rather than owning
77
+ * them, so losing the race is the normal case and must be reported back
78
+ * instead of silently swallowed.
79
+ *
80
+ * Bound to the process-wide approval registry
81
+ * (`createApprovalRegistry` in `@wrongstack/core/hq`), which is the only
82
+ * holder of the resolver closures.
83
+ */
84
+ resolveApproval?: ((toolUseId: string, decision: 'yes' | 'no' | 'always' | 'deny', sessionId?: string | undefined) => boolean) | undefined;
85
+ resolveUserInput?: ((requestId: string, response: UserInputResponse, sessionId?: string | undefined) => boolean) | undefined;
71
86
  /** Apply a lifecycle transition through the project's Kanban IPC client. */
72
87
  kanbanTransition?: ((input: {
73
88
  boardId: string;
@@ -6,6 +6,12 @@ export interface HqAuthState {
6
6
  tokenStats(): NonNullable<HqSnapshot['totals']['tokenStats']>;
7
7
  }
8
8
  interface HqAuthStateOptions {
9
+ /**
10
+ * The server's `requireBrowserAuth` option, stamped onto the state so every
11
+ * `hqAuthRequired(mutableAuth)` gate observes it without having to be handed
12
+ * it separately. See the field's doc on `HqRouterMutableAuth`.
13
+ */
14
+ requireBrowserAuth?: boolean | undefined;
9
15
  /**
10
16
  * Invoked after every {@link HqAuthState.apply}, with the freshly projected
11
17
  * `mutableAuth`. The server wires this to the WS-010 exposure re-assessment
@@ -38,7 +38,16 @@ export type HqBrowserAuthResult = HqBrowserAuthContext | HqCookieAuthContext | u
38
38
  * inline style is not script execution.
39
39
  */
40
40
  export declare function setHqSecurityHeaders(res: http.ServerResponse): void;
41
- export declare function hasTrustedBrowserOrigin(req: http.IncomingMessage, boundHost?: string, boundPort?: number, trustedPublicOrigins?: ReadonlySet<string>, allowFileOrigin?: boolean): boolean;
41
+ export declare function hasTrustedBrowserOrigin(req: http.IncomingMessage, boundHost?: string, boundPort?: number, trustedPublicOrigins?: ReadonlySet<string>, allowFileOrigin?: boolean,
42
+ /**
43
+ * Whether this surface has any credential configured — pass
44
+ * `hqAuthRequired(mutableAuth, requireBrowserAuth)`.
45
+ *
46
+ * Defaults to `false`, the STRICT side, deliberately: a caller that forgets
47
+ * to wire it gets the tighter rule rather than the looser one. That is the
48
+ * opposite of how the WS-077 gates drifted.
49
+ */
50
+ credentialedSurface?: boolean): boolean;
42
51
  export declare function serializeHqSessionCookie(sessionId: string, secret: string): string;
43
52
  export declare function parseHqSessionCookie(value: string, secret: string): string | undefined;
44
53
  export declare function parseCookieHeader(cookieHeader: string | undefined): Record<string, string>;
@@ -1,7 +1,41 @@
1
1
  import type * as http from 'node:http';
2
2
  import { type HqAuthFile } from '@wrongstack/core/hq';
3
3
  import { type HqBrowserAuthResult } from '../../auth.js';
4
+ import type { LoginAttemptStore } from '../../login-attempt-store.js';
4
5
  import type { HqRouterMutableAuth, HqSessionEntry } from '../../types.js';
6
+ /**
7
+ * Record a failed second-factor confirmation and return the new failure count.
8
+ *
9
+ * Shared by every route that verifies a factor outside `/api/login`
10
+ * (totp/disable, password change). Kept here rather than copied per route: the
11
+ * whole point is that each of those routes feeds the SAME per-IP counter, so a
12
+ * attacker cannot get a fresh budget by switching endpoints.
13
+ */
14
+ export declare function recordVerifyFailure(loginAttempts: LoginAttemptStore, clientIp: string): number;
15
+ /** Outcome of {@link consumeTotpCode}. */
16
+ export type TotpConsumeResult = 'ok' | 'invalid' | 'replayed';
17
+ /**
18
+ * Verify a TOTP code AND burn it, so it cannot be presented a second time.
19
+ *
20
+ * A TOTP code stays valid for its whole time step plus the verification
21
+ * window — roughly 90 seconds. Without a single-use record, one observed code
22
+ * can be replayed at a *different* endpoint for as long as that window lasts.
23
+ * That is not theoretical here: `/api/login/verify` advanced
24
+ * `totpLastUsedCounter` and the other two verification sites
25
+ * (`totp/disable`, `auth/password`) called the counter-less `verifyTotp`, so a
26
+ * code already spent on a login replayed into removing 2FA outright or
27
+ * rotating the password and cookie secret.
28
+ *
29
+ * Kept in this module for the same reason as {@link recordVerifyFailure}: the
30
+ * property only holds if EVERY site that verifies a code shares one counter.
31
+ * A route that verifies a TOTP code by any other means is a bug —
32
+ * `hq-totp-single-use.test.ts` enumerates the call sites to keep it that way.
33
+ *
34
+ * The counter is advanced in memory first and persisted best-effort, matching
35
+ * the pre-existing behaviour at `/api/login/verify`: a failed write must not
36
+ * turn a consumed code back into an unconsumed one for this process.
37
+ */
38
+ export declare function consumeTotpCode(code: string, mutableAuth: HqRouterMutableAuth, dataDir: string, applyAuthFile: ApplyHqAuthFile): Promise<TotpConsumeResult>;
5
39
  export declare function isLoopbackRequest(req: http.IncomingMessage): boolean;
6
40
  export declare const HQ_AUTH_ADMIN_CAPABILITY = "auth.admin";
7
41
  export declare function callerCanAdministerAuth(auth: HqBrowserAuthResult): boolean;
@@ -3,7 +3,9 @@ import type { LoginAttemptStore } from '../../login-attempt-store.js';
3
3
  import type { HqRouterMutableAuth, HqSessionEntry } from '../../types.js';
4
4
  import { type ApplyHqAuthFile } from './common.js';
5
5
  export declare function handleApiAuthStatus(req: http.IncomingMessage, res: http.ServerResponse, url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, requireBrowserAuth: boolean | undefined, trustedPublicOrigins: Set<string>, secureCookies: boolean | undefined): Promise<void>;
6
- export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number, sessionCapabilities?: string[]): Promise<void>;
6
+ export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number,
7
+ /** Where `auth.json` lives — needed to persist a transparent KDF upgrade. */
8
+ dataDir: string, sessionCapabilities?: string[]): Promise<void>;
7
9
  export declare function handleApiLogout(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, secureCookies: boolean | undefined): Promise<void>;
8
- export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined, applyAuthFile: ApplyHqAuthFile): Promise<void>;
10
+ export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined, applyAuthFile: ApplyHqAuthFile, loginAttempts: LoginAttemptStore, trustedProxyHops: number): Promise<void>;
9
11
  //# sourceMappingURL=password-routes.d.ts.map
@@ -103,6 +103,24 @@ export interface HqRouterMutableAuth {
103
103
  * the surface fails closed instead of opening up.
104
104
  */
105
105
  requireAuthFloor?: boolean | undefined;
106
+ /**
107
+ * The server's `requireBrowserAuth` option, carried on the state rather than
108
+ * passed to every gate.
109
+ *
110
+ * `hqAuthRequired` takes it as an optional second argument, and five gates
111
+ * called it with one — `command-handlers`, three in `mailbox-handlers`, and
112
+ * `mailbox-gateway-manager` — so a public relay did not raise the auth
113
+ * requirement on those routes. That is the WS-077 drift class the helper's
114
+ * own docstring warns about, recurring on the same routes. Holding the value
115
+ * on the state makes the one-argument call correct instead of incomplete.
116
+ *
117
+ * Required, not optional, and deliberately so. Optional would mean a second
118
+ * construction site could omit it and every one-argument gate would silently
119
+ * read `undefined` — the same drift this field exists to end, one level down.
120
+ * With `exactOptionalPropertyTypes` the compiler now makes every builder say
121
+ * what it means, including `undefined`.
122
+ */
123
+ requireBrowserAuth: boolean | undefined;
106
124
  }
107
125
  /**
108
126
  * Session record stored in the server-side `sessions` Map.
@@ -10,9 +10,9 @@ import {
10
10
  readLocalSubagentTranscript,
11
11
  sanitizeApiError,
12
12
  startHqServer
13
- } from "./chunk-3I5ZJXM5.js";
13
+ } from "./chunk-XIZZ2EXG.js";
14
14
  import "./chunk-HXJYHAR4.js";
15
- import "./chunk-I7V2OYVH.js";
15
+ import "./chunk-5EA6OTLJ.js";
16
16
  import "./chunk-Q7E3BPDU.js";
17
17
  export {
18
18
  DEFAULT_HOST,
@@ -27,4 +27,4 @@ export {
27
27
  sanitizeApiError,
28
28
  startHqServer
29
29
  };
30
- //# sourceMappingURL=hq-server-CNUTBWTK.js.map
30
+ //# sourceMappingURL=hq-server-G3VVVMIG.js.map
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  applyNodeEnvDefault,
4
4
  applySessionShellDefault
5
- } from "./chunk-O4A3HHKD.js";
5
+ } from "./chunk-672UXYL3.js";
6
6
  import {
7
7
  CLI_VERSION
8
8
  } from "./chunk-XJXDOF63.js";
@@ -16,20 +16,21 @@ async function main(argv) {
16
16
  applySessionShellDefault();
17
17
  const earlyFlags = parseArgs(argv).flags;
18
18
  if (earlyFlags["help"] === true || earlyFlags["version"] === true) {
19
- const { handleHelpVersionShortCircuit } = await import("./short-circuit-flags-YDMLAQ5R.js");
19
+ const { handleHelpVersionShortCircuit } = await import("./short-circuit-flags-RAT6UZVI.js");
20
20
  const earlyExit = await handleHelpVersionShortCircuit(argv);
21
21
  if (earlyExit !== null) return earlyExit;
22
22
  }
23
- const { initializeCli } = await import("./cli-context-WFC3BNGM.js");
23
+ const { initializeCli } = await import("./cli-context-TIAP6YQK.js");
24
24
  const cliCtx = await initializeCli(argv);
25
25
  if (typeof cliCtx === "number") return cliCtx;
26
- const { runInteractive } = await import("./cli-main-GU3YZLMO.js");
26
+ const { runInteractive } = await import("./cli-main-AKJHDSHX.js");
27
27
  return runInteractive(cliCtx);
28
28
  }
29
29
 
30
30
  // src/cli-entry-point.ts
31
31
  import { resolve } from "node:path";
32
32
  import { pathToFileURL } from "node:url";
33
+ import { scrubErrorText } from "@wrongstack/core/security";
33
34
  import { installCrashShield, runFatalSalvageSync, writeErr } from "@wrongstack/core/utils";
34
35
  var SQLITE_WARNING_RE = /sqlite is an experimental feature/i;
35
36
  function installSqliteWarningFilter() {
@@ -128,7 +129,9 @@ function runAsMain(mainFn) {
128
129
  },
129
130
  (err) => {
130
131
  runFatalSalvageSync();
131
- writeErr((err instanceof Error ? err.stack : String(err)) + "\n");
132
+ writeErr(
133
+ scrubErrorText(err instanceof Error ? err.stack ?? err.message : String(err)) + "\n"
134
+ );
132
135
  process.exitCode = 1;
133
136
  scheduleForcedExit(1);
134
137
  }
@@ -1,4 +1,4 @@
1
- import type { InputReader, PromptOption } from '@wrongstack/core/types';
1
+ import type { InputReader, PromptOption, ReadKeyOptions } from '@wrongstack/core/types';
2
2
  interface ReadlineInputReaderOptions {
3
3
  historyFile?: string | undefined;
4
4
  prompt?: string | undefined;
@@ -30,7 +30,7 @@ export declare class ReadlineInputReader implements InputReader {
30
30
  * protect and the ANSI clear/repaint would be noise in a file.
31
31
  */
32
32
  private installPromptGuard;
33
- readKey(prompt: string, options: PromptOption[]): Promise<string>;
33
+ readKey(prompt: string, options: PromptOption[], opts?: ReadKeyOptions): Promise<string>;
34
34
  /**
35
35
  * Read a line of input while masking each character with a bullet so the
36
36
  * user gets visual confirmation that bytes are arriving (especially on