@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
@@ -23,7 +23,11 @@ import { pathToFileURL } from "node:url";
23
23
  import { Context } from "@wrongstack/core/agent";
24
24
  import { ToolExecutor } from "@wrongstack/core/execution";
25
25
  import { ToolRegistry } from "@wrongstack/core/registry";
26
- import { AutoApprovePermissionPolicy, DefaultSecretScrubber } from "@wrongstack/core/security";
26
+ import {
27
+ AutoApprovePermissionPolicy,
28
+ DefaultSecretScrubber,
29
+ ToolCapabilities
30
+ } from "@wrongstack/core/security";
27
31
  import { normalizeTokenSavingTier } from "@wrongstack/core/types";
28
32
  import {
29
33
  DEFAULT_MCP_INSERTION_MAX_BYTES,
@@ -33,11 +37,9 @@ import {
33
37
  } from "@wrongstack/mcp";
34
38
  import { registerBuiltinToolTier } from "@wrongstack/tools/tool-tier";
35
39
  import { wireKanbanPorts } from "@wrongstack/runtime";
36
- var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
37
- async evaluate() {
38
- return { permission: "auto", source: "default" };
39
- }
40
- };
40
+ function yoloServePolicy() {
41
+ return new AutoApprovePermissionPolicy(Object.values(ToolCapabilities));
42
+ }
41
43
  function parseToolsFlag(flags, positional) {
42
44
  const raw = flags["tools"];
43
45
  const csv = typeof raw === "string" ? raw : raw === true ? positional?.[0] ?? "" : "";
@@ -226,7 +228,7 @@ async function serveMcpStdio(deps, positional) {
226
228
  controller.signal,
227
229
  resolveServeFsRestriction(deps.config)
228
230
  );
229
- const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
231
+ const permissionPolicy = yolo ? yoloServePolicy() : new AutoApprovePermissionPolicy();
230
232
  const executor = new ToolExecutor(registry, {
231
233
  permissionPolicy,
232
234
  secretScrubber: new DefaultSecretScrubber(),
@@ -440,4 +442,4 @@ function isRecord(value) {
440
442
  export {
441
443
  mcpCmd
442
444
  };
443
- //# sourceMappingURL=mcp-HOMLC4WE.js.map
445
+ //# sourceMappingURL=mcp-FX7TKPTV.js.map
@@ -1,8 +1,39 @@
1
1
  import { Context } from '@wrongstack/core/agent';
2
2
  import { ToolRegistry } from '@wrongstack/core/registry';
3
+ import { AutoApprovePermissionPolicy } from '@wrongstack/core/security';
3
4
  import type { PermissionPolicy, Tool } from '@wrongstack/core/types';
4
5
  import { type MCPServerPrompt, type MCPServerResource } from '@wrongstack/mcp';
5
6
  import type { SubcommandDeps } from './subcommands/contracts.js';
7
+ /**
8
+ * `--yolo` for `mcp serve`: widen the CAPABILITY allowlist, keep every guard.
9
+ *
10
+ * This replaced an `AllowAllPermissionPolicy extends AutoApprovePermissionPolicy`
11
+ * whose `evaluate()` returned `{ permission: 'auto' }` unconditionally. That
12
+ * override skipped the whole base implementation — the sensitive-read denial,
13
+ * the leader deny rules, the destructive-command classifier, the agent-state
14
+ * write guard, and `LOCKED_DESTRUCTIVE_KINDS`. It was the only place in the
15
+ * codebase where `agent-state` and `credential-bind` could be un-gated, so a
16
+ * third-party MCP client could have written `hooks` into config.json (boot-time
17
+ * RCE) or `auto: true` into trust.json.
18
+ *
19
+ * It did not actually get there — but only by accident, and the accident is the
20
+ * reason this is a rewrite rather than a patch. The override returned
21
+ * `source: 'default'` where the base class returns `source: 'yolo'`, so
22
+ * `ToolExecutor.capabilityDowngraded` re-armed and forced `confirm`; `mcp serve`
23
+ * has no confirmAwaiter, so the call hard-errored. Changing that one string
24
+ * literal to `'yolo'` — which reads as a trivial consistency cleanup — would
25
+ * have made the bypass live. Safety must not rest on a string nobody knows is
26
+ * load-bearing.
27
+ *
28
+ * Granting every capability is what `--yolo` actually means, and it is what
29
+ * lets `ToolExecutor` trust an `auto` instead of downgrading it (see the
30
+ * `dangerousNotAllowed` reasoning in `auto-approve-policy.ts`). The destructive
31
+ * classifier is deliberately NOT relaxed: `yoloConfirmKinds` is left at its
32
+ * default, which gates every destructive kind. Destructive calls therefore
33
+ * still require a confirmation this surface cannot answer, exactly as before —
34
+ * this change removes a latent bypass without widening anything.
35
+ */
36
+ export declare function yoloServePolicy(): AutoApprovePermissionPolicy;
6
37
  /**
7
38
  * Resolve the `--tools` whitelist for `mcp serve`.
8
39
  *
@@ -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-U6PGWDMZ.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-YDMLAQ5R.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-BIWE22O5.js";
4
+ } from "./chunk-RLCKKLJA.js";
5
5
  export {
6
6
  subcommandNames,
7
7
  subcommands
8
8
  };
9
- //# sourceMappingURL=subcommands-7N4RIK6H.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;
@@ -1,14 +1,14 @@
1
- import {
2
- createHqCommandDispatcher,
3
- createProjectKanbanAssignHandler,
4
- createProjectKanbanTransitionHandler
5
- } from "./chunk-KNMIBKLV.js";
6
1
  import {
7
2
  createKanbanRunMirror
8
3
  } from "./chunk-VCYZ3WZV.js";
9
4
  import {
10
5
  WEBUI_SESSION_CHILD_CAPABILITIES
11
6
  } from "./chunk-GPPRNAKW.js";
7
+ import {
8
+ createHqCommandDispatcher,
9
+ createProjectKanbanAssignHandler,
10
+ createProjectKanbanTransitionHandler
11
+ } from "./chunk-JY52HWL6.js";
12
12
  import {
13
13
  startCliHqConnection
14
14
  } from "./chunk-TE3Q32JP.js";
@@ -37,6 +37,7 @@ import {
37
37
  envFlag,
38
38
  findFreePort,
39
39
  findInstalledPackageJson,
40
+ integrationConnectSources,
40
41
  isStrictPort,
41
42
  resolveAuthToken,
42
43
  resolvePendingConfirmsForSession,
@@ -47,8 +48,8 @@ import {
47
48
  startWebUILiveStatusLogger,
48
49
  toSessionHistoryEntries
49
50
  } from "@wrongstack/webui-server";
50
- import { WebSocketServer } from "ws";
51
51
  import { verifyClient as verifyWsClient2 } from "@wrongstack/webui-server/server/ws-auth";
52
+ import { WebSocketServer } from "ws";
52
53
 
53
54
  // src/webui-server/client-registration.ts
54
55
  import { mailboxSessionTag } from "@wrongstack/core/coordination";
@@ -69,10 +70,13 @@ function createWebuiClientRegistration(deps) {
69
70
  capabilities: options.capabilities
70
71
  }),
71
72
  ...control ? {
72
- createCommandHandler: (mailbox) => createHqCommandDispatcher({
73
+ createCommandHandler: (mailbox, approvals) => createHqCommandDispatcher({
73
74
  steerMailbox: mailbox,
74
75
  interruptLeader: control.interruptLeader,
75
76
  allowRunCommand: control.allowRunCommand,
77
+ // Same registry the approval bridge publishes from, so every
78
+ // prompt HQ shows is one this handler can actually answer.
79
+ resolveApproval: (toolUseId, decision, sessionId) => approvals.resolve(toolUseId, decision, sessionId),
76
80
  ...deps.projectRoot ? {
77
81
  kanbanTransition: createProjectKanbanTransitionHandler(deps.projectRoot),
78
82
  kanbanAssign: createProjectKanbanAssignHandler(deps.projectRoot)
@@ -294,7 +298,7 @@ function startWebuiCredentialWatcher({
294
298
  }
295
299
  } catch {
296
300
  }
297
- const routingChanged = snapshot.fallbackModels !== void 0 || snapshot.fallbackBridge !== void 0 || hadFallbackBridge || snapshot.fallbackProfiles !== void 0 || snapshot.favoriteModels !== void 0 || snapshot.favoriteModelsOnly !== void 0 || snapshot.modelMatrix !== void 0 || snapshot.fallbackAuto !== void 0;
301
+ const routingChanged = snapshot.fallbackModels !== void 0 || snapshot.fallbackBridge !== void 0 || hadFallbackBridge || snapshot.fallbackProfiles !== void 0 || snapshot.favoriteModels !== void 0 || snapshot.disabledModels !== void 0 || snapshot.favoriteModelsOnly !== void 0 || snapshot.modelMatrix !== void 0 || snapshot.fallbackAuto !== void 0;
298
302
  if (routingChanged) {
299
303
  const configStore = opts.agent.container?.safeResolve?.(TOKENS.ConfigStore);
300
304
  configStore?.update({
@@ -302,6 +306,7 @@ function startWebuiCredentialWatcher({
302
306
  fallbackBridge: snapshot.fallbackBridge ?? "",
303
307
  ...snapshot.fallbackProfiles !== void 0 ? { fallbackProfiles: snapshot.fallbackProfiles } : {},
304
308
  ...snapshot.favoriteModels !== void 0 ? { favoriteModels: snapshot.favoriteModels } : {},
309
+ ...snapshot.disabledModels !== void 0 ? { disabledModels: snapshot.disabledModels } : {},
305
310
  ...snapshot.favoriteModelsOnly !== void 0 ? { favoriteModelsOnly: snapshot.favoriteModelsOnly } : {},
306
311
  ...snapshot.modelMatrix !== void 0 ? { modelMatrix: snapshot.modelMatrix } : {},
307
312
  ...snapshot.fallbackAuto !== void 0 ? { fallbackAuto: snapshot.fallbackAuto } : {}
@@ -782,6 +787,12 @@ function createPrefsSeeding(opts) {
782
787
  opts.appConfig = { ...opts.appConfig, ...patch };
783
788
  };
784
789
  const persistPrefs = async (payload) => {
790
+ if (typeof payload["provider"] === "string") {
791
+ patchLiveAppConfig({ provider: payload["provider"] });
792
+ }
793
+ if (typeof payload["model"] === "string") {
794
+ patchLiveAppConfig({ model: payload["model"] });
795
+ }
785
796
  if (Array.isArray(payload["fallbackModels"])) {
786
797
  patchLiveAppConfig({ fallbackModels: payload["fallbackModels"] });
787
798
  }
@@ -793,6 +804,9 @@ function createPrefsSeeding(opts) {
793
804
  if (Array.isArray(payload["favoriteModels"])) {
794
805
  patchLiveAppConfig({ favoriteModels: payload["favoriteModels"] });
795
806
  }
807
+ if (Array.isArray(payload["disabledModels"])) {
808
+ patchLiveAppConfig({ disabledModels: payload["disabledModels"] });
809
+ }
796
810
  if (typeof payload["favoriteModelsOnly"] === "boolean") {
797
811
  patchLiveAppConfig({ favoriteModelsOnly: payload["favoriteModelsOnly"] });
798
812
  }
@@ -852,7 +866,9 @@ function createPrefsSeeding(opts) {
852
866
  // src/webui-server/route-contexts.ts
853
867
  import * as path5 from "node:path";
854
868
  import {
869
+ normalizeSubagentModelPlan,
855
870
  seedSessionSubagentPolicy,
871
+ setSessionSubagentModelPlan,
856
872
  setSessionSubagentsAllowed
857
873
  } from "@wrongstack/core/coordination";
858
874
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
@@ -980,6 +996,10 @@ function createWebuiRouteContexts({
980
996
  sessionId ? getSessionAgent(sessionId).ctx : opts.agent.ctx,
981
997
  allowed
982
998
  ),
999
+ setSubagentModelPlan: (plan, sessionId) => setSessionSubagentModelPlan(
1000
+ sessionId ? getSessionAgent(sessionId).ctx : opts.agent.ctx,
1001
+ normalizeSubagentModelPlan(plan)
1002
+ ),
983
1003
  persist: persistPrefs,
984
1004
  setYolo: opts.onYoloSwitch,
985
1005
  setAutonomy: opts.onAutonomySwitch,
@@ -1101,6 +1121,7 @@ function createWebuiRouteContexts({
1101
1121
  };
1102
1122
  const connectionCtx = {
1103
1123
  agent: opts.agent,
1124
+ events: opts.events,
1104
1125
  getAgent: getSessionAgent,
1105
1126
  // Non-creating peek for the hasSession ownership gate (background-tab
1106
1127
  // requests are legitimate; arbitrary strings are not).
@@ -1499,6 +1520,10 @@ async function runWebUI(opts) {
1499
1520
  requireToken,
1500
1521
  deferListen: surface === "simpleui",
1501
1522
  strictPort,
1523
+ // HQ / WrongProxy status chips fetch those endpoints straight from the
1524
+ // browser; without their origins in `connect-src` the page's own CSP
1525
+ // blocks the probe and both chips report a healthy server as down.
1526
+ getExtraConnectSrc: () => integrationConnectSources(opts.appConfig),
1502
1527
  ...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
1503
1528
  ...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
1504
1529
  });
@@ -1511,9 +1536,7 @@ async function runWebUI(opts) {
1511
1536
  wsHost: host,
1512
1537
  expectedToken: wsToken,
1513
1538
  requireToken,
1514
- allowedHostnames: [publicUrl, publicWsUrl].filter(
1515
- (value) => Boolean(value)
1516
- ),
1539
+ allowedHostnames: [publicUrl, publicWsUrl].filter((value) => Boolean(value)),
1517
1540
  allowBrowserUrlToken: Boolean(publicWsUrl),
1518
1541
  allowCrossPortLoopbackCookie: process.env["WRONGSTACK_WEBUI_DEV_CROSS_PORT_WS"] === "1"
1519
1542
  });
@@ -1648,6 +1671,7 @@ async function runWebUI(opts) {
1648
1671
  ),
1649
1672
  modelsRegistry: opts.modelsRegistry,
1650
1673
  providerAuthRegistry: opts.providerAuthRegistry,
1674
+ getDisabledModels: () => opts.appConfig?.disabledModels ?? [],
1651
1675
  send,
1652
1676
  broadcast,
1653
1677
  log: (m) => console.log(m)
@@ -1974,4 +1998,4 @@ async function runWebUI(opts) {
1974
1998
  export {
1975
1999
  runWebUI
1976
2000
  };
1977
- //# sourceMappingURL=webui-server-OBV5JYJV.js.map
2001
+ //# sourceMappingURL=webui-server-3NYXBZHX.js.map
@@ -5,6 +5,7 @@ import type { Config, SessionWriter } from '@wrongstack/core/types';
5
5
  import type { MCPRegistry } from '@wrongstack/mcp';
6
6
  import { createHqCommandDispatcher, type HqCommandController } from '../hq-command-controller.js';
7
7
  import type { KanbanHqSyncStats } from '../kanban-hq-sync.js';
8
+ import type { ApprovalMirrorRef } from '../permission-prompt-mirror.js';
8
9
  /**
9
10
  * Mutable holder for the HQ publisher reference. The ref is created in
10
11
  * cli-main.ts before `brainMailbox` (which captures it via closure) and
@@ -28,6 +29,12 @@ interface SetupHqTelemetryDeps {
28
29
  teardownHandlers: (() => void)[];
29
30
  mailboxSessionTag: (sessionId: string) => string;
30
31
  hqPublisherRef: HqPublisherRef;
32
+ /**
33
+ * Late-bound handle the plain REPL's prompt delegate reads through. Populated
34
+ * here because this is where the registry is created; until then the REPL
35
+ * prompt simply runs unmirrored.
36
+ */
37
+ approvalMirror?: ApprovalMirrorRef | undefined;
31
38
  mcpRegistry: Pick<MCPRegistry, 'onOperation' | 'operationalHealth'>;
32
39
  }
33
40
  interface HqTelemetryResult {