dominds 1.25.18 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) 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 +0 -1
  8. package/dist/bootstrap/rtws-cli.js +9 -3
  9. package/dist/cli/cert.d.ts +2 -0
  10. package/dist/cli/cert.js +198 -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.d.ts +1 -0
  18. package/dist/cli.js +21 -16
  19. package/dist/docs/cli-usage.md +43 -7
  20. package/dist/docs/cli-usage.zh.md +43 -7
  21. package/dist/docs/dialog-persistence.md +1 -1
  22. package/dist/docs/dominds-terminology.md +2 -2
  23. package/dist/server/auth.d.ts +7 -0
  24. package/dist/server/auth.js +15 -4
  25. package/dist/server/certificates.d.ts +61 -0
  26. package/dist/server/certificates.js +418 -0
  27. package/dist/server/dominds-self-update-restart-helper.d.ts +15 -0
  28. package/dist/server/dominds-self-update-restart-helper.js +305 -0
  29. package/dist/server/dominds-self-update.js +162 -120
  30. package/dist/server/network-hosts.d.ts +5 -0
  31. package/dist/server/network-hosts.js +188 -0
  32. package/dist/server/server-core.d.ts +12 -1
  33. package/dist/server/server-core.js +26 -4
  34. package/dist/server/websocket-handler.d.ts +3 -2
  35. package/dist/server/websocket-handler.js +15 -8
  36. package/dist/server.d.ts +5 -0
  37. package/dist/server.js +105 -10
  38. package/package.json +3 -3
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.normalizeNetworkHost = normalizeNetworkHost;
37
+ exports.isLanHttpsHost = isLanHttpsHost;
38
+ exports.resolveHttpUrlHostForBindHost = resolveHttpUrlHostForBindHost;
39
+ exports.resolveLanHttpsHostsForBindHost = resolveLanHttpsHostsForBindHost;
40
+ exports.resolveDefaultLanHttpsHosts = resolveDefaultLanHttpsHosts;
41
+ const dgram = __importStar(require("node:dgram"));
42
+ const net = __importStar(require("node:net"));
43
+ const os = __importStar(require("node:os"));
44
+ const OUTBOUND_PROBE_HOST = 'github.com';
45
+ const OUTBOUND_PROBE_PORT = 443;
46
+ const OUTBOUND_PROBE_TIMEOUT_MS = 800;
47
+ function normalizeNetworkHost(host) {
48
+ const trimmed = host.trim();
49
+ const unwrapped = trimmed.startsWith('[') && trimmed.endsWith(']') ? trimmed.slice(1, -1) : trimmed;
50
+ const withoutZone = stripIpv6ZoneId(unwrapped);
51
+ const mappedIpv4 = parseIpv4MappedIpv6Host(withoutZone.toLowerCase());
52
+ return mappedIpv4 ?? withoutZone;
53
+ }
54
+ function isLanHttpsHost(host) {
55
+ const normalizedHost = normalizeNetworkHost(host).toLowerCase();
56
+ if (normalizedHost === '' || normalizedHost === 'localhost' || normalizedHost === 'loopback') {
57
+ return false;
58
+ }
59
+ const ipVersion = net.isIP(normalizedHost);
60
+ if (ipVersion === 4) {
61
+ return normalizedHost !== '0.0.0.0' && !normalizedHost.startsWith('127.');
62
+ }
63
+ if (ipVersion === 6) {
64
+ const mappedIpv4 = parseIpv4MappedIpv6Host(normalizedHost);
65
+ if (mappedIpv4 !== null) {
66
+ return isLanHttpsHost(mappedIpv4);
67
+ }
68
+ return (normalizedHost !== '::' &&
69
+ normalizedHost !== '0:0:0:0:0:0:0:0' &&
70
+ normalizedHost !== '::1' &&
71
+ normalizedHost !== '0:0:0:0:0:0:0:1');
72
+ }
73
+ return true;
74
+ }
75
+ function resolveHttpUrlHostForBindHost(bindHost) {
76
+ const normalizedHost = normalizeNetworkHost(bindHost);
77
+ if (normalizedHost === '0.0.0.0' ||
78
+ normalizedHost === '::' ||
79
+ normalizedHost === '0:0:0:0:0:0:0:0') {
80
+ return 'localhost';
81
+ }
82
+ return normalizedHost;
83
+ }
84
+ async function resolveLanHttpsHostsForBindHost(bindHost) {
85
+ const normalizedHost = normalizeNetworkHost(bindHost);
86
+ if (normalizedHost === '0.0.0.0') {
87
+ return uniqueHosts([
88
+ ...(await getUdpOutboundHosts('ipv4')),
89
+ ...getNetworkInterfaceHosts('ipv4'),
90
+ os.hostname(),
91
+ ]).filter(isLanHttpsHost);
92
+ }
93
+ if (normalizedHost === '::' || normalizedHost === '0:0:0:0:0:0:0:0') {
94
+ return uniqueHosts([
95
+ ...(await getUdpOutboundHosts('ipv6')),
96
+ ...(await getUdpOutboundHosts('ipv4')),
97
+ ...getNetworkInterfaceHosts('ipv6'),
98
+ ...getNetworkInterfaceHosts('ipv4'),
99
+ os.hostname(),
100
+ ]).filter(isLanHttpsHost);
101
+ }
102
+ return isLanHttpsHost(normalizedHost) ? [normalizedHost] : [];
103
+ }
104
+ async function resolveDefaultLanHttpsHosts() {
105
+ return await resolveLanHttpsHostsForBindHost('::');
106
+ }
107
+ async function getUdpOutboundHosts(kind) {
108
+ const host = await getUdpOutboundHost(kind);
109
+ return host === null ? [] : [host];
110
+ }
111
+ async function getUdpOutboundHost(kind) {
112
+ return await new Promise((resolve) => {
113
+ const socket = dgram.createSocket(kind === 'ipv4' ? 'udp4' : 'udp6');
114
+ let settled = false;
115
+ const finish = (host) => {
116
+ if (settled)
117
+ return;
118
+ settled = true;
119
+ clearTimeout(timeout);
120
+ socket.removeAllListeners('error');
121
+ socket.close();
122
+ resolve(host === null ? null : normalizeNetworkHost(host));
123
+ };
124
+ const timeout = setTimeout(() => {
125
+ finish(null);
126
+ }, OUTBOUND_PROBE_TIMEOUT_MS);
127
+ timeout.unref();
128
+ socket.once('error', () => {
129
+ finish(null);
130
+ });
131
+ socket.connect(OUTBOUND_PROBE_PORT, OUTBOUND_PROBE_HOST, () => {
132
+ const address = socket.address();
133
+ if (typeof address === 'string') {
134
+ finish(null);
135
+ return;
136
+ }
137
+ finish(address.address);
138
+ });
139
+ });
140
+ }
141
+ function getNetworkInterfaceHosts(kind) {
142
+ const hosts = [];
143
+ const families = os.networkInterfaces();
144
+ for (const entries of Object.values(families)) {
145
+ if (entries === undefined)
146
+ continue;
147
+ for (const entry of entries) {
148
+ if (entry.internal)
149
+ continue;
150
+ if (kind === 'ipv4' && entry.family === 'IPv4') {
151
+ hosts.push(entry.address);
152
+ }
153
+ if (kind === 'ipv6' && entry.family === 'IPv6') {
154
+ hosts.push(stripIpv6ZoneId(entry.address));
155
+ }
156
+ }
157
+ }
158
+ return hosts;
159
+ }
160
+ function uniqueHosts(rawHosts) {
161
+ const hosts = [];
162
+ const seen = new Set();
163
+ for (const rawHost of rawHosts) {
164
+ const host = normalizeNetworkHost(rawHost);
165
+ if (host === '')
166
+ continue;
167
+ const key = host.toLowerCase();
168
+ if (seen.has(key))
169
+ continue;
170
+ seen.add(key);
171
+ hosts.push(host);
172
+ }
173
+ return hosts;
174
+ }
175
+ function stripIpv6ZoneId(address) {
176
+ const zoneIndex = address.indexOf('%');
177
+ return zoneIndex === -1 ? address : address.slice(0, zoneIndex);
178
+ }
179
+ function parseIpv4MappedIpv6Host(host) {
180
+ const dottedQuadIndex = host.lastIndexOf(':');
181
+ if (dottedQuadIndex === -1)
182
+ return null;
183
+ const dottedQuad = host.slice(dottedQuadIndex + 1);
184
+ if (net.isIP(dottedQuad) !== 4)
185
+ return null;
186
+ const prefix = host.slice(0, dottedQuadIndex).toLowerCase();
187
+ return prefix === '::ffff' || prefix === '0:0:0:0:0:ffff' ? dottedQuad : null;
188
+ }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import * as http from 'http';
7
7
  import { IncomingMessage, ServerResponse } from 'http';
