dominds 1.25.19 → 1.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +17 -0
  2. package/README.zh.md +17 -0
  3. package/dist/apps/runtime.d.ts +2 -4
  4. package/dist/apps-host/client.d.ts +2 -5
  5. package/dist/apps-host/ipc-types.d.ts +2 -5
  6. package/dist/apps-host/ipc-types.js +5 -1
  7. package/dist/bootstrap/rtws-cli.d.ts +1 -0
  8. package/dist/bootstrap/rtws-cli.js +8 -8
  9. package/dist/cli/cert.d.ts +4 -0
  10. package/dist/cli/cert.js +247 -0
  11. package/dist/cli/create.d.ts +1 -1
  12. package/dist/cli/create.js +1 -1
  13. package/dist/cli/read.js +1 -1
  14. package/dist/cli/tui.js +1 -1
  15. package/dist/cli/webui.d.ts +1 -1
  16. package/dist/cli/webui.js +35 -5
  17. package/dist/cli-runner.d.ts +31 -0
  18. package/dist/cli-runner.js +349 -0
  19. package/dist/cli.d.ts +0 -30
  20. package/dist/cli.js +540 -292
  21. package/dist/docs/cli-usage.md +40 -3
  22. package/dist/docs/cli-usage.zh.md +40 -3
  23. package/dist/docs/dialog-persistence.md +1 -1
  24. package/dist/docs/dominds-terminology.md +2 -2
  25. package/dist/llm/kernel-driver/drive.js +19 -14
  26. package/dist/runtime/driver-messages.d.ts +2 -0
  27. package/dist/runtime/driver-messages.js +24 -4
  28. package/dist/server/auth.d.ts +7 -0
  29. package/dist/server/auth.js +15 -4
  30. package/dist/server/certificates.d.ts +61 -0
  31. package/dist/server/certificates.js +418 -0
  32. package/dist/server/dominds-self-update.js +33 -121
  33. package/dist/server/network-hosts.d.ts +5 -0
  34. package/dist/server/network-hosts.js +277 -0
  35. package/dist/server/server-core.d.ts +12 -1
  36. package/dist/server/server-core.js +26 -4
  37. package/dist/server/websocket-handler.d.ts +3 -2
  38. package/dist/server/websocket-handler.js +15 -8
  39. package/dist/server-debug.d.ts +2 -0
  40. package/dist/server-debug.js +79 -0
  41. package/dist/server.d.ts +5 -0
  42. package/dist/server.js +101 -126
  43. package/dist/supervisor-protocol.d.ts +15 -0
  44. package/dist/supervisor-protocol.js +4 -0
  45. package/package.json +6 -5
  46. package/dist/server/dominds-self-update-restart-helper.d.ts +0 -15
  47. package/dist/server/dominds-self-update-restart-helper.js +0 -305
package/dist/cli.js CHANGED
@@ -1,33 +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
- * create - Create a new runtime workspace (rtws) from a template
18
- * install - Install a Dominds App into this rtws
19
- * doctor - Diagnose Dominds App state in this rtws
20
- * enable - Enable an installed Dominds App in this rtws
21
- * disable - Disable an installed Dominds App in this rtws
22
- * uninstall- Uninstall a Dominds App from this rtws
23
- * update - Update installed Dominds App(s)
24
- * new - Alias for create
25
- * help - Show help
26
- *
27
- * Global installation:
28
- * pnpm add -g dominds
29
- * dominds webui
30
- */
31
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
32
4
  if (k2 === undefined) k2 = k;
33
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -61,304 +33,580 @@ var __importStar = (this && this.__importStar) || (function () {
61
33
  return result;
62
34
  };
63
35
  })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
64
39
  Object.defineProperty(exports, "__esModule", { value: true });
65
- exports.configureEnvProxySupport = configureEnvProxySupport;
66
40
  exports.main = main;
67
- const fs = __importStar(require("fs"));
68
- const http = __importStar(require("node:http"));
69
- const path = __importStar(require("path"));
70
- const runtime_1 = require("./apps/runtime");
71
- 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");
72
46
  const rtws_cli_1 = require("./bootstrap/rtws-cli");
