@wrongstack/cli 1.0.6 → 1.0.8

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 (34) hide show
  1. package/dist/{acp-GOQURNC2.js → acp-A5EQEXQO.js} +61 -6
  2. package/dist/{auth-WBXXIQUO.js → auth-3K6EN5JP.js} +3 -3
  3. package/dist/boot/container-wiring.d.ts +2 -0
  4. package/dist/{chunk-VUVOMXWP.js → chunk-3VXYY5JC.js} +8 -1
  5. package/dist/{chunk-NR7BD73C.js → chunk-4OLAZIIN.js} +2 -2
  6. package/dist/{chunk-5A5GCSJC.js → chunk-FSZ5QU3M.js} +2 -2
  7. package/dist/{chunk-KNMIBKLV.js → chunk-JY52HWL6.js} +47 -1
  8. package/dist/{chunk-QDQPF4PB.js → chunk-RLCKKLJA.js} +7 -7
  9. package/dist/{chunk-VPS2SJW4.js → chunk-RMQNCHGO.js} +5 -3
  10. package/dist/{chunk-PQZPHGGD.js → chunk-XIZZ2EXG.js} +34 -2
  11. package/dist/{cli-context-BYKJDAFF.js → cli-context-TIAP6YQK.js} +83 -12
  12. package/dist/cli-context.d.ts +2 -0
  13. package/dist/{cli-main-5WZBH3WA.js → cli-main-AKJHDSHX.js} +1006 -634
  14. package/dist/config-doctor.d.ts +1 -1
  15. package/dist/execute-deps.d.ts +3 -1
  16. package/dist/{execution-Z6A26EG6.js → execution-FJE27VQ6.js} +80 -3
  17. package/dist/{hq-V4PHEETW.js → hq-G5PCP325.js} +7 -5
  18. package/dist/hq-command-controller.d.ts +15 -0
  19. package/dist/{hq-server-MZ7DV6X7.js → hq-server-G3VVVMIG.js} +2 -2
  20. package/dist/index.js +3 -3
  21. package/dist/input-reader.d.ts +2 -2
  22. package/dist/{modeldiag-46CMWUNM.js → modeldiag-CJSFHZ3J.js} +55 -31
  23. package/dist/permission-prompt-mirror.d.ts +70 -0
  24. package/dist/permission-prompt.d.ts +10 -1
  25. package/dist/{providers-models-6MOGAOVK.js → providers-models-LFNZ5WI7.js} +2 -2
  26. package/dist/{short-circuit-flags-KBTYSJTW.js → short-circuit-flags-RAT6UZVI.js} +2 -2
  27. package/dist/slash-commands/subagent-models.d.ts +4 -0
  28. package/dist/subagent-models/panel-service.d.ts +28 -0
  29. package/dist/subcommands/handlers/modeldiag-test.d.ts +7 -0
  30. package/dist/{subcommands-DEYSYHIM.js → subcommands-VFVUTYAV.js} +2 -2
  31. package/dist/webui-server/prefs-seeding.d.ts +8 -0
  32. package/dist/{webui-server-OBV5JYJV.js → webui-server-3NYXBZHX.js} +36 -12
  33. package/dist/wiring/hq-telemetry.d.ts +7 -0
  34. 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,7 +1255,10 @@ 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
1264
  import { sessionScopedPath as sessionScopedPath2, toErrorMessage } from "@wrongstack/core/utils";
@@ -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);
@@ -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();
@@ -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-Z6A26EG6.js.map
6920
+ //# sourceMappingURL=execution-FJE27VQ6.js.map
@@ -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-MZ7DV6X7.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-V4PHEETW.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;
@@ -10,7 +10,7 @@ import {
10
10
  readLocalSubagentTranscript,
11
11
  sanitizeApiError,
12
12
  startHqServer
13
- } from "./chunk-PQZPHGGD.js";
13
+ } from "./chunk-XIZZ2EXG.js";
14
14
  import "./chunk-HXJYHAR4.js";
15
15
  import "./chunk-5EA6OTLJ.js";
16
16
  import "./chunk-Q7E3BPDU.js";
@@ -27,4 +27,4 @@ export {
27
27
  sanitizeApiError,
28
28
  startHqServer
29
29
  };
30
- //# sourceMappingURL=hq-server-MZ7DV6X7.js.map
30
+ //# sourceMappingURL=hq-server-G3VVVMIG.js.map
package/dist/index.js CHANGED
@@ -16,14 +16,14 @@ 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-KBTYSJTW.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-BYKJDAFF.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-5WZBH3WA.js");
26
+ const { runInteractive } = await import("./cli-main-AKJHDSHX.js");
27
27
  return runInteractive(cliCtx);
