@agent-link/agent 0.1.289 → 0.1.292
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/action.d.ts +7 -0
- package/dist/action.js.map +1 -1
- package/dist/cli.js +137 -25
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.js +25 -3
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +12 -0
- package/dist/connection.js +145 -4
- package/dist/connection.js.map +1 -1
- package/dist/encryption.d.ts +4 -0
- package/dist/encryption.js +13 -0
- package/dist/encryption.js.map +1 -1
- package/dist/index.js +54 -10
- package/dist/index.js.map +1 -1
- package/dist/local-control-client.d.ts +15 -0
- package/dist/local-control-client.js +100 -0
- package/dist/local-control-client.js.map +1 -0
- package/dist/local-control-http.d.ts +7 -0
- package/dist/local-control-http.js +208 -0
- package/dist/local-control-http.js.map +1 -0
- package/dist/local-control.d.ts +74 -0
- package/dist/local-control.js +55 -0
- package/dist/local-control.js.map +1 -0
- package/dist/process-control.d.ts +17 -0
- package/dist/process-control.js +48 -0
- package/dist/process-control.js.map +1 -0
- package/dist/shutdown-coordinator.d.ts +19 -0
- package/dist/shutdown-coordinator.js +67 -0
- package/dist/shutdown-coordinator.js.map +1 -0
- package/dist/upgrade-job.d.ts +25 -0
- package/dist/upgrade-job.js +59 -0
- package/dist/upgrade-job.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,8 +10,11 @@ export function enableLogTimestamps() {
|
|
|
10
10
|
if (!process.stdout.isTTY)
|
|
11
11
|
enableLogTimestamps();
|
|
12
12
|
import { saveRuntimeState, clearRuntimePid, writePidFile, encodeBase64Url } from './config.js';
|
|
13
|
-
import { connect, disconnect, getE2EKey } from './connection.js';
|
|
13
|
+
import { beginShutdown, connect, disconnect, getConnectionStatus, getE2EKey, sendAndFlush, } from './connection.js';
|
|
14
14
|
import { shutdownScheduler } from './scheduler.js';
|
|
15
|
+
import { createLocalControlService } from './local-control.js';
|
|
16
|
+
import { startLocalControlServer } from './local-control-http.js';
|
|
17
|
+
import { createShutdownCoordinator } from './shutdown-coordinator.js';
|
|
15
18
|
const require = createRequire(import.meta.url);
|
|
16
19
|
const qrcode = require('qrcode-terminal');
|
|
17
20
|
/** Highlight a URL with bold + underline ANSI codes if stdout supports color. */
|
|
@@ -34,7 +37,40 @@ export async function start(config, daemon = false, pidFile, launchSpecPath, lau
|
|
|
34
37
|
const e2eKey = getE2EKey();
|
|
35
38
|
const e2eFragment = e2eKey ? `#/?k=${encodeBase64Url(e2eKey)}` : '';
|
|
36
39
|
const sessionUrl = `${httpBase}/${config.entra ? 'ms' : 's'}/${sessionId}${e2eFragment}`;
|
|
37
|
-
|
|
40
|
+
const startedAt = new Date().toISOString();
|
|
41
|
+
let controlServer = null;
|
|
42
|
+
const shutdownCoordinator = createShutdownCoordinator({
|
|
43
|
+
markShuttingDown: () => beginShutdown(),
|
|
44
|
+
steps: [
|
|
45
|
+
{
|
|
46
|
+
name: 'notify-web',
|
|
47
|
+
run: async (request) => {
|
|
48
|
+
await sendAndFlush({
|
|
49
|
+
type: request.reason === 'upgrade' ? 'agent_upgrading' : 'agent_stopping',
|
|
50
|
+
reason: request.reason,
|
|
51
|
+
requestId: request.requestId,
|
|
52
|
+
jobId: request.jobId,
|
|
53
|
+
targetVersion: request.targetVersion,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{ name: 'scheduler', run: () => shutdownScheduler() },
|
|
58
|
+
{ name: 'connection-resources', run: () => disconnect() },
|
|
59
|
+
{ name: 'control-plane', run: () => controlServer?.close() },
|
|
60
|
+
{ name: 'runtime-state', run: () => clearRuntimePid() },
|
|
61
|
+
],
|
|
62
|
+
exit: (code) => process.exit(code),
|
|
63
|
+
});
|
|
64
|
+
const controlService = createLocalControlService({
|
|
65
|
+
version: require('../package.json').version,
|
|
66
|
+
startedAt,
|
|
67
|
+
config,
|
|
68
|
+
getConnectionStatus,
|
|
69
|
+
getShutdownStatus: () => shutdownCoordinator.status(),
|
|
70
|
+
requestShutdown: (request) => shutdownCoordinator.request(request),
|
|
71
|
+
});
|
|
72
|
+
controlServer = await startLocalControlServer(controlService);
|
|
73
|
+
// Persist runtime state so `agentlink status` can discover the exact process.
|
|
38
74
|
saveRuntimeState({
|
|
39
75
|
pid: process.pid,
|
|
40
76
|
sessionId,
|
|
@@ -42,10 +78,11 @@ export async function start(config, daemon = false, pidFile, launchSpecPath, lau
|
|
|
42
78
|
server: config.server,
|
|
43
79
|
name: config.name,
|
|
44
80
|
dir: config.dir,
|
|
45
|
-
startedAt
|
|
81
|
+
startedAt,
|
|
46
82
|
agent: config.agent,
|
|
47
83
|
launchSpecPath,
|
|
48
84
|
launchSpecDigest,
|
|
85
|
+
controlPlane: controlServer.discovery,
|
|
49
86
|
});
|
|
50
87
|
console.log('');
|
|
51
88
|
console.log(`[AgentLink] Session URL: ${highlightUrl(sessionUrl)}`);
|
|
@@ -58,15 +95,22 @@ export async function start(config, daemon = false, pidFile, launchSpecPath, lau
|
|
|
58
95
|
writePidFile(pidFile, { pid: process.pid, sessionUrl, password: config.password });
|
|
59
96
|
}
|
|
60
97
|
// Keep process alive, handle graceful shutdown
|
|
61
|
-
const shutdown = () => {
|
|
98
|
+
const shutdown = (signal) => {
|
|
62
99
|
console.log('\n[AgentLink] Shutting down...');
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
if (shutdownCoordinator.status().phase !== 'idle')
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
shutdownCoordinator.request({
|
|
104
|
+
reason: 'service',
|
|
105
|
+
requestId: `signal-${signal}-${Date.now()}`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('[AgentLink] Failed to schedule shutdown:', error);
|
|
110
|
+
}
|
|
67
111
|
};
|
|
68
|
-
process.on('SIGINT', shutdown);
|
|
69
|
-
process.on('SIGTERM', shutdown);
|
|
112
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
113
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
70
114
|
}
|
|
71
115
|
catch (err) {
|
|
72
116
|
const msg = err.message || String(err);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAGvC,oFAAoF;AACpF,MAAM,UAAU,mBAAmB;IACjC,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAU,EAAE,CAAC;QACjH,MAAM,IAAI,GAAI,OAAe,CAAC,MAAM,CAAC,CAAC;QACrC,OAAe,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3G,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;IAAE,mBAAmB,EAAE,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RuntimeState } from './config.js';
|
|
2
|
+
import type { LocalControlStatus, ShutdownAccepted, ShutdownReason } from './local-control.js';
|
|
3
|
+
export declare class LocalControlUnavailableError extends Error {
|
|
4
|
+
constructor(message: string, options?: ErrorOptions);
|
|
5
|
+
}
|
|
6
|
+
export declare class LocalControlResponseError extends Error {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly code: string;
|
|
9
|
+
constructor(status: number, code: string, message: string);
|
|
10
|
+
}
|
|
11
|
+
export declare function getLocalControlStatus(state: RuntimeState, timeoutMs?: number): Promise<LocalControlStatus>;
|
|
12
|
+
export declare function requestLocalControlShutdown(state: RuntimeState, reason: ShutdownReason, options?: {
|
|
13
|
+
jobId?: string;
|
|
14
|
+
targetVersion?: string;
|
|
15
|
+
}, timeoutMs?: number): Promise<ShutdownAccepted>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { request as httpRequest } from 'http';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
export class LocalControlUnavailableError extends Error {
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.name = 'LocalControlUnavailableError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class LocalControlResponseError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
code;
|
|
12
|
+
constructor(status, code, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = 'LocalControlResponseError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function callControl(state, method, path, body, timeoutMs = 1500) {
|
|
20
|
+
const discovery = state.controlPlane;
|
|
21
|
+
if (!discovery || discovery.apiVersion !== 1 || discovery.host !== '127.0.0.1') {
|
|
22
|
+
throw new LocalControlUnavailableError('Agent does not advertise Local Control Plane v1');
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isSafeInteger(discovery.port) || discovery.port < 1 || discovery.port > 65535 || !discovery.token) {
|
|
25
|
+
throw new LocalControlUnavailableError('Agent local control discovery is invalid');
|
|
26
|
+
}
|
|
27
|
+
const requestId = randomUUID();
|
|
28
|
+
const payload = body ? JSON.stringify({ ...body, requestId }) : undefined;
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const request = httpRequest({
|
|
31
|
+
host: discovery.host,
|
|
32
|
+
port: discovery.port,
|
|
33
|
+
path,
|
|
34
|
+
method,
|
|
35
|
+
headers: {
|
|
36
|
+
Host: `${discovery.host}:${discovery.port}`,
|
|
37
|
+
Authorization: `Bearer ${discovery.token}`,
|
|
38
|
+
'X-Request-Id': requestId,
|
|
39
|
+
...(payload ? {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
42
|
+
'Idempotency-Key': requestId,
|
|
43
|
+
} : {}),
|
|
44
|
+
},
|
|
45
|
+
}, (response) => {
|
|
46
|
+
const chunks = [];
|
|
47
|
+
let size = 0;
|
|
48
|
+
response.on('data', (chunk) => {
|
|
49
|
+
size += chunk.length;
|
|
50
|
+
if (size > 64 * 1024) {
|
|
51
|
+
request.destroy(new Error('Local control response exceeded 64 KiB'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
});
|
|
56
|
+
response.on('end', () => {
|
|
57
|
+
let envelope;
|
|
58
|
+
try {
|
|
59
|
+
envelope = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
reject(new LocalControlResponseError(502, 'INVALID_RESPONSE', 'Local control returned invalid JSON'));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300 || !envelope.ok) {
|
|
66
|
+
reject(new LocalControlResponseError(response.statusCode ?? 500, envelope.error?.code ?? 'CONTROL_ERROR', envelope.error?.message ?? 'Local control request failed'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (envelope.data === undefined) {
|
|
70
|
+
reject(new LocalControlResponseError(502, 'INVALID_RESPONSE', 'Local control response did not include data'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(envelope.data);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
request.setTimeout(timeoutMs, () => {
|
|
77
|
+
request.destroy(new Error(`Local control request timed out after ${timeoutMs}ms`));
|
|
78
|
+
});
|
|
79
|
+
request.on('error', (error) => {
|
|
80
|
+
reject(new LocalControlUnavailableError(`Local control is unavailable: ${error.message}`, { cause: error }));
|
|
81
|
+
});
|
|
82
|
+
if (payload)
|
|
83
|
+
request.write(payload);
|
|
84
|
+
request.end();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export function getLocalControlStatus(state, timeoutMs) {
|
|
88
|
+
return callControl(state, 'GET', '/v1/status', undefined, timeoutMs);
|
|
89
|
+
}
|
|
90
|
+
export function requestLocalControlShutdown(state, reason, options = {}, timeoutMs) {
|
|
91
|
+
if (!state.controlPlane?.capabilities.includes('lifecycle.shutdown')) {
|
|
92
|
+
throw new LocalControlUnavailableError('Agent does not advertise lifecycle.shutdown');
|
|
93
|
+
}
|
|
94
|
+
return callControl(state, 'POST', '/v1/lifecycle/shutdown', {
|
|
95
|
+
reason,
|
|
96
|
+
expectedPid: state.pid,
|
|
97
|
+
...options,
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=local-control-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-control-client.js","sourceRoot":"","sources":["../src/local-control-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type LocalControlService } from './local-control.js';
|
|
2
|
+
import type { ControlPlaneDiscovery } from './config.js';
|
|
3
|
+
export interface LocalControlServer {
|
|
4
|
+
discovery: ControlPlaneDiscovery;
|
|
5
|
+
close(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function startLocalControlServer(service: LocalControlService): Promise<LocalControlServer>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { randomBytes, randomUUID, timingSafeEqual } from 'crypto';
|
|
3
|
+
import { LOCAL_CONTROL_API_VERSION, LocalControlError, } from './local-control.js';
|
|
4
|
+
const HOST = '127.0.0.1';
|
|
5
|
+
const MAX_BODY_BYTES = 16 * 1024;
|
|
6
|
+
const MAX_AUTH_FAILURES_PER_MINUTE = 30;
|
|
7
|
+
const VALID_REASONS = new Set(['upgrade', 'stop', 'service']);
|
|
8
|
+
function writeJson(response, status, envelope) {
|
|
9
|
+
const payload = JSON.stringify(envelope);
|
|
10
|
+
response.writeHead(status, {
|
|
11
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
12
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
13
|
+
'Cache-Control': 'no-store',
|
|
14
|
+
'X-Content-Type-Options': 'nosniff',
|
|
15
|
+
});
|
|
16
|
+
response.end(payload);
|
|
17
|
+
}
|
|
18
|
+
function safeTokenEqual(actual, expected) {
|
|
19
|
+
const actualBuffer = Buffer.from(actual);
|
|
20
|
+
const expectedBuffer = Buffer.from(expected);
|
|
21
|
+
return actualBuffer.length === expectedBuffer.length
|
|
22
|
+
&& timingSafeEqual(actualBuffer, expectedBuffer);
|
|
23
|
+
}
|
|
24
|
+
function isAllowedHost(host, port) {
|
|
25
|
+
if (!host)
|
|
26
|
+
return false;
|
|
27
|
+
const normalized = host.toLowerCase();
|
|
28
|
+
return normalized === HOST
|
|
29
|
+
|| normalized === 'localhost'
|
|
30
|
+
|| normalized === `${HOST}:${port}`
|
|
31
|
+
|| normalized === `localhost:${port}`;
|
|
32
|
+
}
|
|
33
|
+
async function readJsonBody(request) {
|
|
34
|
+
const contentType = request.headers['content-type'] ?? '';
|
|
35
|
+
if (!contentType.toLowerCase().startsWith('application/json')) {
|
|
36
|
+
throw new LocalControlError(415, 'UNSUPPORTED_MEDIA_TYPE', 'Content-Type must be application/json');
|
|
37
|
+
}
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let size = 0;
|
|
40
|
+
for await (const chunk of request) {
|
|
41
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
42
|
+
size += buffer.length;
|
|
43
|
+
if (size > MAX_BODY_BYTES) {
|
|
44
|
+
throw new LocalControlError(413, 'BODY_TOO_LARGE', `Request body exceeds ${MAX_BODY_BYTES} bytes`);
|
|
45
|
+
}
|
|
46
|
+
chunks.push(buffer);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new LocalControlError(400, 'INVALID_JSON', 'Request body must contain valid JSON');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function parseShutdownRequest(value, headerRequestId) {
|
|
56
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
57
|
+
throw new LocalControlError(400, 'INVALID_REQUEST', 'Shutdown request must be a JSON object');
|
|
58
|
+
}
|
|
59
|
+
const body = value;
|
|
60
|
+
const allowedKeys = new Set(['reason', 'requestId', 'expectedPid', 'jobId', 'targetVersion']);
|
|
61
|
+
const unknownKey = Object.keys(body).find((key) => !allowedKeys.has(key));
|
|
62
|
+
if (unknownKey) {
|
|
63
|
+
throw new LocalControlError(400, 'UNKNOWN_FIELD', `Unknown shutdown field: ${unknownKey}`);
|
|
64
|
+
}
|
|
65
|
+
if (typeof body.reason !== 'string' || !VALID_REASONS.has(body.reason)) {
|
|
66
|
+
throw new LocalControlError(400, 'INVALID_REASON', 'reason must be upgrade, stop, or service');
|
|
67
|
+
}
|
|
68
|
+
const requestId = body.requestId ?? headerRequestId;
|
|
69
|
+
if (typeof requestId !== 'string' || requestId.length < 1 || requestId.length > 128) {
|
|
70
|
+
throw new LocalControlError(400, 'INVALID_REQUEST_ID', 'requestId must be a non-empty string up to 128 characters');
|
|
71
|
+
}
|
|
72
|
+
if (body.expectedPid !== undefined
|
|
73
|
+
&& (!Number.isSafeInteger(body.expectedPid) || body.expectedPid <= 0)) {
|
|
74
|
+
throw new LocalControlError(400, 'INVALID_EXPECTED_PID', 'expectedPid must be a positive integer');
|
|
75
|
+
}
|
|
76
|
+
for (const field of ['jobId', 'targetVersion']) {
|
|
77
|
+
if (body[field] !== undefined
|
|
78
|
+
&& (typeof body[field] !== 'string' || body[field].length < 1 || body[field].length > 128)) {
|
|
79
|
+
throw new LocalControlError(400, 'INVALID_REQUEST', `${field} must be a non-empty string up to 128 characters`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
reason: body.reason,
|
|
84
|
+
requestId,
|
|
85
|
+
expectedPid: body.expectedPid,
|
|
86
|
+
jobId: body.jobId,
|
|
87
|
+
targetVersion: body.targetVersion,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function getRequestId(request) {
|
|
91
|
+
const idempotencyKey = request.headers['idempotency-key'];
|
|
92
|
+
const headerRequestId = request.headers['x-request-id'];
|
|
93
|
+
const candidate = Array.isArray(idempotencyKey) ? idempotencyKey[0] : idempotencyKey
|
|
94
|
+
?? (Array.isArray(headerRequestId) ? headerRequestId[0] : headerRequestId);
|
|
95
|
+
return typeof candidate === 'string' && candidate.length > 0 && candidate.length <= 128
|
|
96
|
+
? candidate
|
|
97
|
+
: randomUUID();
|
|
98
|
+
}
|
|
99
|
+
export async function startLocalControlServer(service) {
|
|
100
|
+
const token = randomBytes(32).toString('base64url');
|
|
101
|
+
const sockets = new Set();
|
|
102
|
+
let authWindowStartedAt = Date.now();
|
|
103
|
+
let authFailures = 0;
|
|
104
|
+
let port = 0;
|
|
105
|
+
const server = createServer(async (request, response) => {
|
|
106
|
+
const requestId = getRequestId(request);
|
|
107
|
+
try {
|
|
108
|
+
if (!isAllowedHost(request.headers.host, port)) {
|
|
109
|
+
throw new LocalControlError(400, 'INVALID_HOST', 'Host header must target the local control endpoint');
|
|
110
|
+
}
|
|
111
|
+
if (request.headers.origin !== undefined) {
|
|
112
|
+
throw new LocalControlError(403, 'BROWSER_REQUEST_REJECTED', 'Browser-originated requests are not allowed');
|
|
113
|
+
}
|
|
114
|
+
if (request.method === 'OPTIONS') {
|
|
115
|
+
throw new LocalControlError(405, 'METHOD_NOT_ALLOWED', 'CORS preflight is not supported');
|
|
116
|
+
}
|
|
117
|
+
if (Date.now() - authWindowStartedAt >= 60_000) {
|
|
118
|
+
authWindowStartedAt = Date.now();
|
|
119
|
+
authFailures = 0;
|
|
120
|
+
}
|
|
121
|
+
const authorization = request.headers.authorization ?? '';
|
|
122
|
+
const suppliedToken = authorization.startsWith('Bearer ') ? authorization.slice(7) : '';
|
|
123
|
+
if (!safeTokenEqual(suppliedToken, token)) {
|
|
124
|
+
authFailures++;
|
|
125
|
+
if (authFailures > MAX_AUTH_FAILURES_PER_MINUTE) {
|
|
126
|
+
throw new LocalControlError(429, 'AUTH_RATE_LIMITED', 'Too many authentication failures');
|
|
127
|
+
}
|
|
128
|
+
throw new LocalControlError(401, 'UNAUTHORIZED', 'A valid bearer token is required');
|
|
129
|
+
}
|
|
130
|
+
if (request.method === 'GET' && request.url === '/v1/health') {
|
|
131
|
+
writeJson(response, 200, { ok: true, requestId, data: service.health() });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (request.method === 'GET' && request.url === '/v1/status') {
|
|
135
|
+
writeJson(response, 200, { ok: true, requestId, data: service.status() });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (request.method === 'POST' && request.url === '/v1/lifecycle/shutdown') {
|
|
139
|
+
const body = await readJsonBody(request);
|
|
140
|
+
const shutdownRequest = parseShutdownRequest(body, requestId);
|
|
141
|
+
const accepted = service.shutdown(shutdownRequest);
|
|
142
|
+
writeJson(response, 202, { ok: true, requestId: shutdownRequest.requestId, data: accepted });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
throw new LocalControlError(404, 'NOT_FOUND', 'Unknown local control route');
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const controlError = error instanceof LocalControlError
|
|
149
|
+
? error
|
|
150
|
+
: new LocalControlError(500, 'INTERNAL_ERROR', 'Local control request failed');
|
|
151
|
+
console.warn(`[Control] ${request.method} ${request.url} -> ${controlError.status} ${controlError.code} (requestId=${requestId})`);
|
|
152
|
+
if (!response.headersSent) {
|
|
153
|
+
writeJson(response, controlError.status, {
|
|
154
|
+
ok: false,
|
|
155
|
+
requestId,
|
|
156
|
+
error: { code: controlError.code, message: controlError.message },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
response.destroy();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
server.on('connection', (socket) => {
|
|
165
|
+
sockets.add(socket);
|
|
166
|
+
socket.on('close', () => sockets.delete(socket));
|
|
167
|
+
});
|
|
168
|
+
await new Promise((resolve, reject) => {
|
|
169
|
+
server.once('error', reject);
|
|
170
|
+
server.listen(0, HOST, () => {
|
|
171
|
+
server.off('error', reject);
|
|
172
|
+
resolve();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
const address = server.address();
|
|
176
|
+
if (!address || typeof address === 'string') {
|
|
177
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
178
|
+
throw new Error('Local control server did not receive a TCP address');
|
|
179
|
+
}
|
|
180
|
+
port = address.port;
|
|
181
|
+
return {
|
|
182
|
+
discovery: {
|
|
183
|
+
apiVersion: LOCAL_CONTROL_API_VERSION,
|
|
184
|
+
host: HOST,
|
|
185
|
+
port,
|
|
186
|
+
token,
|
|
187
|
+
capabilities: [...service.health().capabilities],
|
|
188
|
+
},
|
|
189
|
+
async close() {
|
|
190
|
+
if (!server.listening)
|
|
191
|
+
return;
|
|
192
|
+
await new Promise((resolve) => {
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
for (const socket of sockets)
|
|
195
|
+
socket.destroy();
|
|
196
|
+
resolve();
|
|
197
|
+
}, 1000);
|
|
198
|
+
timer.unref();
|
|
199
|
+
server.close(() => {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
resolve();
|
|
202
|
+
});
|
|
203
|
+
server.closeIdleConnections?.();
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=local-control-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-control-http.js","sourceRoot":"","sources":["../src/local-control-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA0D,MAAM,MAAM,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAElE,OAAO,EACL,yBAAyB,EACzB,iBAAiB,GAIlB,MAAM,oBAAoB,CAAC;AAG5B,MAAM,IAAI,GAAG,WAAW,CAAC;AACzB,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC;AACjC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AACxC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAc9E,SAAS,SAAS,CAAC,QAAwB,EAAE,MAAc,EAAE,QAA0B;IACrF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE;QACzB,cAAc,EAAE,iCAAiC;QACjD,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;QAC5C,eAAe,EAAE,UAAU;QAC3B,wBAAwB,EAAE,SAAS;KACpC,CAAC,CAAC;IACH,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,QAAgB;IACtD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,YAAY,CAAC,MAAM,KAAK,cAAc,CAAC,MAAM;WAC/C,eAAe,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,aAAa,CAAC,IAAwB,EAAE,IAAY;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,UAAU,KAAK,IAAI;WACrB,UAAU,KAAK,WAAW;WAC1B,UAAU,KAAK,GAAG,IAAI,IAAI,IAAI,EAAE;WAChC,UAAU,KAAK,aAAa,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,OAAwB;IAClD,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC1D,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,wBAAwB,EAAE,uCAAuC,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC;QACtB,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;YAC1B,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,EAAE,wBAAwB,cAAc,QAAQ,CAAC,CAAC;QACrG,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,sCAAsC,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAc,EAAE,eAAuB;IACnE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,wCAAwC,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;IAC9F,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,2BAA2B,UAAU,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAwB,CAAC,EAAE,CAAC;QACzF,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,EAAE,0CAA0C,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,eAAe,CAAC;IACpD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACpF,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,oBAAoB,EAAE,2DAA2D,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;WAC3B,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,IAAK,IAAI,CAAC,WAAsB,IAAI,CAAC,CAAC,EAAE,CAAC;QACtF,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,sBAAsB,EAAE,wCAAwC,CAAC,CAAC;IACrG,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,eAAe,CAAU,EAAE,CAAC;QACxD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS;eACtB,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;YAC/F,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,GAAG,KAAK,kDAAkD,CAAC,CAAC;QAClH,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAwB;QACrC,SAAS;QACT,WAAW,EAAE,IAAI,CAAC,WAAiC;QACnD,KAAK,EAAE,IAAI,CAAC,KAA2B;QACvC,aAAa,EAAE,IAAI,CAAC,aAAmC;KACxD,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,OAAwB;IAC5C,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc;WAC/E,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;IAC7E,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,IAAI,GAAG;QACrF,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,UAAU,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAA4B;IACxE,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACrC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,MAAM,MAAM,GAAW,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QAC9D,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;gBAC/C,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,oDAAoD,CAAC,CAAC;YACzG,CAAC;YACD,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzC,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,0BAA0B,EAAE,6CAA6C,CAAC,CAAC;YAC9G,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACjC,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,oBAAoB,EAAE,iCAAiC,CAAC,CAAC;YAC5F,CAAC;YAED,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,mBAAmB,IAAI,MAAM,EAAE,CAAC;gBAC/C,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACjC,YAAY,GAAG,CAAC,CAAC;YACnB,CAAC;YACD,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;YAC1D,MAAM,aAAa,GAAG,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACxF,IAAI,CAAC,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC1C,YAAY,EAAE,CAAC;gBACf,IAAI,YAAY,GAAG,4BAA4B,EAAE,CAAC;oBAChD,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,mBAAmB,EAAE,kCAAkC,CAAC,CAAC;gBAC5F,CAAC;gBACD,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,kCAAkC,CAAC,CAAC;YACvF,CAAC;YAED,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;gBAC7D,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC1E,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;gBAC7D,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC1E,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,KAAK,wBAAwB,EAAE,CAAC;gBAC1E,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;gBACzC,MAAM,eAAe,GAAG,oBAAoB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;gBACnD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;gBAC7F,OAAO;YACT,CAAC;YAED,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,WAAW,EAAE,6BAA6B,CAAC,CAAC;QAC/E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,iBAAiB;gBACrD,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,IAAI,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,EAAE,8BAA8B,CAAC,CAAC;YACjF,OAAO,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,OAAO,YAAY,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,eAAe,SAAS,GAAG,CAAC,CAAC;YACnI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAC1B,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE;oBACvC,EAAE,EAAE,KAAK;oBACT,SAAS;oBACT,KAAK,EAAE,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE;iBAClE,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;QACjC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE;YAC1B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5B,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpB,OAAO;QACL,SAAS,EAAE;YACT,UAAU,EAAE,yBAAyB;YACrC,IAAI,EAAE,IAAI;YACV,IAAI;YACJ,KAAK;YACL,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC;SACjD;QACD,KAAK,CAAC,KAAK;YACT,IAAI,CAAC,MAAM,CAAC,SAAS;gBAAE,OAAO;YAC9B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC5B,KAAK,MAAM,MAAM,IAAI,OAAO;wBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC/C,OAAO,EAAE,CAAC;gBACZ,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;oBAChB,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;gBACH,MAAM,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAClC,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AgentConfig } from './config.js';
|
|
2
|
+
export declare const LOCAL_CONTROL_API_VERSION = 1;
|
|
3
|
+
export declare const LOCAL_CONTROL_CAPABILITIES: readonly ["health.read", "status.read", "lifecycle.shutdown"];
|
|
4
|
+
export type LocalControlCapability = typeof LOCAL_CONTROL_CAPABILITIES[number];
|
|
5
|
+
export type ShutdownReason = 'upgrade' | 'stop' | 'service';
|
|
6
|
+
export type ShutdownPhase = 'idle' | 'accepted' | 'shutting-down' | 'completed' | 'failed';
|
|
7
|
+
export interface ShutdownRequest {
|
|
8
|
+
reason: ShutdownReason;
|
|
9
|
+
requestId: string;
|
|
10
|
+
expectedPid?: number;
|
|
11
|
+
jobId?: string;
|
|
12
|
+
targetVersion?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ShutdownAccepted {
|
|
15
|
+
requestId: string;
|
|
16
|
+
reason: ShutdownReason;
|
|
17
|
+
acceptedAt: string;
|
|
18
|
+
phase: ShutdownPhase;
|
|
19
|
+
}
|
|
20
|
+
export interface ConnectionStatus {
|
|
21
|
+
websocketConnected: boolean;
|
|
22
|
+
relayReady: boolean;
|
|
23
|
+
acceptingWork: boolean;
|
|
24
|
+
sessionId: string | null;
|
|
25
|
+
workDir: string;
|
|
26
|
+
backend: string;
|
|
27
|
+
conversations: number;
|
|
28
|
+
}
|
|
29
|
+
export interface LocalControlStatus {
|
|
30
|
+
apiVersion: number;
|
|
31
|
+
capabilities: readonly LocalControlCapability[];
|
|
32
|
+
pid: number;
|
|
33
|
+
version: string;
|
|
34
|
+
startedAt: string;
|
|
35
|
+
uptimeSeconds: number;
|
|
36
|
+
shutdown: {
|
|
37
|
+
phase: ShutdownPhase;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
reason?: ShutdownReason;
|
|
40
|
+
};
|
|
41
|
+
connection: ConnectionStatus;
|
|
42
|
+
config: {
|
|
43
|
+
server: string;
|
|
44
|
+
dir: string;
|
|
45
|
+
name: string;
|
|
46
|
+
agent: string;
|
|
47
|
+
entra: boolean;
|
|
48
|
+
autoStart: boolean | null;
|
|
49
|
+
passwordConfigured: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare class LocalControlError extends Error {
|
|
53
|
+
readonly status: number;
|
|
54
|
+
readonly code: string;
|
|
55
|
+
constructor(status: number, code: string, message: string);
|
|
56
|
+
}
|
|
57
|
+
export interface LocalControlService {
|
|
58
|
+
health(): {
|
|
59
|
+
status: 'ok';
|
|
60
|
+
apiVersion: number;
|
|
61
|
+
capabilities: readonly LocalControlCapability[];
|
|
62
|
+
};
|
|
63
|
+
status(): LocalControlStatus;
|
|
64
|
+
shutdown(request: ShutdownRequest): ShutdownAccepted;
|
|
65
|
+
}
|
|
66
|
+
export interface LocalControlServiceDeps {
|
|
67
|
+
version: string;
|
|
68
|
+
startedAt: string;
|
|
69
|
+
config: AgentConfig;
|
|
70
|
+
getConnectionStatus: () => ConnectionStatus;
|
|
71
|
+
getShutdownStatus: () => LocalControlStatus['shutdown'];
|
|
72
|
+
requestShutdown: (request: ShutdownRequest) => ShutdownAccepted;
|
|
73
|
+
}
|
|
74
|
+
export declare function createLocalControlService(deps: LocalControlServiceDeps): LocalControlService;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const LOCAL_CONTROL_API_VERSION = 1;
|
|
2
|
+
export const LOCAL_CONTROL_CAPABILITIES = [
|
|
3
|
+
'health.read',
|
|
4
|
+
'status.read',
|
|
5
|
+
'lifecycle.shutdown',
|
|
6
|
+
];
|
|
7
|
+
export class LocalControlError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
code;
|
|
10
|
+
constructor(status, code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = 'LocalControlError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createLocalControlService(deps) {
|
|
18
|
+
return {
|
|
19
|
+
health() {
|
|
20
|
+
return {
|
|
21
|
+
status: 'ok',
|
|
22
|
+
apiVersion: LOCAL_CONTROL_API_VERSION,
|
|
23
|
+
capabilities: LOCAL_CONTROL_CAPABILITIES,
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
status() {
|
|
27
|
+
return {
|
|
28
|
+
apiVersion: LOCAL_CONTROL_API_VERSION,
|
|
29
|
+
capabilities: LOCAL_CONTROL_CAPABILITIES,
|
|
30
|
+
pid: process.pid,
|
|
31
|
+
version: deps.version,
|
|
32
|
+
startedAt: deps.startedAt,
|
|
33
|
+
uptimeSeconds: Math.max(0, Math.floor(process.uptime())),
|
|
34
|
+
shutdown: deps.getShutdownStatus(),
|
|
35
|
+
connection: deps.getConnectionStatus(),
|
|
36
|
+
config: {
|
|
37
|
+
server: deps.config.server,
|
|
38
|
+
dir: deps.config.dir,
|
|
39
|
+
name: deps.config.name,
|
|
40
|
+
agent: deps.config.agent ?? 'claude',
|
|
41
|
+
entra: deps.config.entra === true,
|
|
42
|
+
autoStart: deps.config.autoStart ?? null,
|
|
43
|
+
passwordConfigured: Boolean(deps.config.password),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
shutdown(request) {
|
|
48
|
+
if (request.expectedPid !== undefined && request.expectedPid !== process.pid) {
|
|
49
|
+
throw new LocalControlError(409, 'PID_MISMATCH', `Control request expected PID ${request.expectedPid}, but agent PID is ${process.pid}`);
|
|
50
|
+
}
|
|
51
|
+
return deps.requestShutdown(request);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=local-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-control.js","sourceRoot":"","sources":["../src/local-control.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,aAAa;IACb,aAAa;IACb,oBAAoB;CACZ,CAAC;AAuDX,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAExB;IACA;IAFlB,YACkB,MAAc,EACd,IAAY,EAC5B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAI5B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAiBD,MAAM,UAAU,yBAAyB,CAAC,IAA6B;IACrE,OAAO;QACL,MAAM;YACJ,OAAO;gBACL,MAAM,EAAE,IAAI;gBACZ,UAAU,EAAE,yBAAyB;gBACrC,YAAY,EAAE,0BAA0B;aACzC,CAAC;QACJ,CAAC;QAED,MAAM;YACJ,OAAO;gBACL,UAAU,EAAE,yBAAyB;gBACrC,YAAY,EAAE,0BAA0B;gBACxC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBACxD,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE;gBAClC,UAAU,EAAE,IAAI,CAAC,mBAAmB,EAAE;gBACtC,MAAM,EAAE;oBACN,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;oBAC1B,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG;oBACpB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;oBACtB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,QAAQ;oBACpC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;oBACjC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI;oBACxC,kBAAkB,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;iBAClD;aACF,CAAC;QACJ,CAAC;QAED,QAAQ,CAAC,OAAO;YACd,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;gBAC7E,MAAM,IAAI,iBAAiB,CACzB,GAAG,EACH,cAAc,EACd,gCAAgC,OAAO,CAAC,WAAW,sBAAsB,OAAO,CAAC,GAAG,EAAE,CACvF,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RuntimeState } from './config.js';
|
|
2
|
+
import type { ShutdownReason } from './local-control.js';
|
|
3
|
+
export type StopMode = 'control-plane' | 'legacy-force-kill' | 'control-timeout-force-kill';
|
|
4
|
+
export interface StopAgentOptions {
|
|
5
|
+
controlTimeoutMs?: number;
|
|
6
|
+
exitTimeoutMs?: number;
|
|
7
|
+
afterControlAccepted?: () => void | Promise<void>;
|
|
8
|
+
beforeForceKill?: () => void | Promise<void>;
|
|
9
|
+
isAlive?: (pid: number) => boolean;
|
|
10
|
+
forceKill?: (pid: number) => boolean;
|
|
11
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
12
|
+
shutdownMetadata?: {
|
|
13
|
+
jobId?: string;
|
|
14
|
+
targetVersion?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare function stopAgentProcess(state: RuntimeState, reason: ShutdownReason, options?: StopAgentOptions): Promise<StopMode>;
|