dominds 1.26.0 → 1.26.2

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.
package/dist/cli.js CHANGED
@@ -1,34 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- /**
4
- * Main CLI entry point for dominds
5
- *
6
- * Usage:
7
- * dominds [subcommand] [options]
8
- *
9
- * Subcommands:
10
- * webui - Start WebUI server (default)
11
- * tui - Start Text User Interface
12
- * run - Run task dialog (alias for tui)
13
- * read - Read team configuration
14
- * man - Render toolset manual to stdout
15
- * manual - Alias for man
16
- * validate_team_def - Validate explicit team toolset declarations
17
- * cert - Create and inspect local HTTPS certificates
18
- * create - Create a new runtime workspace (rtws) from a template
19
- * install - Install a Dominds App into this rtws
20
- * doctor - Diagnose Dominds App state in this rtws
21
- * enable - Enable an installed Dominds App in this rtws
22
- * disable - Disable an installed Dominds App in this rtws
23
- * uninstall- Uninstall a Dominds App from this rtws
24
- * update - Update installed Dominds App(s)
25
- * new - Alias for create
26
- * help - Show help
27
- *
28
- * Global installation:
29
- * pnpm add -g dominds
30
- * dominds webui
31
- */
32
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
33
4
  if (k2 === undefined) k2 = k;
34
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -62,315 +33,580 @@ var __importStar = (this && this.__importStar) || (function () {
62
33
  return result;
63
34
  };
64
35
  })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
65
39
  Object.defineProperty(exports, "__esModule", { value: true });
66
- exports.configureEnvProxySupport = configureEnvProxySupport;
67
40
  exports.main = main;
68
- const fs = __importStar(require("fs"));
69
- const http = __importStar(require("node:http"));
70
- const path = __importStar(require("path"));
71
- const runtime_1 = require("./apps/runtime");
72
- const dotenv_1 = require("./bootstrap/dotenv");
41
+ const child_process_1 = require("child_process");
42
+ const promises_1 = __importDefault(require("fs/promises"));
43
+ const net_1 = __importDefault(require("net"));
44
+ const path_1 = __importDefault(require("path"));
45
+ const time_1 = require("@longrun-ai/kernel/utils/time");
73
46
  const rtws_cli_1 = require("./bootstrap/rtws-cli");
