@wrongstack/cli 0.300.0 → 0.302.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{acp-IDNS2YVE.js → acp-DATHHPNT.js} +34 -15
  2. package/dist/acp-server-agent.d.ts +1 -1
  3. package/dist/{audit-2ZSFGNIF.js → audit-QRCLEHHW.js} +5 -1
  4. package/dist/{auth-6T6ZOT2R.js → auth-PJD3JRH2.js} +150 -37
  5. package/dist/boot/dispatch-webui.d.ts +3 -16
  6. package/dist/boot/launch-menu.d.ts +2 -4
  7. package/dist/boot/tool-registry.d.ts +3 -0
  8. package/dist/boot/webui-session-child.d.ts +91 -0
  9. package/dist/chimera-reviewer-policy.d.ts +10 -3
  10. package/dist/chimera-work-registry.d.ts +19 -0
  11. package/dist/{chronicle-QQEQFSSE.js → chronicle-63MFCZN6.js} +26 -5
  12. package/dist/{chunk-YWUPHRGB.js → chunk-263G3FMB.js} +33 -10
  13. package/dist/{chunk-GYDQABMA.js → chunk-2KDOFTTM.js} +44 -32
  14. package/dist/{chunk-V76R7DC5.js → chunk-3MOUBRKZ.js} +311 -309
  15. package/dist/chunk-CSAPOCBP.js +24 -0
  16. package/dist/{chunk-N7ULWBO5.js → chunk-ETOEGL3V.js} +3 -7
  17. package/dist/{chunk-3MWUZMOL.js → chunk-GUHQQG63.js} +14 -3
  18. package/dist/chunk-KE7E7DPX.js +38 -0
  19. package/dist/{chunk-PEMN4T5O.js → chunk-MT6FGXO3.js} +23 -4
  20. package/dist/chunk-PFVVUH5B.js +127 -0
  21. package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
  22. package/dist/{chunk-4WJKVQJY.js → chunk-ZECLGUTF.js} +15 -3
  23. package/dist/cli-context.d.ts +2 -0
  24. package/dist/{cli-main-7B3G2YSG.js → cli-main-EOJ74CI4.js} +336 -99
  25. package/dist/execute-deps.d.ts +3 -0
  26. package/dist/{execution-SHGM7BVC.js → execution-CQ772VPS.js} +246 -97
  27. package/dist/execution-chimera-cascade.d.ts +3 -4
  28. package/dist/execution-chimera-review.d.ts +2 -3
  29. package/dist/execution-cleanup.d.ts +3 -2
  30. package/dist/execution.d.ts +1 -1
  31. package/dist/{export-TPORYVRZ.js → export-DOBGPY23.js} +12 -1
  32. package/dist/fleet/host-context.d.ts +1 -1
  33. package/dist/fleet/host-helpers.d.ts +14 -0
  34. package/dist/fleet/status-broadcast.d.ts +1 -1
  35. package/dist/{hq-DVDD4XPV.js → hq-LN5CJHCM.js} +2 -2
  36. package/dist/hq-server/routes.d.ts +10 -1
  37. package/dist/hq-server/ws.d.ts +17 -6
  38. package/dist/{hq-server-DQHLNHDL.js → hq-server-S7HZG3PA.js} +3 -2
  39. package/dist/index.js +142 -100
  40. package/dist/live-settings-input.d.ts +5 -0
  41. package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
  42. package/dist/{modeldiag-YOQZHBQV.js → modeldiag-3DOK4BWW.js} +2 -2
  43. package/dist/{plugin-usage-EOG4ET4S.js → plugin-usage-YEVLOR33.js} +7 -3
  44. package/dist/{plugins-56CMWMPR.js → plugins-K3BAUP4D.js} +3 -3
  45. package/dist/profile-config-path.d.ts +0 -2
  46. package/dist/provider-helpers.d.ts +0 -5
  47. package/dist/{providers-models-K6RT7ABI.js → providers-models-WEZ4EGLL.js} +6 -5
  48. package/dist/{replay-KCTXNMZQ.js → replay-FCBNHW3K.js} +5 -1
  49. package/dist/{rewind-EJMVFVLS.js → rewind-AOD3JIYX.js} +5 -1
  50. package/dist/{sessions-config-NLCMK3BY.js → sessions-config-XXN7267N.js} +8 -3
  51. package/dist/subcommands/flags.d.ts +23 -0
  52. package/dist/terminal-format.d.ts +15 -0
  53. package/dist/{webui-server-G3EKFXLL.js → webui-server-NFNRGNNT.js} +54 -29
  54. package/dist/webui-server-options.d.ts +7 -0
  55. package/dist/wiring/brain-and-orchestration.d.ts +1 -1
  56. package/dist/wiring/mailbox-bridge-bootstrap.d.ts +3 -1
  57. package/dist/wiring/management-tools.d.ts +9 -1
  58. package/dist/wiring/pipeline.d.ts +5 -5
  59. package/dist/wiring/plugins.d.ts +2 -3
  60. package/dist/wiring/provider-utility-tools.d.ts +1 -0
  61. package/dist/wiring/tools.d.ts +4 -5
  62. package/package.json +23 -23