73
- const create_1 = require("./cli/create");
74
- const disable_1 = require("./cli/disable");
75
- const doctor_1 = require("./cli/doctor");
76
- const enable_1 = require("./cli/enable");
77
- const install_1 = require("./cli/install");
78
- const manual_1 = require("./cli/manual");
79
- const read_1 = require("./cli/read");
80
- const tui_1 = require("./cli/tui");
81
- const uninstall_1 = require("./cli/uninstall");
82
- const update_1 = require("./cli/update");
83
- const validate_team_def_1 = require("./cli/validate-team-def");
84
- const webui_1 = require("./cli/webui");
85
- const process_title_1 = require("./process-title");
86
- require("./tools/builtins");
87
- function configureEnvProxySupport() {
88
- const domindsUseEnvProxy = process.env.DOMINDS_USE_ENV_PROXY?.trim();
89
- if (domindsUseEnvProxy === '0') {
90
- 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
+ };
91
67
  }
92
- try {
93
- const setGlobalProxyFromEnv = http.setGlobalProxyFromEnv;
94
- if (typeof setGlobalProxyFromEnv !== 'function') {
95
- console.error('Error: DOMINDS_USE_ENV_PROXY requires Node.js 24.5+ because http.setGlobalProxyFromEnv() is unavailable.');
96
- process.exit(1);
97
- }
98
- 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;
99
82
  }
100
- catch (err) {
101
- console.error('Error: invalid proxy environment configuration:', err instanceof Error ? err.message : String(err));
102
- 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');
103
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
+ };
104
150
  }
105
- function printHelp() {
106
- console.log(`
107
- Dominds CLI - AI-driven DevOps framework with persistent memory
108
-
109
- Usage:
110
- dominds [-C <abs-dir>] [subcommand] [options]
111
-
112
- Global Options:
113
- -C <abs-dir> Change to absolute runtime workspace directory (rtws) before running
114
-
115
- Subcommands:
116
- webui [options] Start WebUI server (default)
117
- tui [options] Start Text User Interface
118
- run [options] Run task dialog (alias for tui)
119
- read [options] Read team configuration
120
- man [options] Render toolset manual to stdout
121
- manual [options] Alias for man
122
- validate_team_def [options] Validate explicit team toolset declarations
123
- create [options] Create a new runtime workspace (rtws) from a template
124
- install [options] Install a Dominds App into this rtws
125
- doctor [options] Read-only diagnosis across manifest/lock/configuration/resolution/handshake
126
- enable [options] Enable an installed Dominds App in this rtws
127
- disable [options] Disable an installed Dominds App in this rtws
128
- uninstall [options] Uninstall a Dominds App from this rtws
129
- update [options] Update installed Dominds App(s)
130
- new [options] Alias for create
131
- help Show this help message
132
-
133
- Examples:
134
- dominds # Start WebUI server (default)
135
- dominds webui # Start WebUI server
136
- dominds -C /path/to/my-ws webui # Start in specific rtws
137
- dominds tui --help # Show TUI help
138
- dominds run task.tsk # Run task dialog
139
- dominds read # Read team configuration
140
- dominds man ws_read --lang zh --all
141
- dominds validate_team_def # Validate toolset references in .minds/team.yaml
142
- dominds create web-scaffold my-project # Create rtws from a template
143
- dominds doctor @longrun-ai/web-dev # Diagnose a single app across all app-state layers
144
-
145
- Installation:
146
- pnpm add -g dominds
147
-
148
- For detailed help on a specific subcommand:
149
- dominds <subcommand> --help
150
- `);
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
+ });
151
167
  }
152
- function printVersion() {
168
+ async function appendSupervisorTraceBestEffort(message, event, details = {}) {
153
169
  try {
154
- const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
155
- console.log(`dominds v${packageJson.version}`);
170
+ await appendSupervisorTrace(message, event, details);
156
171
  }
157
- catch {
158
- 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}`);
159
175
  }
160
176
  }
161
- async function main(argv = process.argv.slice(2)) {
162
- let parsed;
163
- try {
164
- 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);
165
254
  }
166
- catch (err) {
167
- console.error('Error:', err instanceof Error ? err.message : String(err));
168
- 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(', ')}`);
169
276
  }
