agent-message 0.1.3 → 0.2.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.
- package/README.md +143 -25
- package/npm/bin/agent-message.mjs +247 -58
- package/npm/runtime/agent_gateway.mjs +5 -2
- package/npm/runtime/bin/agent-message-cli-darwin-amd64 +0 -0
- package/npm/runtime/bin/agent-message-cli-darwin-arm64 +0 -0
- package/npm/runtime/bin/agent-message-server-darwin-amd64 +0 -0
- package/npm/runtime/bin/agent-message-server-darwin-arm64 +0 -0
- package/npm/runtime/web-dist/assets/index-BIXs-4JX.js +195 -0
- package/npm/runtime/web-dist/assets/index-BdQqmJ4R.css +1 -0
- package/npm/runtime/web-dist/index.html +2 -2
- package/npm/runtime/web-dist/sw.js +2 -1
- package/package.json +3 -2
- package/npm/runtime/web-dist/assets/index-4VmoBZF3.js +0 -182
- package/npm/runtime/web-dist/assets/index-D_RPU5JN.css +0 -1
- package/npm/runtime/web-dist/workbox-8c29f6e4.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn, spawnSync } from 'node:child_process'
|
|
4
|
+
import { generateKeyPairSync } from 'node:crypto'
|
|
4
5
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
5
6
|
import { access, constants } from 'node:fs/promises'
|
|
6
7
|
import os from 'node:os'
|
|
@@ -15,6 +16,8 @@ const DEFAULT_WEB_PORT = 45788
|
|
|
15
16
|
const STARTUP_ATTEMPTS = 40
|
|
16
17
|
const STARTUP_DELAY_MS = 500
|
|
17
18
|
const PROCESS_STOP_DELAY_MS = 1000
|
|
19
|
+
const DEFAULT_WEB_PUSH_SUBJECT = 'mailto:agent-message@local.invalid'
|
|
20
|
+
const DEFAULT_TUNNEL_WEB_PUSH_SUBJECT = 'https://agent-message.namjaeyoun.com'
|
|
18
21
|
|
|
19
22
|
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
20
23
|
const packageRoot = resolve(scriptDir, '..', '..')
|
|
@@ -22,6 +25,12 @@ const bundleRoot = resolve(packageRoot, 'npm', 'runtime')
|
|
|
22
25
|
const bundleBinDir = join(bundleRoot, 'bin')
|
|
23
26
|
const bundleGatewayPath = join(bundleRoot, 'agent_gateway.mjs')
|
|
24
27
|
const bundleWebDistDir = join(bundleRoot, 'web-dist')
|
|
28
|
+
const sourceServerDir = resolve(packageRoot, 'server')
|
|
29
|
+
const sourceWebDir = resolve(packageRoot, 'web')
|
|
30
|
+
const sourceGatewayPath = resolve(packageRoot, 'deploy', 'agent_gateway.mjs')
|
|
31
|
+
const sourceWebDistDir = resolve(sourceWebDir, 'dist')
|
|
32
|
+
const tunnelConfigPath = resolve(packageRoot, 'deploy', 'agent_tunnel_config.yml')
|
|
33
|
+
const tunnelName = 'agent-namjaeyoun-com'
|
|
25
34
|
|
|
26
35
|
const lifecycleCommands = new Set(['start', 'stop', 'status'])
|
|
27
36
|
|
|
@@ -39,7 +48,9 @@ async function main() {
|
|
|
39
48
|
|
|
40
49
|
if (lifecycleCommands.has(command)) {
|
|
41
50
|
const options = parseLifecycleOptions(rest)
|
|
42
|
-
|
|
51
|
+
if (!options.dev) {
|
|
52
|
+
await ensureBundleReady()
|
|
53
|
+
}
|
|
43
54
|
|
|
44
55
|
if (command === 'start') {
|
|
45
56
|
await startStack(options)
|
|
@@ -59,14 +70,16 @@ async function main() {
|
|
|
59
70
|
|
|
60
71
|
function printRootUsage() {
|
|
61
72
|
console.error(`Usage:
|
|
62
|
-
agent-message start [--runtime-dir <dir>] [--api-host <host>] [--api-port <port>] [--web-host <host>] [--web-port <port>]
|
|
63
|
-
agent-message stop [--runtime-dir <dir>]
|
|
64
|
-
agent-message status [--runtime-dir <dir>] [--api-host <host>] [--api-port <port>] [--web-host <host>] [--web-port <port>]
|
|
73
|
+
agent-message start [--dev] [--with-tunnel] [--runtime-dir <dir>] [--api-host <host>] [--api-port <port>] [--web-host <host>] [--web-port <port>]
|
|
74
|
+
agent-message stop [--dev] [--with-tunnel] [--runtime-dir <dir>]
|
|
75
|
+
agent-message status [--dev] [--runtime-dir <dir>] [--api-host <host>] [--api-port <port>] [--web-host <host>] [--web-port <port>]
|
|
65
76
|
agent-message <existing-cli-command> [...args]`)
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
function parseLifecycleOptions(args) {
|
|
69
80
|
const options = {
|
|
81
|
+
dev: false,
|
|
82
|
+
withTunnel: false,
|
|
70
83
|
runtimeDir: join(os.homedir(), '.agent-message'),
|
|
71
84
|
apiHost: DEFAULT_API_HOST,
|
|
72
85
|
apiPort: DEFAULT_API_PORT,
|
|
@@ -81,6 +94,14 @@ function parseLifecycleOptions(args) {
|
|
|
81
94
|
process.exit(0)
|
|
82
95
|
}
|
|
83
96
|
|
|
97
|
+
if (arg === '--dev') {
|
|
98
|
+
options.dev = true
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (arg === '--with-tunnel' || arg === '--all') {
|
|
102
|
+
options.withTunnel = true
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
84
105
|
if (arg === '--runtime-dir') {
|
|
85
106
|
options.runtimeDir = requireOptionValue(args, ++index, arg)
|
|
86
107
|
continue
|
|
@@ -164,20 +185,25 @@ function resolveBinaryPath(baseName) {
|
|
|
164
185
|
function runtimePaths(runtimeDir) {
|
|
165
186
|
return {
|
|
166
187
|
runtimeDir,
|
|
188
|
+
binDir: join(runtimeDir, 'bin'),
|
|
167
189
|
logDir: join(runtimeDir, 'logs'),
|
|
168
190
|
uploadDir: join(runtimeDir, 'uploads'),
|
|
169
191
|
serverLog: join(runtimeDir, 'logs', 'server.log'),
|
|
170
192
|
gatewayLog: join(runtimeDir, 'logs', 'gateway.log'),
|
|
193
|
+
tunnelLog: join(runtimeDir, 'logs', 'named-tunnel.log'),
|
|
171
194
|
serverPidfile: join(runtimeDir, 'server.pid'),
|
|
172
195
|
gatewayPidfile: join(runtimeDir, 'gateway.pid'),
|
|
196
|
+
tunnelPidfile: join(runtimeDir, 'named-tunnel.pid'),
|
|
173
197
|
stackMetadataPath: join(runtimeDir, 'stack.json'),
|
|
174
198
|
sqlitePath: join(runtimeDir, 'agent_message.sqlite'),
|
|
199
|
+
webPushConfigPath: join(runtimeDir, 'web-push.json'),
|
|
175
200
|
}
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
async function startStack(options) {
|
|
179
204
|
const paths = runtimePaths(options.runtimeDir)
|
|
180
205
|
mkdirSync(paths.runtimeDir, { recursive: true })
|
|
206
|
+
mkdirSync(paths.binDir, { recursive: true })
|
|
181
207
|
mkdirSync(paths.logDir, { recursive: true })
|
|
182
208
|
mkdirSync(paths.uploadDir, { recursive: true })
|
|
183
209
|
|
|
@@ -185,38 +211,55 @@ async function startStack(options) {
|
|
|
185
211
|
|
|
186
212
|
writeFileSync(paths.serverLog, '')
|
|
187
213
|
writeFileSync(paths.gatewayLog, '')
|
|
214
|
+
if (options.withTunnel) {
|
|
215
|
+
ensureTunnelTargetMatchesDefaults(options)
|
|
216
|
+
writeFileSync(paths.tunnelLog, '')
|
|
217
|
+
}
|
|
188
218
|
|
|
189
|
-
const
|
|
190
|
-
const gatewayChild = { pid: null }
|
|
219
|
+
const launchSpec = options.dev ? buildDevLaunchSpec(paths) : buildBundledLaunchSpec()
|
|
191
220
|
|
|
192
221
|
try {
|
|
193
|
-
const
|
|
222
|
+
const webPushConfig = ensureWebPushConfig(paths, options)
|
|
223
|
+
const serverChild = spawnDetached(launchSpec.serverCommand, launchSpec.serverArgs, {
|
|
194
224
|
...process.env,
|
|
195
225
|
SERVER_ADDR: `${options.apiHost}:${options.apiPort}`,
|
|
196
226
|
DB_DRIVER: 'sqlite',
|
|
197
227
|
SQLITE_DSN: paths.sqlitePath,
|
|
198
228
|
UPLOAD_DIR: paths.uploadDir,
|
|
199
229
|
CORS_ALLOWED_ORIGINS: '*',
|
|
230
|
+
WEB_PUSH_VAPID_PUBLIC_KEY: webPushConfig.publicKey,
|
|
231
|
+
WEB_PUSH_VAPID_PRIVATE_KEY: webPushConfig.privateKey,
|
|
232
|
+
WEB_PUSH_SUBJECT: webPushConfig.subject,
|
|
200
233
|
}, paths.serverLog)
|
|
201
234
|
writeFileSync(paths.serverPidfile, `${serverChild.pid}\n`)
|
|
202
235
|
|
|
203
236
|
await waitForHttp(`http://${options.apiHost}:${options.apiPort}/healthz`, 'API server')
|
|
204
237
|
|
|
205
|
-
gatewayChild
|
|
206
|
-
|
|
207
|
-
|
|
238
|
+
const gatewayChild = spawnDetached(
|
|
239
|
+
launchSpec.gatewayCommand,
|
|
240
|
+
launchSpec.gatewayArgs,
|
|
208
241
|
{
|
|
209
242
|
...process.env,
|
|
210
243
|
AGENT_GATEWAY_HOST: options.webHost,
|
|
211
244
|
AGENT_GATEWAY_PORT: String(options.webPort),
|
|
212
245
|
AGENT_API_ORIGIN: `http://${options.apiHost}:${options.apiPort}`,
|
|
213
|
-
AGENT_WEB_DIST:
|
|
246
|
+
AGENT_WEB_DIST: launchSpec.webDistDir,
|
|
214
247
|
},
|
|
215
248
|
paths.gatewayLog,
|
|
216
|
-
)
|
|
249
|
+
)
|
|
217
250
|
writeFileSync(paths.gatewayPidfile, `${gatewayChild.pid}\n`)
|
|
218
251
|
|
|
219
252
|
await waitForHttp(`http://${options.webHost}:${options.webPort}`, 'web gateway')
|
|
253
|
+
if (options.withTunnel) {
|
|
254
|
+
const tunnelChild = spawnDetached(
|
|
255
|
+
'cloudflared',
|
|
256
|
+
['tunnel', '--config', tunnelConfigPath, 'run', tunnelName],
|
|
257
|
+
process.env,
|
|
258
|
+
paths.tunnelLog,
|
|
259
|
+
)
|
|
260
|
+
writeFileSync(paths.tunnelPidfile, `${tunnelChild.pid}\n`)
|
|
261
|
+
await waitForLogMessage(paths.tunnelLog, 'Registered tunnel connection', 'named tunnel')
|
|
262
|
+
}
|
|
220
263
|
writeStackMetadata(paths.stackMetadataPath, options)
|
|
221
264
|
} catch (error) {
|
|
222
265
|
await stopStack(options, { quiet: true })
|
|
@@ -227,16 +270,122 @@ async function startStack(options) {
|
|
|
227
270
|
console.log(`API: http://${options.apiHost}:${options.apiPort}`)
|
|
228
271
|
console.log(`Web: http://${options.webHost}:${options.webPort}`)
|
|
229
272
|
console.log(`Logs: ${paths.serverLog} ${paths.gatewayLog}`)
|
|
273
|
+
console.log(`Web Push: ${paths.webPushConfigPath}`)
|
|
274
|
+
if (options.withTunnel) {
|
|
275
|
+
console.log(`Tunnel: https://agent.namjaeyoun.com`)
|
|
276
|
+
console.log(`Tunnel log: ${paths.tunnelLog}`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function ensureWebPushConfig(paths, options) {
|
|
281
|
+
const envPublicKey = process.env.WEB_PUSH_VAPID_PUBLIC_KEY?.trim() ?? ''
|
|
282
|
+
const envPrivateKey = process.env.WEB_PUSH_VAPID_PRIVATE_KEY?.trim() ?? ''
|
|
283
|
+
const envSubject = process.env.WEB_PUSH_SUBJECT?.trim() ?? ''
|
|
284
|
+
const defaultSubject = resolveDefaultWebPushSubject(options)
|
|
285
|
+
|
|
286
|
+
if ((envPublicKey && !envPrivateKey) || (!envPublicKey && envPrivateKey)) {
|
|
287
|
+
throw new Error('WEB_PUSH_VAPID_PUBLIC_KEY and WEB_PUSH_VAPID_PRIVATE_KEY must be set together.')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (envPublicKey && envPrivateKey) {
|
|
291
|
+
return {
|
|
292
|
+
publicKey: envPublicKey,
|
|
293
|
+
privateKey: envPrivateKey,
|
|
294
|
+
subject: envSubject || defaultSubject,
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const stored = readStoredWebPushConfig(paths.webPushConfigPath)
|
|
299
|
+
if (stored) {
|
|
300
|
+
const storedSubject = stored.subject || ''
|
|
301
|
+
const shouldMigrateDefaultSubject =
|
|
302
|
+
storedSubject === '' ||
|
|
303
|
+
(storedSubject === DEFAULT_WEB_PUSH_SUBJECT && defaultSubject !== DEFAULT_WEB_PUSH_SUBJECT)
|
|
304
|
+
const resolved = {
|
|
305
|
+
publicKey: stored.publicKey,
|
|
306
|
+
privateKey: stored.privateKey,
|
|
307
|
+
subject: envSubject || (shouldMigrateDefaultSubject ? defaultSubject : storedSubject),
|
|
308
|
+
}
|
|
309
|
+
if (resolved.subject !== stored.subject) {
|
|
310
|
+
writeStoredWebPushConfig(paths.webPushConfigPath, resolved)
|
|
311
|
+
}
|
|
312
|
+
return resolved
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const generated = generateWebPushConfig(envSubject || defaultSubject)
|
|
316
|
+
writeStoredWebPushConfig(paths.webPushConfigPath, generated)
|
|
317
|
+
return generated
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function resolveDefaultWebPushSubject(options) {
|
|
321
|
+
if (options?.withTunnel) {
|
|
322
|
+
return DEFAULT_TUNNEL_WEB_PUSH_SUBJECT
|
|
323
|
+
}
|
|
324
|
+
return DEFAULT_WEB_PUSH_SUBJECT
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function readStoredWebPushConfig(path) {
|
|
328
|
+
if (!existsSync(path)) {
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'))
|
|
334
|
+
const publicKey = typeof parsed.publicKey === 'string' ? parsed.publicKey.trim() : ''
|
|
335
|
+
const privateKey = typeof parsed.privateKey === 'string' ? parsed.privateKey.trim() : ''
|
|
336
|
+
const subject = typeof parsed.subject === 'string' ? parsed.subject.trim() : ''
|
|
337
|
+
if (!publicKey || !privateKey) {
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
publicKey,
|
|
342
|
+
privateKey,
|
|
343
|
+
subject: subject || DEFAULT_WEB_PUSH_SUBJECT,
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
return null
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function writeStoredWebPushConfig(path, config) {
|
|
351
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function generateWebPushConfig(subject) {
|
|
355
|
+
const { privateKey, publicKey } = generateKeyPairSync('ec', {
|
|
356
|
+
namedCurve: 'prime256v1',
|
|
357
|
+
})
|
|
358
|
+
const privateJWK = privateKey.export({ format: 'jwk' })
|
|
359
|
+
const publicJWK = publicKey.export({ format: 'jwk' })
|
|
360
|
+
|
|
361
|
+
if (
|
|
362
|
+
typeof privateJWK.d !== 'string' ||
|
|
363
|
+
typeof publicJWK.x !== 'string' ||
|
|
364
|
+
typeof publicJWK.y !== 'string'
|
|
365
|
+
) {
|
|
366
|
+
throw new Error('failed to generate VAPID keys')
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const x = Buffer.from(publicJWK.x, 'base64url')
|
|
370
|
+
const y = Buffer.from(publicJWK.y, 'base64url')
|
|
371
|
+
const publicKeyBytes = Buffer.concat([Buffer.from([0x04]), x, y])
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
publicKey: publicKeyBytes.toString('base64url'),
|
|
375
|
+
privateKey: privateJWK.d,
|
|
376
|
+
subject,
|
|
377
|
+
}
|
|
230
378
|
}
|
|
231
379
|
|
|
232
380
|
async function stopStack(options, { quiet }) {
|
|
233
381
|
const paths = runtimePaths(options.runtimeDir)
|
|
234
382
|
const stoppedServer = await killFromPidfile(paths.serverPidfile)
|
|
235
383
|
const stoppedGateway = await killFromPidfile(paths.gatewayPidfile)
|
|
384
|
+
const stoppedTunnel = await killFromPidfile(paths.tunnelPidfile)
|
|
236
385
|
rmSync(paths.stackMetadataPath, { force: true })
|
|
237
386
|
|
|
238
387
|
if (!quiet) {
|
|
239
|
-
if (stoppedServer || stoppedGateway) {
|
|
388
|
+
if (stoppedServer || stoppedGateway || stoppedTunnel) {
|
|
240
389
|
console.log('Agent Message is stopped.')
|
|
241
390
|
} else {
|
|
242
391
|
console.log('Agent Message is not running.')
|
|
@@ -250,77 +399,86 @@ async function printStatus(options) {
|
|
|
250
399
|
const gatewayPid = readPidfile(paths.gatewayPidfile)
|
|
251
400
|
const serverRunning = serverPid !== null && isPidAlive(serverPid)
|
|
252
401
|
const gatewayRunning = gatewayPid !== null && isPidAlive(gatewayPid)
|
|
402
|
+
const tunnelPid = readPidfile(paths.tunnelPidfile)
|
|
403
|
+
const tunnelRunning = tunnelPid !== null && isPidAlive(tunnelPid)
|
|
253
404
|
const apiHealthy = serverRunning ? await isHttpReady(`http://${options.apiHost}:${options.apiPort}/healthz`) : false
|
|
254
405
|
const webHealthy = gatewayRunning ? await isHttpReady(`http://${options.webHost}:${options.webPort}`) : false
|
|
255
406
|
|
|
256
407
|
console.log(`API server: ${serverRunning ? 'running' : 'stopped'}${apiHealthy ? ' (healthy)' : ''}`)
|
|
257
408
|
console.log(`Web gateway: ${gatewayRunning ? 'running' : 'stopped'}${webHealthy ? ' (healthy)' : ''}`)
|
|
409
|
+
if (tunnelPid !== null) {
|
|
410
|
+
console.log(`Named tunnel: ${tunnelRunning ? 'running' : 'stopped'}`)
|
|
411
|
+
}
|
|
258
412
|
console.log(`Runtime dir: ${paths.runtimeDir}`)
|
|
259
413
|
console.log(`API URL: http://${options.apiHost}:${options.apiPort}`)
|
|
260
414
|
console.log(`Web URL: http://${options.webHost}:${options.webPort}`)
|
|
261
415
|
}
|
|
262
416
|
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (result.error) {
|
|
272
|
-
throw result.error
|
|
417
|
+
function buildBundledLaunchSpec() {
|
|
418
|
+
return {
|
|
419
|
+
serverCommand: resolveBinaryPath('agent-message-server'),
|
|
420
|
+
serverArgs: [],
|
|
421
|
+
gatewayCommand: process.execPath,
|
|
422
|
+
gatewayArgs: [bundleGatewayPath],
|
|
423
|
+
webDistDir: bundleWebDistDir,
|
|
273
424
|
}
|
|
425
|
+
}
|
|
274
426
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
427
|
+
function buildDevLaunchSpec(paths) {
|
|
428
|
+
ensureDevSourcesReady()
|
|
429
|
+
|
|
430
|
+
if (!existsSync(join(sourceWebDir, 'node_modules'))) {
|
|
431
|
+
runForeground('npm', ['ci'], { cwd: sourceWebDir })
|
|
278
432
|
}
|
|
433
|
+
runForeground('node', ['./scripts/generate-message-json-render-catalog-prompt.mjs'], { cwd: packageRoot })
|
|
434
|
+
runForeground('npm', ['run', 'build'], { cwd: sourceWebDir })
|
|
279
435
|
|
|
280
|
-
|
|
281
|
-
}
|
|
436
|
+
const serverBinaryPath = join(paths.binDir, 'agent-message-server')
|
|
437
|
+
runForeground('go', ['build', '-o', serverBinaryPath, '.'], { cwd: sourceServerDir })
|
|
282
438
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
439
|
+
return {
|
|
440
|
+
serverCommand: serverBinaryPath,
|
|
441
|
+
serverArgs: [],
|
|
442
|
+
gatewayCommand: process.execPath,
|
|
443
|
+
gatewayArgs: [sourceGatewayPath],
|
|
444
|
+
webDistDir: sourceWebDistDir,
|
|
286
445
|
}
|
|
446
|
+
}
|
|
287
447
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
448
|
+
function ensureDevSourcesReady() {
|
|
449
|
+
const requiredPaths = [sourceServerDir, sourceWebDir, sourceGatewayPath]
|
|
450
|
+
for (const target of requiredPaths) {
|
|
451
|
+
if (!existsSync(target)) {
|
|
452
|
+
throw new Error(`development mode requires a local checkout with ${target}`)
|
|
453
|
+
}
|
|
291
454
|
}
|
|
292
|
-
|
|
293
|
-
return ['--server-url', localServerURL, ...args]
|
|
294
455
|
}
|
|
295
456
|
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
457
|
+
function ensureTunnelTargetMatchesDefaults(options) {
|
|
458
|
+
if (options.webHost !== DEFAULT_WEB_HOST || options.webPort !== DEFAULT_WEB_PORT) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`--with-tunnel requires the default web listener ${DEFAULT_WEB_HOST}:${DEFAULT_WEB_PORT} to match ${tunnelConfigPath}.`,
|
|
461
|
+
)
|
|
462
|
+
}
|
|
299
463
|
}
|
|
300
464
|
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
465
|
+
function delegateToBundledCli(args) {
|
|
466
|
+
const cliBinary = resolveBinaryPath('agent-message-cli')
|
|
467
|
+
const result = spawnSync(cliBinary, args, {
|
|
468
|
+
stdio: 'inherit',
|
|
469
|
+
env: process.env,
|
|
470
|
+
})
|
|
304
471
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const serverPid = readPidfile(paths.serverPidfile)
|
|
308
|
-
if (serverPid === null || !isPidAlive(serverPid)) {
|
|
309
|
-
return null
|
|
472
|
+
if (result.error) {
|
|
473
|
+
throw result.error
|
|
310
474
|
}
|
|
311
475
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const apiHost = typeof metadata.apiHost === 'string' ? metadata.apiHost.trim() : ''
|
|
316
|
-
const apiPort = metadata.apiPort
|
|
317
|
-
if (!apiHost || !Number.isInteger(apiPort) || apiPort <= 0 || apiPort > 65535) {
|
|
318
|
-
return null
|
|
319
|
-
}
|
|
320
|
-
return `http://${apiHost}:${apiPort}`
|
|
321
|
-
} catch {
|
|
322
|
-
return null
|
|
476
|
+
if (result.signal) {
|
|
477
|
+
process.kill(process.pid, result.signal)
|
|
478
|
+
return
|
|
323
479
|
}
|
|
480
|
+
|
|
481
|
+
process.exit(result.status ?? 1)
|
|
324
482
|
}
|
|
325
483
|
|
|
326
484
|
function writeStackMetadata(path, options) {
|
|
@@ -355,6 +513,27 @@ function spawnDetached(command, args, env, logFile) {
|
|
|
355
513
|
}
|
|
356
514
|
}
|
|
357
515
|
|
|
516
|
+
function runForeground(command, args, { cwd }) {
|
|
517
|
+
const result = spawnSync(command, args, {
|
|
518
|
+
cwd,
|
|
519
|
+
env: process.env,
|
|
520
|
+
stdio: 'inherit',
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
if (result.error) {
|
|
524
|
+
throw result.error
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (result.signal) {
|
|
528
|
+
process.kill(process.pid, result.signal)
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if ((result.status ?? 1) !== 0) {
|
|
533
|
+
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status ?? 1}`)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
358
537
|
function readPidfile(pidfile) {
|
|
359
538
|
if (!existsSync(pidfile)) {
|
|
360
539
|
return null
|
|
@@ -409,6 +588,16 @@ async function waitForHttp(url, label) {
|
|
|
409
588
|
throw new Error(`${label} did not become ready: ${url}`)
|
|
410
589
|
}
|
|
411
590
|
|
|
591
|
+
async function waitForLogMessage(logFile, message, label) {
|
|
592
|
+
for (let attempt = 0; attempt < STARTUP_ATTEMPTS; attempt += 1) {
|
|
593
|
+
if (existsSync(logFile) && readFileSync(logFile, 'utf8').includes(message)) {
|
|
594
|
+
return
|
|
595
|
+
}
|
|
596
|
+
await sleep(STARTUP_DELAY_MS)
|
|
597
|
+
}
|
|
598
|
+
throw new Error(`${label} did not become ready: ${message}`)
|
|
599
|
+
}
|
|
600
|
+
|
|
412
601
|
async function isHttpReady(url) {
|
|
413
602
|
try {
|
|
414
603
|
const controller = new AbortController()
|
|
@@ -4,8 +4,8 @@ import http from 'node:http'
|
|
|
4
4
|
import { basename, extname, join, normalize, resolve } from 'node:path'
|
|
5
5
|
|
|
6
6
|
const host = process.env.AGENT_GATEWAY_HOST ?? '127.0.0.1'
|
|
7
|
-
const port = Number(process.env.AGENT_GATEWAY_PORT ?? '
|
|
8
|
-
const apiOrigin = process.env.AGENT_API_ORIGIN ?? 'http://127.0.0.1:
|
|
7
|
+
const port = Number(process.env.AGENT_GATEWAY_PORT ?? '45788')
|
|
8
|
+
const apiOrigin = process.env.AGENT_API_ORIGIN ?? 'http://127.0.0.1:8080'
|
|
9
9
|
const distDir = resolve(process.env.AGENT_WEB_DIST ?? join(process.cwd(), 'web', 'dist'))
|
|
10
10
|
const indexPath = join(distDir, 'index.html')
|
|
11
11
|
|
|
@@ -87,6 +87,9 @@ async function proxyRequest(req, res) {
|
|
|
87
87
|
upstream.status,
|
|
88
88
|
Object.fromEntries(upstream.headers.entries()),
|
|
89
89
|
)
|
|
90
|
+
if (upstream.headers.get('content-type')?.startsWith('text/event-stream')) {
|
|
91
|
+
res.flushHeaders()
|
|
92
|
+
}
|
|
90
93
|
|
|
91
94
|
if (!upstream.body) {
|
|
92
95
|
res.end()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|