28
28
  }
29
29
 
@@ -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
@@ -52,6 +52,17 @@ var MODEL_PROFILES = [
52
52
  costTier: "premium",
53
53
  speedTier: "normal"
54
54
  },
55
+ {
56
+ provider: "openai",
57
+ // Must precede /gpt-4/ — every gpt-4o-mini id also contains gpt-4.
58
+ pattern: /gpt-4o-mini/i,
59
+ family: "GPT-4o Mini",
60
+ strengths: ["speed"],
61
+ bestFor: ["lightweight", "docs"],
62
+ avoidFor: ["planning"],
63
+ costTier: "budget",
64
+ speedTier: "fast"
65
+ },
55
66
  {
56
67
  provider: "openai",
57
68
  pattern: /gpt-4/i,
@@ -62,9 +73,11 @@ var MODEL_PROFILES = [
62
73
  speedTier: "fast"
63
74
  },
64
75
  {
65
- provider: "openai",
66
- pattern: /gpt-4o-mini/i,
67
- family: "GPT-4o Mini",
76
+ provider: "google",
77
+ // Must precede /gemini-(?:2\.5|3)/ — flash variants (gemini-2.5-flash,
78
+ // gemini-3-flash, …) are budget/fast models and need this profile.
79
+ pattern: /gemini.*flash/i,
80
+ family: "Gemini Flash",
68
81
  strengths: ["speed"],
69
82
  bestFor: ["lightweight", "docs"],
70
83
  avoidFor: ["planning"],
@@ -80,16 +93,6 @@ var MODEL_PROFILES = [
80
93
  costTier: "standard",
81
94
  speedTier: "normal"
82
95
  },
83
- {
84
- provider: "google",
85
- pattern: /gemini.*flash/i,
86
- family: "Gemini Flash",
87
- strengths: ["speed"],
88
- bestFor: ["lightweight", "docs"],
89
- avoidFor: ["planning"],
90
- costTier: "budget",
91
- speedTier: "fast"
92
- },
93
96
  {
94
97
  provider: "deepseek",
95
98
  pattern: /deepseek/i,
@@ -685,7 +688,11 @@ async function runModeldiagBench(args, deps, providers, config, hasKey) {
685
688
 
686
689
  // src/subcommands/handlers/modeldiag-test.ts
687
690
  import { color as color4, toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
688
- import { makeProviderFromConfig as makeProviderFromConfig2, setOAuthTokenPersister } from "@wrongstack/providers";
691
+ import {
692
+ buildProviderFactoriesFromRegistry,
693
+ makeProviderFromConfig as makeProviderFromConfig2,
694
+ setOAuthTokenPersister
695
+ } from "@wrongstack/providers";
689
696
 
690
697
  // src/subcommands/handlers/model-smoke-test.ts
691
698
  import { ProviderError } from "@wrongstack/core/types";
@@ -876,6 +883,28 @@ async function runModelSmokeTests(params) {
876
883
  }
877
884
 
878
885
  // src/subcommands/handlers/modeldiag-test.ts
886
+ async function createModelDiagSmokeProvider(params) {
887
+ const { providerId, config, modelsRegistry, providerFactories } = params;
888
+ const saved = config.providers?.[providerId];
889
+ const factoryType = saved?.type ?? providerId;
890
+ const resolved = await modelsRegistry.getProvider(providerId).catch(() => void 0) ?? (factoryType !== providerId ? await modelsRegistry.getProvider(factoryType).catch(() => void 0) : void 0);
891
+ const providerConfig = {
892
+ ...providerId === config.provider ? {
893
+ ...config.apiKey ? { apiKey: config.apiKey } : {},
894
+ ...config.baseUrl ? { baseUrl: config.baseUrl } : {}
895
+ } : {},
896
+ ...saved,
897
+ // Keep the user-visible alias on the factory input. The selected factory
898
+ // already captures the canonical models.dev provider and its per-model
899
+ // wire metadata.
900
+ type: providerId,
901
+ ...saved?.family ?? resolved?.family ? { family: saved?.family ?? resolved?.family } : {},
902
+ ...saved?.baseUrl ?? resolved?.apiBase ? { baseUrl: saved?.baseUrl ?? resolved?.apiBase } : {},
903
+ ...saved?.envVars ?? resolved?.envVars ? { envVars: saved?.envVars ?? resolved?.envVars } : {}
904
+ };
905
+ const factory = providerFactories.get(factoryType);
906
+ return factory ? factory.create(providerConfig) : makeProviderFromConfig2(providerId, providerConfig);
907
+ }
879
908
  async function runModeldiagTest(args, deps, config) {
880
909
  const writeLine = (line = "") => {
881
910
  deps.renderer.write(`${line}
@@ -1010,25 +1039,20 @@ async function runModeldiagTest(args, deps, config) {
1010
1039
  });
1011
1040
  let results;
1012
1041
  try {
1042
+ const providerFactories = new Map(
1043
+ (await buildProviderFactoriesFromRegistry({ registry: deps.modelsRegistry })).map(
1044
+ (factory) => [factory.type, factory]
1045
+ )
1046
+ );
1013
1047
  results = await runModelSmokeTests({
1014
1048
  targets,
1015
1049
  options: smokeOptions,
1016
- createProvider: async (providerId) => {
1017
- const saved = config.providers?.[providerId];
1018
- const resolved = await deps.modelsRegistry.getProvider(providerId).catch(() => void 0) ?? (saved?.type && saved.type !== providerId ? await deps.modelsRegistry.getProvider(saved.type).catch(() => void 0) : void 0);
1019
- const providerConfig = {
1020
- ...providerId === config.provider ? {
1021
- ...config.apiKey ? { apiKey: config.apiKey } : {},
1022
- ...config.baseUrl ? { baseUrl: config.baseUrl } : {}
1023
- } : {},
1024
- ...saved,
1025
- type: providerId,
1026
- ...saved?.family ?? resolved?.family ? { family: saved?.family ?? resolved?.family } : {},
1027
- ...saved?.baseUrl ?? resolved?.apiBase ? { baseUrl: saved?.baseUrl ?? resolved?.apiBase } : {},
1028
- ...saved?.envVars ?? resolved?.envVars ? { envVars: saved?.envVars ?? resolved?.envVars } : {}
1029
- };
1030
- return makeProviderFromConfig2(providerId, providerConfig);
1031
- },
1050
+ createProvider: (providerId) => createModelDiagSmokeProvider({
1051
+ providerId,
1052
+ config,
1053
+ modelsRegistry: deps.modelsRegistry,
1054
+ providerFactories
1055
+ }),
1032
1056
  onTargetComplete: resultLines
1033
1057
  });
1034
1058
  } finally {
@@ -1329,4 +1353,4 @@ var modeldiagCmd = async (args, deps) => {
1329
1353
  export {
1330
1354
  modeldiagCmd
1331
1355
  };
1332
- //# sourceMappingURL=modeldiag-46CMWUNM.js.map
1356
+ //# sourceMappingURL=modeldiag-CJSFHZ3J.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Mirror the plain REPL's permission prompt to HQ.
3
+ *
4
+ * Every other surface converges on the executor's confirm path, so one
5
+ * listener on the EventBus mirrors them all. The REPL does not: its prompt is
6
+ * asked by the permission policy's own `promptDelegate` inside `evaluate()`,
7
+ * which never reaches that path and raises no event. Without this wrapper the
8
+ * REPL is the one surface whose approvals are invisible from HQ — a partial
9
+ * guarantee that is worse than either a whole one or none, because an operator
10
+ * cannot tell which kind of session they are looking at.
11
+ *
12
+ * Two things make this honest rather than a hack:
13
+ *
14
+ * - **The terminal read is cancellable.** When HQ answers first, the pending
15
+ * `readKey` is aborted so stdin comes out of raw mode and the user's next
16
+ * keystroke is not eaten by a question nobody is asking.
17
+ * - **The mirrored card has a heartbeat, not a fake deadline.** A terminal
18
+ * prompt waits for a keypress however long that takes, so it has no natural
19
+ * deadline to publish — but a card with no deadline breaks the single
20
+ * invariant the dashboard relies on ("pending cannot outlive its
21
+ * deadline"), and a card with a fake one vanishes while the question is
22
+ * still on screen. Renewing a short window on a timer keeps both true.
23
+ *
24
+ * The terminal prompt's own behaviour is deliberately unchanged: it still
25
+ * blocks until answered, with no timeout of its own.
26
+ *
27
+ * @module permission-prompt-mirror
28
+ */
29
+ import type { ApprovalRegistry } from '@wrongstack/core/hq';
30
+ import type { Tool } from '@wrongstack/core/types';
31
+ type PromptDecision = 'yes' | 'no' | 'always' | 'deny';
32
+ type PromptDelegate = (tool: Tool, input: unknown, suggestedPattern: string) => Promise<PromptDecision>;
33
+ type AbortablePromptDelegate = (tool: Tool, input: unknown, suggestedPattern: string, signal: AbortSignal) => Promise<PromptDecision>;
34
+ /**
35
+ * How long a mirrored REPL card stays pending without a renewal, and how often
36
+ * it is renewed. The gap between them is the worst-case time a card outlives
37
+ * the process that raised it — a laptop that sleeps mid-prompt should not park
38
+ * a permanent card on someone's dashboard.
39
+ */
40
+ export declare const REPL_MIRROR_TTL_MS = 60000;
41
+ export declare const REPL_MIRROR_HEARTBEAT_MS = 20000;
42
+ export interface MirroredPromptDelegateDeps {
43
+ /** The real terminal prompt. Must honour the abort signal it is handed. */
44
+ inner: AbortablePromptDelegate;
45
+ /**
46
+ * Looked up per call, not captured: the registry is created during boot and
47
+ * this delegate is built before it exists. Returning undefined simply means
48
+ * no mirroring — the terminal prompt still works.
49
+ */
50
+ getRegistry: () => ApprovalRegistry | undefined;
51
+ /** The session the prompt belongs to, so an HQ answer lands on it. */
52
+ getSessionId?: (() => string | undefined) | undefined;
53
+ now?: (() => number) | undefined;
54
+ }
55
+ export declare function makeMirroredPromptDelegate(deps: MirroredPromptDelegateDeps): PromptDelegate;
56
+ /**
57
+ * Late-bound handle to the approval registry.
58
+ *
59
+ * The prompt delegate is built during container wiring; the registry is
60
+ * created later, once the HQ telemetry layer comes up. Rather than reorder
61
+ * boot for a feature that is optional by nature, the delegate reads through
62
+ * this holder at call time — a prompt only ever fires long after both exist.
63
+ * Empty simply means no mirroring.
64
+ */
65
+ export interface ApprovalMirrorRef {
66
+ current?: ApprovalRegistry | undefined;
67
+ sessionId?: (() => string | undefined) | undefined;
68
+ }
69
+ export {};
70
+ //# sourceMappingURL=permission-prompt-mirror.d.ts.map
@@ -2,7 +2,16 @@ import type { InputReader, Tool } from '@wrongstack/core/types';
2
2
  type PromptDecision = 'yes' | 'no' | 'always' | 'deny';
3
3
  /** Signature the Agent expects for confirming tool calls. */
4
4
  export type ConfirmAwaiter = (tool: Tool, input: unknown, toolUseId: string, suggestedPattern: string) => Promise<'yes' | 'no' | 'always' | 'deny'>;
5
- export declare function makePromptDelegate(reader: InputReader): (tool: Tool, input: unknown, suggestedPattern: string) => Promise<PromptDecision>;
5
+ /**
6
+ * The terminal approval prompt.
7
+ *
8
+ * `signal` exists because the same question can now be answered somewhere else
9
+ * — from the HQ dashboard — while this prompt is on screen. Aborting takes it
10
+ * down and, crucially, gets stdin back out of raw mode; see
11
+ * `permission-prompt-mirror.ts`. Absent, behaviour is exactly as before: the
12
+ * prompt blocks until a key is pressed.
13
+ */
14
+ export declare function makePromptDelegate(reader: InputReader): (tool: Tool, input: unknown, suggestedPattern: string, signal?: AbortSignal) => Promise<PromptDecision>;
6
15
  /**
7
16
  * Create a ConfirmAwaiter for the CLI path. Wraps makePromptDelegate
8
17
  * with the ConfirmAwaiter type signature expected by the Agent.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  visibleModelIds
3
- } from "./chunk-VUVOMXWP.js";
3
+ } from "./chunk-3VXYY5JC.js";
4
4
  import {
5
5
  activeProfileConfigPath
6
6
  } from "./chunk-YMXXOOFN.js";
@@ -660,4 +660,4 @@ export {
660
660
  modelsCmd,
661
661
  providersCmd
662
662
  };
663
- //# sourceMappingURL=providers-models-6MOGAOVK.js.map
663
+ //# sourceMappingURL=providers-models-LFNZ5WI7.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  handleHelpVersionShortCircuit
3
- } from "./chunk-NR7BD73C.js";
3
+ } from "./chunk-4OLAZIIN.js";
4
4
  import "./chunk-6WRKAACA.js";
5
5
  import "./chunk-XJXDOF63.js";
6
6
  import "./chunk-EK2P53GL.js";
7
7
  export {
8
8
  handleHelpVersionShortCircuit
9
9
  };
10
- //# sourceMappingURL=short-circuit-flags-KBTYSJTW.js.map
10
+ //# sourceMappingURL=short-circuit-flags-RAT6UZVI.js.map
@@ -0,0 +1,4 @@
1
+ import type { SlashCommand } from '@wrongstack/core/types';
2
+ import type { SlashCommandContext } from './command-context.js';
3
+ export declare function buildSubagentModelsCommand(opts: SlashCommandContext): SlashCommand;
4
+ //# sourceMappingURL=subagent-models.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Host bridge for the TUI `/subagent-models` panel.
3
+ *
4
+ * The panel is presentational; the session-scoped plan lives in core and is
5
+ * journaled through the session writer, which only the CLI has. Mutations
6
+ * return an error STRING rather than throwing so a rejected write lands as a
7
+ * panel hint instead of unmounting the TUI.
8
+ */
9
+ import { setSessionSubagentModelPlan } from '@wrongstack/core/coordination';
10
+ import type { SubagentModelsPanelHost } from '@wrongstack/tui';
11
+ /** The slice of the agent context the plan writer needs. */
12
+ type PlanWriteContext = Parameters<typeof setSessionSubagentModelPlan>[0];
13
+ export interface SubagentModelsPanelServiceDeps {
14
+ /** The session's own provider/model — the target of "use session model". */
15
+ getSessionTarget?: (() => {
16
+ provider?: string;
17
+ model?: string;
18
+ }) | undefined;
19
+ /**
20
+ * Live agent context — re-read on every call so a session swap (F1/F10,
21
+ * /resume) is picked up. Typed off the core writer contract rather than the
22
+ * full AgentContext so this bridge only depends on what it actually uses.
23
+ */
24
+ getContext: () => PlanWriteContext | undefined;
25
+ }
26
+ export declare function createSubagentModelsPanelHost(deps: SubagentModelsPanelServiceDeps): SubagentModelsPanelHost;
27
+ export {};
28
+ //# sourceMappingURL=panel-service.d.ts.map
@@ -3,6 +3,13 @@
3
3
  *
4
4
  * @module subcommands/handlers/modeldiag-test
5
5
  */
6
+ import type { Config, ModelsRegistry, Provider, ProviderFactory } from '@wrongstack/core/types';
6
7
  import type { SubcommandDeps } from '../contracts.js';
8
+ export declare function createModelDiagSmokeProvider(params: {
9
+ providerId: string;
10
+ config: Config;
11
+ modelsRegistry: ModelsRegistry;
12
+ providerFactories: ReadonlyMap<string, ProviderFactory>;
13
+ }): Promise<Provider>;
7
14
  export declare function runModeldiagTest(args: string[], deps: SubcommandDeps, config: Record<string, unknown>): Promise<number>;
8
15
  //# sourceMappingURL=modeldiag-test.d.ts.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  subcommandNames,
3
3
  subcommands
4
- } from "./chunk-QDQPF4PB.js";
4
+ } from "./chunk-RLCKKLJA.js";
5
5
  export {
6
6
  subcommandNames,
7
7
  subcommands
8
8
  };
9
- //# sourceMappingURL=subcommands-DEYSYHIM.js.map
9
+ //# sourceMappingURL=subcommands-VFVUTYAV.js.map
@@ -10,9 +10,17 @@ interface CliWebUIOptions {
10
10
  globalConfigPath?: string | undefined;
11
11
  profileConfigPath: string;
12
12
  appConfig?: {
13
+ /**
14
+ * The live default selection. Carried here so a model switch can move
15
+ * it in-process, not just in config.json — `session.new` reads the
16
+ * live object to stamp a new tab.
17
+ */
18
+ provider?: string | undefined;
19
+ model?: string | undefined;
13
20
  fallbackModels?: string[] | undefined;
14
21
  fallbackProfiles?: Record<string, string[]> | undefined;
15
22
  favoriteModels?: string[] | undefined;
23
+ disabledModels?: string[] | undefined;
16
24
  favoriteModelsOnly?: boolean | undefined;
17
25
  modelAvailabilitySchedule?: ModelBlackoutRule[] | undefined;
18
26
  fallbackAuto?: boolean | undefined;