170
- const args = parsed.argv;
171
- // Handle no arguments - default to webui
172
- if (args.length === 0) {
173
- if (parsed.chdir) {
174
- try {
175
- 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;
176
300
  }
177
- catch (err) {
178
- console.error(`Error: failed to change directory to '${parsed.chdir}':`, err);
179
- 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;
180
371
  }
181
- }
182
- (0, dotenv_1.loadRtwsDotenv)({ cwd: process.cwd() });
183
- configureEnvProxySupport();
184
- await runSubcommand('webui', []);
185
- 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;
186
382
  }
187
- const subcommand = args[0];
188
- const subcommandArgs = args.slice(1);
189
- // Handle help and version flags
190
- if (subcommand === '-h' || subcommand === '--help' || subcommand === 'help') {
191
- printHelp();
192
- 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;
193
387
  }
194
- if (subcommand === '-v' || subcommand === '--version') {
195
- printVersion();
196
- 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);
197
411
  }
198
- const shouldSkipRtwsSetup = subcommandArgs.includes('--help') ||
199
- (subcommand === 'tui' && subcommandArgs.includes('-h')) ||
200
- (subcommand === 'run' && subcommandArgs.includes('-h')) ||
201
- (subcommand === 'read' && subcommandArgs.includes('-h')) ||
202
- (subcommand === 'validate_team_def' && subcommandArgs.includes('-h')) ||
203
- (subcommand === 'man' && subcommandArgs.includes('-h')) ||
204
- (subcommand === 'manual' && subcommandArgs.includes('-h')) ||
205
- ((subcommand === 'create' || subcommand === 'new') && subcommandArgs.includes('-h')) ||
206
- (subcommand === 'install' &&
207
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
208
- (subcommand === 'doctor' &&
209
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
210
- (subcommand === 'enable' &&
211
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
212
- (subcommand === 'disable' &&
213
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
214
- (subcommand === 'uninstall' &&
215
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h'))) ||
216
- (subcommand === 'update' &&
217
- (subcommandArgs.includes('--help') || subcommandArgs.includes('-h')));
218
- if (!shouldSkipRtwsSetup) {
219
- if (parsed.chdir) {
220
- try {
221
- 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;
222
419
  }
223
- catch (err) {
224
- console.error(`Error: failed to change directory to '${parsed.chdir}':`, err);
225
- process.exit(1);
420
+ if (restartKillTimer !== null) {
421
+ clearTimeout(restartKillTimer);
422
+ restartKillTimer = null;
226
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;
227
504
  }
228
- // Load runtime workspace env files into process.env once, in the main entry.
229
- // Precedence: `.env` then `.env.local` (later overwrites earlier), and both
230
- // overwrite any existing process.env values.
231
- (0, dotenv_1.loadRtwsDotenv)({ cwd: process.cwd() });
232
- configureEnvProxySupport();
233
- }
234
- const shouldLoadApps = subcommand !== 'webui' &&
235
- subcommand !== 'create' &&
236
- subcommand !== 'new' &&
237
- subcommand !== 'install' &&
238
- subcommand !== 'doctor' &&
239
- subcommand !== 'enable' &&
240
- subcommand !== 'disable' &&
241
- subcommand !== 'uninstall' &&
242
- subcommand !== 'update';
243
- if (!shouldSkipRtwsSetup && shouldLoadApps) {
244
- try {
245
- // Register toolset proxies so Team.load() can validate toolset bindings (read/man/manual included).
246
- await (0, runtime_1.registerEnabledAppsToolProxies)({ rtwsRootAbs: process.cwd() });
247
- // Start apps-host only for interactive runtime commands (do not auto-start app frontends for read/man/manual).
248
- const shouldStartAppsHost = subcommand === 'tui' || subcommand === 'run';
249
- if (shouldStartAppsHost) {
250
- await (0, runtime_1.initAppsRuntime)({
251
- rtwsRootAbs: process.cwd(),
252
- kernel: { 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,
253
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,
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);
254
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;
255
572
  }
256
- catch (err) {
257
- console.error('Error: failed to load enabled apps:', err instanceof Error ? err.message : String(err));
258
- process.exit(1);
259
- }
260
- }
261
- // Route to appropriate subcommand
262
- switch (subcommand) {
263
- case 'webui':
264
- await runSubcommand('webui', subcommandArgs);
265
- break;
266
- case 'tui':
267
- case 'run':
268
- await runSubcommand('tui', subcommandArgs);
269
- break;
270
- case 'read':
271
- await runSubcommand('read', subcommandArgs);
272
- break;
273
- case 'man':
274
- await runSubcommand('manual', subcommandArgs);
275
- break;
276
- case 'manual':
277
- await runSubcommand('manual', subcommandArgs);
278
- break;
279
- case 'validate_team_def':
280
- await runSubcommand('validate_team_def', subcommandArgs);
281
- break;
282
- case 'create':
283
- case 'new':
284
- await runSubcommand('create', subcommandArgs);
285
- break;
286
- case 'install':
287
- await runSubcommand('install', subcommandArgs);
288
- break;
289
- case 'doctor':
290
- await runSubcommand('doctor', subcommandArgs);
291
- break;
292
- case 'enable':
293
- await runSubcommand('enable', subcommandArgs);
294
- break;
295
- case 'disable':
296
- await runSubcommand('disable', subcommandArgs);
297
- break;
298
- case 'uninstall':
299
- await runSubcommand('uninstall', subcommandArgs);
300
- break;
301
- case 'update':
302
- await runSubcommand('update', subcommandArgs);
303
- break;
304
- default:
305
- console.error(`Error: Unknown subcommand '${subcommand}'`);
306
- console.error(`Run 'dominds help' for usage information.`);
307
- 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);
308
578
  }
309
579
  }
310
- async function runSubcommand(subcommand, args) {
580
+ async function main(argv = process.argv.slice(2)) {
581
+ let parsed;
311
582
  try {
312
- (0, process_title_1.setRtwsProcessTitle)();
313
- if (subcommand === 'webui') {
314
- await (0, webui_1.main)(args);
315
- }
316
- else if (subcommand === 'tui') {
317
- await (0, tui_1.main)(args);
318
- }
319
- else if (subcommand === 'read') {
320
- await (0, read_1.main)(args);
321
- }
322
- else if (subcommand === 'manual') {
323
- await (0, manual_1.main)(args);
324
- }
325
- else if (subcommand === 'validate_team_def') {
326
- await (0, validate_team_def_1.main)(args);
327
- }
328
- else if (subcommand === 'create') {
329
- await (0, create_1.main)(args);
330
- }
331
- else if (subcommand === 'install') {
332
- await (0, install_1.main)(args);
333
- }
334
- else if (subcommand === 'doctor') {
335
- await (0, doctor_1.main)(args);
336
- }
337
- else if (subcommand === 'enable') {
338
- await (0, enable_1.main)(args);
339
- }
340
- else if (subcommand === 'disable') {
341
- await (0, disable_1.main)(args);
342
- }
343
- else if (subcommand === 'uninstall') {
344
- await (0, uninstall_1.main)(args);
345
- }
346
- else if (subcommand === 'update') {
347
- await (0, update_1.main)(args);
348
- }
349
- else {
350
- console.error(`Error: Subcommand '${subcommand}' not implemented`);
351
- 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}`);
352
587
  }
353
588
  }
354
- catch (err) {
355
- console.error(`Failed to execute subcommand '${subcommand}':`, err);
589
+ catch (error) {
590
+ const message = error instanceof Error ? error.message : String(error);
591
+ console.error(`Error: ${message}`);
356
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;
357
600
  }
601
+ const exitCode = isLongRunningCommand(parsed.runnerArgv)
602
+ ? await runSupervised(parsed)
603
+ : await runOneShot(parsed);
604
+ process.exit(exitCode);
358
605
  }
359
606
  if (require.main === module) {
360
- main().catch((err) => {
361
- 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}`);
362
610
  process.exit(1);
363
611
  });
364
612
  }