8
+ import type { Server as HttpsServer } from 'node:https';
8
9
  import type { ParsedUrlQuery } from 'querystring';
9
10
  import type { WebSocket } from 'ws';
10
11
  import type { AuthConfig } from './auth';
@@ -12,11 +13,18 @@ export interface ServerConfig {
12
13
  port: number;
13
14
  host: string;
14
15
  mode: 'development' | 'production';
16
+ tls?: {
17
+ cert: string;
18
+ key: string;
19
+ certPath: string;
20
+ keyPath: string;
21
+ };
15
22
  clients?: Set<WebSocket>;
16
23
  staticRoot?: string;
17
24
  enableLiveReload?: boolean;
18
25
  auth?: AuthConfig;
19
26
  }
27
+ export type HttpServerLike = http.Server | HttpsServer;
20
28
  export interface RequestHandler {
21
29
  (req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery): Promise<boolean>;
22
30
  }
@@ -27,6 +35,8 @@ export declare class HttpServerCore {
27
35
  private server;
28
36
  private config;
29
37
  private customHandlers;
38
+ private started;
39
+ private stopping;
30
40
  constructor(config: ServerConfig);
31
41
  /**
32
42
  * Add custom request handler
@@ -57,7 +67,8 @@ export declare class HttpServerCore {
57
67
  /**
58
68
  * Get the underlying HTTP server
59
69
  */
60
- getHttpServer(): http.Server;
70
+ getHttpServer(): HttpServerLike;
71
+ getScheme(): 'http' | 'https';
61
72
  /**
62
73
  * Resolve dominds installation root
63
74
  */
@@ -44,6 +44,7 @@ exports.sendText = sendText;
44
44
  * Core HTTP server functionality shared between production and development servers
45
45
  */
46
46
  const http = __importStar(require("http"));
47
+ const https = __importStar(require("node:https"));
47
48
  const path = __importStar(require("path"));
48
49
  const log_1 = require("../log");
49
50
  const api_routes_1 = require("./api-routes");
@@ -72,8 +73,14 @@ function toParsedUrlQuery(searchParams) {
72
73
  class HttpServerCore {
73
74
  constructor(config) {
74
75
  this.customHandlers = [];
76
+ this.started = false;
77
+ this.stopping = null;
75
78
  this.config = config;
76
- this.server = http.createServer(this.handleRequest.bind(this));
79
+ const requestHandler = this.handleRequest.bind(this);
80
+ this.server =
81
+ config.tls === undefined
82
+ ? http.createServer(requestHandler)
83
+ : https.createServer({ cert: config.tls.cert, key: config.tls.key }, requestHandler);
77
84
  }
78
85
  /**
79
86
  * Add custom request handler
@@ -212,7 +219,8 @@ class HttpServerCore {
212
219
  ? address.port
213
220
  : this.config.port;
214
221
  this.config = { ...this.config, port: actualPort };
215
- log.debug(`Server listening on http://${this.config.host}:${actualPort}`);
222
+ this.started = true;
223
+ log.debug(`Server listening on ${this.getScheme()}://${this.config.host}:${actualPort}`);
216
224
  resolve(actualPort);
217
225
  };
218
226
  this.server.once('error', onError);
@@ -224,12 +232,23 @@ class HttpServerCore {
224
232
  * Stop the server
225
233
  */
226
234
  stop() {
227
- return new Promise((resolve) => {
228
- this.server.close(() => {
235
+ if (this.stopping !== null)
236
+ return this.stopping;
237
+ if (!this.started)
238
+ return Promise.resolve();
239
+ this.stopping = new Promise((resolve, reject) => {
240
+ this.server.close((error) => {
241
+ this.started = false;
242
+ this.stopping = null;
243
+ if (error !== undefined) {
244
+ reject(error);
245
+ return;
246
+ }
229
247
  log.info('Server stopped');
230
248
  resolve();
231
249
  });
232
250
  });
251
+ return this.stopping;
233
252
  }
234
253
  /**
235
254
  * Get the underlying HTTP server
@@ -237,6 +256,9 @@ class HttpServerCore {
237
256
  getHttpServer() {
238
257
  return this.server;
239
258
  }
259
+ getScheme() {
260
+ return this.config.tls === undefined ? 'http' : 'https';
261
+ }
240
262
  /**
241
263
  * Resolve dominds installation root
242
264
  */
@@ -1,6 +1,7 @@
1
1
  import type { DomindsRuntimeMode, WebSocketMessage } from '@longrun-ai/kernel/types';
2
2
  import { type LanguageCode } from '@longrun-ai/kernel/types/language';
3
- import type { Server } from 'http';
3
+ import type { Server as HttpServer } from 'http';
4
+ import type { Server as HttpsServer } from 'https';
4
5
  import { WebSocket, WebSocketServer } from 'ws';
5
6
  import { Dialog } from '../dialog';
6
7
  import type { AuthConfig } from './auth';
@@ -28,7 +29,7 @@ export declare function handleWebSocketMessage(ws: WebSocket, packet: WebSocketM
28
29
  /**
29
30
  * Setup WebSocket server with dialog handling
30
31
  */
31
- export declare function setupWebSocketServer(httpServer: Server, clients: Set<WebSocket>, auth: AuthConfig, serverWorkLanguage: LanguageCode, mode: DomindsRuntimeMode): WebSocketServer;
32
+ export declare function setupWebSocketServer(httpServers: HttpServer | HttpsServer | readonly (HttpServer | HttpsServer)[], clients: Set<WebSocket>, auth: AuthConfig, serverWorkLanguage: LanguageCode, mode: DomindsRuntimeMode): readonly WebSocketServer[];
32
33
  /**
33
34
  * Clean up all event channels and subscriptions
34
35
  */
@@ -1855,8 +1855,9 @@ async function handleReceiveHumanReply(ws, packet) {
1855
1855
  /**
1856
1856
  * Setup WebSocket server with dialog handling
1857
1857
  */
1858
- function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage, mode) {
1859
- const wss = new ws_1.WebSocketServer({ server: httpServer });
1858
+ function setupWebSocketServer(httpServers, clients, auth, serverWorkLanguage, mode) {
1859
+ const servers = Array.isArray(httpServers) ? httpServers : [httpServers];
1860
+ const webSocketServers = servers.map((server) => new ws_1.WebSocketServer({ server }));
1860
1861
  const runtimeStatusPubChan = (0, evt_1.createPubChan)();
1861
1862
  const broadcastToClients = (msg) => {
1862
1863
  const data = JSON.stringify(msg);
@@ -1944,7 +1945,7 @@ function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage, mod
1944
1945
  });
1945
1946
  });
1946
1947
  (0, team_config_updates_1.startTeamConfigWatcher)();
1947
- httpServer.once('close', () => {
1948
+ const cleanupRuntime = () => {
1948
1949
  (0, team_config_updates_1.stopTeamConfigWatcher)();
1949
1950
  (0, team_config_updates_1.clearTeamConfigBroadcaster)();
1950
1951
  (0, tool_availability_updates_1.clearToolAvailabilityBroadcaster)();
@@ -1954,9 +1955,12 @@ function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage, mod
1954
1955
  (0, persistence_1.setDialogsQuarantinedBroadcaster)(null);
1955
1956
  broadcastDialogsIndexMessage = null;
1956
1957
  broadcastDiligencePushUpdatedMessage = null;
1957
- void wss.close();
1958
- });
1959
- wss.on('connection', (ws, req) => {
1958
+ for (const wss of webSocketServers) {
1959
+ void wss.close();
1960
+ }
1961
+ };
1962
+ servers[0]?.once('close', cleanupRuntime);
1963
+ const onConnection = (ws, req) => {
1960
1964
  const authCheck = (0, auth_1.getWebSocketAuthCheck)(req, auth);
1961
1965
  if (authCheck.kind !== 'ok') {
1962
1966
  ws.close(4401, 'unauthorized');
@@ -2036,8 +2040,11 @@ function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage, mod
2036
2040
  // Clean up client subscriptions on error
2037
2041
  cleanupWsClient(ws);
2038
2042
  });
2039
- });
2040
- return wss;
2043
+ };
2044
+ for (const wss of webSocketServers) {
2045
+ wss.on('connection', onConnection);
2046
+ }
2047
+ return webSocketServers;
2041
2048
  }
2042
2049
  function isRecord(value) {
2043
2050
  return typeof value === 'object' && value !== null;
package/dist/server.d.ts CHANGED
@@ -6,6 +6,7 @@ export type ServerOptions = {
6
6
  port?: number;
7
7
  host?: string;
8
8
  mode?: 'dev' | 'prod';
9
+ certsDirAbs?: string;
9
10
  startBackendDriver?: boolean;
10
11
  returnAfterListen?: boolean;
11
12
  strictPort?: boolean;
@@ -13,9 +14,13 @@ export type ServerOptions = {
13
14
  };
14
15
  export type StartedServer = {
15
16
  httpServer: HttpServerCore;
17
+ httpsServer?: HttpServerCore;
16
18
  auth: AuthConfig;
17
19
  host: string;
20
+ urlHost: string;
18
21
  port: number;
22
+ httpsPort?: number;
23
+ httpsUrlHost?: string;
19
24
  mode: 'dev' | 'prod';
20
25
  };
21
26
  export declare function startServer(opts?: ServerOptions): Promise<StartedServer>;
package/dist/server.js CHANGED
@@ -54,7 +54,9 @@ const open_generation_recovery_1 = require("./recovery/open-generation-recovery"
54
54
  const reply_delivery_recovery_1 = require("./recovery/reply-delivery-recovery");
55
55
  const work_language_1 = require("./runtime/work-language");
56
56
  const auth_1 = require("./server/auth");
57
+ const certificates_1 = require("./server/certificates");
57
58
  const dominds_self_update_1 = require("./server/dominds-self-update");
59
+ const network_hosts_1 = require("./server/network-hosts");
58
60
  const port_selection_1 = require("./server/port-selection");
59
61
  const server_core_1 = require("./server/server-core");
60
62
  const websocket_handler_1 = require("./server/websocket-handler");
@@ -128,6 +130,15 @@ function getErrnoCode(error) {
128
130
  const withCode = error;
129
131
  return typeof withCode.code === 'string' ? withCode.code : undefined;
130
132
  }
133
+ function buildAdjacentHttpsPortCandidates(params) {
134
+ if (params.preferredPort < 1 || params.preferredPort > 65535)
135
+ return [];
136
+ return (0, port_selection_1.buildWebuiPortCandidates)({
137
+ preferredPort: params.preferredPort,
138
+ strictPort: false,
139
+ direction: params.direction,
140
+ });
141
+ }
131
142
  async function runPostListenStartup(params) {
132
143
  await new Promise((resolve) => {
133
144
  setImmediate(resolve);
@@ -177,7 +188,6 @@ async function startServer(opts = {}) {
177
188
  strictPort,
178
189
  direction: portAutoDirection,
179
190
  });
180
- log.info(`Starting server in ${mode} mode on ${host}:${preferredPort} (${strictPort ? 'strict port' : `auto port ${portAutoDirection}`}; working language: ${(0, work_language_1.getWorkLanguage)()} from ${source})`);
181
191
  // WebSocket clients set
182
192
  const clients = new Set();
183
193
  const serverMode = mode === 'dev' ? 'development' : 'production';
@@ -185,6 +195,26 @@ async function startServer(opts = {}) {
185
195
  mode: serverMode,
186
196
  env: process.env,
187
197
  });
198
+ const certificateLookup = await (0, certificates_1.findAutoHttpsCertificateForHost)({
199
+ host,
200
+ certsDirAbs: opts.certsDirAbs,
201
+ });
202
+ for (const diagnostic of certificateLookup.diagnostics) {
203
+ log.warn(diagnostic.message);
204
+ }
205
+ const tls = certificateLookup.kind === 'found' && !strictPort ? certificateLookup.certificate : undefined;
206
+ const urlHost = (0, network_hosts_1.resolveHttpUrlHostForBindHost)(host);
207
+ log.info(`Starting server in ${mode} mode on ${(0, auth_1.formatServerOrigin)({
208
+ scheme: 'http',
209
+ host: urlHost,
210
+ port: preferredPort,
211
+ })} (bind ${host}; ${strictPort ? 'strict port' : `auto port ${portAutoDirection}`}; working language: ${(0, work_language_1.getWorkLanguage)()} from ${source})`);
212
+ if (certificateLookup.kind === 'found' && strictPort) {
213
+ log.info(`HTTPS certificate found but Dominds HTTPS is disabled for strict --port; assuming HTTPS, if needed, is handled by a front proxy (${certificateLookup.certificate.certPath})`);
214
+ }
215
+ else if (tls !== undefined) {
216
+ log.info(`HTTPS certificate available: ${tls.certPath} (matched host ${tls.matchedHost})`);
217
+ }
188
218
  let startedCore = null;
189
219
  let boundPort = null;
190
220
  for (const candidatePort of portCandidates) {
@@ -199,7 +229,6 @@ async function startServer(opts = {}) {
199
229
  const candidateServer = (0, server_core_1.createHttpServer)(config);
200
230
  try {
201
231
  boundPort = await candidateServer.start();
202
- (0, websocket_handler_1.setupWebSocketServer)(candidateServer.getHttpServer(), clients, auth, (0, work_language_1.getWorkLanguage)(), config.mode);
203
232
  startedCore = candidateServer;
204
233
  break;
205
234
  }
@@ -209,9 +238,6 @@ async function startServer(opts = {}) {
209
238
  log.warn(`WebUI port ${candidatePort} is already in use; trying the next ${nextDirection} port`);
210
239
  continue;
211
240
  }
212
- if (boundPort !== null && startedCore === null) {
213
- await candidateServer.stop();
214
- }
215
241
  throw error;
216
242
  }
217
243
  }
@@ -225,8 +251,66 @@ async function startServer(opts = {}) {
225
251
  if (!strictPort && boundPort !== preferredPort) {
226
252
  log.warn(`WebUI preferred port ${preferredPort} was unavailable; listening on ${boundPort}`);
227
253
  }
254
+ let httpsServer;
255
+ let httpsPort;
256
+ let httpsUrlHost;
257
+ if (tls !== undefined) {
258
+ const preferredHttpsPort = portAutoDirection === 'up' ? boundPort + 1 : boundPort - 1;
259
+ const httpsPortCandidates = buildAdjacentHttpsPortCandidates({
260
+ preferredPort: preferredHttpsPort,
261
+ direction: portAutoDirection,
262
+ });
263
+ for (const candidatePort of httpsPortCandidates) {
264
+ const config = {
265
+ mode: serverMode,
266
+ staticRoot: 'webapp/dist',
267
+ host,
268
+ port: candidatePort,
269
+ tls,
270
+ clients,
271
+ auth,
272
+ };
273
+ const candidateServer = (0, server_core_1.createHttpServer)(config);
274
+ try {
275
+ httpsPort = await candidateServer.start();
276
+ httpsServer = candidateServer;
277
+ httpsUrlHost = tls.matchedHost;
278
+ break;
279
+ }
280
+ catch (error) {
281
+ if (getErrnoCode(error) === 'EADDRINUSE') {
282
+ const nextDirection = portAutoDirection === 'down' ? 'lower' : 'higher';
283
+ log.warn(`WebUI HTTPS port ${candidatePort} is already in use; trying the next ${nextDirection} port`);
284
+ continue;
285
+ }
286
+ await startedCore.stop();
287
+ throw error;
288
+ }
289
+ }
290
+ if (httpsServer === undefined || httpsPort === undefined || httpsUrlHost === undefined) {
291
+ await startedCore.stop();
292
+ throw new Error(`Failed to start WebUI HTTPS: no available adjacent port found from ${preferredHttpsPort}`);
293
+ }
294
+ log.info(`HTTPS WebUI ready: ${(0, auth_1.formatServerOrigin)({
295
+ scheme: 'https',
296
+ host: httpsUrlHost,
297
+ port: httpsPort,
298
+ })}`);
299
+ }
300
+ (0, websocket_handler_1.setupWebSocketServer)(httpsServer === undefined
301
+ ? startedCore.getHttpServer()
302
+ : [startedCore.getHttpServer(), httpsServer.getHttpServer()], clients, auth, (0, work_language_1.getWorkLanguage)(), serverMode);
228
303
  const postListenStartupToken = { canceled: false };
229
304
  const httpServer = attachPostListenStartupCancellation(startedCore, postListenStartupToken);
305
+ if (httpsServer !== undefined) {
306
+ const originalStop = httpServer.stop.bind(httpServer);
307
+ const httpsServerToStop = httpsServer;
308
+ httpServer.stop = async () => {
309
+ postListenStartupToken.canceled = true;
310
+ await httpsServerToStop.stop();
311
+ await originalStop();
312
+ };
313
+ }
230
314
  try {
231
315
  const rtwsRootAbs = process.cwd();
232
316
  (0, dominds_self_update_1.configureDomindsSelfUpdate)({
@@ -259,7 +343,7 @@ async function startServer(opts = {}) {
259
343
  }
260
344
  const postListenStartup = runPostListenStartup({
261
345
  rtwsRootAbs,
262
- kernel: { host, port: boundPort },
346
+ kernel: { scheme: 'http', host: urlHost, port: boundPort },
263
347
  startBackendDriver,
264
348
  token: postListenStartupToken,
265
349
  });
@@ -279,7 +363,17 @@ async function startServer(opts = {}) {
279
363
  }
280
364
  throw error;
281
365
  }
282
- return { httpServer, auth, host, port: boundPort, mode };
366
+ return {
367
+ httpServer,
368
+ httpsServer,
369
+ auth,
370
+ host,
371
+ urlHost,
372
+ port: boundPort,
373
+ httpsPort,
374
+ httpsUrlHost,
375
+ mode,
376
+ };
283
377
  }
284
378
  // Main function for CLI execution
285
379
  async function main() {
@@ -287,13 +381,14 @@ async function main() {
287
381
  // Handle working directory change from -C flag
288
382
  const wsDir = cliArgs['C'];
289
383
  if (wsDir) {
290
- // Resolve to absolute path before changing directory
291
- const absoluteWsDir = path.isAbsolute(wsDir) ? wsDir : path.resolve(process.cwd(), wsDir);
384
+ if (!path.isAbsolute(wsDir)) {
385
+ throw new Error(`-C requires an absolute directory path: ${wsDir}`);
386
+ }
292
387
  try {
293
388
  process.chdir(wsDir);
294
389
  }
295
390
  catch (err) {
296
- log.warn(`Failed to change working directory to ${wsDir}:`, err);
391
+ throw new Error(`Failed to change working directory to ${wsDir}: ${err instanceof Error ? err.message : String(err)}`);
297
392
  }
298
393
  }
299
394
  // 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.26.0",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {
@@ -52,9 +52,9 @@
52
52
  "ws": "^8.19.0",
53
53
  "yaml": "^2.8.2",
54
54
  "zod": "^4.3.6",
55
+ "@longrun-ai/kernel": "1.16.0",
55
56
  "@longrun-ai/codex-auth": "0.13.0",
56
- "@longrun-ai/kernel": "1.15.12",
57
- "@longrun-ai/shell": "1.15.12"
57
+ "@longrun-ai/shell": "1.16.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^25.3.5",