@wrongstack/cli 0.301.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.
@@ -13,10 +13,10 @@ import {
13
13
  runOAuthLoginKind,
14
14
  runOAuthLoginMenu,
15
15
  validateFamily
16
- } from "./chunk-O7XNGCRT.js";
16
+ } from "./chunk-3MOUBRKZ.js";
17
17
  import {
18
18
  parseAuthFlags
19
- } from "./chunk-MHF2IJDE.js";
19
+ } from "./chunk-MT6FGXO3.js";
20
20
  import "./chunk-ETOEGL3V.js";
21
21
  import "./chunk-PYTFS4MC.js";
22
22
  import {
@@ -614,4 +614,4 @@ async function runAuthRemove(deps, providerId) {
614
614
  export {
615
615
  authCmd
616
616
  };
617
- //# sourceMappingURL=auth-JASC4K3F.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.
@@ -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
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LOCAL_LLM_PRESETS,
3
3
  runLiveProviderPicker
4
- } from "./chunk-MHF2IJDE.js";
4
+ } from "./chunk-MT6FGXO3.js";
5
5
  import {
6
6
  openBrowser,
7
7
  runCodexOAuthLogin,
@@ -1335,4 +1335,4 @@ export {
1335
1335
  addKeyForProvider,
1336
1336
  runAuthLocal
1337
1337
  };
1338
- //# sourceMappingURL=chunk-O7XNGCRT.js.map
1338
+ //# sourceMappingURL=chunk-3MOUBRKZ.js.map
@@ -33,6 +33,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
33
33
  "prompt",
34
34
  "metrics",
35
35
  "webui",
36
+ "webui-session-child",
36
37
  "simpleui",
37
38
  "full-auto",
38
39
  "desktop",
@@ -137,6 +138,7 @@ function parseArgs(argv) {
137
138
  }
138
139
  function normalizeSurfaceAliases(flags, positional) {
139
140
  if (flags["simpleui"]) flags["webui"] = true;
141
+ if (flags["webui-session-child"]) flags["webui"] = true;
140
142
  const first = positional[0];
141
143
  if (first === "simpleui") {
142
144
  flags["simpleui"] = true;
@@ -1037,4 +1039,4 @@ export {
1037
1039
  runLiveProviderPicker,
1038
1040
  runPicker
1039
1041
  };
1040
- //# sourceMappingURL=chunk-MHF2IJDE.js.map
1042
+ //# sourceMappingURL=chunk-MT6FGXO3.js.map
@@ -0,0 +1,127 @@
1
+
2
+ // src/boot/webui-session-child.ts
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { atomicWrite, toErrorMessage } from "@wrongstack/core/utils";
6
+ var WEBUI_SESSION_CHILD_PROTOCOL_VERSION = 1;
7
+ var WEBUI_SESSION_CHILD_CAPABILITIES = [
8
+ "multi-session-child",
9
+ "single-port-http-ws",
10
+ "session-affinity",
11
+ "graceful-shutdown"
12
+ ];
13
+ function flagValue(flags, names) {
14
+ for (const name of names) {
15
+ if (!Object.hasOwn(flags, name)) continue;
16
+ const value = flags[name];
17
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
18
+ throw new Error(`--${name} requires a value`);
19
+ }
20
+ return void 0;
21
+ }
22
+ function boolFlag(flags, names) {
23
+ for (const name of names) {
24
+ if (!Object.hasOwn(flags, name)) continue;
25
+ const value = flags[name];
26
+ if (typeof value === "boolean") return value;
27
+ const normalized = String(value).trim().toLowerCase();
28
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
29
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
30
+ throw new Error(`--${name} must be a boolean value`);
31
+ }
32
+ return void 0;
33
+ }
34
+ function parsePositiveInteger(value, label) {
35
+ if (value === void 0) throw new Error(`${label} is required`);
36
+ const parsed = Number.parseInt(value, 10);
37
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer`);
38
+ return parsed;
39
+ }
40
+ function parsePort(value) {
41
+ if (value === void 0) return void 0;
42
+ const parsed = Number.parseInt(value, 10);
43
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
44
+ throw new Error("--port must be a port between 1 and 65535");
45
+ }
46
+ return parsed;
47
+ }
48
+ function requiredString(value, label) {
49
+ if (!value || value.trim() === "") throw new Error(`${label} is required`);
50
+ return value.trim();
51
+ }
52
+ function requireAbsolutePath(value, label) {
53
+ const resolved = requiredString(value, label);
54
+ if (!path.isAbsolute(resolved)) throw new Error(`${label} must be an absolute path`);
55
+ return path.resolve(resolved);
56
+ }
57
+ function parseWebuiSessionChildOptions(flags) {
58
+ if (flags["webui-session-child"] !== true) return null;
59
+ const projectRoot = requireAbsolutePath(flagValue(flags, ["project-root"]), "--project-root");
60
+ const workingDir = requireAbsolutePath(flagValue(flags, ["working-dir"]), "--working-dir");
61
+ const relativeWorkingDir = path.relative(projectRoot, workingDir);
62
+ if (relativeWorkingDir.startsWith("..") || path.isAbsolute(relativeWorkingDir)) {
63
+ throw new Error("--working-dir must be inside --project-root");
64
+ }
65
+ const sessionId = flagValue(flags, ["session-id"]);
66
+ const resume = boolFlag(flags, ["resume"]) ?? false;
67
+ if (sessionId && !resume) throw new Error("--session-id requires --resume");
68
+ if (resume && !sessionId) throw new Error("--resume requires --session-id in child mode");
69
+ const host = flagValue(flags, ["webui-host", "host"]);
70
+ const port = parsePort(flagValue(flags, ["webui-port", "http-port", "port", "ws-port"]));
71
+ const token = flagValue(flags, ["webui-token", "token"]);
72
+ const publicUrl = flagValue(flags, ["webui-public-url", "public-url"]);
73
+ const publicWsUrl = flagValue(flags, ["webui-public-ws-url", "public-ws-url"]);
74
+ const requireToken = boolFlag(flags, ["webui-require-token", "require-token"]);
75
+ return {
76
+ enabled: true,
77
+ projectRoot,
78
+ workingDir,
79
+ ...sessionId ? { sessionId } : {},
80
+ resume,
81
+ ...host ? { host } : {},
82
+ ...port !== void 0 ? { port } : {},
83
+ ...token ? { token } : {},
84
+ ...publicUrl ? { publicUrl } : {},
85
+ ...publicWsUrl ? { publicWsUrl } : {},
86
+ ...requireToken !== void 0 ? { requireToken } : {},
87
+ strictPort: boolFlag(flags, ["strict-port"]) ?? false,
88
+ parentPid: parsePositiveInteger(flagValue(flags, ["parent-pid"]), "--parent-pid"),
89
+ parentShellId: requiredString(flagValue(flags, ["parent-shell-id"]), "--parent-shell-id"),
90
+ runtimeId: requiredString(flagValue(flags, ["runtime-id"]), "--runtime-id"),
91
+ readyFile: requireAbsolutePath(flagValue(flags, ["ready-file"]), "--ready-file"),
92
+ attachable: boolFlag(flags, ["attachable"]) ?? true,
93
+ protocolVersion: parsePositiveInteger(
94
+ flagValue(flags, ["protocol-version"]) ?? String(WEBUI_SESSION_CHILD_PROTOCOL_VERSION),
95
+ "--protocol-version"
96
+ )
97
+ };
98
+ }
99
+ async function writeWebuiSessionChildReady(readyFile, payload) {
100
+ await fs.mkdir(path.dirname(readyFile), { recursive: true });
101
+ await atomicWrite(readyFile, `${JSON.stringify(payload, null, 2)}
102
+ `, { mode: 384 });
103
+ }
104
+ async function writeWebuiSessionChildError(readyFile, input) {
105
+ const payload = {
106
+ type: "webui.session_child.error",
107
+ protocolVersion: input.protocolVersion ?? WEBUI_SESSION_CHILD_PROTOCOL_VERSION,
108
+ ...input.runtimeId ? { runtimeId: input.runtimeId } : {},
109
+ ...input.parentShellId ? { parentShellId: input.parentShellId } : {},
110
+ pid: process.pid,
111
+ phase: input.phase,
112
+ recoverable: input.recoverable,
113
+ message: toErrorMessage(input.error),
114
+ ...input.details ? { details: input.details } : {}
115
+ };
116
+ await fs.mkdir(path.dirname(readyFile), { recursive: true });
117
+ await atomicWrite(readyFile, `${JSON.stringify(payload, null, 2)}
118
+ `, { mode: 384 });
119
+ }
120
+
121
+ export {
122
+ WEBUI_SESSION_CHILD_CAPABILITIES,
123
+ parseWebuiSessionChildOptions,
124
+ writeWebuiSessionChildReady,
125
+ writeWebuiSessionChildError
126
+ };
127
+ //# sourceMappingURL=chunk-PFVVUH5B.js.map
@@ -13,6 +13,7 @@
13
13
  * Returns a `CliContext` object with all the dependencies the rest of
14
14
  * main() needs, or a numeric exit code when a short-circuit fires.
15
15
  */
16
+ import { type WebuiSessionChildOptions } from './boot/webui-session-child.js';
16
17
  import { wireContainer } from './boot/container-wiring.js';
17
18
  import type { EventBus } from '@wrongstack/core/kernel';
18
19
  import type { ConfigStore } from '@wrongstack/core/types';
@@ -22,6 +23,7 @@ export interface CliContext extends BootContext {
22
23
  events: EventBus;
23
24
  container: ReturnType<typeof wireContainer>['container'];
24
25
  configStore: ConfigStore;
26
+ webuiSessionChild?: WebuiSessionChildOptions | undefined;
25
27
  }
26
28
  /**
27
29
  * Run all pre-boot and boot-phase setup. Returns a `CliContext` on success,
@@ -80,11 +80,11 @@ import {
80
80
  runClaudeOAuthLogin,
81
81
  runCopilotOAuthLogin,
82
82
  validateFamily
83
- } from "./chunk-O7XNGCRT.js";
83
+ } from "./chunk-3MOUBRKZ.js";
84
84
  import {
85
85
  LOCAL_LLM_PRESETS,
86
86
  parseSpawnFlags
87
- } from "./chunk-MHF2IJDE.js";
87
+ } from "./chunk-MT6FGXO3.js";
88
88
  import {
89
89
  buildPickableProviders,
90
90
  visibleModelIds
@@ -30335,7 +30335,8 @@ async function runInteractive(cliCtx) {
30335
30335
  events,
30336
30336
  container,
30337
30337
  configStore,
30338
- updateInfo
30338
+ updateInfo,
30339
+ webuiSessionChild
30339
30340
  } = cliCtx;
30340
30341
  const profileConfigPath = activeProfileConfigPath(wpaths, config);
30341
30342
  const modeStore = container.resolve(TOKENS9.ModeStore);
@@ -31011,7 +31012,7 @@ async function runInteractive(cliCtx) {
31011
31012
  onEvent: evOn
31012
31013
  });
31013
31014
  const savedProviderCfg = config.providers?.[config.provider];
31014
- const { execute } = await import("./execution-LHMSMDHB.js");
31015
+ const { execute } = await import("./execution-CQ772VPS.js");
31015
31016
  const stopHeapWatchdog = startSharedHeapWatchdog({
31016
31017
  collectStats: () => {
31017
31018
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -31069,6 +31070,7 @@ async function runInteractive(cliCtx) {
31069
31070
  projectRoot,
31070
31071
  flags,
31071
31072
  positional,
31073
+ webuiSessionChild,
31072
31074
  // Forward preflight's update-info so the TUI banner can render
31073
31075
  // the "(update available: v…)" indicator next to the version
31074
31076
  // chip. May be undefined (e.g. when the registry check was
@@ -31243,4 +31245,4 @@ export {
31243
31245
  CLI_VERSION,
31244
31246
  runInteractive
31245
31247
  };
31246
- //# sourceMappingURL=cli-main-XWC57MGE.js.map
31248
+ //# sourceMappingURL=cli-main-EOJ74CI4.js.map
@@ -15,6 +15,7 @@ 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
17
  import type { QueueStore } from '@wrongstack/core/storage';
18
+ import type { WebuiSessionChildOptions } from './boot/webui-session-child.js';
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';
20
21
  import type { MCPRegistry } from '@wrongstack/mcp';
@@ -98,6 +99,8 @@ export interface CoreDeps {
98
99
  * is consumed; the TUI reuses the result without making a second request.
99
100
  */
100
101
  updateInfo?: UpdateInfo | undefined;
102
+ /** Internal one-session WebUI child launch metadata, when --webui-session-child is active. */
103
+ webuiSessionChild?: WebuiSessionChildOptions | undefined;
101
104
  }
102
105
  /** Session + state stores. */
103
106
  export interface SessionDeps {
@@ -25,6 +25,11 @@ import {
25
25
  trySaveSpecFromAIOutput,
26
26
  trySaveTasksFromAIOutput
27
27
  } from "./chunk-XTIXVJXP.js";
28
+ import {
29
+ WEBUI_SESSION_CHILD_CAPABILITIES,
30
+ writeWebuiSessionChildError,
31
+ writeWebuiSessionChildReady
32
+ } from "./chunk-PFVVUH5B.js";
28
33
  import {
29
34
  theme
30
35
  } from "./chunk-QD544J2B.js";
@@ -47,7 +52,7 @@ import {
47
52
  import "./chunk-7OCVIDC7.js";
48
53
 
49
54
  // src/execution.ts
50
- import * as path11 from "node:path";
55
+ import * as path12 from "node:path";
51
56
  import { effectiveFallbackChain as effectiveFallbackChain2, setQueuedMessagesSnapshot as setQueuedMessagesSnapshot2 } from "@wrongstack/core/agent";
52
57
  import { attachTodosCheckpoint as attachTodosCheckpoint2 } from "@wrongstack/core/storage";
53
58
  import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
@@ -155,6 +160,7 @@ async function runTuiDispatch(options) {
155
160
  }
156
161
 
157
162
  // src/boot/dispatch-webui.ts
163
+ import * as path from "node:path";
158
164
  import { color as color2 } from "@wrongstack/core/utils";
159
165
  async function runWebUIDispatch(ctx) {
160
166
  const {
@@ -190,12 +196,14 @@ async function runWebUIDispatch(ctx) {
190
196
  agentTranscripts,
191
197
  sddSubagentFactory,
192
198
  onKanbanDispatch,
193
- getFleetBudget
199
+ getFleetBudget,
200
+ webuiSessionChild
194
201
  } = ctx;
195
- const isSimpleUi = flags["simpleui"] === true;
202
+ const isSessionChild = Boolean(webuiSessionChild);
203
+ const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
196
204
  agent.disableInteractiveConfirmation();
197
205
  renderer.setSilent(true);
198
- const { runWebUI } = await import("./webui-server-DD35QFNO.js");
206
+ const { runWebUI } = await import("./webui-server-NFNRGNNT.js");
199
207
  const flagValue = (names) => {
200
208
  for (const name of names) {
201
209
  if (!Object.hasOwn(flags, name)) continue;
@@ -238,22 +246,32 @@ async function runWebUIDispatch(ctx) {
238
246
  let webuiRequireToken;
239
247
  let frontendDistDir;
240
248
  try {
241
- webuiHost = flagValue(["webui-host", "host"]) ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
249
+ webuiHost = webuiSessionChild?.host ?? flagValue(["webui-host", "host"]) ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
242
250
  const defaultPort = isSimpleUi ? 3466 : 3456;
243
- webuiPort = parsePort(
251
+ webuiPort = webuiSessionChild?.port ?? parsePort(
244
252
  flagValue(["webui-port", "http-port", "port", "ws-port"]) ?? process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? process.env["WS_PORT"],
245
253
  defaultPort,
246
254
  "--port"
247
255
  );
248
- webuiAccessToken = flagValue(["webui-token"]) ?? process.env["WEBUI_TOKEN"] ?? process.env["WEBUI_AUTH_TOKEN"];
249
- webuiPublicUrl = flagValue(["webui-public-url", "public-url"]) ?? process.env["WEBUI_PUBLIC_URL"];
250
- webuiPublicWsUrl = flagValue(["webui-public-ws-url", "public-ws-url"]) ?? process.env["WEBUI_PUBLIC_WS_URL"];
251
- webuiRequireToken = flagBoolean(["webui-require-token", "require-token"]) ?? envFlag("WEBUI_REQUIRE_TOKEN");
256
+ webuiAccessToken = webuiSessionChild?.token ?? flagValue(["webui-token"]) ?? process.env["WEBUI_TOKEN"] ?? process.env["WEBUI_AUTH_TOKEN"];
257
+ webuiPublicUrl = webuiSessionChild?.publicUrl ?? flagValue(["webui-public-url", "public-url"]) ?? process.env["WEBUI_PUBLIC_URL"];
258
+ webuiPublicWsUrl = webuiSessionChild?.publicWsUrl ?? flagValue(["webui-public-ws-url", "public-ws-url"]) ?? process.env["WEBUI_PUBLIC_WS_URL"];
259
+ webuiRequireToken = webuiSessionChild?.requireToken ?? flagBoolean(["webui-require-token", "require-token"]) ?? envFlag("WEBUI_REQUIRE_TOKEN");
252
260
  if (isSimpleUi) {
253
261
  const { ensureSimpleUiDistDir } = await import("./simpleui-dist-JIFGUJO7.js");
254
262
  frontendDistDir = await ensureSimpleUiDistDir();
255
263
  }
256
264
  } catch (err) {
265
+ if (webuiSessionChild) {
266
+ await writeWebuiSessionChildError(webuiSessionChild.readyFile, {
267
+ protocolVersion: webuiSessionChild.protocolVersion,
268
+ runtimeId: webuiSessionChild.runtimeId,
269
+ parentShellId: webuiSessionChild.parentShellId,
270
+ phase: "validate_args",
271
+ recoverable: false,
272
+ error: err
273
+ }).catch(() => void 0);
274
+ }
257
275
  renderer.setSilent(false);
258
276
  renderer.writeInfo(color2.red(` ${err instanceof Error ? err.message : String(err)}`));
259
277
  return 1;
@@ -271,9 +289,11 @@ async function runWebUIDispatch(ctx) {
271
289
  publicUrl: webuiPublicUrl,
272
290
  publicWsUrl: webuiPublicWsUrl,
273
291
  requireToken: webuiRequireToken,
292
+ strictPort: webuiSessionChild?.strictPort,
274
293
  projectRoot,
275
294
  appConfig: config,
276
- open: !!flags.open,
295
+ open: webuiSessionChild ? false : !!flags.open,
296
+ webuiSessionChild,
277
297
  agentTranscripts,
278
298
  hqAllowExec: flagBoolean(["hq-allow-exec"]) ?? false,
279
299
  modelsRegistry,
@@ -316,7 +336,50 @@ async function runWebUIDispatch(ctx) {
316
336
  // listening, using the RESOLVED port. Requested ports auto-advance past
317
337
  // busy ports inside runWebUI, so a banner printed up-front lies whenever
318
338
  // the default when it is taken (a second instance, leftover socket).
319
- onListening: ({ url }) => {
339
+ onListening: ({ httpPort, host, url, authToken }) => {
340
+ if (webuiSessionChild) {
341
+ void writeWebuiSessionChildReady(webuiSessionChild.readyFile, {
342
+ type: "webui.session_child.ready",
343
+ protocolVersion: webuiSessionChild.protocolVersion,
344
+ runtime: {
345
+ role: "session-child",
346
+ runtimeId: webuiSessionChild.runtimeId,
347
+ pid: process.pid,
348
+ parentPid: webuiSessionChild.parentPid,
349
+ parentShellId: webuiSessionChild.parentShellId,
350
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
351
+ },
352
+ project: {
353
+ projectRoot,
354
+ workingDir: webuiSessionChild.workingDir,
355
+ projectSlug: path.basename(projectRoot) || projectRoot,
356
+ projectName: path.basename(projectRoot) || projectRoot
357
+ },
358
+ session: {
359
+ sessionId: session.id,
360
+ created: !webuiSessionChild.resume,
361
+ resumed: webuiSessionChild.resume,
362
+ provider: config.provider,
363
+ model: config.model
364
+ },
365
+ endpoint: {
366
+ surface: "webui",
367
+ host,
368
+ httpPort,
369
+ url,
370
+ ...webuiPublicUrl ? { publicUrl: webuiPublicUrl } : {},
371
+ ...webuiPublicWsUrl ? { publicWsUrl: webuiPublicWsUrl } : {},
372
+ requireToken: webuiRequireToken,
373
+ auth: authToken ? { scheme: "registry-token", tokenPresent: true, token: authToken } : { scheme: "none", tokenPresent: false }
374
+ },
375
+ registry: {
376
+ webuiInstanceRegistered: true,
377
+ sessionRegistered: true
378
+ },
379
+ capabilities: [...WEBUI_SESSION_CHILD_CAPABILITIES]
380
+ }).catch(() => void 0);
381
+ return;
382
+ }
320
383
  const surface = isSimpleUi ? "SimpleUI" : "WebUI";
321
384
  renderer.writeInfo(
322
385
  ` \u2726 ${terminalText(surface, "success", { bold: true })} ${terminalText("running \u2192", "muted")} ${terminalLink(url)}`
@@ -346,6 +409,16 @@ async function runWebUIDispatch(ctx) {
346
409
  );
347
410
  resolve3(0);
348
411
  }).catch((err) => {
412
+ if (webuiSessionChild) {
413
+ void writeWebuiSessionChildError(webuiSessionChild.readyFile, {
414
+ protocolVersion: webuiSessionChild.protocolVersion,
415
+ runtimeId: webuiSessionChild.runtimeId,
416
+ parentShellId: webuiSessionChild.parentShellId,
417
+ phase: "start_server",
418
+ recoverable: false,
419
+ error: err
420
+ }).catch(() => void 0);
421
+ }
349
422
  renderer.setSilent(false);
350
423
  renderer.writeInfo(
351
424
  color2.red(
@@ -362,8 +435,8 @@ async function runWebUIDispatch(ctx) {
362
435
  function resolveExecutionMode(positional, flags) {
363
436
  const prompt = typeof flags["prompt"] === "string" ? flags["prompt"] : "";
364
437
  if (positional.length > 0 || prompt.length > 0) return "single-shot";
365
- if (flags.tui && flags["no-tui"] !== true && !flags.webui) return "tui";
366
- if (flags.webui) return "webui";
438
+ if (flags.tui && flags["no-tui"] !== true && !flags.webui && !flags["webui-session-child"]) return "tui";
439
+ if (flags.webui || flags["webui-session-child"]) return "webui";
367
440
  return "repl";
368
441
  }
369
442
 
@@ -503,7 +576,7 @@ function createTuiCoordinatorCallbacks({
503
576
  }
504
577
 
505
578
  // src/boot/tui-coordinator-setup.ts
506
- import * as path from "node:path";
579
+ import * as path2 from "node:path";
507
580
  import { AutonomousCoordinator } from "@wrongstack/core/coordination";
508
581
  function setupAutonomousCoordinator(ctx) {
509
582
  const { state, events, context, wpaths, mailbox, director, getDirector, coordinatorController, onCoordinatorStopSetter } = ctx;
@@ -512,7 +585,7 @@ function setupAutonomousCoordinator(ctx) {
512
585
  const currentDirector = getDirector?.() ?? director;
513
586
  if (!currentDirector) return null;
514
587
  const transcript = context.session.transcriptPath;
515
- const sessionDir = transcript ? path.dirname(transcript) : wpaths.projectDir;
588
+ const sessionDir = transcript ? path2.dirname(transcript) : wpaths.projectDir;
516
589
  const llmProvider = {
517
590
  decide: async (prompt) => {
518
591
  const sysPrompt = [
@@ -723,10 +796,10 @@ function wireGoal(events) {
723
796
  }
724
797
 
725
798
  // src/boot/tui-live-sessions.ts
726
- import * as path2 from "node:path";
799
+ import * as path3 from "node:path";
727
800
  async function getLiveSessions(ctx) {
728
801
  const { SessionRegistry } = await import("@wrongstack/core/storage");
729
- const globalRoot = path2.dirname(ctx.state.wpaths.globalConfig);
802
+ const globalRoot = path3.dirname(ctx.state.wpaths.globalConfig);
730
803
  const registry = new SessionRegistry(globalRoot);
731
804
  const sessions = await registry.list();
732
805
  return sessions.filter((s) => s.status !== "stale").map((s) => ({
@@ -756,7 +829,7 @@ function onSwitchToSession(ctx, _sessionId, targetRoot, projectName) {
756
829
  }
757
830
 
758
831
  // src/boot/tui-project-picker-callback.ts
759
- import * as path3 from "node:path";
832
+ import * as path4 from "node:path";
760
833
  import { color as color3 } from "@wrongstack/core/utils";
761
834
  async function getProjectPickerItems(ctx) {
762
835
  const { buildPickerItems } = await import("./project-picker-BOCWHBXP.js");
@@ -770,7 +843,7 @@ async function onProjectSelect(ctx, slug, kind) {
770
843
  try {
771
844
  if (kind === "action") {
772
845
  if (slug === "new-session") {
773
- const name = path3.basename(state.projectRoot) || state.projectRoot;
846
+ const name = path4.basename(state.projectRoot) || state.projectRoot;
774
847
  const err2 = await switchProjectInPlace2(state.projectRoot, name);
775
848
  if (err2) renderer.write(color3.red(`Project switch failed: ${err2}
776
849
  `));
@@ -781,8 +854,8 @@ async function onProjectSelect(ctx, slug, kind) {
781
854
  const manifest = await loadManifest(state.wpaths.globalConfig);
782
855
  const project = manifest.projects.find((p) => p.slug === slug);
783
856
  if (!project) return;
784
- const targetRoot = path3.resolve(project.root);
785
- if (path3.resolve(state.projectRoot) === targetRoot) return;
857
+ const targetRoot = path4.resolve(project.root);
858
+ if (path4.resolve(state.projectRoot) === targetRoot) return;
786
859
  const fleetStatus = director?.status();
787
860
  const fleetRunning = fleetStatus?.subagents.filter((a) => a.status === "running").length ?? 0;
788
861
  const eternalActive = getEternalEngine?.()?.currentState === "running";
@@ -820,7 +893,7 @@ ${parts.join("\n")}
820
893
  import { spawn } from "node:child_process";
821
894
  import { createRequire } from "node:module";
822
895
  import * as fs from "node:fs/promises";
823
- import * as path4 from "node:path";
896
+ import * as path5 from "node:path";
824
897
  import { color as color4 } from "@wrongstack/core/utils";
825
898
  async function handleProjectSwitchSpawn(opts) {
826
899
  const PROJECT_SWITCH_EXIT_CODE = 42;
@@ -833,8 +906,8 @@ async function handleProjectSwitchSpawn(opts) {
833
906
  try {
834
907
  const req = createRequire(import.meta.url);
835
908
  const pkgPath = req.resolve("@wrongstack/cli/package.json");
836
- const pkgDir = path4.dirname(pkgPath);
837
- cliPath = path4.join(pkgDir, "dist", "index.js");
909
+ const pkgDir = path5.dirname(pkgPath);
910
+ cliPath = path5.join(pkgDir, "dist", "index.js");
838
911
  await fs.access(cliPath);
839
912
  } catch {
840
913
  cliPath = process.argv[1] ?? "";
@@ -870,7 +943,7 @@ async function handleProjectSwitchSpawn(opts) {
870
943
 
871
944
  // src/boot/tui-project-switch.ts
872
945
  import * as fs2 from "node:fs/promises";
873
- import * as path5 from "node:path";
946
+ import * as path6 from "node:path";
874
947
  import {
875
948
  DefaultSystemPromptBuilder,
876
949
  setQueuedMessagesSnapshot
@@ -891,7 +964,7 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
891
964
  skillLoader,
892
965
  attachTodosCheckpoint: attachTodosCheckpoint3
893
966
  } = ctx;
894
- const resolved = path5.resolve(targetRoot);
967
+ const resolved = path6.resolve(targetRoot);
895
968
  const stat2 = await fs2.stat(resolved).catch(() => null);
896
969
  if (!stat2?.isDirectory()) return `Cannot switch: not a directory: ${resolved}`;
897
970
  const oldWriter = context.session;
@@ -930,7 +1003,7 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
930
1003
  await state.activateSessionIdentity(nextWriter.id, {
931
1004
  projectSlug: nextWpaths.projectSlug,
932
1005
  projectRoot: resolved,
933
- projectName: displayName || path5.basename(resolved),
1006
+ projectName: displayName || path6.basename(resolved),
934
1007
  workingDir: resolved
935
1008
  });
936
1009
  } catch (err) {
@@ -1067,7 +1140,7 @@ async function onSDDOutput(output) {
1067
1140
  }
1068
1141
 
1069
1142
  // src/boot/tui-session-resume.ts
1070
- import * as path6 from "node:path";
1143
+ import * as path7 from "node:path";
1071
1144
  import { attachTodosCheckpoint, loadTodosCheckpoint } from "@wrongstack/core/storage";
1072
1145
  import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
1073
1146
  async function resumeSession(ctx, sessionId) {
@@ -1093,7 +1166,7 @@ async function resumeSession(ctx, sessionId) {
1093
1166
  }
1094
1167
  try {
1095
1168
  const { SessionRegistry } = await import("@wrongstack/core/storage");
1096
- const registry = new SessionRegistry(path6.dirname(state.wpaths.globalConfig));
1169
+ const registry = new SessionRegistry(path7.dirname(state.wpaths.globalConfig));
1097
1170
  const live = (await registry.list()).find(
1098
1171
  (s) => s.sessionId === canonicalSessionId && s.status !== "stale" && s.pid !== process.pid
1099
1172
  );
@@ -1304,7 +1377,7 @@ async function resumeSession(ctx, sessionId) {
1304
1377
 
1305
1378
  // src/boot/tui-settings-adapter.ts
1306
1379
  import * as fs3 from "node:fs/promises";
1307
- import * as path7 from "node:path";
1380
+ import * as path8 from "node:path";
1308
1381
  import { decryptConfigSecrets, encryptConfigSecrets, noOpVault } from "@wrongstack/core/security";
1309
1382
  import { normalizeTokenSavingTier, resolveFleetChatVerbosity } from "@wrongstack/core/types";
1310
1383
  import { atomicWrite, deepMerge } from "@wrongstack/core/utils";
@@ -1595,7 +1668,7 @@ function createSettingsAdapter(ctx) {
1595
1668
  const isProjectTarget = actualTarget === wpaths.inProjectConfig;
1596
1669
  const toWrite = isProjectTarget ? filterSafeForProject(mergedToWrite) : mergedToWrite;
1597
1670
  const encrypted = encryptConfigSecrets(toWrite, noOpVault);
1598
- await fs3.mkdir(path7.dirname(actualTarget), { recursive: true });
1671
+ await fs3.mkdir(path8.dirname(actualTarget), { recursive: true });
1599
1672
  await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
1600
1673
  const currentConfig = configStore.get();
1601
1674
  const nextModelRuntime = {
@@ -2520,7 +2593,7 @@ function createChimeraWorkRegistry() {
2520
2593
 
2521
2594
  // src/execution-chimera-cascade.ts
2522
2595
  import * as fsp from "node:fs/promises";
2523
- import * as path8 from "node:path";
2596
+ import * as path9 from "node:path";
2524
2597
  import { emitReviewIfChanged } from "@wrongstack/core/plugin";
2525
2598
 
2526
2599
  // src/chimera-review-task.ts
@@ -2883,7 +2956,7 @@ async function maybeReReviewCascade({
2883
2956
  const reReadFiles = [];
2884
2957
  for (const f of bundle.files) {
2885
2958
  try {
2886
- const absPath = path8.join(bundle.cwd, f.path);
2959
+ const absPath = path9.join(bundle.cwd, f.path);
2887
2960
  const content = await fsp.readFile(absPath, "utf8");
2888
2961
  reReadFiles.push({ path: f.path, status: "modified", content });
2889
2962
  } catch {
@@ -2952,7 +3025,7 @@ async function maybeReReviewCascade({
2952
3025
 
2953
3026
  // src/execution-chimera-review.ts
2954
3027
  import { randomUUID as randomUUID3 } from "node:crypto";
2955
- import path9 from "node:path";
3028
+ import path10 from "node:path";
2956
3029
  import { effectiveFallbackChain } from "@wrongstack/core/agent";
2957
3030
  import {
2958
3031
  CHIMERA_REVIEW_PROMPT,
@@ -2965,7 +3038,7 @@ import {
2965
3038
  function normalizeFileKeyForCitation(raw, cwd) {
2966
3039
  const forward = raw.replace(/\\/g, "/").replace(/^\.\//, "");
2967
3040
  const isAbsolute = forward.startsWith("/") || /^[a-zA-Z]:\//.test(forward);
2968
- const pathMod = process.platform === "win32" ? path9.win32 : path9;
3041
+ const pathMod = process.platform === "win32" ? path10.win32 : path10;
2969
3042
  const relative = isAbsolute ? pathMod.relative(cwd, forward).replace(/\\/g, "/").replace(/^\.\//, "") : forward;
2970
3043
  return process.platform === "win32" ? relative.toLowerCase() : relative;
2971
3044
  }
@@ -3163,16 +3236,16 @@ function installChimeraReviewHandler({
3163
3236
  const changedBasenameIndex = /* @__PURE__ */ new Map();
3164
3237
  for (const file of p.files) {
3165
3238
  const normalized = normalizeFileKeyForCitation(file.path, p.cwd);
3166
- const basename5 = path9.basename(file.path).toLowerCase();
3167
- const bucket = changedBasenameIndex.get(basename5);
3239
+ const basename6 = path10.basename(file.path).toLowerCase();
3240
+ const bucket = changedBasenameIndex.get(basename6);
3168
3241
  if (bucket) bucket.push(normalized);
3169
- else changedBasenameIndex.set(basename5, [normalized]);
3242
+ else changedBasenameIndex.set(basename6, [normalized]);
3170
3243
  }
3171
3244
  const droppedCitations = citedFindings.filter((finding) => {
3172
3245
  const citedKey = normalizeFileKeyForCitation(finding.location.file, p.cwd);
3173
3246
  if (citedPaths.has(citedKey)) return false;
3174
- const basename5 = path9.basename(finding.location.file).toLowerCase();
3175
- const bucket = changedBasenameIndex.get(basename5);
3247
+ const basename6 = path10.basename(finding.location.file).toLowerCase();
3248
+ const bucket = changedBasenameIndex.get(basename6);
3176
3249
  return !(bucket && bucket.length === 1);
3177
3250
  });
3178
3251
  const effectiveHasFindings = reviewHasFindings && (parsedReview.findings.length === 0 || parsedReview.findings.length > droppedCitations.length);
@@ -3694,7 +3767,7 @@ function printBanner(renderer, projectName) {
3694
3767
 
3695
3768
  // src/repl-client-registration.ts
3696
3769
  import * as crypto from "node:crypto";
3697
- import * as path10 from "node:path";
3770
+ import * as path11 from "node:path";
3698
3771
  import {
3699
3772
  getSharedProjectMailbox,
3700
3773
  resolveProjectDir
@@ -3707,7 +3780,7 @@ function registerReplClient(opts) {
3707
3780
  const hqConnection = startCliHqConnection({
3708
3781
  clientKind: "repl",
3709
3782
  projectRoot: replProjectRoot,
3710
- projectName: path10.basename(replProjectRoot),
3783
+ projectName: path11.basename(replProjectRoot),
3711
3784
  appConfig: opts.appConfig,
3712
3785
  capabilities: ["telemetry.publish", "mailbox.summary"]
3713
3786
  });
@@ -3721,7 +3794,7 @@ function registerReplClient(opts) {
3721
3794
  clientMailbox.registerClient({
3722
3795
  clientId,
3723
3796
  sessionId: opts.getSessionId?.() ?? replProjectRoot,
3724
- name: `REPL [${path10.basename(replProjectRoot)}]`,
3797
+ name: `REPL [${path11.basename(replProjectRoot)}]`,
3725
3798
  source: "repl",
3726
3799
  pid: process.pid
3727
3800
  }).then(() => {
@@ -4515,7 +4588,8 @@ async function execute(deps) {
4515
4588
  slashRegistry,
4516
4589
  tokenCounter,
4517
4590
  activateSessionIdentity,
4518
- updateInfo: initialUpdateInfo
4591
+ updateInfo: initialUpdateInfo,
4592
+ webuiSessionChild
4519
4593
  },
4520
4594
  session: {
4521
4595
  session,
@@ -4983,6 +5057,7 @@ async function execute(deps) {
4983
5057
  sddSubagentFactory,
4984
5058
  statusTracker,
4985
5059
  updateInfo: bootUpdateInfo,
5060
+ webuiSessionChild,
4986
5061
  getFleetBudget: () => {
4987
5062
  const d = getDirector?.() ?? null;
4988
5063
  if (!d) return null;
@@ -5025,7 +5100,7 @@ async function execute(deps) {
5025
5100
  attachments,
5026
5101
  effectiveMaxContext,
5027
5102
  getEffectiveMaxContext,
5028
- projectName: path11.basename(projectRoot) || void 0,
5103
+ projectName: path12.basename(projectRoot) || void 0,
5029
5104
  projectRoot,
5030
5105
  appConfig: config,
5031
5106
  getSessionId: () => agent.ctx.session?.id ?? session.id,
@@ -5084,4 +5159,4 @@ export {
5084
5159
  execute,
5085
5160
  resolveReviewerFallbackModels
5086
5161
  };
5087
- //# sourceMappingURL=execution-LHMSMDHB.js.map
5162
+ //# sourceMappingURL=execution-CQ772VPS.js.map
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ parseWebuiSessionChildOptions,
4
+ writeWebuiSessionChildError
5
+ } from "./chunk-PFVVUH5B.js";
2
6
  import {
3
7
  applySimpleUiFullAutoProfile,
4
8
  detectProjectFacts,
@@ -37,7 +41,7 @@ import {
37
41
  parseArgs,
38
42
  runPicker,
39
43
  saveToGlobalConfig
40
- } from "./chunk-MHF2IJDE.js";
44
+ } from "./chunk-MT6FGXO3.js";
41
45
  import {
42
46
  isKeylessLocalProvider,
43
47
  visibleModelIds
@@ -1499,7 +1503,7 @@ function compactSingleLine(text) {
1499
1503
  var loaders = {
1500
1504
  acp: async () => (await import("./acp-DATHHPNT.js")).acpCmd,
1501
1505
  init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
1502
- auth: async () => (await import("./auth-JASC4K3F.js")).authCmd,
1506
+ auth: async () => (await import("./auth-PJD3JRH2.js")).authCmd,
1503
1507
  update: async () => (await import("./update-MV6TUACD.js")).updateCmd,
1504
1508
  sessions: async () => (await import("./sessions-config-XXN7267N.js")).sessionsCmd,
1505
1509
  config: async () => (await import("./sessions-config-XXN7267N.js")).configCmd,
@@ -2477,6 +2481,7 @@ var PORT_TIMEOUT_MS = 8e3;
2477
2481
  function shouldSkipMenu(argv, flags, positional) {
2478
2482
  if (flags["no-menu"] === true) return true;
2479
2483
  if (flags["webui"] === true) return true;
2484
+ if (flags["webui-session-child"] === true) return true;
2480
2485
  if (flags["simpleui"] === true) return true;
2481
2486
  if (flags["hq"] === true) return true;
2482
2487
  if (flags["desktop"] === true) return true;
@@ -2992,6 +2997,7 @@ function bindReplayToContainer(opts) {
2992
2997
  // src/cli-context.ts
2993
2998
  import { setOAuthTokenPersister } from "@wrongstack/providers";
2994
2999
  import { TOKENS as TOKENS4 } from "@wrongstack/core/kernel";
3000
+ import { writeErr as writeErr4 } from "@wrongstack/core/utils";
2995
3001
  async function initializeCli(argv) {
2996
3002
  applyNodeEnvDefault();
2997
3003
  applySessionShellDefault();
@@ -3001,6 +3007,28 @@ async function initializeCli(argv) {
3001
3007
  const desktopExit = await handleDesktopShortCircuit(earlyFlags, argv);
3002
3008
  if (desktopExit !== null) return desktopExit;
3003
3009
  const { flags: _earlyForMenu, positional: _positionalForMenu } = parseArgs(argv);
3010
+ let webuiSessionChild;
3011
+ if (_earlyForMenu["webui-session-child"] === true) {
3012
+ try {
3013
+ webuiSessionChild = parseWebuiSessionChildOptions(_earlyForMenu) ?? void 0;
3014
+ } catch (err) {
3015
+ const readyFile = typeof _earlyForMenu["ready-file"] === "string" ? _earlyForMenu["ready-file"] : void 0;
3016
+ if (readyFile) {
3017
+ await writeWebuiSessionChildError(readyFile, {
3018
+ runtimeId: typeof _earlyForMenu["runtime-id"] === "string" ? _earlyForMenu["runtime-id"] : void 0,
3019
+ parentShellId: typeof _earlyForMenu["parent-shell-id"] === "string" ? _earlyForMenu["parent-shell-id"] : void 0,
3020
+ phase: "validate_args",
3021
+ recoverable: false,
3022
+ error: err
3023
+ }).catch(() => void 0);
3024
+ }
3025
+ writeErr4(
3026
+ `WebUI session child argument error: ${err instanceof Error ? err.message : String(err)}
3027
+ `
3028
+ );
3029
+ return 2;
3030
+ }
3031
+ }
3004
3032
  const launchMenuReader = new ReadlineInputReader();
3005
3033
  let menuResult;
3006
3034
  try {
@@ -3034,7 +3062,55 @@ async function initializeCli(argv) {
3034
3062
  if (hqAfterMenu !== null) return hqAfterMenu;
3035
3063
  }
3036
3064
  }
3037
- const hqExit = await handleHqShortCircuit(parseArgs(effectiveArgv).flags);
3065
+ const effectiveFlags = parseArgs(effectiveArgv).flags;
3066
+ try {
3067
+ webuiSessionChild = webuiSessionChild ?? parseWebuiSessionChildOptions(effectiveFlags) ?? void 0;
3068
+ } catch (err) {
3069
+ const readyFile = typeof effectiveFlags["ready-file"] === "string" ? effectiveFlags["ready-file"] : void 0;
3070
+ if (readyFile) {
3071
+ await writeWebuiSessionChildError(readyFile, {
3072
+ runtimeId: typeof effectiveFlags["runtime-id"] === "string" ? effectiveFlags["runtime-id"] : void 0,
3073
+ parentShellId: typeof effectiveFlags["parent-shell-id"] === "string" ? effectiveFlags["parent-shell-id"] : void 0,
3074
+ phase: "validate_args",
3075
+ recoverable: false,
3076
+ error: err
3077
+ }).catch(() => void 0);
3078
+ }
3079
+ writeErr4(`WebUI session child argument error: ${err instanceof Error ? err.message : String(err)}
3080
+ `);
3081
+ return 2;
3082
+ }
3083
+ if (webuiSessionChild) {
3084
+ try {
3085
+ process.chdir(webuiSessionChild.workingDir);
3086
+ if (webuiSessionChild.resume && webuiSessionChild.sessionId) {
3087
+ const currentResume = effectiveFlags["resume"];
3088
+ if (typeof currentResume === "string" && currentResume !== webuiSessionChild.sessionId) {
3089
+ throw new Error(
3090
+ `--resume ${currentResume} conflicts with --session-id ${webuiSessionChild.sessionId}`
3091
+ );
3092
+ }
3093
+ if (currentResume !== webuiSessionChild.sessionId) {
3094
+ effectiveArgv = [...effectiveArgv, "--resume", webuiSessionChild.sessionId];
3095
+ }
3096
+ }
3097
+ } catch (err) {
3098
+ await writeWebuiSessionChildError(webuiSessionChild.readyFile, {
3099
+ protocolVersion: webuiSessionChild.protocolVersion,
3100
+ runtimeId: webuiSessionChild.runtimeId,
3101
+ parentShellId: webuiSessionChild.parentShellId,
3102
+ phase: "validate_args",
3103
+ recoverable: false,
3104
+ error: err
3105
+ }).catch(() => void 0);
3106
+ writeErr4(
3107
+ `WebUI session child argument error: ${err instanceof Error ? err.message : String(err)}
3108
+ `
3109
+ );
3110
+ return 2;
3111
+ }
3112
+ }
3113
+ const hqExit = await handleHqShortCircuit(effectiveFlags);
3038
3114
  if (hqExit !== null) return hqExit;
3039
3115
  const ctx = await boot(effectiveArgv);
3040
3116
  if (typeof ctx === "number") return ctx;
@@ -3106,7 +3182,8 @@ async function initializeCli(argv) {
3106
3182
  updateInfo: refreshedUpdateInfo,
3107
3183
  events,
3108
3184
  container,
3109
- configStore
3185
+ configStore,
3186
+ webuiSessionChild
3110
3187
  };
3111
3188
  }
3112
3189
 
@@ -3114,7 +3191,7 @@ async function initializeCli(argv) {
3114
3191
  async function main(argv) {
3115
3192
  const cliCtx = await initializeCli(argv);
3116
3193
  if (typeof cliCtx === "number") return cliCtx;
3117
- const { runInteractive } = await import("./cli-main-XWC57MGE.js");
3194
+ const { runInteractive } = await import("./cli-main-EOJ74CI4.js");
3118
3195
  return runInteractive(cliCtx);
3119
3196
  }
3120
3197
 
@@ -7,6 +7,9 @@ import {
7
7
  import {
8
8
  startCliHqConnection
9
9
  } from "./chunk-JW75HY4F.js";
10
+ import {
11
+ WEBUI_SESSION_CHILD_CAPABILITIES
12
+ } from "./chunk-PFVVUH5B.js";
10
13
  import {
11
14
  loadConfigProviders,
12
15
  mutateConfigProviders
@@ -872,7 +875,7 @@ async function runWebUI(opts) {
872
875
  const surface = opts.surface ?? "webui";
873
876
  const surfaceDefaults = surface === "simpleui" ? { http: 3466 } : { http: 3456 };
874
877
  const requestedHttpPort = opts.httpPort ?? opts.port ?? surfaceDefaults.http;
875
- const strictPort = process.env["WEBUI_STRICT_PORT"] === "1" || process.env["WEBUI_STRICT_PORT"] === "true";
878
+ const strictPort = opts.strictPort ?? (process.env["WEBUI_STRICT_PORT"] === "1" || process.env["WEBUI_STRICT_PORT"] === "true");
876
879
  let httpPort = requestedHttpPort;
877
880
  if (!strictPort) {
878
881
  httpPort = await findFreePort(host, requestedHttpPort);
@@ -1089,24 +1092,47 @@ async function runWebUI(opts) {
1089
1092
  `[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`). WS bridge still active on ws://${host}:${httpPort}.`
1090
1093
  );
1091
1094
  }
1095
+ const currentSessionId = () => opts.agent.ctx.session?.id ?? opts.session.id;
1092
1096
  const registryBaseDir = globalRoot;
1097
+ let webuiInstanceRegistered = false;
1093
1098
  if (opts.projectRoot) {
1094
- registerWebuiInstance({
1095
- pid: process.pid,
1096
- surface,
1097
- host,
1098
- httpPort,
1099
- publicUrl,
1100
- projectRoot: opts.projectRoot,
1101
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1102
- registryBaseDir,
1103
- // Lets a same-project TUI/REPL authenticate `POST /api/fleet/ping` now
1104
- // that the API requires a token on every bind (H3).
1105
- authToken: wsToken
1106
- });
1099
+ const registration = Promise.resolve(
1100
+ registerWebuiInstance({
1101
+ pid: process.pid,
1102
+ surface,
1103
+ host,
1104
+ httpPort,
1105
+ publicUrl,
1106
+ projectRoot: opts.projectRoot,
1107
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1108
+ registryBaseDir,
1109
+ // Lets a same-project TUI/REPL authenticate `POST /api/fleet/ping` now
1110
+ // that the API requires a token on every bind (H3).
1111
+ authToken: wsToken,
1112
+ ...opts.webuiSessionChild ? {
1113
+ role: "session-child",
1114
+ sessionId: currentSessionId(),
1115
+ parentPid: opts.webuiSessionChild.parentPid,
1116
+ parentShellId: opts.webuiSessionChild.parentShellId,
1117
+ runtimeId: opts.webuiSessionChild.runtimeId,
1118
+ attachable: opts.webuiSessionChild.attachable,
1119
+ lastReadyAt: (/* @__PURE__ */ new Date()).toISOString(),
1120
+ protocolVersion: opts.webuiSessionChild.protocolVersion,
1121
+ capabilities: [...WEBUI_SESSION_CHILD_CAPABILITIES]
1122
+ } : {}
1123
+ })
1124
+ ).then(
1125
+ (value) => value !== false,
1126
+ () => false
1127
+ );
1128
+ if (opts.webuiSessionChild) {
1129
+ webuiInstanceRegistered = await registration;
1130
+ } else {
1131
+ void registration;
1132
+ webuiInstanceRegistered = true;
1133
+ }
1107
1134
  }
1108
1135
  const eventUnsubscribers = [];
1109
- const currentSessionId = () => opts.agent.ctx.session?.id ?? opts.session.id;
1110
1136
  const sessionPayload = (payload) => {
1111
1137
  const provided = payload["sessionId"];
1112
1138
  const sessionId = typeof provided === "string" && provided.length > 0 ? provided : currentSessionId();
@@ -1406,7 +1432,14 @@ async function runWebUI(opts) {
1406
1432
  console.log(`[WebUI] WebSocket server running on ws://${host}:${httpPort}`);
1407
1433
  try {
1408
1434
  setupEvents();
1409
- opts.onListening?.({ httpPort, wsPort, host, url: accessUrl });
1435
+ opts.onListening?.({
1436
+ httpPort,
1437
+ wsPort,
1438
+ host,
1439
+ url: accessUrl,
1440
+ authToken: wsToken,
1441
+ webuiInstanceRegistered
1442
+ });
1410
1443
  } catch (err) {
1411
1444
  consoleLogger.error("setup_events_failed", { message: toErrorMessage(err) });
1412
1445
  }
@@ -1550,4 +1583,4 @@ async function runWebUI(opts) {
1550
1583
  export {
1551
1584
  runWebUI
1552
1585
  };
1553
- //# sourceMappingURL=webui-server-DD35QFNO.js.map
1586
+ //# sourceMappingURL=webui-server-NFNRGNNT.js.map
@@ -1,5 +1,6 @@
1
1
  import type { Agent } from '@wrongstack/core/agent';
2
2
  import type { BrainArbiter } from '@wrongstack/core/coordination';
3
+ import type { WebuiSessionChildOptions } from './boot/webui-session-child.js';
3
4
  import type { BrainAutoRisk } from '@wrongstack/core/execution';
4
5
  import type { EventBus } from '@wrongstack/core/kernel';
5
6
  import type { TrustBoundary } from '@wrongstack/core/security';
@@ -36,6 +37,10 @@ export interface CliWebUIOptions {
36
37
  surface?: 'webui' | 'simpleui' | undefined;
37
38
  /** Fixed access token/password. Defaults to WEBUI_TOKEN or random per process. */
38
39
  accessToken?: string | undefined;
40
+ /** Fail instead of auto-advancing when the requested HTTP/WS port is busy. */
41
+ strictPort?: boolean | undefined;
42
+ /** Internal one-session child launch metadata for the multi-session parent shell. */
43
+ webuiSessionChild?: WebuiSessionChildOptions | undefined;
39
44
  /**
40
45
  * Live fleet concurrency + lifetime spawn budget for WebUI (issue #323).
41
46
  * Merged into `fleet.concurrency_update` broadcasts.
@@ -81,6 +86,8 @@ export interface CliWebUIOptions {
81
86
  wsPort: number;
82
87
  host: string;
83
88
  url: string;
89
+ authToken: string;
90
+ webuiInstanceRegistered: boolean;
84
91
  }) => void;
85
92
  modelsRegistry?: ModelsRegistry | undefined;
86
93
  globalConfigPath?: string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/cli",
3
- "version": "0.301.0",
3
+ "version": "0.302.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack CLI — terminal AI coding agent with provider catalog from models.dev. Provides `wrongstack` and `wstack` binaries.",
6
6
  "keywords": [
@@ -42,30 +42,30 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "ws": "^8.21.1",
45
- "@wrongstack/acp": "0.301.0",
46
- "@wrongstack/mcp": "0.301.0",
47
- "@wrongstack/bench": "0.301.0",
48
- "@wrongstack/plug-lsp": "0.301.0",
49
- "@wrongstack/kanban": "0.301.0",
50
- "@wrongstack/providers": "0.301.0",
51
- "@wrongstack/plugins": "0.301.0",
52
- "@wrongstack/sdd": "0.301.0",
53
- "@wrongstack/requirement-intake": "0.301.0",
54
- "@wrongstack/runtime": "0.301.0",
55
- "@wrongstack/security-scanner": "0.301.0",
56
- "@wrongstack/core": "0.301.0",
57
- "@wrongstack/simpleui": "0.301.0",
58
- "@wrongstack/techstack": "0.301.0",
59
- "@wrongstack/sage": "0.301.0",
60
- "@wrongstack/telegram": "0.301.0",
61
- "@wrongstack/tui": "0.301.0",
62
- "@wrongstack/tools": "0.301.0",
63
- "@wrongstack/webui": "0.301.0",
64
- "@wrongstack/webui-hq": "0.301.0",
65
- "@wrongstack/webui-server": "0.301.0"
45
+ "@wrongstack/plug-lsp": "0.302.0",
46
+ "@wrongstack/acp": "0.302.0",
47
+ "@wrongstack/bench": "0.302.0",
48
+ "@wrongstack/core": "0.302.0",
49
+ "@wrongstack/runtime": "0.302.0",
50
+ "@wrongstack/plugins": "0.302.0",
51
+ "@wrongstack/providers": "0.302.0",
52
+ "@wrongstack/mcp": "0.302.0",
53
+ "@wrongstack/sdd": "0.302.0",
54
+ "@wrongstack/kanban": "0.302.0",
55
+ "@wrongstack/security-scanner": "0.302.0",
56
+ "@wrongstack/sage": "0.302.0",
57
+ "@wrongstack/requirement-intake": "0.302.0",
58
+ "@wrongstack/techstack": "0.302.0",
59
+ "@wrongstack/telegram": "0.302.0",
60
+ "@wrongstack/webui": "0.302.0",
61
+ "@wrongstack/simpleui": "0.302.0",
62
+ "@wrongstack/tools": "0.302.0",
63
+ "@wrongstack/tui": "0.302.0",
64
+ "@wrongstack/webui-hq": "0.302.0",
65
+ "@wrongstack/webui-server": "0.302.0"
66
66
  },
67
67
  "optionalDependencies": {
68
- "@wrongstack/desktop": "0.301.0"
68
+ "@wrongstack/desktop": "0.302.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^26.1.2",