74
- const cert_1 = require("./cli/cert");
75
- const create_1 = require("./cli/create");
76
- const disable_1 = require("./cli/disable");
77
- const doctor_1 = require("./cli/doctor");
78
- const enable_1 = require("./cli/enable");
79
- const install_1 = require("./cli/install");
80
- const manual_1 = require("./cli/manual");
81
- const read_1 = require("./cli/read");
82
- const tui_1 = require("./cli/tui");
83
- const uninstall_1 = require("./cli/uninstall");
84
- const update_1 = require("./cli/update");
85
- const validate_team_def_1 = require("./cli/validate-team-def");
86
- const webui_1 = require("./cli/webui");
87
- const process_title_1 = require("./process-title");
88
- require("./tools/builtins");
89
- function configureEnvProxySupport() {
90
- const domindsUseEnvProxy = process.env.DOMINDS_USE_ENV_PROXY?.trim();
91
- if (domindsUseEnvProxy === '0') {
92
- return;
47
+ const supervisor_protocol_1 = require("./supervisor-protocol");
48
+ const RUNNER_JS_FILENAME = 'cli-runner.js';
49
+ const RUNNER_TS_FILENAME = 'cli-runner.ts';
50
+ const INITIAL_RESTART_BACKOFF_MS = 1000;
51
+ const MAX_RESTART_BACKOFF_MS = 30 * 60 * 1000;
52
+ const PORT_RELEASE_TIMEOUT_MS = 15000;
53
+ const PORT_PROBE_INTERVAL_MS = 150;
54
+ const RESTART_RUNNER_EXIT_GRACE_MS = 30000;
55
+ const RESTART_RUNNER_KILL_GRACE_MS = 5000;
56
+ function getDefaultRunnerCommandSpec() {
57
+ if (__filename.endsWith('.ts')) {
58
+ return {
59
+ command: process.execPath,
60
+ args: [
61
+ require.resolve('tsx/cli'),
62
+ '--tsconfig',
63
+ path_1.default.resolve(__dirname, 'tsconfig.dev.json'),
64
+ path_1.default.resolve(__dirname, RUNNER_TS_FILENAME),
65
+ ],
66
+ };
93
67
  }
94
- try {
95
- const setGlobalProxyFromEnv = http.setGlobalProxyFromEnv;
96
- if (typeof setGlobalProxyFromEnv !== 'function') {
97
- console.error('Error: DOMINDS_USE_ENV_PROXY requires Node.js 24.5+ because http.setGlobalProxyFromEnv() is unavailable.');
98
- process.exit(1);
99
- }
100
- setGlobalProxyFromEnv(process.env);
68
+ return { command: process.execPath, args: [path_1.default.resolve(__dirname, RUNNER_JS_FILENAME)] };
69
+ }
70
+ function isLongRunningCommand(argv) {
71
+ return argv.length === 0 || argv[0] === 'webui';
72
+ }
73
+ function isDevelopmentMode(argv) {
74
+ if (process.env.NODE_ENV === 'dev')
75
+ return true;
76
+ for (let i = 0; i < argv.length; i++) {
77
+ const arg = argv[i];
78
+ if (arg === '--mode')
79
+ return argv[i + 1] === 'dev';
80
+ if (arg === '--mode=dev')
81
+ return true;
101
82
  }
102
- catch (err) {
103
- console.error('Error: invalid proxy environment configuration:', err instanceof Error ? err.message : String(err));
104
- process.exit(1);
83
+ return false;
84
+ }
85
+ function parseSupervisorArgs(argv) {
86
+ const launchCwd = process.cwd();
87
+ const parsed = (0, rtws_cli_1.extractGlobalRtwsChdir)({ argv, baseCwd: launchCwd });
88
+ return {
89
+ cwd: parsed.chdir ?? launchCwd,
90
+ runnerArgv: parsed.argv,
91
+ };
92
+ }
93
+ function isRecord(value) {
94
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
95
+ }
96
+ function isRestartRunKind(value) {
97
+ return value === 'global' || value === 'npx';
98
+ }
99
+ function parseRunnerMessage(message) {
100
+ if (!isRecord(message))
101
+ return { kind: 'ignore' };
102
+ if (message['type'] !== supervisor_protocol_1.DOMINDS_SUPERVISOR_RESTART_WEBUI)
103
+ return { kind: 'ignore' };
104
+ const cwd = message['cwd'];
105
+ const host = message['host'];
106
+ const port = message['port'];
107
+ const traceFile = message['traceFile'];
108
+ const debugDir = message['debugDir'];
109
+ const currentVersion = message['currentVersion'];
110
+ const targetVersion = message['targetVersion'];
111
+ const runKind = message['runKind'];
112
+ const invalid = (reason) => ({
113
+ kind: 'invalid',
114
+ reason,
115
+ traceFile: typeof traceFile === 'string' ? traceFile : null,
116
+ debugDir: typeof debugDir === 'string' ? debugDir : null,
117
+ });
118
+ if (typeof cwd !== 'string')
119
+ return invalid('cwd must be a string');
120
+ if (typeof host !== 'string')
121
+ return invalid('host must be a string');
122
+ if (typeof port !== 'number' || !Number.isInteger(port) || port <= 0 || port > 65535) {
123
+ return invalid('port must be an integer in 1..65535');
124
+ }
125
+ if (typeof traceFile !== 'string')
126
+ return invalid('traceFile must be a string');
127
+ if (typeof debugDir !== 'string')
128
+ return invalid('debugDir must be a string');
129
+ if (typeof currentVersion !== 'string')
130
+ return invalid('currentVersion must be a string');
131
+ if (targetVersion !== null && typeof targetVersion !== 'string') {
132
+ return invalid('targetVersion must be a string or null');
105
133
  }
134
+ if (!isRestartRunKind(runKind))
135
+ return invalid('runKind must be global or npx');
136
+ return {
137
+ kind: 'restart',
138
+ message: {
139
+ type: supervisor_protocol_1.DOMINDS_SUPERVISOR_RESTART_WEBUI,
140
+ cwd,
141
+ host,
142
+ port,
143
+ traceFile,
144
+ debugDir,
145
+ currentVersion,
146
+ targetVersion,
147
+ runKind,
148
+ },
149
+ };
106
150
  }
107
- function printHelp() {
108
- console.log(`
109
- Dominds CLI - AI-driven DevOps framework with persistent memory
110
-
111
- Usage:
112
- dominds [-C <abs-dir>] [subcommand] [options]
113
-
114
- Global Options:
115
- -C <abs-dir> Change to absolute runtime workspace directory (rtws) before running
116
-
117
- Subcommands:
118
- webui [options] Start WebUI server (default)
119
- tui [options] Start Text User Interface
120
- run [options] Run task dialog (alias for tui)
121
- read [options] Read team configuration
122
- man [options] Render toolset manual to stdout
123
- manual [options] Alias for man
124
- validate_team_def [options] Validate explicit team toolset declarations
125
- cert [options] Create and inspect local HTTPS certificates
126
- create [options] Create a new runtime workspace (rtws) from a template
127
- install [options] Install a Dominds App into this rtws
128
- doctor [options] Read-only diagnosis across manifest/lock/configuration/resolution/handshake
129
- enable [options] Enable an installed Dominds App in this rtws
130
- disable [options] Disable an installed Dominds App in this rtws
131
- uninstall [options] Uninstall a Dominds App from this rtws
132
- update [options] Update installed Dominds App(s)
133
- new [options] Alias for create
134
- help Show this help message
135
-
136
- Examples:
137
- dominds # Start WebUI server (default)
138
- dominds webui # Start WebUI server
139
- dominds -C /path/to/my-ws webui # Start in specific rtws
140
- dominds tui --help # Show TUI help
141
- dominds run task.tsk # Run task dialog
142
- dominds read # Read team configuration
143
- dominds man ws_read --lang zh --all
144
- dominds validate_team_def # Validate toolset references in .minds/team.yaml
145
- dominds cert create --host 192.168.1.10
146
- dominds create web-scaffold my-project # Create rtws from a template
147
- dominds doctor @longrun-ai/web-dev # Diagnose a single app across all app-state layers
148
-
149
- Installation:
150
- pnpm add -g dominds
151
-
152
- For detailed help on a specific subcommand:
153
- dominds <subcommand> --help
154
- `);
151
+ async function appendSupervisorTrace(message, event, details = {}) {
152
+ const record = {
153
+ ...details,
154
+ event,
155
+ capturedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
156
+ supervisorPid: process.pid,
157
+ platform: process.platform,
158
+ };
159
+ await promises_1.default.mkdir(message.debugDir, { recursive: true });
160
+ await promises_1.default.appendFile(message.traceFile, `${JSON.stringify(record)}\n`, 'utf8');
161
+ }
162
+ function appendSupervisorTraceSoon(message, event, details = {}) {
163
+ void appendSupervisorTrace(message, event, details).catch((error) => {
164
+ const text = error instanceof Error ? error.message : String(error);
165
+ console.error(`Failed to write Dominds supervisor trace: ${text}`);
166
+ });
155
167
  }
156
- function printVersion() {
168
+ async function appendSupervisorTraceBestEffort(message, event, details = {}) {
157
169
  try {
158
- const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
159
- console.log(`dominds v${packageJson.version}`);
170
+ await appendSupervisorTrace(message, event, details);
160
171
  }
161
- catch {
162
- console.log('dominds (version unknown)');
172
+ catch (error) {
173
+ const text = error instanceof Error ? error.message : String(error);
174
+ console.error(`Failed to write Dominds supervisor trace: ${text}`);
163
175
  }
164
176
  }
165
- async function main(argv = process.argv.slice(2)) {
166
- let parsed;
167
- try {
168
- parsed = (0, rtws_cli_1.extractGlobalRtwsChdir)({ argv });
177
+ function appendInvalidSupervisorTraceSoon(parsed, event = 'supervisor.invalid_restart_message', details = {}) {
178
+ if (parsed.traceFile === null || parsed.debugDir === null)
179
+ return;
180
+ const message = {
181
+ type: supervisor_protocol_1.DOMINDS_SUPERVISOR_RESTART_WEBUI,
182
+ cwd: process.cwd(),
183
+ host: 'unknown',
184
+ port: 1,
185
+ traceFile: parsed.traceFile,
186
+ debugDir: parsed.debugDir,
187
+ currentVersion: 'unknown',
188
+ targetVersion: null,
189
+ runKind: 'global',
190
+ };
191
+ appendSupervisorTraceSoon(message, event, {
192
+ ...details,
193
+ reason: parsed.reason,
194
+ });
195
+ }
196
+ async function delayUnlessStopping(ms, stopState) {
197
+ if (stopState.stopping)
198
+ return;
199
+ await new Promise((resolve) => {
200
+ let settled = false;
201
+ const finish = () => {
202
+ if (settled)
203
+ return;
204
+ settled = true;
205
+ clearTimeout(timer);
206
+ clearInterval(poll);
207
+ resolve();
208
+ };
209
+ const timer = setTimeout(finish, ms);
210
+ const poll = setInterval(() => {
211
+ if (!stopState.stopping)
212
+ return;
213
+ finish();
214
+ }, 100);
215
+ });
216
+ }
217
+ function getPortProbeHost(host) {
218
+ if (host === '0.0.0.0')
219
+ return '127.0.0.1';
220
+ if (host === '::')
221
+ return '::1';
222
+ return host;
223
+ }
224
+ function isPortBusy(host, port) {
225
+ return new Promise((resolve) => {
226
+ const socket = net_1.default.createConnection({ host, port });
227
+ let settled = false;
228
+ const finish = (busy) => {
229
+ if (settled)
230
+ return;
231
+ settled = true;
232
+ socket.destroy();
233
+ resolve(busy);
234
+ };
235
+ socket.once('connect', () => finish(true));
236
+ socket.once('error', () => finish(false));
237
+ socket.setTimeout(1000, () => finish(true));
238
+ });
239
+ }
240
+ async function waitForPortRelease(params) {
241
+ const probeHost = getPortProbeHost(params.host);
242
+ const deadline = Date.now() + PORT_RELEASE_TIMEOUT_MS;
243
+ let consecutiveReady = 0;
244
+ while (!params.stopState.stopping && Date.now() < deadline) {
245
+ if (!(await isPortBusy(probeHost, params.port))) {
246
+ consecutiveReady += 1;
247
+ if (consecutiveReady >= 2)
248
+ return true;
249
+ }
250
+ else {
251
+ consecutiveReady = 0;
252
+ }
253
+ await delayUnlessStopping(PORT_PROBE_INTERVAL_MS, params.stopState);
169
254
  }
170
- catch (err) {
171
- console.error('Error:', err instanceof Error ? err.message : String(err));
172
- process.exit(1);
255
+ return false;
256
+ }
257
+ async function resolveNpmCommandSpec() {
258
+ const nodeDir = path_1.default.dirname(process.execPath);
259
+ const candidates = process.platform === 'win32'
260
+ ? [path_1.default.join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js')]
261
+ : [
262
+ path_1.default.join(nodeDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
263
+ path_1.default.join(nodeDir, '..', 'share', 'nodejs', 'npm', 'bin', 'npm-cli.js'),
264
+ ];
265
+ for (const candidate of candidates) {
266
+ try {
267
+ await promises_1.default.access(candidate);
268
+ return { command: process.execPath, args: [candidate] };
269
+ }
270
+ catch {
271
+ continue;
272
+ }
273
+ }
274
+ if (process.platform === 'win32') {
275
+ throw new Error(`Cannot find bundled npm CLI at ${candidates.join(', ')}`);
173
276
  }
174
- const args = parsed.argv;
175
- // Handle no arguments - default to webui
176
- if (args.length === 0) {
177
- if (parsed.chdir) {
178
- try {
179
- process.chdir(parsed.chdir);
277
+ return { command: 'npm', args: [] };
278
+ }
279
+ async function captureCommandStdout(command, args) {
280
+ return await new Promise((resolve, reject) => {
281
+ let stdout = '';
282
+ let stderr = '';
283
+ const child = (0, child_process_1.spawn)(command, [...args], {
284
+ stdio: ['ignore', 'pipe', 'pipe'],
285
+ windowsHide: true,
286
+ });
287
+ child.stdout?.setEncoding('utf8');
288
+ child.stderr?.setEncoding('utf8');
289
+ child.stdout?.on('data', (chunk) => {
290
+ stdout += chunk;
291
+ });
292
+ child.stderr?.on('data', (chunk) => {
293
+ stderr += chunk;
294
+ });
295
+ child.once('error', reject);
296
+ child.once('exit', (code, signal) => {
297
+ if (code === 0 && signal === null) {
298
+ resolve(stdout);
299
+ return;
180
300
  }
181
- catch (err) {
182
- console.error(`Error: failed to change directory to '${parsed.chdir}':`, err);
183
- process.exit(1);
301
+ reject(new Error(`Command failed while resolving dominds@latest runner (code=${String(code)}, signal=${String(signal)}): ${stderr.trim()}`));
302
+ });
303
+ });
304
+ }
305
+ async function resolveNpxLatestRunnerEntrypoint() {
306
+ const npm = await resolveNpmCommandSpec();
307
+ const packageJsonPath = (await captureCommandStdout(npm.command, [
308
+ ...npm.args,
309
+ 'exec',
310
+ '-y',
311
+ '--package',
312
+ 'dominds@latest',
313
+ '--',
314
+ process.execPath,
315
+ '-e',
316
+ [
317
+ "const fs=require('fs');",
318
+ "const path=require('path');",
319
+ "for (const dir of (process.env.PATH||'').split(path.delimiter)) {",
320
+ " if (path.basename(dir) !== '.bin') continue;",
321
+ " const candidate=path.join(path.dirname(dir),'dominds','package.json');",
322
+ ' if (fs.existsSync(candidate)) {',
323
+ ' process.stdout.write(fs.realpathSync(candidate));',
324
+ ' process.exit(0);',
325
+ ' }',
326
+ '}',
327
+ "process.stderr.write('Cannot find dominds package in npm exec PATH');",
328
+ 'process.exit(1);',
329
+ ].join(' '),
330
+ ])).trim();
331
+ if (packageJsonPath === '') {
332
+ throw new Error('npm exec dominds@latest did not return a package path');
333
+ }
334
+ const packageRoot = path_1.default.dirname(packageJsonPath);
335
+ const runnerEntrypoint = path_1.default.join(packageRoot, 'dist', RUNNER_JS_FILENAME);
336
+ await promises_1.default.access(runnerEntrypoint);
337
+ return runnerEntrypoint;
338
+ }
339
+ function spawnRunner(params) {
340
+ return (0, child_process_1.spawn)(params.commandSpec.command, [...params.commandSpec.args, ...params.argv], {
341
+ cwd: params.cwd,
342
+ env: {
343
+ ...process.env,
344
+ DOMINDS_SUPERVISOR_PID: String(process.pid),
345
+ },
346
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
347
+ windowsHide: false,
348
+ });
349
+ }
350
+ function isRunningChild(child) {
351
+ return child.exitCode === null && child.signalCode === null;
352
+ }
353
+ async function runOneShot(params) {
354
+ const commandSpec = getDefaultRunnerCommandSpec();
355
+ const child = (0, child_process_1.spawn)(commandSpec.command, [...commandSpec.args, ...params.runnerArgv], {
356
+ cwd: params.cwd,
357
+ env: process.env,
358
+ stdio: 'inherit',
359
+ windowsHide: false,
360
+ });
361
+ return await new Promise((resolve) => {
362
+ child.once('error', (error) => {
363
+ console.error(`Failed to start dominds-runner: ${error.message}`);
364
+ resolve(1);
365
+ });
366
+ child.once('exit', (code, signal) => {
367
+ if (signal !== null) {
368
+ console.error(`dominds-runner terminated by signal: ${signal}`);
369
+ resolve(1);
370
+ return;
184
371
  }
185
- }
186
- (0, dotenv_1.loadRtwsDotenv)({ cwd: process.cwd() });
187
- configureEnvProxySupport();
188
- await runSubcommand('webui', []);
189
- return;
372
+ resolve(code ?? 1);
373
+ });
374
+ });
375
+ }
376
+ async function runDevelopmentRunner(params) {
377
+ try {
378
+ process.chdir(params.cwd);
379
+ const { main: runnerMain } = await Promise.resolve().then(() => __importStar(require('./cli-runner')));
380
+ await runnerMain(params.runnerArgv);
381
+ return 0;
190
382
  }
191
- const subcommand = args[0];
192
- const subcommandArgs = args.slice(1);
193
- // Handle help and version flags
194
- if (subcommand === '-h' || subcommand === '--help' || subcommand === 'help') {
195
- printHelp();
196
- process.exit(0);
383
+ catch (error) {
384
+ const message = error instanceof Error ? error.message : String(error);
385
+ console.error(`dominds development runner failed: ${message}`);
386
+ return 1;
197
387
  }
198
- if (subcommand === '-v' || subcommand === '--version') {
199
- printVersion();
200
- process.exit(0);
388
+ }
389
+ async function runSupervised(params) {
390
+ let backoffMs = INITIAL_RESTART_BACKOFF_MS;
391
+ const stopState = { stopping: false };
392
+ let activeChild = null;
393
+ const restartState = {
394
+ pending: null,
395
+ };
396
+ const invalidRestartState = { pending: null };
397
+ let runnerCommandSpec = getDefaultRunnerCommandSpec();
398
+ const stopFromSignal = (signal) => {
399
+ if (stopState.stopping)
400
+ return;
401
+ stopState.stopping = true;
402
+ const child = activeChild;
403
+ if (child !== null && child.exitCode === null && child.signalCode === null) {
404
+ child.kill(signal);
405
+ }
406
+ };
407
+ process.once('SIGINT', stopFromSignal);
408
+ process.once('SIGTERM', stopFromSignal);
409
+ if (process.platform === 'win32') {
410
+ process.once('SIGBREAK', stopFromSignal);
201
411
  }
202
- const shouldSkipRtwsSetup = subcommand === 'cert' ||
203
- subcommandArgs.includes('--help') ||
204
- (subcommand === 'tui' && subcommandArgs.includes('-h')) ||
205
- (subcommand === 'run' && subcommandArgs.includes('-h')) ||
206
- (subcommand === 'read' && subcommandArgs.includes('-h')) ||
207
- (subcommand === 'validate_team_def' && subcommandArgs.includes('-h')) ||
208
- (subcommand === 'man' && subcommandArgs.includes('-h')) ||
209
- (subcommand === 'manual' && subcommandArgs.includes('-h')) ||
210
- ((subcommand === 'create' || subcommand === 'new') && subcommandArgs.includes('-h')) ||
211
- (subcommand === 'install' &&
212
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
213
- (subcommand === 'doctor' &&
214
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
215
- (subcommand === 'enable' &&
216
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
217
- (subcommand === 'disable' &&
218
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
219
- (subcommand === 'uninstall' &&
220
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
221
- (subcommand === 'update' &&
222
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h')));
223
- if (!shouldSkipRtwsSetup) {
224
- if (parsed.chdir) {
225
- try {
226
- process.chdir(parsed.chdir);
412
+ while (true) {
413
+ let restartExitTimer = null;
414
+ let restartKillTimer = null;
415
+ const clearRestartExitEnforcement = () => {
416
+ if (restartExitTimer !== null) {
417
+ clearTimeout(restartExitTimer);
418
+ restartExitTimer = null;
227
419
  }
228
- catch (err) {
229
- console.error(`Error: failed to change directory to '${parsed.chdir}':`, err);
230
- process.exit(1);
420
+ if (restartKillTimer !== null) {
421
+ clearTimeout(restartKillTimer);
422
+ restartKillTimer = null;
231
423
  }
424
+ };
425
+ const scheduleRestartExitEnforcement = (child, message) => {
426
+ clearRestartExitEnforcement();
427
+ restartExitTimer = setTimeout(() => {
428
+ if (!isRunningChild(child))
429
+ return;
430
+ appendSupervisorTraceSoon(message, 'supervisor.restart_runner_exit_timeout', {
431
+ runnerPid: child.pid ?? null,
432
+ graceMs: RESTART_RUNNER_EXIT_GRACE_MS,
433
+ });
434
+ console.error(`dominds-runner did not exit within ${String(RESTART_RUNNER_EXIT_GRACE_MS)}ms after restart request; sending SIGTERM`);
435
+ child.kill('SIGTERM');
436
+ restartKillTimer = setTimeout(() => {
437
+ if (!isRunningChild(child))
438
+ return;
439
+ appendSupervisorTraceSoon(message, 'supervisor.restart_runner_kill_timeout', {
440
+ runnerPid: child.pid ?? null,
441
+ graceMs: RESTART_RUNNER_KILL_GRACE_MS,
442
+ });
443
+ console.error(`dominds-runner still did not exit after SIGTERM; sending SIGKILL before restart`);
444
+ child.kill('SIGKILL');
445
+ }, RESTART_RUNNER_KILL_GRACE_MS);
446
+ }, RESTART_RUNNER_EXIT_GRACE_MS);
447
+ };
448
+ const scheduleInvalidRestartExitEnforcement = (child, parsed) => {
449
+ clearRestartExitEnforcement();
450
+ restartKillTimer = setTimeout(() => {
451
+ if (!isRunningChild(child))
452
+ return;
453
+ appendInvalidSupervisorTraceSoon(parsed, 'supervisor.invalid_restart_runner_kill_timeout', {
454
+ runnerPid: child.pid ?? null,
455
+ graceMs: RESTART_RUNNER_KILL_GRACE_MS,
456
+ });
457
+ console.error(`dominds-runner did not exit after invalid restart message; sending SIGKILL`);
458
+ child.kill('SIGKILL');
459
+ }, RESTART_RUNNER_KILL_GRACE_MS);
460
+ };
461
+ const child = spawnRunner({
462
+ cwd: params.cwd,
463
+ argv: params.runnerArgv,
464
+ commandSpec: runnerCommandSpec,
465
+ });
466
+ activeChild = child;
467
+ const exit = await new Promise((resolve) => {
468
+ child.once('error', (error) => {
469
+ console.error(`Failed to start dominds-runner: ${error.message}`);
470
+ resolve({ code: 1, signal: null });
471
+ });
472
+ child.on('message', (raw) => {
473
+ const parsed = parseRunnerMessage(raw);
474
+ if (parsed.kind === 'ignore')
475
+ return;
476
+ if (parsed.kind === 'invalid') {
477
+ if (invalidRestartState.pending !== null)
478
+ return;
479
+ console.error(`Invalid dominds-runner restart message: ${parsed.reason}`);
480
+ invalidRestartState.pending = parsed.reason;
481
+ appendInvalidSupervisorTraceSoon(parsed);
482
+ child.kill('SIGTERM');
483
+ scheduleInvalidRestartExitEnforcement(child, parsed);
484
+ return;
485
+ }
486
+ const { message } = parsed;
487
+ restartState.pending = message;
488
+ appendSupervisorTraceSoon(message, 'supervisor.restart_requested', {
489
+ runnerPid: child.pid ?? null,
490
+ cwd: message.cwd,
491
+ host: message.host,
492
+ port: message.port,
493
+ });
494
+ scheduleRestartExitEnforcement(child, message);
495
+ });
496
+ child.once('exit', (code, signal) => {
497
+ clearRestartExitEnforcement();
498
+ resolve({ code, signal });
499
+ });
500
+ });
501
+ activeChild = null;
502
+ if (stopState.stopping) {
503
+ return exit.signal === null ? (exit.code ?? 0) : 1;
232
504
  }
233
- // Load runtime workspace env files into process.env once, in the main entry.
234
- // Precedence: `.env` then `.env.local` (later overwrites earlier), and both
235
- // overwrite any existing process.env values.
236
- (0, dotenv_1.loadRtwsDotenv)({ cwd: process.cwd() });
237
- configureEnvProxySupport();
238
- }
239
- const shouldLoadApps = subcommand !== 'webui' &&
240
- subcommand !== 'cert' &&
241
- subcommand !== 'create' &&
242
- subcommand !== 'new' &&
243
- subcommand !== 'install' &&
244
- subcommand !== 'doctor' &&
245
- subcommand !== 'enable' &&
246
- subcommand !== 'disable' &&
247
- subcommand !== 'uninstall' &&
248
- subcommand !== 'update';
249
- if (!shouldSkipRtwsSetup && shouldLoadApps) {
250
- try {
251
- // Register toolset proxies so Team.load() can validate toolset bindings (read/man/manual included).
252
- await (0, runtime_1.registerEnabledAppsToolProxies)({ rtwsRootAbs: process.cwd() });
253
- // Start apps-host only for interactive runtime commands (do not auto-start app frontends for read/man/manual).
254
- const shouldStartAppsHost = subcommand === 'tui' || subcommand === 'run';
255
- if (shouldStartAppsHost) {
256
- await (0, runtime_1.initAppsRuntime)({
257
- rtwsRootAbs: process.cwd(),
258
- kernel: { scheme: 'http', host: '127.0.0.1', port: 0 },
505
+ if (invalidRestartState.pending !== null) {
506
+ console.error(`dominds supervisor stopped after invalid restart message: ${invalidRestartState.pending}`);
507
+ return 1;
508
+ }
509
+ if (restartState.pending !== null) {
510
+ const restart = restartState.pending;
511
+ restartState.pending = null;
512
+ while (true) {
513
+ await appendSupervisorTraceBestEffort(restart, 'supervisor.wait_port_release.start', {
514
+ host: restart.host,
515
+ port: restart.port,
516
+ timeoutMs: PORT_RELEASE_TIMEOUT_MS,
517
+ });
518
+ const released = await waitForPortRelease({
519
+ host: restart.host,
520
+ port: restart.port,
521
+ stopState,
522
+ });
523
+ await appendSupervisorTraceBestEffort(restart, 'supervisor.wait_port_release.finish', {
524
+ host: restart.host,
525
+ port: restart.port,
526
+ released,
527
+ });
528
+ if (stopState.stopping)
529
+ return 0;
530
+ if (released)
531
+ break;
532
+ await appendSupervisorTraceBestEffort(restart, 'supervisor.restart_blocked_port_busy', {
533
+ host: restart.host,
534
+ port: restart.port,
535
+ retryDelayMs: backoffMs,
259
536
  });
537
+ console.error(`Dominds restart is waiting because port ${restart.host}:${String(restart.port)} is still busy; retrying in ${String(backoffMs)}ms`);
538
+ await delayUnlessStopping(backoffMs, stopState);
539
+ if (stopState.stopping)
540
+ return 0;
541
+ backoffMs = Math.min(backoffMs * 2, MAX_RESTART_BACKOFF_MS);
260
542
  }
543
+ if (restart.runKind === 'npx') {
544
+ while (true) {
545
+ try {
546
+ const runnerEntrypoint = await resolveNpxLatestRunnerEntrypoint();
547
+ runnerCommandSpec = { command: process.execPath, args: [runnerEntrypoint] };
548
+ await appendSupervisorTraceBestEffort(restart, 'supervisor.npx_latest_runner_resolved', {
549
+ runnerEntrypoint,
550
+ });
551
+ break;
552
+ }
553
+ catch (error) {
554
+ const message = error instanceof Error ? error.message : String(error);
555
+ await appendSupervisorTraceBestEffort(restart, 'supervisor.npx_latest_runner_error', {
556
+ message,
557
+ retryDelayMs: backoffMs,
558
+ });
559
+ console.error(`Dominds restart could not resolve dominds@latest runner: ${message}; retrying in ${String(backoffMs)}ms`);
560
+ await delayUnlessStopping(backoffMs, stopState);
561
+ if (stopState.stopping)
562
+ return 0;
563
+ backoffMs = Math.min(backoffMs * 2, MAX_RESTART_BACKOFF_MS);
564
+ }
565
+ }
566
+ }
567
+ else {
568
+ runnerCommandSpec = getDefaultRunnerCommandSpec();
569
+ }
570
+ backoffMs = INITIAL_RESTART_BACKOFF_MS;
571
+ continue;
261
572
  }
262
- catch (err) {
263
- console.error('Error: failed to load enabled apps:', err instanceof Error ? err.message : String(err));
264
- process.exit(1);
265
- }
266
- }
267
- // Route to appropriate subcommand
268
- switch (subcommand) {
269
- case 'webui':
270
- await runSubcommand('webui', subcommandArgs);
271
- break;
272
- case 'tui':
273
- case 'run':
274
- await runSubcommand('tui', subcommandArgs);
275
- break;
276
- case 'read':
277
- await runSubcommand('read', subcommandArgs);
278
- break;
279
- case 'man':
280
- await runSubcommand('manual', subcommandArgs);
281
- break;
282
- case 'manual':
283
- await runSubcommand('manual', subcommandArgs);
284
- break;
285
- case 'validate_team_def':
286
- await runSubcommand('validate_team_def', subcommandArgs);
287
- break;
288
- case 'cert':
289
- await runSubcommand('cert', subcommandArgs);
290
- break;
291
- case 'create':
292
- case 'new':
293
- await runSubcommand('create', subcommandArgs);
294
- break;
295
- case 'install':
296
- await runSubcommand('install', subcommandArgs);
297
- break;
298
- case 'doctor':
299
- await runSubcommand('doctor', subcommandArgs);
300
- break;
301
- case 'enable':
302
- await runSubcommand('enable', subcommandArgs);
303
- break;
304
- case 'disable':
305
- await runSubcommand('disable', subcommandArgs);
306
- break;
307
- case 'uninstall':
308
- await runSubcommand('uninstall', subcommandArgs);
309
- break;
310
- case 'update':
311
- await runSubcommand('update', subcommandArgs);
312
- break;
313
- default:
314
- console.error(`Error: Unknown subcommand '${subcommand}'`);
315
- console.error(`Run 'dominds help' for usage information.`);
316
- process.exit(1);
573
+ console.error(`dominds-runner exited unexpectedly (code=${String(exit.code)}, signal=${String(exit.signal)}); restarting in ${String(backoffMs)}ms`);
574
+ await delayUnlessStopping(backoffMs, stopState);
575
+ if (stopState.stopping)
576
+ return 0;
577
+ backoffMs = Math.min(backoffMs * 2, MAX_RESTART_BACKOFF_MS);
317
578
  }
318
579
  }
319
- async function runSubcommand(subcommand, args) {
580
+ async function main(argv = process.argv.slice(2)) {
581
+ let parsed;
320
582
  try {
321
- (0, process_title_1.setRtwsProcessTitle)();
322
- if (subcommand === 'webui') {
323
- await (0, webui_1.main)(args);
324
- }
325
- else if (subcommand === 'tui') {
326
- await (0, tui_1.main)(args);
327
- }
328
- else if (subcommand === 'read') {
329
- await (0, read_1.main)(args);
330
- }
331
- else if (subcommand === 'manual') {
332
- await (0, manual_1.main)(args);
333
- }
334
- else if (subcommand === 'validate_team_def') {
335
- await (0, validate_team_def_1.main)(args);
336
- }
337
- else if (subcommand === 'cert') {
338
- await (0, cert_1.main)(args);
339
- }
340
- else if (subcommand === 'create') {
341
- await (0, create_1.main)(args);
342
- }
343
- else if (subcommand === 'install') {
344
- await (0, install_1.main)(args);
345
- }
346
- else if (subcommand === 'doctor') {
347
- await (0, doctor_1.main)(args);
348
- }
349
- else if (subcommand === 'enable') {
350
- await (0, enable_1.main)(args);
351
- }
352
- else if (subcommand === 'disable') {
353
- await (0, disable_1.main)(args);
354
- }
355
- else if (subcommand === 'uninstall') {
356
- await (0, uninstall_1.main)(args);
357
- }
358
- else if (subcommand === 'update') {
359
- await (0, update_1.main)(args);
360
- }
361
- else {
362
- console.error(`Error: Subcommand '${subcommand}' not implemented`);
363
- process.exit(1);
583
+ parsed = parseSupervisorArgs(argv);
584
+ const stat = await promises_1.default.stat(parsed.cwd);
585
+ if (!stat.isDirectory()) {
586
+ throw new Error(`rtws path is not a directory: ${parsed.cwd}`);
364
587
  }
365
588
  }
366
- catch (err) {
367
- console.error(`Failed to execute subcommand '${subcommand}': ${err instanceof Error ? err.message : String(err)}`);
589
+ catch (error) {
590
+ const message = error instanceof Error ? error.message : String(error);
591
+ console.error(`Error: ${message}`);
368
592
  process.exit(1);
593
+ return;
594
+ }
595
+ if (isLongRunningCommand(parsed.runnerArgv) && isDevelopmentMode(parsed.runnerArgv)) {
596
+ const exitCode = await runDevelopmentRunner(parsed);
597
+ if (exitCode !== 0)
598
+ process.exit(exitCode);
599
+ return;
369
600
  }
601
+ const exitCode = isLongRunningCommand(parsed.runnerArgv)
602
+ ? await runSupervised(parsed)
603
+ : await runOneShot(parsed);
604
+ process.exit(exitCode);
370
605
  }
371
606
  if (require.main === module) {
372
- main().catch((err) => {
373
- console.error('Unhandled error:', err);
607
+ main().catch((error) => {
608
+ const message = error instanceof Error ? error.message : String(error);
609
+ console.error(`Unhandled dominds supervisor error: ${message}`);
374
610
  process.exit(1);
375
611
  });
376
612
  }