@@ -32,13 +32,14 @@ import { WebSocketServer } from "ws";
32
32
 
33
33
  // src/acp-server-agent.ts
34
34
  import { Agent, Context, createDefaultPipelines } from "@wrongstack/core/agent";
35
- import { AutoApprovePermissionPolicy, ToolCapabilities } from "@wrongstack/core/security";
36
- import { DefaultLogger } from "@wrongstack/core/infrastructure";
35
+ import { ToolExecutor } from "@wrongstack/core/execution";
36
+ import { DefaultLogger, DefaultTokenCounter } from "@wrongstack/core/infrastructure";
37
37
  import { EventBus, TOKENS } from "@wrongstack/core/kernel";
38
- import { ToolValidationError } from "@wrongstack/core/types";
39
38
  import { ToolRegistry } from "@wrongstack/core/registry";
40
- import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
41
- import { ToolExecutor } from "@wrongstack/core/execution";
39
+ import { AutoApprovePermissionPolicy, ToolCapabilities } from "@wrongstack/core/security";
40
+ import {
41
+ ToolValidationError
42
+ } from "@wrongstack/core/types";
42
43
  import { createDefaultContainer } from "@wrongstack/runtime";
43
44
  var AcpServerConfigError = class extends Error {
44
45
  constructor(message) {
@@ -123,7 +124,8 @@ function makeAcpRead(base, api) {
123
124
  mutating: false,
124
125
  execute: async (input) => {
125
126
  const i = input ?? {};
126
- if (!i.path) throw new ToolValidationError({ message: "read: path is required", field: "path" });
127
+ if (!i.path)
128
+ throw new ToolValidationError({ message: "read: path is required", field: "path" });
127
129
  const content = await api.readTextFile({
128
130
  path: i.path,
129
131
  ...typeof i.offset === "number" ? { line: i.offset } : {},
@@ -142,7 +144,8 @@ function makeAcpWrite(base, api) {
142
144
  ...base,
143
145
  execute: async (input) => {
144
146
  const i = input ?? {};
145
- if (!i.path) throw new ToolValidationError({ message: "write: path is required", field: "path" });
147
+ if (!i.path)
148
+ throw new ToolValidationError({ message: "write: path is required", field: "path" });
146
149
  await api.writeTextFile({ path: i.path, content: i.content ?? "" });
147
150
  return { path: i.path, ok: true };
148
151
  }
@@ -153,8 +156,13 @@ function makeAcpEdit(base, api) {
153
156
  ...base,
154
157
  execute: async (input) => {
155
158
  const i = input ?? {};
156
- if (!i.path) throw new ToolValidationError({ message: "edit: path is required", field: "path" });
157
- if (!i.old_string) throw new ToolValidationError({ message: "edit: old_string is required", field: "old_string" });
159
+ if (!i.path)
160
+ throw new ToolValidationError({ message: "edit: path is required", field: "path" });
161
+ if (!i.old_string)
162
+ throw new ToolValidationError({
163
+ message: "edit: old_string is required",
164
+ field: "old_string"
165
+ });
158
166
  const before = await api.readTextFile({ path: i.path });
159
167
  const occurrences = before.split(i.old_string).length - 1;
160
168
  if (occurrences === 0) {
@@ -182,7 +190,8 @@ function makeAcpBash(base, api) {
182
190
  ...base,
183
191
  execute: async (input) => {
184
192
  const i = input ?? {};
185
- if (!i.command) throw new ToolValidationError({ message: "bash: command is required", field: "command" });
193
+ if (!i.command)
194
+ throw new ToolValidationError({ message: "bash: command is required", field: "command" });
186
195
  const { output, exitCode } = await api.runTerminal({
187
196
  command: "sh",
188
197
  args: ["-c", i.command],
@@ -219,8 +228,7 @@ function buildAcpServerAgentFactory(deps, options = {}) {
219
228
  const { provider, providerRegistry } = await bootProvider();
220
229
  const events = new EventBus();
221
230
  const source = deps.toolRegistry ?? new ToolRegistry();
222
- const tools = new ToolRegistry();
223
- for (const t of source.list()) tools.register(t);
231
+ const tools = source.clone();
224
232
  if (api) {
225
233
  wireClientBackedTools(tools, api);
226
234
  }
@@ -242,7 +250,7 @@ function buildAcpServerAgentFactory(deps, options = {}) {
242
250
  projectRoot: cwd,
243
251
  allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? false,
244
252
  model: config.model,
245
- tools: [...tools.list()],
253
+ tools: [...tools.listForProvider()],
246
254
  agentId: "acp-server",
247
255
  agentName: "wrongstack-acp"
248
256
  });
@@ -481,7 +489,12 @@ async function runACPWebSocketServer(deps, port) {
481
489
  return;
482
490
  }
483
491
  transport.receive(msg);
484
- void handler.handleMessage(msg);
492
+ void handler.handleMessage(msg).catch((err) => {
493
+ deps.renderer.writeWarning(
494
+ `ACP: failed to handle message: ${err instanceof Error ? err.message : String(err)}
495
+ `
496
+ );
497
+ });
485
498
  });
486
499
  const teardown = () => {
487
500
  handler.close();
@@ -499,6 +512,12 @@ async function runACPWebSocketServer(deps, port) {
499
512
  await new Promise((resolve) => {
500
513
  const shutdown = () => {
501
514
  deps.renderer.writeWarning("\nShutting down ACP WebSocket server...");
515
+ for (const client of wss.clients) {
516
+ try {
517
+ client.terminate();
518
+ } catch {
519
+ }
520
+ }
502
521
  wss.close();
503
522
  resolve();
504
523
  };
@@ -889,4 +908,4 @@ ${renderAcpBenchText(result)}
889
908
  export {
890
909
  acpCmd
891
910
  };
892
- //# sourceMappingURL=acp-IDNS2YVE.js.map
911
+ //# sourceMappingURL=acp-DATHHPNT.js.map
@@ -13,9 +13,9 @@
13
13
  * belong in the interactive host. The ACP server is a headless single-turn
14
14
  * surface; a future PR can layer richer session behaviour if needed.
15
15
  */
16
+ import type { RunTurnApi } from '@wrongstack/acp/agent';
16
17
  import { Agent } from '@wrongstack/core/agent';
17
18
  import { type Logger, type PermissionDecision, type PermissionPolicy, type Tool } from '@wrongstack/core/types';
18
- import type { RunTurnApi } from '@wrongstack/acp/agent';
19
19
  import type { SubcommandDeps } from './subcommands/index.js';
20
20
  /**
21
21
  * Error thrown when the server cannot start because no model provider is
@@ -1,9 +1,13 @@
1
+ import {
2
+ restoreFlags
3
+ } from "./chunk-CSAPOCBP.js";
1
4
  import "./chunk-7OCVIDC7.js";
2
5
 
3
6
  // src/subcommands/handlers/audit.ts
4
7
  import { color, resolveWstackPaths } from "@wrongstack/core/utils";
5
8
  import { ToolAuditLog } from "@wrongstack/core/storage";
6
9
  var auditCmd = async (args, deps) => {
10
+ args = restoreFlags(args, deps, ["list", "l"]);
7
11
  const wpaths = resolveWstackPaths({
8
12
  projectRoot: deps.projectRoot,
9
13
  userHome: deps.userHome
@@ -99,4 +103,4 @@ async function listAudits(log, dir, deps) {
99
103
  export {
100
104
  auditCmd
101
105
  };
102
- //# sourceMappingURL=audit-2ZSFGNIF.js.map
106
+ //# sourceMappingURL=audit-QRCLEHHW.js.map
@@ -13,11 +13,11 @@ import {
13
13
  runOAuthLoginKind,
14
14
  runOAuthLoginMenu,
15
15
  validateFamily
16
- } from "./chunk-V76R7DC5.js";
16
+ } from "./chunk-3MOUBRKZ.js";
17
17
  import {
18
18
  parseAuthFlags
19
- } from "./chunk-PEMN4T5O.js";
20
- import "./chunk-N7ULWBO5.js";
19
+ } from "./chunk-MT6FGXO3.js";
20
+ import "./chunk-ETOEGL3V.js";
21
21
  import "./chunk-PYTFS4MC.js";
22
22
  import {
23
23
  loadConfigProviders,
@@ -27,14 +27,17 @@ import {
27
27
  nowIso,
28
28
  writeKeysBack
29
29
  } from "./chunk-PJHET45A.js";
30
- import "./chunk-YWUPHRGB.js";
30
+ import {
31
+ restoreFlags
32
+ } from "./chunk-CSAPOCBP.js";
33
+ import "./chunk-263G3FMB.js";
31
34
  import {
32
35
  activeProfileConfigPath
33
- } from "./chunk-DPJZQCVP.js";
36
+ } from "./chunk-YMXXOOFN.js";
34
37
  import "./chunk-7OCVIDC7.js";
35
38
 
36
39
  // src/subcommands/handlers/auth.ts
37
- import { color as color3 } from "@wrongstack/core/utils";
40
+ import { color as color4 } from "@wrongstack/core/utils";
38
41
 
39
42
  // src/auth-menu/direct.ts
40
43
  async function runAuthDirect(deps, opts) {
@@ -311,8 +314,94 @@ ${color2.amber("?")} Pick: `)).trim().toLowerCase();
311
314
  }
312
315
  }
313
316
 
317
+ // src/subcommands/handlers/auth-local-help.ts
318
+ import { color as color3 } from "@wrongstack/core/utils";
319
+ var LOCAL_AUTH_FLAGS = [
320
+ {
321
+ flag: "--name <ollama|vllm|lmstudio>",
322
+ description: "Pick a preset (skip the interactive picker). Defaults to the first run; set this for scripting."
323
+ },
324
+ {
325
+ flag: "--base-url <url>",
326
+ description: "Override the preset default (e.g. `http://gpu-host:8000/v1` for a remote vLLM). Empty falls back to the preset."
327
+ },
328
+ {
329
+ flag: "--no-key / --skip-key",
330
+ description: "Skip the API-key prompt even for presets that accept one. Use for non-TTY scripted setups."
331
+ },
332
+ {
333
+ flag: "--no-probe / --skip-probe",
334
+ description: "Skip the health probe. Use when the local server is not running yet but the config must be persisted."
335
+ },
336
+ {
337
+ flag: "--probe-only",
338
+ description: "Run the probe and report, do not save. Use to re-check a previously-saved provider."
339
+ },
340
+ {
341
+ flag: "--model <spec> / -m <spec>",
342
+ description: "Pre-populate the saved `models` allowlist. `--model first` / `<N>` consume the probe; `<csv>` is a literal list; bare `--model` clears the allowlist."
343
+ },
344
+ {
345
+ flag: "--audit [target]",
346
+ description: "Emit JSONL audit events for the save lifecycle. Bare / `stdout` \u2192 stdout; `stderr` \u2192 stderr; `<path>` \u2192 file. Default is silent."
347
+ }
348
+ ];
349
+ var LOCAL_FLAG_COLUMN_WIDTH = 38;
350
+ var LOCAL_AUTH_HELP_USAGE = "wstack auth local [--name <id>] [--base-url <url>] [--no-key] [--no-probe|--probe-only] [--model <spec>] [--audit [target]]";
351
+ function wantsLocalHelp(args) {
352
+ return args.includes("--help") || args.includes("-h");
353
+ }
354
+ function renderAuthLocalHelpToString() {
355
+ const lines = [
356
+ color3.bold("wstack auth local \u2014 quick-add Ollama / vLLM / LM Studio"),
357
+ color3.dim(" Pre-fills the base URL, runs a health probe (`GET /v1/models`),"),
358
+ color3.dim(" and persists the allowlist so you can `wstack --provider <id>`"),
359
+ color3.dim(" right away. Use `--no-probe` to skip when the local server is not"),
360
+ color3.dim(" running yet; use `--audit <file>` to capture the save lifecycle."),
361
+ "",
362
+ color3.bold("Usage"),
363
+ ` ${LOCAL_AUTH_HELP_USAGE}`,
364
+ "",
365
+ color3.bold("Flags")
366
+ ];
367
+ for (const { flag, description } of LOCAL_AUTH_FLAGS) {
368
+ const padded = flag.padEnd(LOCAL_FLAG_COLUMN_WIDTH, " ");
369
+ lines.push(` ${color3.cyan(padded)}${description}`);
370
+ }
371
+ lines.push("");
372
+ lines.push(color3.dim(" See also: `wstack --help` for the top-level command list."));
373
+ lines.push(
374
+ color3.dim(
375
+ " Examples: `wstack auth local --name ollama --no-probe --model 'llama3.1:8b'`"
376
+ )
377
+ );
378
+ lines.push(
379
+ color3.dim(
380
+ " `wstack auth local --name ollama --audit /var/log/wstack-auth.jsonl`"
381
+ )
382
+ );
383
+ return lines.join("\n") + "\n";
384
+ }
385
+
314
386
  // src/subcommands/handlers/auth.ts
315
387
  var authCmd = async (args, deps) => {
388
+ args = restoreFlags(args, deps, [
389
+ "label",
390
+ "family",
391
+ "base-url",
392
+ "env",
393
+ // `auth local` flags — see the `local` branch below.
394
+ "name",
395
+ "no-key",
396
+ "skip-key",
397
+ "no-probe",
398
+ "skip-probe",
399
+ "probe-only",
400
+ "model",
401
+ "m",
402
+ "help",
403
+ "h"
404
+ ]);
316
405
  const flags = parseAuthFlags(args);
317
406
  const profileConfigPath = activeProfileConfigPath(deps.paths, deps.config);
318
407
  const menuDeps = {
@@ -329,6 +418,30 @@ var authCmd = async (args, deps) => {
329
418
  if (first === "list" || first === "ls") {
330
419
  return runAuthList(menuDeps);
331
420
  }
421
+ if (first === "local") {
422
+ if (wantsLocalHelp(args)) {
423
+ deps.renderer.write(`${renderAuthLocalHelpToString()}
424
+ `);
425
+ return 0;
426
+ }
427
+ const value = (name) => {
428
+ const i = args.indexOf(`--${name}`);
429
+ return i >= 0 ? args[i + 1] : void 0;
430
+ };
431
+ const has = (...names) => names.some((n) => args.includes(`--${n}`));
432
+ const modelIdx = args.indexOf("--model") >= 0 ? args.indexOf("--model") : args.indexOf("-m");
433
+ return runAuthLocal(menuDeps, {
434
+ name: flags.positional[1] ?? value("name"),
435
+ baseUrl: flags.baseUrl ?? value("base-url"),
436
+ label: flags.label,
437
+ skipKey: has("no-key", "skip-key"),
438
+ noProbe: has("no-probe", "skip-probe"),
439
+ probeOnly: has("probe-only"),
440
+ // Bare `--model` (no value, or followed by another flag) clears the
441
+ // allowlist, which is why an empty string is meaningful here.
442
+ ...modelIdx >= 0 ? { models: args[modelIdx + 1]?.startsWith("-") ? "" : args[modelIdx + 1] ?? "" } : {}
443
+ });
444
+ }
332
445
  if (first === "status") {
333
446
  const pid = flags.positional[1];
334
447
  if (!pid) {
@@ -352,7 +465,7 @@ var authCmd = async (args, deps) => {
352
465
  if (kind) return runOAuthLoginKind(menuDeps, kind);
353
466
  deps.renderer.writeError("OAuth login is only supported for ChatGPT, Claude, and GitHub Copilot.");
354
467
  deps.renderer.write(
355
- color3.dim(" Sign in with ChatGPT: ") + color3.bold("wstack auth login chatgpt") + "\n" + color3.dim(" Sign in with Claude: ") + color3.bold("wstack auth login claude") + "\n" + color3.dim(" Sign in with Copilot: ") + color3.bold("wstack auth login copilot") + "\n" + color3.dim(" For an API key instead: ") + color3.bold(`wstack auth ${pid}`) + "\n"
468
+ color4.dim(" Sign in with ChatGPT: ") + color4.bold("wstack auth login chatgpt") + "\n" + color4.dim(" Sign in with Claude: ") + color4.bold("wstack auth login claude") + "\n" + color4.dim(" Sign in with Copilot: ") + color4.bold("wstack auth login copilot") + "\n" + color4.dim(" For an API key instead: ") + color4.bold(`wstack auth ${pid}`) + "\n"
356
469
  );
357
470
  return 1;
358
471
  }
@@ -375,14 +488,14 @@ async function runAuthList(deps) {
375
488
  const ids = Object.keys(providers).sort();
376
489
  if (ids.length === 0) {
377
490
  deps.renderer.write(
378
- `${color3.dim("No providers configured.")}
379
- ${color3.dim("Run")} ${color3.bold("wstack auth")} ${color3.dim("to add one.")}
491
+ `${color4.dim("No providers configured.")}
492
+ ${color4.dim("Run")} ${color4.bold("wstack auth")} ${color4.dim("to add one.")}
380
493
  `
381
494
  );
382
495
  return 0;
383
496
  }
384
497
  deps.renderer.write(`
385
- ${color3.bold("Saved providers")} ${color3.dim(`(${ids.length})`)}
498
+ ${color4.bold("Saved providers")} ${color4.dim(`(${ids.length})`)}
386
499
 
387
500
  `);
388
501
  for (const id of ids) {
@@ -390,34 +503,34 @@ ${color3.bold("Saved providers")} ${color3.dim(`(${ids.length})`)}
390
503
  if (!cfg) continue;
391
504
  const keys = normalizeKeys(cfg);
392
505
  const active = cfg.activeKey ?? keys[0]?.label;
393
- const famTag = cfg.family ? `${cfg.family}` : color3.amber("no-family");
394
- const aliasHint = cfg.type && cfg.type !== id ? color3.dim(` (\u2192 ${cfg.type})`) : "";
395
- const modelHint = cfg.models && cfg.models.length > 0 ? color3.dim(` [${cfg.models.length} models]`) : "";
396
- deps.renderer.write(` ${color3.bold(id)}${aliasHint}
506
+ const famTag = cfg.family ? `${cfg.family}` : color4.amber("no-family");
507
+ const aliasHint = cfg.type && cfg.type !== id ? color4.dim(` (\u2192 ${cfg.type})`) : "";
508
+ const modelHint = cfg.models && cfg.models.length > 0 ? color4.dim(` [${cfg.models.length} models]`) : "";
509
+ deps.renderer.write(` ${color4.bold(id)}${aliasHint}
397
510
  `);
398
511
  deps.renderer.write(
399
- ` family: ${famTag} baseUrl: ${cfg.baseUrl ?? color3.dim("unset")}${modelHint}
512
+ ` family: ${famTag} baseUrl: ${cfg.baseUrl ?? color4.dim("unset")}${modelHint}
400
513
  `
401
514
  );
402
515
  if (keys.length === 0) {
403
- deps.renderer.write(` ${color3.amber("no keys")}
516
+ deps.renderer.write(` ${color4.amber("no keys")}
404
517
  `);
405
518
  } else {
406
519
  deps.renderer.write(
407
- ` ${color3.dim(`${keys.length} key${keys.length === 1 ? "" : "s"}:`)}
520
+ ` ${color4.dim(`${keys.length} key${keys.length === 1 ? "" : "s"}:`)}
408
521
  `
409
522
  );
410
523
  for (const k of keys) {
411
- const marker = k.label === active ? color3.green("\u25CF") : color3.dim("\u25CB");
524
+ const marker = k.label === active ? color4.green("\u25CF") : color4.dim("\u25CB");
412
525
  deps.renderer.write(
413
- ` ${marker} ${k.label.padEnd(18)} ${maskedKey(k.apiKey)} ${color3.dim(k.createdAt)}
526
+ ` ${marker} ${k.label.padEnd(18)} ${maskedKey(k.apiKey)} ${color4.dim(k.createdAt)}
414
527
  `
415
528
  );
416
529
  }
417
530
  }
418
531
  deps.renderer.write("\n");
419
532
  }
420
- deps.renderer.write(color3.dim(`Manage: wstack auth Add key: wstack auth <provider>
533
+ deps.renderer.write(color4.dim(`Manage: wstack auth Add key: wstack auth <provider>
421
534
  `));
422
535
  return 0;
423
536
  }
@@ -433,7 +546,7 @@ async function runAuthStatus(deps, providerId) {
433
546
  if (!cfg) {
434
547
  deps.renderer.writeError(`Provider "${providerId}" not found in config.`);
435
548
  deps.renderer.write(
436
- color3.dim(`Run ${color3.bold("wstack auth list")} to see saved providers.
549
+ color4.dim(`Run ${color4.bold("wstack auth list")} to see saved providers.
437
550
  `)
438
551
  );
439
552
  return 1;
@@ -442,32 +555,32 @@ async function runAuthStatus(deps, providerId) {
442
555
  const active = cfg.activeKey ?? keys[0]?.label;
443
556
  const lines = [
444
557
  `
445
- ${color3.bold(providerId)} ${cfg.family ? color3.dim(`[${cfg.family}]`) : color3.amber("[no family]")}`,
558
+ ${color4.bold(providerId)} ${cfg.family ? color4.dim(`[${cfg.family}]`) : color4.amber("[no family]")}`,
446
559
  "",
447
- ` type: ${color3.cyan(cfg.type ?? providerId)}`,
448
- ` family: ${cfg.family ? color3.cyan(cfg.family) : color3.dim("unset")}`,
449
- ` baseUrl: ${cfg.baseUrl ? color3.cyan(cfg.baseUrl) : color3.dim("unset")}`
560
+ ` type: ${color4.cyan(cfg.type ?? providerId)}`,
561
+ ` family: ${cfg.family ? color4.cyan(cfg.family) : color4.dim("unset")}`,
562
+ ` baseUrl: ${cfg.baseUrl ? color4.cyan(cfg.baseUrl) : color4.dim("unset")}`
450
563
  ];
451
564
  if (cfg.models?.length) {
452
- lines.push(` models: ${color3.cyan(cfg.models.join(", "))}`);
565
+ lines.push(` models: ${color4.cyan(cfg.models.join(", "))}`);
453
566
  }
454
567
  if (cfg.envVars?.length) {
455
- lines.push(` envVars: ${color3.cyan(cfg.envVars.join(", "))}`);
568
+ lines.push(` envVars: ${color4.cyan(cfg.envVars.join(", "))}`);
456
569
  }
457
570
  lines.push("");
458
571
  if (keys.length === 0) {
459
- lines.push(color3.amber(" (no keys saved)"));
572
+ lines.push(color4.amber(" (no keys saved)"));
460
573
  } else {
461
- lines.push(` ${color3.dim("Keys:")}`);
574
+ lines.push(` ${color4.dim("Keys:")}`);
462
575
  for (const k of keys) {
463
- const marker = k.label === active ? color3.green("\u25CF") : color3.dim("\u25CB");
576
+ const marker = k.label === active ? color4.green("\u25CF") : color4.dim("\u25CB");
464
577
  lines.push(
465
- ` ${marker} ${color3.bold(k.label.padEnd(18))} ${maskedKey(k.apiKey)} ${color3.dim(k.createdAt)}`
578
+ ` ${marker} ${color4.bold(k.label.padEnd(18))} ${maskedKey(k.apiKey)} ${color4.dim(k.createdAt)}`
466
579
  );
467
580
  }
468
581
  }
469
582
  lines.push("");
470
- lines.push(color3.dim(`Manage: wstack auth \u2192 pick ${providerId}`));
583
+ lines.push(color4.dim(`Manage: wstack auth \u2192 pick ${providerId}`));
471
584
  deps.renderer.write(lines.join("\n") + "\n");
472
585
  return 0;
473
586
  }
@@ -478,19 +591,19 @@ async function runAuthRemove(deps, providerId) {
478
591
  return 1;
479
592
  }
480
593
  deps.renderer.write(
481
- `${color3.amber("!")} This will remove "${providerId}" and all its saved keys.
594
+ `${color4.amber("!")} This will remove "${providerId}" and all its saved keys.
482
595
  `
483
596
  );
484
- const answer = (await deps.reader.readLine(` ${color3.amber("?")} Confirm removal? ${color3.dim("[y/N]")} `)).trim().toLowerCase();
597
+ const answer = (await deps.reader.readLine(` ${color4.amber("?")} Confirm removal? ${color4.dim("[y/N]")} `)).trim().toLowerCase();
485
598
  if (answer !== "y" && answer !== "yes") {
486
- deps.renderer.write(color3.dim("Cancelled.\n"));
599
+ deps.renderer.write(color4.dim("Cancelled.\n"));
487
600
  return 0;
488
601
  }
489
602
  try {
490
603
  await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
491
604
  delete all[providerId];
492
605
  });
493
- deps.renderer.write(` ${color3.green("\u2713")} Removed ${color3.bold(providerId)}.
606
+ deps.renderer.write(` ${color4.green("\u2713")} Removed ${color4.bold(providerId)}.
494
607
  `);
495
608
  return 0;
496
609
  } catch (err) {
@@ -501,4 +614,4 @@ async function runAuthRemove(deps, providerId) {
501
614
  export {
502
615
  authCmd
503
616
  };
504
- //# sourceMappingURL=auth-6T6ZOT2R.js.map
617
+ //# sourceMappingURL=auth-PJD3JRH2.js.map
@@ -1,19 +1,4 @@
1
- /**
2
- * WebUI dispatch — extracted from the tail of `execute()`.
3
- *
4
- * PR 6 of Issue #29 (partial). The TUI-vs-REPL-vs-WebUI fork at the
5
- * end of `execute()` is a ~1,600-line `if/else if/else if/else`
6
- * chain. The WebUI branch is the most self-contained of the four: it
7
- * constructs a `runWebUI` options object from already-available deps,
8
- * wires SIGINT handling, and returns an exit code. Extracting it
9
- * first lets `execute()` shrink by ~100 lines and isolates the
10
- * WebUI-specific wiring (port resolution, browser banner and autonomy
11
- * forwarding) in a single named module.
12
- *
13
- * The TUI branch (~1,388 lines) and the single-shot branch are left
14
- * inline — they are too deeply coupled to local mutable state for a
15
- * single-PR extraction.
16
- */
1
+ import { type WebuiSessionChildOptions } from './webui-session-child.js';
17
2
  import type { Agent } from '@wrongstack/core/agent';
18
3
  import type { BrainArbiter } from '@wrongstack/core/coordination';
19
4
  import type { JournalEntry } from '@wrongstack/core/goal';
@@ -99,6 +84,8 @@ export interface WebUIDispatchContext {
99
84
  }) => Promise<string>) | undefined;
100
85
  /** Live fleet budget for WebUI concurrency/spawn gauges (issue #323). */
101
86
  getFleetBudget?: CliWebUIOptions['getFleetBudget'];
87
+ /** Internal one-session child launch metadata, when --webui-session-child is active. */
88
+ webuiSessionChild?: WebuiSessionChildOptions | undefined;
102
89
  }
103
90
  /**
104
91
  * Run the WebUI server and block until it shuts down.
@@ -50,13 +50,11 @@ declare const DEFAULT_PORTS: Record<Exclude<LaunchMenuMode, 'tui-repl'>, number>
50
50
  * - `--webui`, `--simpleui`, `--hq`, or `--desktop` flag is set
51
51
  * (the user already chose a surface)
52
52
  * - stdin is not a TTY (CI, pipes, redirects)
53
- * - the first positional looks like a known subcommand (auth,
54
- * init, mcp, plugin, doctor, version, help, …)
55
53
  * - `--no-interactive` or `--skip` (existing "skip all prompts"
56
54
  * contract from boot.ts)
57
55
  * - a one-shot prompt (`--prompt <x>`) is supplied
58
- * - any positional argument is present (treats it like the user
59
- * is forwarding a one-shot query, not launching a surface)
56
+ * - ANY positional argument is present a subcommand or a one-shot
57
+ * query. The user already said what they wanted.
60
58
  */
61
59
  export declare function shouldSkipMenu(argv: string[], flags: Record<string, string | boolean>, positional: string[]): boolean;
62
60
  interface RunLaunchMenuDeps {
@@ -15,6 +15,9 @@ interface RegisterBuiltinToolsDeps {
15
15
  descriptionMode?: ToolDescriptionModeConfig | undefined;
16
16
  resultRenderMode?: ToolResultRenderModeConfig | undefined;
17
17
  disabledTools?: string[] | undefined;
18
+ nextsteps?: {
19
+ enabled?: boolean | undefined;
20
+ } | undefined;
18
21
  exec?: {
19
22
  allow?: string[] | undefined;
20
23
  deny?: string[] | undefined;
@@ -0,0 +1,91 @@
1
+ export declare const WEBUI_SESSION_CHILD_PROTOCOL_VERSION = 1;
2
+ export declare const WEBUI_SESSION_CHILD_CAPABILITIES: readonly ['multi-session-child', 'single-port-http-ws', 'session-affinity', 'graceful-shutdown'];
3
+ export type WebuiSessionChildPhase = 'validate_args' | 'boot_config' | 'claim_session' | 'create_session' | 'resume_session' | 'bind_port' | 'register_instance' | 'register_session' | 'start_server' | 'unknown';
4
+ export interface WebuiSessionChildOptions {
5
+ enabled: boolean;
6
+ projectRoot: string;
7
+ workingDir: string;
8
+ sessionId?: string | undefined;
9
+ resume: boolean;
10
+ host?: string | undefined;
11
+ port?: number | undefined;
12
+ token?: string | undefined;
13
+ publicUrl?: string | undefined;
14
+ publicWsUrl?: string | undefined;
15
+ requireToken?: boolean | undefined;
16
+ strictPort: boolean;
17
+ parentPid: number;
18
+ parentShellId: string;
19
+ runtimeId: string;
20
+ readyFile: string;
21
+ attachable: boolean;
22
+ protocolVersion: number;
23
+ }
24
+ export interface WebuiSessionChildReadyPayload {
25
+ type: 'webui.session_child.ready';
26
+ protocolVersion: number;
27
+ runtime: {
28
+ role: 'session-child';
29
+ runtimeId: string;
30
+ pid: number;
31
+ parentPid: number;
32
+ parentShellId: string;
33
+ startedAt: string;
34
+ };
35
+ project: {
36
+ projectRoot: string;
37
+ workingDir: string;
38
+ projectSlug: string;
39
+ projectName: string;
40
+ };
41
+ session: {
42
+ sessionId: string;
43
+ created: boolean;
44
+ resumed: boolean;
45
+ title?: string | undefined;
46
+ provider?: string | undefined;
47
+ model?: string | undefined;
48
+ };
49
+ endpoint: {
50
+ surface: 'webui';
51
+ host: string;
52
+ httpPort: number;
53
+ url: string;
54
+ publicUrl?: string | undefined;
55
+ publicWsUrl?: string | undefined;
56
+ requireToken: boolean;
57
+ auth: {
58
+ scheme: 'registry-token' | 'cookie-bootstrap' | 'none';
59
+ tokenPresent: boolean;
60
+ token?: string | undefined;
61
+ };
62
+ };
63
+ registry: {
64
+ webuiInstanceRegistered: boolean;
65
+ sessionRegistered: boolean;
66
+ };
67
+ capabilities: string[];
68
+ }
69
+ export interface WebuiSessionChildErrorPayload {
70
+ type: 'webui.session_child.error';
71
+ protocolVersion: number;
72
+ runtimeId?: string | undefined;
73
+ parentShellId?: string | undefined;
74
+ pid: number;
75
+ phase: WebuiSessionChildPhase;
76
+ recoverable: boolean;
77
+ message: string;
78
+ details?: Record<string, unknown> | undefined;
79
+ }
80
+ export declare function parseWebuiSessionChildOptions(flags: Record<string, string | boolean>): WebuiSessionChildOptions | null;
81
+ export declare function writeWebuiSessionChildReady(readyFile: string, payload: WebuiSessionChildReadyPayload): Promise<void>;
82
+ export declare function writeWebuiSessionChildError(readyFile: string, input: {
83
+ protocolVersion?: number | undefined;
84
+ runtimeId?: string | undefined;
85
+ parentShellId?: string | undefined;
86
+ phase: WebuiSessionChildPhase;
87
+ recoverable: boolean;
88
+ error: unknown;
89
+ details?: Record<string, unknown> | undefined;
90
+ }): Promise<void>;
91
+ //# sourceMappingURL=webui-session-child.d.ts.map
@@ -17,9 +17,10 @@ export declare function __resetReviewerRoundRobinCursor(value?: number): void;
17
17
  /**
18
18
  * Assign provider/model/fallbackModels for a Chimera reviewer spawn.
19
19
  *
20
- * Builds the pool from the configured primary + fallback chain, then picks
21
- * the next entry via round-robin. When the pool has <=1 usable entry the
22
- * original primary/fallbacks are returned unchanged.
20
+ * Builds the pool from the configured primary + fallback chain, then chooses
21
+ * either the next round-robin entry or a random starting entry. The injected
22
+ * random source keeps the random policy deterministic in tests. When the pool
23
+ * has <=1 usable entry the original primary/fallbacks are returned unchanged.
23
24
  *
24
25
  * When a {@link ProviderModelStatusTracker} is supplied, blocked entries
25
26
  * (waiting-room / token-reset-limit room) are filtered from both the pool
@@ -27,6 +28,12 @@ export declare function __resetReviewerRoundRobinCursor(value?: number): void;
27
28
  * a concurrent reviewer turn. Without the tracker, the legacy pre-waiting-
28
29
  * room behavior is preserved.
29
30
  */
31
+ export declare function assignReviewerModels(provider: string, model: string, fallbackModels: readonly string[], selection?: 'round-robin' | 'random', statusTracker?: ProviderModelStatusTracker | undefined, random?: () => number): {
32
+ provider: string;
33
+ model: string;
34
+ fallbackModels: string[];
35
+ };
36
+ /** Backward-compatible round-robin entry point for existing callers. */
30
37
  export declare function assignReviewerModelsRoundRobin(provider: string, model: string, fallbackModels: readonly string[], statusTracker?: ProviderModelStatusTracker | undefined): {
31
38
  provider: string;
32
39
  model: string;
@@ -0,0 +1,19 @@
1
+ export interface ChimeraWorkRegistry {
2
+ /** Track one review/cascade promise until it settles. */
3
+ track(work: Promise<void>): boolean;
4
+ /** Snapshot currently pending work for parent-before-child ordering. */
5
+ pending(): Promise<void> | undefined;
6
+ /** Stop accepting new roots after draining all accepted work to a fixed point. */
7
+ drainAndClose(): Promise<void>;
8
+ readonly size: number;
9
+ }
10
+ /**
11
+ * Session-scoped registry for Chimera review and cascade work.
12
+ *
13
+ * A single Promise slot loses overlapping reviews. A single Promise.all snapshot
14
+ * can also miss cascades or re-reviews registered while an earlier batch is
15
+ * settling. This registry retains every accepted promise and drains repeatedly
16
+ * until no accepted work remains, then seals admission.
17
+ */
18
+ export declare function createChimeraWorkRegistry(): ChimeraWorkRegistry;
19
+ //# sourceMappingURL=chimera-work-registry.d.ts.map