dominds 1.25.18 → 1.25.19

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.
@@ -4,5 +4,4 @@ export type RtwsCliParseResult = Readonly<{
4
4
  }>;
5
5
  export declare function extractGlobalRtwsChdir(params: Readonly<{
6
6
  argv: ReadonlyArray<string>;
7
- baseCwd: string;
8
7
  }>): RtwsCliParseResult;
@@ -38,6 +38,12 @@ const path = __importStar(require("path"));
38
38
  function extractGlobalRtwsChdir(params) {
39
39
  let chdir;
40
40
  const out = [];
41
+ const readAbsoluteChdir = (argName, value) => {
42
+ if (!path.isAbsolute(value)) {
43
+ throw new Error(`${argName} requires an absolute directory path: ${value}`);
44
+ }
45
+ return value;
46
+ };
41
47
  for (let i = 0; i < params.argv.length; i++) {
42
48
  const arg = params.argv[i];
43
49
  if (arg === '--') {
@@ -49,7 +55,7 @@ function extractGlobalRtwsChdir(params) {
49
55
  if (typeof next !== 'string' || next.length === 0 || next === '--') {
50
56
  throw new Error(`${arg} requires a directory argument`);
51
57
  }
52
- chdir = path.isAbsolute(next) ? next : path.resolve(params.baseCwd, next);
58
+ chdir = readAbsoluteChdir(arg, next);
53
59
  i++;
54
60
  continue;
55
61
  }
@@ -57,14 +63,14 @@ function extractGlobalRtwsChdir(params) {
57
63
  const value = arg.slice('--cwd='.length);
58
64
  if (value.length === 0)
59
65
  throw new Error(`--cwd requires a directory argument`);
60
- chdir = path.isAbsolute(value) ? value : path.resolve(params.baseCwd, value);
66
+ chdir = readAbsoluteChdir('--cwd', value);
61
67
  continue;
62
68
  }
63
69
  if (arg.startsWith('--chdir=')) {
64
70
  const value = arg.slice('--chdir='.length);
65
71
  if (value.length === 0)
66
72
  throw new Error(`--chdir requires a directory argument`);
67
- chdir = path.isAbsolute(value) ? value : path.resolve(params.baseCwd, value);
73
+ chdir = readAbsoluteChdir('--chdir', value);
68
74
  continue;
69
75
  }
70
76
  out.push(arg);
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Notes:
10
10
  * - Template can be a short name (resolved via DOMINDS_TEMPLATE_BASE) or a git URL.
11
- * - rtws directory is `process.cwd()`. Use 'dominds -C <dir> create ...' to create under another base dir.
11
+ * - rtws directory is `process.cwd()`. Use 'dominds -C <abs-dir> create ...' to create under another base dir.
12
12
  */
13
13
  declare function main(argv?: readonly string[]): Promise<void>;
14
14
  export { main };
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Notes:
11
11
  * - Template can be a short name (resolved via DOMINDS_TEMPLATE_BASE) or a git URL.
12
- * - rtws directory is `process.cwd()`. Use 'dominds -C <dir> create ...' to create under another base dir.
12
+ * - rtws directory is `process.cwd()`. Use 'dominds -C <abs-dir> create ...' to create under another base dir.
13
13
  */
14
14
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
15
  if (k2 === undefined) k2 = k;
package/dist/cli/read.js CHANGED
@@ -59,7 +59,7 @@ function printUsage() {
59
59
  console.log('`--audit` runs prompt audit via hidden teammate @fuxi using default LLM config (skips when unavailable), and also includes static toolset checks (registry vs `.minds/mcp.yaml` declarations).');
60
60
  console.log('When <member-id> is omitted, reads all visible team members.');
61
61
  console.log('');
62
- console.log("Note: rtws (runtime workspace) directory is `process.cwd()`. Use 'dominds -C <dir> read' to run in another rtws.");
62
+ console.log("Note: rtws (runtime workspace) directory is `process.cwd()`. Use 'dominds -C <abs-dir> read' to run in another rtws.");
63
63
  console.log('');
64
64
  console.log('Examples:');
65
65
  console.log(' dominds read # Read all team members');
package/dist/cli/tui.js CHANGED
@@ -76,7 +76,7 @@ function showHelp() {
76
76
  console.log('');
77
77
  console.log('Start or continue a dialog with an AI team member using a Taskdoc.');
78
78
  console.log('');
79
- console.log("Note: rtws (runtime workspace) directory is `process.cwd()`. Use 'dominds -C <dir> tui ...' to run in another rtws.");
79
+ console.log("Note: rtws (runtime workspace) directory is `process.cwd()`. Use 'dominds -C <abs-dir> tui ...' to run in another rtws.");
80
80
  console.log('');
81
81
  console.log('Arguments:');
82
82
  console.log(' <taskdoc-path> Path to Taskdoc (required for dialog)');
package/dist/cli/webui.js CHANGED
@@ -30,7 +30,7 @@ Usage:
30
30
  dominds webui [options]
31
31
 
32
32
  Note:
33
- rtws (runtime workspace) directory is \`process.cwd()\`. Use 'dominds -C <dir> webui' to run in another rtws.
33
+ rtws (runtime workspace) directory is \`process.cwd()\`. Use 'dominds -C <abs-dir> webui' to run in another rtws.
34
34
 
35
35
  Options:
36
36
  -p, --port <port> Port to listen on. Bare port is strict; suffix + tries higher ports; suffix - tries lower ports.
package/dist/cli.js CHANGED
@@ -107,10 +107,10 @@ function printHelp() {
107
107
  Dominds CLI - AI-driven DevOps framework with persistent memory
108
108
 
109
109
  Usage:
110
- dominds [-C <dir>] [subcommand] [options]
110
+ dominds [-C <abs-dir>] [subcommand] [options]
111
111
 
112
112
  Global Options:
113
- -C <dir> Change to runtime workspace directory (rtws) before running
113
+ -C <abs-dir> Change to absolute runtime workspace directory (rtws) before running
114
114
 
115
115
  Subcommands:
116
116
  webui [options] Start WebUI server (default)
@@ -133,7 +133,7 @@ Subcommands:
133
133
  Examples:
134
134
  dominds # Start WebUI server (default)
135
135
  dominds webui # Start WebUI server
136
- dominds -C ./my-ws webui # Start in specific rtws
136
+ dominds -C /path/to/my-ws webui # Start in specific rtws
137
137
  dominds tui --help # Show TUI help
138
138
  dominds run task.tsk # Run task dialog
139
139
  dominds read # Read team configuration
@@ -159,10 +159,9 @@ function printVersion() {
159
159
  }
160
160
  }
161
161
  async function main(argv = process.argv.slice(2)) {
162
- const baseCwd = process.cwd();
163
162
  let parsed;
164
163
  try {
165
- parsed = (0, rtws_cli_1.extractGlobalRtwsChdir)({ argv, baseCwd });
164
+ parsed = (0, rtws_cli_1.extractGlobalRtwsChdir)({ argv });
166
165
  }
167
166
  catch (err) {
168
167
  console.error('Error:', err instanceof Error ? err.message : String(err));
@@ -173,10 +172,7 @@ async function main(argv = process.argv.slice(2)) {
173
172
  if (args.length === 0) {
174
173
  if (parsed.chdir) {
175
174
  try {
176
- const absoluteRtwsRoot = path.isAbsolute(parsed.chdir)
177
- ? parsed.chdir
178
- : path.resolve(baseCwd, parsed.chdir);
179
- process.chdir(absoluteRtwsRoot);
175
+ process.chdir(parsed.chdir);
180
176
  }
181
177
  catch (err) {
182
178
  console.error(`Error: failed to change directory to '${parsed.chdir}':`, err);
@@ -222,10 +218,7 @@ async function main(argv = process.argv.slice(2)) {
222
218
  if (!shouldSkipRtwsSetup) {
223
219
  if (parsed.chdir) {
224
220
  try {
225
- const absoluteRtwsRoot = path.isAbsolute(parsed.chdir)
226
- ? parsed.chdir
227
- : path.resolve(baseCwd, parsed.chdir);
228
- process.chdir(absoluteRtwsRoot);
221
+ process.chdir(parsed.chdir);
229
222
  }
230
223
  catch (err) {
231
224
  console.error(`Error: failed to change directory to '${parsed.chdir}':`, err);
@@ -4,7 +4,7 @@ Chinese version: [中文版](./cli-usage.zh.md)
4
4
 
5
5
  The `dominds` CLI provides a unified entry point, but the **primary interaction experience is the Web UI** (the default `dominds` command). This guide focuses on the Web UI workflow.
6
6
 
7
- > Note: In this document, **rtws (runtime workspace)** refers to the runtime root directory Dominds uses (by default `process.cwd()`, switchable via `-C <dir>`).
7
+ > Note: In this document, **rtws (runtime workspace)** refers to the runtime root directory Dominds uses (by default `process.cwd()`, switchable via `-C <abs-dir>`; `-C` only accepts absolute paths).
8
8
 
9
9
  > Note: `dominds tui` / `dominds run` are currently reserved subcommand names and do not have a stable implementation yet. As a result, this guide does not document TUI options or detailed usage.
10
10
 
@@ -49,7 +49,7 @@ dominds webui [options]
49
49
 
50
50
  # Common: choose port / rtws
51
51
  dominds webui -p 8080
52
- dominds webui -C ./my-rtws
52
+ dominds webui -C /path/to/my-rtws
53
53
 
54
54
  # Minds reader: inspect team configuration
55
55
  dominds read [options] [member-id]
@@ -83,7 +83,7 @@ Start the web-based user interface for the current rtws. This provides a graphic
83
83
 
84
84
  - `-p, --port <port>` - Port to listen on; a bare port binds strictly, suffix `+` tries higher ports, and suffix `-` tries lower ports (omitting `--port` is equivalent to `5666-`)
85
85
  - `-h, --host <host>` - Host to bind to (default: localhost)
86
- - `-C, --cwd <dir>` - Change to rtws directory before starting
86
+ - `-C, --cwd <abs-dir>` - Change to rtws directory before starting; absolute paths only
87
87
  - `--help` - Show help message
88
88
 
89
89
  **Examples:**
@@ -92,7 +92,7 @@ Start the web-based user interface for the current rtws. This provides a graphic
92
92
  dominds
93
93
  dominds webui -p 8080
94
94
  dominds webui -p 8080+
95
- dominds webui -C ./my-rtws
95
+ dominds webui -C /path/to/my-rtws
96
96
  ```
97
97
 
98
98
  **Common use cases:**
@@ -122,7 +122,7 @@ Read and inspect agent prompts/configuration for the rtws. This is commonly used
122
122
 
123
123
  **Options:**
124
124
 
125
- - `-C, --cwd <dir>` - Change to rtws directory before reading
125
+ - `-C, --cwd <abs-dir>` - Change to rtws directory before reading; absolute paths only
126
126
  - `--only-prompt` - Show only system prompts
127
127
  - `--only-mem` - Show only memory
128
128
  - `--help` - Show help message
@@ -132,7 +132,7 @@ Read and inspect agent prompts/configuration for the rtws. This is commonly used
132
132
  ```bash
133
133
  dominds read
134
134
  dominds read developer
135
- dominds read -C ./my-rtws
135
+ dominds read -C /path/to/my-rtws
136
136
  dominds read --only-prompt
137
137
  dominds read --only-mem
138
138
  ```
@@ -190,7 +190,7 @@ dominds
190
190
  dominds webui -p 8080
191
191
 
192
192
  # 3) Start in a specific rtws
193
- dominds webui -C ./my-rtws
193
+ dominds webui -C /path/to/my-rtws
194
194
 
195
195
  # 4) Inspect current team/rtws configuration
196
196
  dominds read
@@ -4,7 +4,7 @@
4
4
 
5
5
  `dominds` 提供统一的命令行入口,但**主要交互界面是 Web UI**(默认命令 `dominds`)。本文档以 Web UI 工作流为主。
6
6
 
7
- > 注:本文统一使用 **rtws(运行时工作区)** 表示 Dominds 运行时使用的根目录(默认等于 `process.cwd()`,可通过 `-C <dir>` 切换)。
7
+ > 注:本文统一使用 **rtws(运行时工作区)** 表示 Dominds 运行时使用的根目录(默认等于 `process.cwd()`,可通过 `-C <abs-dir>` 切换;`-C` 只接受绝对路径)。
8
8
 
9
9
  > 说明:`dominds tui` / `dominds run` 相关功能目前尚未提供稳定实现(子命令名保留用于未来规划),因此本指南不再展开 TUI 的命令选项与用法细节。
10
10
 
@@ -49,7 +49,7 @@ dominds webui [options]
49
49
 
50
50
  # 常用:指定端口 / rtws
51
51
  dominds webui -p 8080
52
- dominds webui -C ./my-rtws
52
+ dominds webui -C /path/to/my-rtws
53
53
 
54
54
  # Minds 阅读器:分析团队配置
55
55
  dominds read [options] [member-id]
@@ -83,7 +83,7 @@ dominds webui [options]
83
83
 
84
84
  - `-p, --port <port>` - 监听端口;裸端口严格绑定,后缀 `+` 向更大端口自动尝试,后缀 `-` 向更小端口自动尝试(未指定时等价于 `5666-`)
85
85
  - `-h, --host <host>` - 绑定的主机(默认:localhost)
86
- - `-C, --cwd <dir>` - 启动前更改 rtws 目录
86
+ - `-C, --cwd <abs-dir>` - 启动前更改 rtws 目录;只接受绝对路径
87
87
  - `--help` - 显示帮助消息
88
88
 
89
89
  **示例:**
@@ -99,7 +99,7 @@ dominds webui -p 8080
99
99
  dominds webui -p 8080+
100
100
 
101
101
  # 在特定 rtws 启动 Web UI
102
- dominds webui -C ./my-rtws
102
+ dominds webui -C /path/to/my-rtws
103
103
  ```
104
104
 
105
105
  **常见用途:**
@@ -129,7 +129,7 @@ dominds read [options] [member-id]
129
129
 
130
130
  **选项:**
131
131
 
132
- - `-C, --cwd <dir>` - 读取前更改 rtws 目录
132
+ - `-C, --cwd <abs-dir>` - 读取前更改 rtws 目录;只接受绝对路径
133
133
  - `--only-prompt` - 仅显示系统提示词
134
134
  - `--only-mem` - 仅显示内存
135
135
  - `--help` - 显示帮助消息
@@ -139,7 +139,7 @@ dominds read [options] [member-id]
139
139
  ```bash
140
140
  dominds read
141
141
  dominds read developer
142
- dominds read -C ./my-rtws
142
+ dominds read -C /path/to/my-rtws
143
143
  dominds read --only-prompt
144
144
  dominds read --only-mem
145
145
  ```
@@ -204,7 +204,7 @@ dominds
204
204
  dominds webui -p 8080
205
205
 
206
206
  # 3) 在特定 rtws 启动
207
- dominds webui -C ./my-rtws
207
+ dominds webui -C /path/to/my-rtws
208
208
 
209
209
  # 4) 查看/核对当前团队配置
210
210
  dominds read
@@ -4,7 +4,7 @@ Chinese version: [中文版](./dialog-persistence.zh.md)
4
4
 
5
5
  This document describes the persistence layer and storage mechanisms for the Dominds dialog system, including file system conventions, data structures, and storage patterns.
6
6
 
7
- > Note: In this document, **rtws (runtime workspace)** refers to Dominds' runtime root directory (by default `process.cwd()`, switchable via `-C <dir>`).
7
+ > Note: In this document, **rtws (runtime workspace)** refers to Dominds' runtime root directory (by default `process.cwd()`, switchable via `-C <abs-dir>`; `-C` only accepts absolute paths).
8
8
 
9
9
  ## Current Implementation Status
10
10
 
@@ -264,8 +264,8 @@ Example / 示例(概念):
264
264
 
265
265
  ### rtws(运行时工作区)
266
266
 
267
- - EN: **rtws** (runtime workspace) is the directory Dominds treats as its runtime root (by default it is `process.cwd()`, and can be changed via `-C <dir>`). Files like `.minds/` and `.dialogs/` live under the rtws.
268
- - ZH: **rtws(运行时工作区)**是 Dominds 运行时使用的根目录(默认等于 `process.cwd()`,可通过 `-C <dir>` 切换)。诸如 `.minds/`、`.dialogs/` 等运行态目录均位于 rtws 下。
267
+ - EN: **rtws** (runtime workspace) is the directory Dominds treats as its runtime root (by default it is `process.cwd()`, and can be changed via `-C <abs-dir>`; `-C` only accepts absolute paths). Files like `.minds/` and `.dialogs/` live under the rtws.
268
+ - ZH: **rtws(运行时工作区)**是 Dominds 运行时使用的根目录(默认等于 `process.cwd()`,可通过 `-C <abs-dir>` 切换;`-C` 只接受绝对路径)。诸如 `.minds/`、`.dialogs/` 等运行态目录均位于 rtws 下。
269
269
 
270
270
  - EN: Wording rule: when the meaning is **rtws**, prefer writing “rtws (runtime workspace)” (or just “rtws” after the first mention) rather than the ambiguous generic “workspace”.
271
271
  - ZH: 用词规则:当语义指向 **rtws** 时,优先写“rtws(运行时工作区)”(或在已定义后只写“rtws”),避免在对外提示/文档中只写“工作区”从而与其他语境的 workspace/workdir 混淆。
@@ -0,0 +1,15 @@
1
+ export type RestartHelperStdioMode = 'inherit' | 'ignore';
2
+ export type RestartHelperPayload = Readonly<{
3
+ command: string;
4
+ args: readonly string[];
5
+ cwd: string;
6
+ host: string;
7
+ port: number;
8
+ retiringPid: number;
9
+ forceKillAfterMs: number;
10
+ probeIntervalMs: number;
11
+ portReleaseTimeoutMs: number;
12
+ stdioMode: RestartHelperStdioMode;
13
+ traceFile: string;
14
+ debugDir: string;
15
+ }>;
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const child_process_1 = require("child_process");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const net_1 = __importDefault(require("net"));
9
+ const time_1 = require("@longrun-ai/kernel/utils/time");
10
+ function isRecord(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ function isStringArray(value) {
14
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
15
+ }
16
+ function readString(value, key) {
17
+ const candidate = value[key];
18
+ if (typeof candidate !== 'string' || candidate.trim() === '') {
19
+ throw new Error(`Invalid Dominds restart helper payload: ${key} must be a non-empty string`);
20
+ }
21
+ return candidate;
22
+ }
23
+ function readNumber(value, key) {
24
+ const candidate = value[key];
25
+ if (typeof candidate !== 'number' || !Number.isFinite(candidate)) {
26
+ throw new Error(`Invalid Dominds restart helper payload: ${key} must be a finite number`);
27
+ }
28
+ return candidate;
29
+ }
30
+ function readPositiveInteger(value, key) {
31
+ const candidate = readNumber(value, key);
32
+ if (!Number.isInteger(candidate) || candidate <= 0) {
33
+ throw new Error(`Invalid Dominds restart helper payload: ${key} must be a positive integer`);
34
+ }
35
+ return candidate;
36
+ }
37
+ function readPort(value, key) {
38
+ const candidate = readPositiveInteger(value, key);
39
+ if (candidate > 65535) {
40
+ throw new Error(`Invalid Dominds restart helper payload: ${key} must be <= 65535`);
41
+ }
42
+ return candidate;
43
+ }
44
+ function parsePayload(raw) {
45
+ if (typeof raw !== 'string' || raw.trim() === '') {
46
+ throw new Error('Missing Dominds restart helper payload');
47
+ }
48
+ const parsed = JSON.parse(raw);
49
+ if (!isRecord(parsed)) {
50
+ throw new Error('Invalid Dominds restart helper payload: expected an object');
51
+ }
52
+ const args = parsed['args'];
53
+ if (!isStringArray(args)) {
54
+ throw new Error('Invalid Dominds restart helper payload: args must be a string array');
55
+ }
56
+ const stdioMode = parsed['stdioMode'];
57
+ if (stdioMode !== 'inherit' && stdioMode !== 'ignore') {
58
+ throw new Error('Invalid Dominds restart helper payload: stdioMode must be inherit or ignore');
59
+ }
60
+ return {
61
+ command: readString(parsed, 'command'),
62
+ args,
63
+ cwd: readString(parsed, 'cwd'),
64
+ host: readString(parsed, 'host'),
65
+ port: readPort(parsed, 'port'),
66
+ retiringPid: readPositiveInteger(parsed, 'retiringPid'),
67
+ forceKillAfterMs: readPositiveInteger(parsed, 'forceKillAfterMs'),
68
+ probeIntervalMs: readPositiveInteger(parsed, 'probeIntervalMs'),
69
+ portReleaseTimeoutMs: readPositiveInteger(parsed, 'portReleaseTimeoutMs'),
70
+ stdioMode,
71
+ traceFile: readString(parsed, 'traceFile'),
72
+ debugDir: readString(parsed, 'debugDir'),
73
+ };
74
+ }
75
+ function trace(payload, event, details = {}) {
76
+ const record = {
77
+ ...details,
78
+ event,
79
+ capturedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
80
+ helperPid: process.pid,
81
+ platform: process.platform,
82
+ };
83
+ try {
84
+ fs_1.default.mkdirSync(payload.debugDir, { recursive: true });
85
+ fs_1.default.appendFileSync(payload.traceFile, `${JSON.stringify(record)}\n`, 'utf8');
86
+ }
87
+ catch (error) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ console.error(`Failed to write Dominds restart helper trace: ${message}`);
90
+ }
91
+ }
92
+ function isPortBusy(payload) {
93
+ return new Promise((resolve) => {
94
+ const socket = net_1.default.createConnection({ host: payload.host, port: payload.port });
95
+ let settled = false;
96
+ const finish = (busy) => {
97
+ if (settled)
98
+ return;
99
+ settled = true;
100
+ socket.destroy();
101
+ resolve(busy);
102
+ };
103
+ socket.once('connect', () => finish(true));
104
+ socket.once('error', () => finish(false));
105
+ socket.setTimeout(1000, () => finish(true));
106
+ });
107
+ }
108
+ function getErrorCode(error) {
109
+ return isRecord(error) && typeof error['code'] === 'string' ? error['code'] : '';
110
+ }
111
+ function assertValidRetiringPid(payload) {
112
+ if (!Number.isInteger(payload.retiringPid) || payload.retiringPid <= 0) {
113
+ throw new Error(`Invalid retiring Dominds pid for restart: ${String(payload.retiringPid)}`);
114
+ }
115
+ if (payload.retiringPid === process.pid) {
116
+ throw new Error(`Refusing to kill restart helper pid ${String(process.pid)}`);
117
+ }
118
+ }
119
+ function isRetiringProcessAlive(payload) {
120
+ assertValidRetiringPid(payload);
121
+ try {
122
+ process.kill(payload.retiringPid, 0);
123
+ return true;
124
+ }
125
+ catch (error) {
126
+ const code = getErrorCode(error);
127
+ if (code === 'ESRCH')
128
+ return false;
129
+ if (code === 'EPERM')
130
+ return true;
131
+ throw error;
132
+ }
133
+ }
134
+ async function delayMs(ms) {
135
+ await new Promise((resolve) => {
136
+ setTimeout(resolve, ms);
137
+ });
138
+ }
139
+ async function waitForRetiringProcessExit(payload, timeoutMs) {
140
+ const deadline = Date.now() + timeoutMs;
141
+ while (Date.now() < deadline) {
142
+ if (!isRetiringProcessAlive(payload))
143
+ return true;
144
+ await delayMs(payload.probeIntervalMs);
145
+ }
146
+ return false;
147
+ }
148
+ async function waitForPortReleaseUntil(payload, deadline) {
149
+ let consecutiveReady = 0;
150
+ while (Date.now() < deadline) {
151
+ if (!(await isPortBusy(payload))) {
152
+ consecutiveReady += 1;
153
+ if (consecutiveReady >= 2)
154
+ return true;
155
+ }
156
+ else {
157
+ consecutiveReady = 0;
158
+ }
159
+ await delayMs(payload.probeIntervalMs);
160
+ }
161
+ return false;
162
+ }
163
+ async function runBestEffortKiller(payload, command, args) {
164
+ await new Promise((resolve) => {
165
+ trace(payload, 'helper.force_kill.spawn', { command, args });
166
+ const killer = (0, child_process_1.spawn)(command, [...args], {
167
+ stdio: payload.stdioMode,
168
+ windowsHide: payload.stdioMode !== 'inherit',
169
+ });
170
+ killer.once('error', (error) => {
171
+ trace(payload, 'helper.force_kill.error', { message: error.message });
172
+ resolve();
173
+ });
174
+ killer.once('exit', (code, signal) => {
175
+ trace(payload, 'helper.force_kill.exit', { code, signal });
176
+ resolve();
177
+ });
178
+ });
179
+ }
180
+ async function forceKillRetiringProcess(payload) {
181
+ assertValidRetiringPid(payload);
182
+ try {
183
+ process.kill(payload.retiringPid, 'SIGKILL');
184
+ }
185
+ catch (error) {
186
+ const code = getErrorCode(error);
187
+ if (code === 'ESRCH')
188
+ return;
189
+ if (process.platform !== 'win32')
190
+ throw error;
191
+ }
192
+ if (process.platform === 'win32') {
193
+ await runBestEffortKiller(payload, 'taskkill.exe', ['/PID', String(payload.retiringPid), '/F']);
194
+ }
195
+ }
196
+ async function runRestartHelper(payload) {
197
+ const detached = payload.stdioMode !== 'inherit';
198
+ trace(payload, 'helper.start', {
199
+ command: payload.command,
200
+ args: payload.args,
201
+ cwd: payload.cwd,
202
+ host: payload.host,
203
+ port: payload.port,
204
+ retiringPid: payload.retiringPid,
205
+ detached,
206
+ stdioMode: payload.stdioMode,
207
+ forceKillAfterMs: payload.forceKillAfterMs,
208
+ portReleaseTimeoutMs: payload.portReleaseTimeoutMs,
209
+ probeIntervalMs: payload.probeIntervalMs,
210
+ });
211
+ const forceKillDeadline = Date.now() + payload.forceKillAfterMs;
212
+ trace(payload, 'helper.wait_retiring_process_exit.start', {
213
+ retiringPid: payload.retiringPid,
214
+ timeoutMs: payload.forceKillAfterMs,
215
+ });
216
+ const exitedGracefully = await waitForRetiringProcessExit(payload, payload.forceKillAfterMs);
217
+ trace(payload, 'helper.wait_retiring_process_exit.finish', {
218
+ retiringPid: payload.retiringPid,
219
+ exited: exitedGracefully,
220
+ });
221
+ if (!exitedGracefully) {
222
+ trace(payload, 'helper.force_kill.start', { retiringPid: payload.retiringPid });
223
+ await forceKillRetiringProcess(payload);
224
+ trace(payload, 'helper.force_kill.finish', { retiringPid: payload.retiringPid });
225
+ trace(payload, 'helper.wait_retiring_process_exit_after_kill.start', {
226
+ retiringPid: payload.retiringPid,
227
+ timeoutMs: payload.portReleaseTimeoutMs,
228
+ });
229
+ const exitedAfterKill = await waitForRetiringProcessExit(payload, payload.portReleaseTimeoutMs);
230
+ trace(payload, 'helper.wait_retiring_process_exit_after_kill.finish', {
231
+ retiringPid: payload.retiringPid,
232
+ exited: exitedAfterKill,
233
+ });
234
+ if (!exitedAfterKill) {
235
+ throw new Error(`Dominds retiring process is still alive after force-killing pid ${String(payload.retiringPid)}`);
236
+ }
237
+ }
238
+ const portReleaseDeadline = Math.max(forceKillDeadline, Date.now() + payload.portReleaseTimeoutMs);
239
+ trace(payload, 'helper.wait_port_release.start', {
240
+ host: payload.host,
241
+ port: payload.port,
242
+ deadlineMsFromNow: portReleaseDeadline - Date.now(),
243
+ });
244
+ const portReleased = await waitForPortReleaseUntil(payload, portReleaseDeadline);
245
+ trace(payload, 'helper.wait_port_release.finish', {
246
+ host: payload.host,
247
+ port: payload.port,
248
+ released: portReleased,
249
+ });
250
+ if (!portReleased) {
251
+ throw new Error(`Dominds restart port is still busy after retiring pid ${String(payload.retiringPid)} exited; port=${String(payload.host)}:${String(payload.port)}`);
252
+ }
253
+ trace(payload, 'helper.spawn_new_process.start', {
254
+ command: payload.command,
255
+ args: payload.args,
256
+ cwd: payload.cwd,
257
+ detached,
258
+ stdioMode: payload.stdioMode,
259
+ });
260
+ const child = (0, child_process_1.spawn)(payload.command, [...payload.args], {
261
+ cwd: payload.cwd,
262
+ env: process.env,
263
+ detached,
264
+ stdio: payload.stdioMode,
265
+ shell: false,
266
+ windowsHide: payload.stdioMode !== 'inherit',
267
+ });
268
+ if (detached)
269
+ child.unref();
270
+ await new Promise((resolve, reject) => {
271
+ child.once('error', (error) => {
272
+ trace(payload, 'helper.spawn_new_process.error', {
273
+ command: payload.command,
274
+ args: payload.args,
275
+ cwd: payload.cwd,
276
+ message: error.message,
277
+ stack: error.stack ?? null,
278
+ });
279
+ reject(error);
280
+ });
281
+ child.once('spawn', resolve);
282
+ });
283
+ trace(payload, 'helper.spawn_new_process.finish', { childPid: child.pid ?? null });
284
+ trace(payload, 'helper.exit', { code: 0 });
285
+ }
286
+ async function main() {
287
+ let payload = null;
288
+ try {
289
+ payload = parsePayload(process.argv[2]);
290
+ await runRestartHelper(payload);
291
+ process.exit(0);
292
+ }
293
+ catch (error) {
294
+ const message = error instanceof Error ? error.message : String(error);
295
+ const stack = error instanceof Error ? (error.stack ?? null) : null;
296
+ if (payload !== null) {
297
+ trace(payload, 'helper.error', { message, stack });
298
+ }
299
+ console.error(message);
300
+ process.exit(1);
301
+ }
302
+ }
303
+ if (require.main === module) {
304
+ void main();
305
+ }
@@ -10,6 +10,7 @@ exports.getDomindsSelfUpdateStatus = getDomindsSelfUpdateStatus;
10
10
  exports.installLatestDominds = installLatestDominds;
11
11
  exports.restartDomindsIntoLatest = restartDomindsIntoLatest;
12
12
  const child_process_1 = require("child_process");
13
+ const crypto_1 = require("crypto");
13
14
  const promises_1 = __importDefault(require("fs/promises"));
14
15
  const path_1 = __importDefault(require("path"));
15
16
  const time_1 = require("@longrun-ai/kernel/utils/time");
@@ -43,6 +44,7 @@ let restartPromise = null;
43
44
  let restartState = IDLE_RESTART_STATE;
44
45
  let installFailureObservation = null;
45
46
  let broadcastStatusUpdate = null;
47
+ let activeRestartTraceFile = null;
46
48
  function normalizeVersionString(value) {
47
49
  return value.trim().replace(/^v/i, '');
48
50
  }
@@ -101,6 +103,55 @@ function normalizeComparablePath(value) {
101
103
  const normalized = path_1.default.normalize(value);
102
104
  return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
103
105
  }
106
+ function sanitizeDebugFileSegment(value) {
107
+ const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
108
+ return sanitized.length > 0 ? sanitized.slice(0, 80) : 'unknown';
109
+ }
110
+ function buildRestartTraceContext() {
111
+ const capturedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
112
+ const debugDir = path_1.default.resolve(process.cwd(), '.dialogs', 'debug');
113
+ const traceFile = path_1.default.join(debugDir, [
114
+ 'dominds-self-update-restart',
115
+ sanitizeDebugFileSegment(capturedAt),
116
+ String(process.pid),
117
+ `${(0, crypto_1.randomUUID)()}.jsonl`,
118
+ ].join('-'));
119
+ return { debugDir, traceFile };
120
+ }
121
+ function getRestartHelperEntrypoint() {
122
+ return path_1.default.resolve(__dirname, 'dominds-self-update-restart-helper.js');
123
+ }
124
+ async function appendRestartTrace(trace, event, details = {}) {
125
+ const payload = {
126
+ ...details,
127
+ event,
128
+ capturedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
129
+ pid: process.pid,
130
+ platform: process.platform,
131
+ rtwsRootAbs: process.cwd(),
132
+ };
133
+ await promises_1.default.mkdir(trace.debugDir, { recursive: true });
134
+ await promises_1.default.appendFile(trace.traceFile, `${JSON.stringify(payload)}\n`, 'utf-8');
135
+ }
136
+ function appendRestartTraceSoon(trace, event, details = {}) {
137
+ void appendRestartTrace(trace, event, details).catch((error) => {
138
+ log.warn('Failed to write Dominds restart trace', error, {
139
+ traceFile: trace.traceFile,
140
+ event,
141
+ });
142
+ });
143
+ }
144
+ async function appendRestartTraceBestEffort(trace, event, details = {}) {
145
+ try {
146
+ await appendRestartTrace(trace, event, details);
147
+ }
148
+ catch (error) {
149
+ log.warn('Failed to write Dominds restart trace', error, {
150
+ traceFile: trace.traceFile,
151
+ event,
152
+ });
153
+ }
154
+ }
104
155
  async function getComparableRealPath(absPath) {
105
156
  try {
106
157
  return normalizeComparablePath(await promises_1.default.realpath(absPath));
@@ -1331,9 +1382,11 @@ async function installLatestDominds() {
1331
1382
  publishStatusUpdateSoon();
1332
1383
  return await getDomindsSelfUpdateStatus();
1333
1384
  }
1334
- function spawnDetachedRestartHelper(params) {
1385
+ async function spawnDetachedRestartHelper(params) {
1335
1386
  const stdioMode = getRestartHelperStdio();
1336
- const helperPayload = JSON.stringify({
1387
+ const helperEntrypoint = getRestartHelperEntrypoint();
1388
+ await promises_1.default.access(helperEntrypoint);
1389
+ const helperPayload = {
1337
1390
  command: params.command,
1338
1391
  args: [...params.args],
1339
1392
  cwd: params.cwd,
@@ -1344,145 +1397,88 @@ function spawnDetachedRestartHelper(params) {
1344
1397
  probeIntervalMs: RESTART_PORT_PROBE_INTERVAL_MS,
1345
1398
  portReleaseTimeoutMs: RESTART_PORT_RELEASE_TIMEOUT_MS,
1346
1399
  stdioMode,
1347
- });
1348
- const helperScript = [
1349
- "const net = require('net');",
1350
- "const { spawn } = require('child_process');",
1351
- 'const payload = JSON.parse(process.argv[1]);',
1352
- 'const detached = payload.stdioMode !== "inherit";',
1353
- 'function isPortBusy() {',
1354
- ' return new Promise((resolve) => {',
1355
- ' const socket = net.createConnection({ host: payload.host, port: payload.port });',
1356
- ' let settled = false;',
1357
- ' const finish = (busy) => {',
1358
- ' if (settled) return;',
1359
- ' settled = true;',
1360
- ' socket.destroy();',
1361
- ' resolve(busy);',
1362
- ' };',
1363
- ' socket.once("connect", () => finish(true));',
1364
- ' socket.once("error", () => finish(false));',
1365
- ' socket.setTimeout(1000, () => finish(true));',
1366
- ' });',
1367
- '}',
1368
- 'function getErrorCode(error) {',
1369
- ' return error && typeof error === "object" && typeof error.code === "string" ? error.code : "";',
1370
- '}',
1371
- 'function assertValidRetiringPid() {',
1372
- ' if (!Number.isInteger(payload.retiringPid) || payload.retiringPid <= 0) {',
1373
- ' throw new Error(`Invalid retiring Dominds pid for restart: ${String(payload.retiringPid)}`);',
1374
- ' }',
1375
- ' if (payload.retiringPid === process.pid) {',
1376
- ' throw new Error(`Refusing to kill restart helper pid ${String(process.pid)}`);',
1377
- ' }',
1378
- '}',
1379
- 'function isRetiringProcessAlive() {',
1380
- ' assertValidRetiringPid();',
1381
- ' try {',
1382
- ' process.kill(payload.retiringPid, 0);',
1383
- ' return true;',
1384
- ' } catch (error) {',
1385
- ' const code = getErrorCode(error);',
1386
- ' if (code === "ESRCH") return false;',
1387
- ' if (code === "EPERM") return true;',
1388
- ' throw error;',
1389
- ' }',
1390
- '}',
1391
- 'async function waitForRetiringProcessExit(timeoutMs) {',
1392
- ' const deadline = Date.now() + timeoutMs;',
1393
- ' while (Date.now() < deadline) {',
1394
- ' if (!isRetiringProcessAlive()) return true;',
1395
- ' await new Promise((resolve) => setTimeout(resolve, payload.probeIntervalMs));',
1396
- ' }',
1397
- ' return false;',
1398
- '}',
1399
- 'async function waitForPortReleaseUntil(deadline) {',
1400
- ' let consecutiveReady = 0;',
1401
- ' while (Date.now() < deadline) {',
1402
- ' if (!(await isPortBusy())) {',
1403
- ' consecutiveReady += 1;',
1404
- ' if (consecutiveReady >= 2) return true;',
1405
- ' } else {',
1406
- ' consecutiveReady = 0;',
1407
- ' }',
1408
- ' await new Promise((resolve) => setTimeout(resolve, payload.probeIntervalMs));',
1409
- ' }',
1410
- ' return false;',
1411
- '}',
1412
- 'function runBestEffortKiller(command, args) {',
1413
- ' return new Promise((resolve) => {',
1414
- ' const killer = spawn(command, args, { stdio: payload.stdioMode, windowsHide: payload.stdioMode !== "inherit" });',
1415
- " killer.once('error', () => resolve());",
1416
- " killer.once('exit', () => resolve());",
1417
- ' });',
1418
- '}',
1419
- 'async function forceKillRetiringProcess() {',
1420
- ' assertValidRetiringPid();',
1421
- ' try {',
1422
- " process.kill(payload.retiringPid, 'SIGKILL');",
1423
- ' } catch (error) {',
1424
- ' const code = getErrorCode(error);',
1425
- ' if (code === "ESRCH") return;',
1426
- ' if (process.platform !== "win32") throw error;',
1427
- ' }',
1428
- ' if (process.platform === "win32") {',
1429
- " await runBestEffortKiller('taskkill.exe', ['/PID', String(payload.retiringPid), '/F']);",
1430
- ' }',
1431
- '}',
1432
- '(async () => {',
1433
- ' try {',
1434
- ' const forceKillDeadline = Date.now() + payload.forceKillAfterMs;',
1435
- ' const exitedGracefully = await waitForRetiringProcessExit(payload.forceKillAfterMs);',
1436
- ' if (!exitedGracefully) {',
1437
- ' await forceKillRetiringProcess();',
1438
- ' const exitedAfterKill = await waitForRetiringProcessExit(payload.portReleaseTimeoutMs);',
1439
- ' if (!exitedAfterKill) {',
1440
- ' throw new Error(`Dominds retiring process is still alive after force-killing pid ${String(payload.retiringPid)}`);',
1441
- ' }',
1442
- ' }',
1443
- ' const portReleaseDeadline = Math.max(forceKillDeadline, Date.now() + payload.portReleaseTimeoutMs);',
1444
- ' const portReleased = await waitForPortReleaseUntil(portReleaseDeadline);',
1445
- ' if (!portReleased) {',
1446
- ' throw new Error(`Dominds restart port is still busy after retiring pid ${String(payload.retiringPid)} exited; port=${String(payload.host)}:${String(payload.port)}`);',
1447
- ' }',
1448
- ' const child = spawn(payload.command, payload.args, { cwd: payload.cwd, env: process.env, detached, stdio: payload.stdioMode, shell: false, windowsHide: payload.stdioMode !== "inherit" });',
1449
- ' if (detached) child.unref();',
1450
- ' await new Promise((resolve, reject) => {',
1451
- " child.once('error', reject);",
1452
- " child.once('spawn', resolve);",
1453
- ' });',
1454
- ' process.exit(0);',
1455
- ' } catch (error) {',
1456
- ' console.error(error instanceof Error ? error.message : String(error));',
1457
- ' process.exit(1);',
1458
- ' }',
1459
- '})();',
1460
- ].join('\n');
1461
- const helper = (0, child_process_1.spawn)(process.execPath, ['-e', helperScript, helperPayload], {
1400
+ traceFile: params.trace.traceFile,
1401
+ debugDir: params.trace.debugDir,
1402
+ };
1403
+ const helper = (0, child_process_1.spawn)(process.execPath, [helperEntrypoint, JSON.stringify(helperPayload)], {
1462
1404
  cwd: params.cwd,
1463
1405
  env: process.env,
1464
1406
  detached: stdioMode !== 'inherit',
1465
1407
  stdio: stdioMode,
1466
1408
  windowsHide: stdioMode !== 'inherit',
1467
1409
  });
1410
+ const helperPid = typeof helper.pid === 'number' ? helper.pid : null;
1411
+ const helperSpawned = new Promise((resolve, reject) => {
1412
+ helper.once('spawn', () => {
1413
+ appendRestartTraceSoon(params.trace, 'parent.helper_spawn_event', {
1414
+ helperPid,
1415
+ });
1416
+ resolve(helperPid);
1417
+ });
1418
+ helper.once('error', (error) => {
1419
+ appendRestartTraceSoon(params.trace, 'parent.helper_error_event', {
1420
+ message: error.message,
1421
+ stack: error.stack ?? null,
1422
+ });
1423
+ reject(error);
1424
+ });
1425
+ });
1426
+ helper.once('exit', (code, signal) => {
1427
+ appendRestartTraceSoon(params.trace, 'parent.helper_exit_event', {
1428
+ code,
1429
+ signal,
1430
+ });
1431
+ });
1468
1432
  if (stdioMode !== 'inherit') {
1469
1433
  helper.unref();
1470
1434
  }
1435
+ return await helperSpawned;
1471
1436
  }
1472
1437
  async function stopAndExitForRestart() {
1473
1438
  const cfg = assertRuntimeConfig();
1439
+ const trace = activeRestartTraceFile === null
1440
+ ? null
1441
+ : { debugDir: path_1.default.dirname(activeRestartTraceFile), traceFile: activeRestartTraceFile };
1474
1442
  let stopSettled = false;
1475
- void cfg
1443
+ if (trace !== null) {
1444
+ await appendRestartTraceBestEffort(trace, 'parent.stop_and_exit.start', {
1445
+ host: cfg.host,
1446
+ port: cfg.port,
1447
+ exitGraceMs: RESTART_EXIT_GRACE_MS,
1448
+ });
1449
+ }
1450
+ const stopPromise = cfg
1476
1451
  .stopServer()
1477
1452
  .then(() => {
1478
1453
  stopSettled = true;
1454
+ if (trace !== null) {
1455
+ return appendRestartTraceBestEffort(trace, 'parent.stop_server.finish', {
1456
+ host: cfg.host,
1457
+ port: cfg.port,
1458
+ });
1459
+ }
1460
+ return undefined;
1479
1461
  })
1480
1462
  .catch((error) => {
1481
1463
  stopSettled = true;
1464
+ if (trace !== null) {
1465
+ return appendRestartTraceBestEffort(trace, 'parent.stop_server.error', {
1466
+ host: cfg.host,
1467
+ port: cfg.port,
1468
+ message: error instanceof Error ? error.message : String(error),
1469
+ stack: error instanceof Error ? (error.stack ?? null) : null,
1470
+ }).then(() => {
1471
+ log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
1472
+ host: cfg.host,
1473
+ port: cfg.port,
1474
+ });
1475
+ });
1476
+ }
1482
1477
  log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
1483
1478
  host: cfg.host,
1484
1479
  port: cfg.port,
1485
1480
  });
1481
+ return undefined;
1486
1482
  });
1487
1483
  cfg.closeWebSocketClients();
1488
1484
  await delayMs(RESTART_EXIT_GRACE_MS);
@@ -1493,6 +1489,17 @@ async function stopAndExitForRestart() {
1493
1489
  graceMs: RESTART_EXIT_GRACE_MS,
1494
1490
  });
1495
1491
  }
1492
+ if (trace !== null) {
1493
+ if (stopSettled) {
1494
+ await stopPromise;
1495
+ }
1496
+ await appendRestartTraceBestEffort(trace, 'parent.process_exit', {
1497
+ host: cfg.host,
1498
+ port: cfg.port,
1499
+ stopSettled,
1500
+ code: 0,
1501
+ });
1502
+ }
1496
1503
  process.exit(0);
1497
1504
  }
1498
1505
  async function restartDomindsIntoLatest() {
@@ -1529,24 +1536,59 @@ async function restartDomindsIntoLatest() {
1529
1536
  }
1530
1537
  restartState = { kind: 'restarting', targetVersion: status.targetVersion };
1531
1538
  publishStatusUpdateSoon();
1539
+ const restartCwd = process.cwd();
1540
+ const trace = buildRestartTraceContext();
1541
+ activeRestartTraceFile = trace.traceFile;
1532
1542
  try {
1533
- spawnDetachedRestartHelper({
1543
+ await appendRestartTraceBestEffort(trace, 'parent.restart_requested', {
1544
+ runKind,
1545
+ currentVersion: status.currentVersion,
1546
+ targetVersion: status.targetVersion,
1547
+ launchCommand: launchSpec.command,
1548
+ launchArgs: launchSpec.args,
1549
+ restartCwd,
1550
+ host: cfg.host,
1551
+ port: cfg.port,
1552
+ traceFile: trace.traceFile,
1553
+ });
1554
+ const helperPid = await spawnDetachedRestartHelper({
1534
1555
  command: launchSpec.command,
1535
1556
  args: launchSpec.args,
1536
- cwd: PROCESS_START_CWD,
1557
+ cwd: restartCwd,
1558
+ host: cfg.host,
1559
+ port: cfg.port,
1560
+ trace,
1561
+ });
1562
+ await appendRestartTraceBestEffort(trace, 'parent.helper_spawned', {
1563
+ helperPid,
1564
+ retiringPid: process.pid,
1537
1565
  host: cfg.host,
1538
1566
  port: cfg.port,
1539
1567
  });
1568
+ log.info('Dominds restart helper spawned', undefined, {
1569
+ helperPid,
1570
+ traceFile: trace.traceFile,
1571
+ });
1540
1572
  setImmediate(() => {
1541
1573
  void stopAndExitForRestart().catch((error) => {
1574
+ void appendRestartTraceBestEffort(trace, 'parent.stop_and_exit.error', {
1575
+ message: error instanceof Error ? error.message : String(error),
1576
+ stack: error instanceof Error ? (error.stack ?? null) : null,
1577
+ });
1542
1578
  log.error('Failed to stop Dominds server during restart', error);
1543
1579
  restartState = previousRestartRequiredState ?? IDLE_RESTART_STATE;
1580
+ activeRestartTraceFile = null;
1544
1581
  publishStatusUpdateSoon();
1545
1582
  });
1546
1583
  });
1547
1584
  }
1548
1585
  catch (error) {
1586
+ await appendRestartTraceBestEffort(trace, 'parent.restart_error', {
1587
+ message: error instanceof Error ? error.message : String(error),
1588
+ stack: error instanceof Error ? (error.stack ?? null) : null,
1589
+ });
1549
1590
  restartState = previousRestartRequiredState ?? IDLE_RESTART_STATE;
1591
+ activeRestartTraceFile = null;
1550
1592
  publishStatusUpdateSoon();
1551
1593
  throw error;
1552
1594
  }
package/dist/server.js CHANGED
@@ -287,13 +287,14 @@ async function main() {
287
287
  // Handle working directory change from -C flag
288
288
  const wsDir = cliArgs['C'];
289
289
  if (wsDir) {
290
- // Resolve to absolute path before changing directory
291
- const absoluteWsDir = path.isAbsolute(wsDir) ? wsDir : path.resolve(process.cwd(), wsDir);
290
+ if (!path.isAbsolute(wsDir)) {
291
+ throw new Error(`-C requires an absolute directory path: ${wsDir}`);
292
+ }
292
293
  try {
293
294
  process.chdir(wsDir);
294
295
  }
295
296
  catch (err) {
296
- log.warn(`Failed to change working directory to ${wsDir}:`, err);
297
+ throw new Error(`Failed to change working directory to ${wsDir}: ${err instanceof Error ? err.message : String(err)}`);
297
298
  }
298
299
  }
299
300
  // Get port, host, and mode from CLI args
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dominds",
3
- "version": "1.25.18",
3
+ "version": "1.25.19",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {