@xenosystem/agent-interface-client 0.1.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/LICENSE +67 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/dist/local/attach.d.ts +116 -0
- package/dist/local/attach.d.ts.map +1 -0
- package/dist/local/attach.js +187 -0
- package/dist/local/attach.js.map +1 -0
- package/dist/local/index.d.ts +4 -0
- package/dist/local/index.d.ts.map +1 -0
- package/dist/local/index.js +23 -0
- package/dist/local/index.js.map +1 -0
- package/dist/local/lease.d.ts +50 -0
- package/dist/local/lease.d.ts.map +1 -0
- package/dist/local/lease.js +120 -0
- package/dist/local/lease.js.map +1 -0
- package/dist/local/transport.d.ts +100 -0
- package/dist/local/transport.d.ts.map +1 -0
- package/dist/local/transport.js +276 -0
- package/dist/local/transport.js.map +1 -0
- package/dist/localAttach.d.ts +116 -0
- package/dist/localAttach.d.ts.map +1 -0
- package/dist/localAttach.js +187 -0
- package/dist/localAttach.js.map +1 -0
- package/dist/localLease.d.ts +50 -0
- package/dist/localLease.d.ts.map +1 -0
- package/dist/localLease.js +120 -0
- package/dist/localLease.js.map +1 -0
- package/dist/localTransport.d.ts +100 -0
- package/dist/localTransport.d.ts.map +1 -0
- package/dist/localTransport.js +276 -0
- package/dist/localTransport.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Agent host wire protocol — framing plus the CLIENT transport.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 This lives in the CLIENT package, not in `contract`, and the reason is load-bearing:
|
|
5
|
+
* `contract` is Node-free and must stay that way. A renderer reaches `contract/index.js`, and
|
|
6
|
+
* Vite fails a browser build on any `node:` builtin ("stat" is not exported by
|
|
7
|
+
* "__vite-browser-external") — `browserBarrelPurity.test.ts` pins that. This module needs
|
|
8
|
+
* `node:net`, `node:crypto` and `node:fs/promises`, so it cannot go there.
|
|
9
|
+
*
|
|
10
|
+
* It is also why the split is client-package-and-below rather than contract-and-below: a surface
|
|
11
|
+
* that wants to ATTACH to a local host needs Node anyway.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
16
|
+
import { chmod, mkdir, unlink } from 'node:fs/promises';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { createConnection, createServer, } from 'node:net';
|
|
19
|
+
import { AgentHostProtocolError, XENO_AGENT_HOST_PROTOCOL_VERSION, isAgentHostMethod, isAgentHostEvent, } from '@xenosystem/agent-interface-contract';
|
|
20
|
+
export const LOCAL_AGENT_HOST_DEFAULT_MAX_FRAME_BYTES = 32 * 1024 * 1024;
|
|
21
|
+
export const LOCAL_AGENT_HOST_ABSOLUTE_MAX_FRAME_BYTES = 64 * 1024 * 1024;
|
|
22
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
23
|
+
/** Node-side AgentHostTransport implementation for a local host authority. */
|
|
24
|
+
export class LocalAgentHostRpcClientTransport {
|
|
25
|
+
endpoint;
|
|
26
|
+
transportToken;
|
|
27
|
+
protocolVersion;
|
|
28
|
+
maxFrameBytes;
|
|
29
|
+
requestTimeoutMs;
|
|
30
|
+
listeners = new Set();
|
|
31
|
+
pending = new Map();
|
|
32
|
+
requestPrefix = randomBytes(12).toString('hex');
|
|
33
|
+
requestSequence = 0;
|
|
34
|
+
socket = null;
|
|
35
|
+
connectPromise = null;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
assertLocalRpcOptions(options.endpoint, options.transportToken);
|
|
38
|
+
this.endpoint = options.endpoint;
|
|
39
|
+
this.transportToken = options.transportToken;
|
|
40
|
+
this.protocolVersion = positiveInteger(options.protocolVersion) || XENO_AGENT_HOST_PROTOCOL_VERSION;
|
|
41
|
+
this.maxFrameBytes = resolveMaxFrameBytes(options.maxFrameBytes);
|
|
42
|
+
this.requestTimeoutMs = positiveInteger(options.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
43
|
+
}
|
|
44
|
+
get connected() {
|
|
45
|
+
return this.socket !== null && !this.socket.destroyed;
|
|
46
|
+
}
|
|
47
|
+
async connect() {
|
|
48
|
+
if (this.connected)
|
|
49
|
+
return;
|
|
50
|
+
if (this.connectPromise)
|
|
51
|
+
return this.connectPromise;
|
|
52
|
+
this.connectPromise = new Promise((resolveConnect, rejectConnect) => {
|
|
53
|
+
const socket = createConnection(this.endpoint);
|
|
54
|
+
const decoder = new JsonLineDecoder(this.maxFrameBytes, (frame) => this.receive(frame));
|
|
55
|
+
const failConnect = (error) => {
|
|
56
|
+
socket.off('connect', connected);
|
|
57
|
+
this.socket = null;
|
|
58
|
+
rejectConnect(error);
|
|
59
|
+
};
|
|
60
|
+
const connected = () => {
|
|
61
|
+
socket.off('error', failConnect);
|
|
62
|
+
socket.setNoDelay(true);
|
|
63
|
+
this.socket = socket;
|
|
64
|
+
resolveConnect();
|
|
65
|
+
};
|
|
66
|
+
socket.once('error', failConnect);
|
|
67
|
+
socket.once('connect', connected);
|
|
68
|
+
socket.on('data', (chunk) => {
|
|
69
|
+
try {
|
|
70
|
+
decoder.push(chunk);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.disconnect(asError(error));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
socket.on('error', (error) => this.disconnect(error));
|
|
77
|
+
socket.on('close', () => this.disconnect(new Error('Local Agent host connection closed.')));
|
|
78
|
+
}).finally(() => {
|
|
79
|
+
this.connectPromise = null;
|
|
80
|
+
});
|
|
81
|
+
return this.connectPromise;
|
|
82
|
+
}
|
|
83
|
+
async request(method, input) {
|
|
84
|
+
await this.connect();
|
|
85
|
+
const socket = this.socket;
|
|
86
|
+
if (!socket || socket.destroyed) {
|
|
87
|
+
throw new AgentHostProtocolError('not_connected', 'Local Agent host is not connected.');
|
|
88
|
+
}
|
|
89
|
+
const id = `${this.requestPrefix}:${++this.requestSequence}`;
|
|
90
|
+
const request = {
|
|
91
|
+
kind: 'request',
|
|
92
|
+
id,
|
|
93
|
+
protocolVersion: this.protocolVersion,
|
|
94
|
+
token: this.transportToken,
|
|
95
|
+
method,
|
|
96
|
+
input: structuredClone(input),
|
|
97
|
+
};
|
|
98
|
+
return new Promise((resolveRequest, rejectRequest) => {
|
|
99
|
+
const requestTimeoutMs = resolveRequestTimeout(method, input, this.requestTimeoutMs);
|
|
100
|
+
const timeout = setTimeout(() => {
|
|
101
|
+
this.pending.delete(id);
|
|
102
|
+
rejectRequest(new AgentHostProtocolError('not_connected', `Local Agent host request timed out: ${method}.`));
|
|
103
|
+
}, requestTimeoutMs);
|
|
104
|
+
this.pending.set(id, {
|
|
105
|
+
resolve: (output) => resolveRequest(output),
|
|
106
|
+
reject: rejectRequest,
|
|
107
|
+
timeout,
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
writeClientFrame(socket, request, this.maxFrameBytes);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
this.pending.delete(id);
|
|
115
|
+
rejectRequest(asError(error));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
subscribe(listener) {
|
|
120
|
+
this.listeners.add(listener);
|
|
121
|
+
return () => {
|
|
122
|
+
this.listeners.delete(listener);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
close() {
|
|
126
|
+
this.disconnect(new Error('Local Agent host client closed.'));
|
|
127
|
+
}
|
|
128
|
+
receive(frame) {
|
|
129
|
+
let value;
|
|
130
|
+
try {
|
|
131
|
+
value = JSON.parse(frame);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
this.disconnect(new AgentHostProtocolError('invalid_response', 'Local Agent host returned malformed JSON.'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (isLocalRpcEvent(value)) {
|
|
138
|
+
for (const listener of [...this.listeners])
|
|
139
|
+
listener(structuredClone(value.event));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!isLocalRpcResponse(value)) {
|
|
143
|
+
this.disconnect(new AgentHostProtocolError('invalid_response', 'Local Agent host returned an invalid RPC frame.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const pending = this.pending.get(value.id);
|
|
147
|
+
if (!pending)
|
|
148
|
+
return;
|
|
149
|
+
clearTimeout(pending.timeout);
|
|
150
|
+
this.pending.delete(value.id);
|
|
151
|
+
if (value.ok)
|
|
152
|
+
pending.resolve(structuredClone(value.output));
|
|
153
|
+
else
|
|
154
|
+
pending.reject(new AgentHostProtocolError(value.error.code, value.error.message));
|
|
155
|
+
}
|
|
156
|
+
disconnect(error) {
|
|
157
|
+
const socket = this.socket;
|
|
158
|
+
this.socket = null;
|
|
159
|
+
if (socket && !socket.destroyed)
|
|
160
|
+
socket.destroy();
|
|
161
|
+
for (const pending of this.pending.values()) {
|
|
162
|
+
clearTimeout(pending.timeout);
|
|
163
|
+
pending.reject(error);
|
|
164
|
+
}
|
|
165
|
+
this.pending.clear();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function resolveRequestTimeout(method, input, fallback) {
|
|
169
|
+
if (method !== 'workspace.command.run')
|
|
170
|
+
return fallback;
|
|
171
|
+
const requested = isRecord(input) && typeof input.timeoutMs === 'number' && Number.isFinite(input.timeoutMs)
|
|
172
|
+
? Math.max(1_000, Math.min(120_000, Math.floor(input.timeoutMs)))
|
|
173
|
+
: 20_000;
|
|
174
|
+
return Math.max(fallback, requested + 5_000);
|
|
175
|
+
}
|
|
176
|
+
export function resolveLocalAgentHostEndpoint(rootDirectory, scope = 'default') {
|
|
177
|
+
if (!rootDirectory.trim())
|
|
178
|
+
throw new Error('Local Agent host rootDirectory is required.');
|
|
179
|
+
if (!scope.trim())
|
|
180
|
+
throw new Error('Local Agent host endpoint scope is required.');
|
|
181
|
+
const digest = createHash('sha256')
|
|
182
|
+
.update(`${resolve(rootDirectory)}\0${scope}`, 'utf8')
|
|
183
|
+
.digest('hex')
|
|
184
|
+
.slice(0, 24);
|
|
185
|
+
return process.platform === 'win32'
|
|
186
|
+
? `\\\\.\\pipe\\xeno-agent-host-${digest}`
|
|
187
|
+
: join(rootDirectory, `agent-host-${digest}.sock`);
|
|
188
|
+
}
|
|
189
|
+
export class JsonLineDecoder {
|
|
190
|
+
maxFrameBytes;
|
|
191
|
+
onFrame;
|
|
192
|
+
buffer = Buffer.alloc(0);
|
|
193
|
+
constructor(maxFrameBytes, onFrame) {
|
|
194
|
+
this.maxFrameBytes = maxFrameBytes;
|
|
195
|
+
this.onFrame = onFrame;
|
|
196
|
+
}
|
|
197
|
+
push(chunk) {
|
|
198
|
+
this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
199
|
+
if (this.buffer.length > this.maxFrameBytes && this.buffer.indexOf(0x0a) < 0) {
|
|
200
|
+
throw new AgentHostProtocolError('invalid_response', 'Local Agent host RPC frame exceeds the size limit.');
|
|
201
|
+
}
|
|
202
|
+
let newline = this.buffer.indexOf(0x0a);
|
|
203
|
+
while (newline >= 0) {
|
|
204
|
+
if (newline > this.maxFrameBytes) {
|
|
205
|
+
throw new AgentHostProtocolError('invalid_response', 'Local Agent host RPC frame exceeds the size limit.');
|
|
206
|
+
}
|
|
207
|
+
const frameBuffer = this.buffer.subarray(0, newline);
|
|
208
|
+
this.buffer = this.buffer.subarray(newline + 1);
|
|
209
|
+
const frame = frameBuffer.toString('utf8').replace(/\r$/, '');
|
|
210
|
+
if (frame)
|
|
211
|
+
this.onFrame(frame);
|
|
212
|
+
newline = this.buffer.indexOf(0x0a);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function isLocalRpcResponse(value) {
|
|
217
|
+
if (!isRecord(value) || value.kind !== 'response' || typeof value.id !== 'string' || typeof value.ok !== 'boolean') {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (value.ok)
|
|
221
|
+
return 'output' in value;
|
|
222
|
+
return isRecord(value.error)
|
|
223
|
+
&& isProtocolErrorCode(value.error.code)
|
|
224
|
+
&& typeof value.error.message === 'string';
|
|
225
|
+
}
|
|
226
|
+
function isLocalRpcEvent(value) {
|
|
227
|
+
return isRecord(value) && value.kind === 'event' && isAgentHostEvent(value.event);
|
|
228
|
+
}
|
|
229
|
+
function writeClientFrame(socket, value, maxFrameBytes) {
|
|
230
|
+
writeFrame(socket, value, maxFrameBytes);
|
|
231
|
+
}
|
|
232
|
+
export function writeFrame(socket, value, maxFrameBytes) {
|
|
233
|
+
const encoded = `${JSON.stringify(value)}\n`;
|
|
234
|
+
if (Buffer.byteLength(encoded, 'utf8') > maxFrameBytes) {
|
|
235
|
+
throw new AgentHostProtocolError('invalid_response', 'Local Agent host RPC frame exceeds the size limit.');
|
|
236
|
+
}
|
|
237
|
+
socket.write(encoded, 'utf8');
|
|
238
|
+
}
|
|
239
|
+
export function safeTokenEquals(left, right) {
|
|
240
|
+
const leftBytes = Buffer.from(left, 'utf8');
|
|
241
|
+
const rightBytes = Buffer.from(right, 'utf8');
|
|
242
|
+
return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes);
|
|
243
|
+
}
|
|
244
|
+
export function assertLocalRpcOptions(endpoint, transportToken) {
|
|
245
|
+
if (!endpoint.trim())
|
|
246
|
+
throw new Error('Local Agent host endpoint is required.');
|
|
247
|
+
if (transportToken.length < 32)
|
|
248
|
+
throw new Error('Local Agent host transportToken must contain at least 32 characters.');
|
|
249
|
+
}
|
|
250
|
+
export function positiveInteger(value) {
|
|
251
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
252
|
+
}
|
|
253
|
+
export function resolveMaxFrameBytes(value) {
|
|
254
|
+
const requested = positiveInteger(value) || LOCAL_AGENT_HOST_DEFAULT_MAX_FRAME_BYTES;
|
|
255
|
+
if (requested > LOCAL_AGENT_HOST_ABSOLUTE_MAX_FRAME_BYTES) {
|
|
256
|
+
throw new Error(`Local Agent host RPC frame limit cannot exceed ${LOCAL_AGENT_HOST_ABSOLUTE_MAX_FRAME_BYTES} bytes.`);
|
|
257
|
+
}
|
|
258
|
+
return requested;
|
|
259
|
+
}
|
|
260
|
+
function isProtocolErrorCode(value) {
|
|
261
|
+
return value === 'invalid_handshake'
|
|
262
|
+
|| value === 'protocol_mismatch'
|
|
263
|
+
|| value === 'surface_mismatch'
|
|
264
|
+
|| value === 'not_connected'
|
|
265
|
+
|| value === 'invalid_response';
|
|
266
|
+
}
|
|
267
|
+
export function asError(error) {
|
|
268
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
269
|
+
}
|
|
270
|
+
export function isRecord(value) {
|
|
271
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
272
|
+
}
|
|
273
|
+
export function isNodeError(error, code) {
|
|
274
|
+
return error instanceof Error && 'code' in error && error.code === code;
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/local/transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attaching to a per-user Agent host — the SURFACE side of ADE §2.7.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 `attachToLocalAgentHost` attaches or refuses; it can never become the host. There is no
|
|
5
|
+
* `createHostTransport` parameter, so there is nothing it could construct even if asked — the
|
|
6
|
+
* dangerous shape (a CLI started while no app is running WINS the lease and is then expected to
|
|
7
|
+
* *be* the host) is unrepresentable rather than merely forbidden.
|
|
8
|
+
*
|
|
9
|
+
* That property is why this file can live in a publishable package at all: everything here is
|
|
10
|
+
* what a thin client legitimately needs, and nothing here can take ownership. Acquisition and the
|
|
11
|
+
* host authority stay in the private `packages/host`.
|
|
12
|
+
*/
|
|
13
|
+
import { type LocalAgentHostLeaseDescriptor } from './localLease.js';
|
|
14
|
+
import { LocalAgentHostRpcClientTransport } from './localTransport.js';
|
|
15
|
+
/**
|
|
16
|
+
* 🔴 STRUCTURAL, not the concrete server/lease classes.
|
|
17
|
+
*
|
|
18
|
+
* The session only ever closes the server and releases or heartbeats the lease — three calls.
|
|
19
|
+
* Typing those fields as the host's concrete `LocalAgentHostRpcServer` and `LocalAgentHostLease`
|
|
20
|
+
* dragged the whole server implementation into this file, which is what kept the session
|
|
21
|
+
* unpublishable. Naming the three calls it actually makes decouples it, and the host's real
|
|
22
|
+
* classes satisfy these by shape with no adapter.
|
|
23
|
+
*/
|
|
24
|
+
export interface ClosableAuthorityServer {
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface RenewableAuthorityLease {
|
|
28
|
+
release(): Promise<unknown>;
|
|
29
|
+
heartbeat(): Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
export type LocalAgentHostAuthorityRole = 'owner' | 'attached-client';
|
|
32
|
+
export declare class LocalAgentHostAuthoritySession {
|
|
33
|
+
readonly role: LocalAgentHostAuthorityRole;
|
|
34
|
+
readonly descriptor: LocalAgentHostLeaseDescriptor;
|
|
35
|
+
readonly transport: LocalAgentHostRpcClientTransport;
|
|
36
|
+
private readonly server;
|
|
37
|
+
private readonly lease;
|
|
38
|
+
private readonly onAuthorityLost;
|
|
39
|
+
private heartbeatTimer;
|
|
40
|
+
private heartbeatPending;
|
|
41
|
+
private closed;
|
|
42
|
+
constructor(options: {
|
|
43
|
+
role: LocalAgentHostAuthorityRole;
|
|
44
|
+
descriptor: LocalAgentHostLeaseDescriptor;
|
|
45
|
+
transport: LocalAgentHostRpcClientTransport;
|
|
46
|
+
server?: ClosableAuthorityServer;
|
|
47
|
+
lease?: RenewableAuthorityLease;
|
|
48
|
+
heartbeatIntervalMs?: number;
|
|
49
|
+
onAuthorityLost?: (error: Error) => void;
|
|
50
|
+
});
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
private heartbeat;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Elects the one local authority. Only the elected owner invokes
|
|
56
|
+
* createHostTransport; every other process attaches to its authenticated RPC
|
|
57
|
+
* endpoint, which prevents duplicate provider processes and split-brain state.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Why a surface could not attach. The REASON, not a boolean.
|
|
61
|
+
*
|
|
62
|
+
* 🔴 A CLI needs to tell its user what to do next, and "could not connect"
|
|
63
|
+
* does not. "No host is running" means start the app; "protocol mismatch" means
|
|
64
|
+
* the two builds disagree and one needs updating; "unreachable" means a host
|
|
65
|
+
* claims to be there and is not answering, which is a different problem again.
|
|
66
|
+
* Collapsing them sends a person to fix the wrong thing.
|
|
67
|
+
*/
|
|
68
|
+
export type AttachLocalAgentHostRefusal = {
|
|
69
|
+
reason: 'no-host';
|
|
70
|
+
detail: string;
|
|
71
|
+
} | {
|
|
72
|
+
reason: 'host-gone';
|
|
73
|
+
detail: string;
|
|
74
|
+
} | {
|
|
75
|
+
reason: 'protocol-mismatch';
|
|
76
|
+
detail: string;
|
|
77
|
+
hostProtocol: number;
|
|
78
|
+
clientProtocol: number;
|
|
79
|
+
} | {
|
|
80
|
+
reason: 'unreachable';
|
|
81
|
+
detail: string;
|
|
82
|
+
};
|
|
83
|
+
export type AttachLocalAgentHostResult = {
|
|
84
|
+
attached: true;
|
|
85
|
+
session: LocalAgentHostAuthoritySession;
|
|
86
|
+
} | ({
|
|
87
|
+
attached: false;
|
|
88
|
+
} & AttachLocalAgentHostRefusal);
|
|
89
|
+
export interface AttachToLocalAgentHostOptions {
|
|
90
|
+
rootDirectory: string;
|
|
91
|
+
protocolVersion?: number;
|
|
92
|
+
connectTimeoutMs?: number;
|
|
93
|
+
requestTimeoutMs?: number;
|
|
94
|
+
/** Injected for tests; production reads the real clock and process table. */
|
|
95
|
+
now?: () => number;
|
|
96
|
+
staleAfterMs?: number;
|
|
97
|
+
isProcessAlive?: (pid: number) => boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Attach to a host someone else owns — and NEVER become one.
|
|
101
|
+
*
|
|
102
|
+
* 🔴 This exists because `startLocalAgentHostAuthority` is acquire-or-attach,
|
|
103
|
+
* and for a surface that is the wrong shape in the most dangerous way: a CLI
|
|
104
|
+
* started while no app is running would WIN the lease and then be asked to
|
|
105
|
+
* build a host, which a thin client has no business owning. ADE §2.7 is
|
|
106
|
+
* explicit that `xeno-agent-cli` "becomes a client of the same per-user host";
|
|
107
|
+
* a client that can silently become the server is not that.
|
|
108
|
+
*
|
|
109
|
+
* 🔴 The defect is made UNREPRESENTABLE rather than documented: there is no
|
|
110
|
+
* `createHostTransport` parameter here, so there is nothing this function could
|
|
111
|
+
* construct even if it wanted to. It inspects, it connects, or it refuses with
|
|
112
|
+
* a reason.
|
|
113
|
+
*/
|
|
114
|
+
export declare function attachToLocalAgentHost(options: AttachToLocalAgentHostOptions): Promise<AttachLocalAgentHostResult>;
|
|
115
|
+
export declare function connectBeforeDeadline(transport: LocalAgentHostRpcClientTransport, timeoutMs: number): Promise<void>;
|
|
116
|
+
//# sourceMappingURL=localAttach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localAttach.d.ts","sourceRoot":"","sources":["../src/localAttach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAIL,KAAK,6BAA6B,EACnC,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,gCAAgC,EAGjC,MAAM,qBAAqB,CAAA;AAE5B;;;;;;;;GAQG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,uBAAuB;IAItC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAC9B;AAGD,MAAM,MAAM,2BAA2B,GAAG,OAAO,GAAG,iBAAiB,CAAA;AAErE,qBAAa,8BAA8B;IACzC,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAA;IAC1C,QAAQ,CAAC,UAAU,EAAE,6BAA6B,CAAA;IAClD,QAAQ,CAAC,SAAS,EAAE,gCAAgC,CAAA;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsC;IACtE,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,MAAM,CAAQ;gBAEV,OAAO,EAAE;QACnB,IAAI,EAAE,2BAA2B,CAAA;QACjC,UAAU,EAAE,6BAA6B,CAAA;QACzC,SAAS,EAAE,gCAAgC,CAAA;QAC3C,MAAM,CAAC,EAAE,uBAAuB,CAAA;QAChC,KAAK,CAAC,EAAE,uBAAuB,CAAA;QAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAA;QAC5B,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;KACzC;IAaK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAYd,SAAS;CAaxB;AAED;;;;GAIG;AACH;;;;;;;;GAQG;AACH,MAAM,MAAM,2BAA2B,GACnC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,MAAM,EAAE,mBAAmB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC7F;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE7C,MAAM,MAAM,0BAA0B,GAClC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,8BAA8B,CAAA;CAAE,GAC3D,CAAC;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG,2BAA2B,CAAC,CAAA;AAEvD,MAAM,WAAW,6BAA6B;IAC5C,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CAC1C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,0BAA0B,CAAC,CAgFrC;AAGD,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,gCAAgC,EAC3C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attaching to a per-user Agent host — the SURFACE side of ADE §2.7.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 `attachToLocalAgentHost` attaches or refuses; it can never become the host. There is no
|
|
5
|
+
* `createHostTransport` parameter, so there is nothing it could construct even if asked — the
|
|
6
|
+
* dangerous shape (a CLI started while no app is running WINS the lease and is then expected to
|
|
7
|
+
* *be* the host) is unrepresentable rather than merely forbidden.
|
|
8
|
+
*
|
|
9
|
+
* That property is why this file can live in a publishable package at all: everything here is
|
|
10
|
+
* what a thin client legitimately needs, and nothing here can take ownership. Acquisition and the
|
|
11
|
+
* host authority stay in the private `packages/host`.
|
|
12
|
+
*/
|
|
13
|
+
import { XENO_AGENT_HOST_PROTOCOL_VERSION, } from '@xenosystem/agent-interface-contract';
|
|
14
|
+
import { inspectLocalAgentHostLease, isLeaseOwnerGone, readLocalAgentHostLeaseConnection, } from './localLease.js';
|
|
15
|
+
import { LocalAgentHostRpcClientTransport, positiveInteger, resolveLocalAgentHostEndpoint, } from './localTransport.js';
|
|
16
|
+
export class LocalAgentHostAuthoritySession {
|
|
17
|
+
role;
|
|
18
|
+
descriptor;
|
|
19
|
+
transport;
|
|
20
|
+
server;
|
|
21
|
+
lease;
|
|
22
|
+
onAuthorityLost;
|
|
23
|
+
heartbeatTimer;
|
|
24
|
+
heartbeatPending = false;
|
|
25
|
+
closed = false;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.role = options.role;
|
|
28
|
+
this.descriptor = { ...options.descriptor };
|
|
29
|
+
this.transport = options.transport;
|
|
30
|
+
this.server = options.server;
|
|
31
|
+
this.lease = options.lease;
|
|
32
|
+
this.onAuthorityLost = options.onAuthorityLost;
|
|
33
|
+
if (this.lease && options.heartbeatIntervalMs) {
|
|
34
|
+
this.heartbeatTimer = setInterval(() => { void this.heartbeat(); }, options.heartbeatIntervalMs);
|
|
35
|
+
this.heartbeatTimer.unref?.();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async close() {
|
|
39
|
+
if (this.closed)
|
|
40
|
+
return;
|
|
41
|
+
this.closed = true;
|
|
42
|
+
if (this.heartbeatTimer) {
|
|
43
|
+
clearInterval(this.heartbeatTimer);
|
|
44
|
+
this.heartbeatTimer = undefined;
|
|
45
|
+
}
|
|
46
|
+
this.transport.close();
|
|
47
|
+
await this.server?.close();
|
|
48
|
+
await this.lease?.release();
|
|
49
|
+
}
|
|
50
|
+
async heartbeat() {
|
|
51
|
+
if (this.closed || this.heartbeatPending || !this.lease)
|
|
52
|
+
return;
|
|
53
|
+
this.heartbeatPending = true;
|
|
54
|
+
try {
|
|
55
|
+
await this.lease.heartbeat();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const failure = asError(error);
|
|
59
|
+
await this.close().catch(() => undefined);
|
|
60
|
+
this.onAuthorityLost?.(failure);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
this.heartbeatPending = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Attach to a host someone else owns — and NEVER become one.
|
|
69
|
+
*
|
|
70
|
+
* 🔴 This exists because `startLocalAgentHostAuthority` is acquire-or-attach,
|
|
71
|
+
* and for a surface that is the wrong shape in the most dangerous way: a CLI
|
|
72
|
+
* started while no app is running would WIN the lease and then be asked to
|
|
73
|
+
* build a host, which a thin client has no business owning. ADE §2.7 is
|
|
74
|
+
* explicit that `xeno-agent-cli` "becomes a client of the same per-user host";
|
|
75
|
+
* a client that can silently become the server is not that.
|
|
76
|
+
*
|
|
77
|
+
* 🔴 The defect is made UNREPRESENTABLE rather than documented: there is no
|
|
78
|
+
* `createHostTransport` parameter here, so there is nothing this function could
|
|
79
|
+
* construct even if it wanted to. It inspects, it connects, or it refuses with
|
|
80
|
+
* a reason.
|
|
81
|
+
*/
|
|
82
|
+
export async function attachToLocalAgentHost(options) {
|
|
83
|
+
const protocolVersion = positiveInteger(options.protocolVersion) || XENO_AGENT_HOST_PROTOCOL_VERSION;
|
|
84
|
+
const connectTimeoutMs = positiveInteger(options.connectTimeoutMs) || 5_000;
|
|
85
|
+
const requestTimeoutMs = positiveInteger(options.requestTimeoutMs) || 30_000;
|
|
86
|
+
const descriptor = await inspectLocalAgentHostLease(options.rootDirectory);
|
|
87
|
+
if (!descriptor) {
|
|
88
|
+
return {
|
|
89
|
+
attached: false,
|
|
90
|
+
reason: 'no-host',
|
|
91
|
+
detail: 'No XENO Agent host is running for this user. Start the Agent app, or run the host detached.',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// 🔴 The SAME staleness rule the acquire path uses — imported, not restated.
|
|
95
|
+
// A crashed owner leaves its lock file behind, and connecting to it would
|
|
96
|
+
// hang until the transport timed out with "unreachable", which reads as a
|
|
97
|
+
// network fault rather than a host that is simply gone.
|
|
98
|
+
if (isLeaseOwnerGone({
|
|
99
|
+
heartbeatAt: descriptor.heartbeatAt,
|
|
100
|
+
pid: descriptor.pid,
|
|
101
|
+
at: (options.now || Date.now)(),
|
|
102
|
+
...(options.staleAfterMs === undefined ? {} : { staleAfterMs: options.staleAfterMs }),
|
|
103
|
+
...(options.isProcessAlive ? { isProcessAlive: options.isProcessAlive } : {}),
|
|
104
|
+
})) {
|
|
105
|
+
return {
|
|
106
|
+
attached: false,
|
|
107
|
+
reason: 'host-gone',
|
|
108
|
+
detail: `The recorded Agent host (pid ${descriptor.pid}) is no longer running. Start the Agent app again.`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (descriptor.protocolVersion !== protocolVersion) {
|
|
112
|
+
return {
|
|
113
|
+
attached: false,
|
|
114
|
+
reason: 'protocol-mismatch',
|
|
115
|
+
hostProtocol: descriptor.protocolVersion,
|
|
116
|
+
clientProtocol: protocolVersion,
|
|
117
|
+
detail: `The running Agent host speaks protocol ${descriptor.protocolVersion} and this client speaks ${protocolVersion}. Update whichever is older.`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const connection = await readLocalAgentHostLeaseConnection(options.rootDirectory);
|
|
121
|
+
if (!connection || connection.descriptor.instanceId !== descriptor.instanceId) {
|
|
122
|
+
// The host restarted between the inspection and the read. Refusing beats
|
|
123
|
+
// connecting to a different instance than the one just vetted.
|
|
124
|
+
return {
|
|
125
|
+
attached: false,
|
|
126
|
+
reason: 'unreachable',
|
|
127
|
+
detail: 'The Agent host changed while connecting. Retry.',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const transport = new LocalAgentHostRpcClientTransport({
|
|
131
|
+
endpoint: connection.descriptor.endpoint,
|
|
132
|
+
transportToken: connection.transportToken,
|
|
133
|
+
protocolVersion,
|
|
134
|
+
requestTimeoutMs,
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
await connectBeforeDeadline(transport, connectTimeoutMs);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
transport.close();
|
|
141
|
+
return {
|
|
142
|
+
attached: false,
|
|
143
|
+
reason: 'unreachable',
|
|
144
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
attached: true,
|
|
149
|
+
session: new LocalAgentHostAuthoritySession({
|
|
150
|
+
// Always a client. There is no branch here that produces an owner, which
|
|
151
|
+
// is the property this function exists to guarantee.
|
|
152
|
+
role: 'attached-client',
|
|
153
|
+
descriptor: connection.descriptor,
|
|
154
|
+
transport,
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export async function connectBeforeDeadline(transport, timeoutMs) {
|
|
159
|
+
const deadline = Date.now() + timeoutMs;
|
|
160
|
+
let lastError;
|
|
161
|
+
do {
|
|
162
|
+
try {
|
|
163
|
+
await transport.connect();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
lastError = asError(error);
|
|
168
|
+
if (!isRetryableConnectionError(lastError) || Date.now() >= deadline)
|
|
169
|
+
break;
|
|
170
|
+
await delay(Math.min(50, Math.max(1, deadline - Date.now())));
|
|
171
|
+
}
|
|
172
|
+
} while (Date.now() < deadline);
|
|
173
|
+
throw lastError || new Error('Unable to connect to the local Agent host authority.');
|
|
174
|
+
}
|
|
175
|
+
function isRetryableConnectionError(error) {
|
|
176
|
+
if (!('code' in error))
|
|
177
|
+
return false;
|
|
178
|
+
const code = error.code;
|
|
179
|
+
return code === 'ENOENT' || code === 'ECONNREFUSED' || code === 'EPIPE';
|
|
180
|
+
}
|
|
181
|
+
function delay(milliseconds) {
|
|
182
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
|
|
183
|
+
}
|
|
184
|
+
function asError(error) {
|
|
185
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=localAttach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localAttach.js","sourceRoot":"","sources":["../src/localAttach.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface LocalAgentHostLeaseDescriptor {
|
|
2
|
+
schemaVersion: 1;
|
|
3
|
+
instanceId: string;
|
|
4
|
+
pid: number;
|
|
5
|
+
endpoint: string;
|
|
6
|
+
protocolVersion: number;
|
|
7
|
+
acquiredAt: number;
|
|
8
|
+
heartbeatAt: number;
|
|
9
|
+
}
|
|
10
|
+
export interface LocalAgentHostLeaseConnectionInfo {
|
|
11
|
+
descriptor: LocalAgentHostLeaseDescriptor;
|
|
12
|
+
/** Sensitive same-user bearer credential. Never expose this to a renderer or diagnostic export. */
|
|
13
|
+
transportToken: string;
|
|
14
|
+
}
|
|
15
|
+
export interface LocalAgentHostLeaseRecord extends LocalAgentHostLeaseDescriptor {
|
|
16
|
+
transportToken: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const DEFAULT_LEASE_STALE_AFTER_MS = 15000;
|
|
19
|
+
/**
|
|
20
|
+
* Is the recorded owner gone?
|
|
21
|
+
*
|
|
22
|
+
* 🔴 TWO conditions, and the second is the one that matters. A missed
|
|
23
|
+
* heartbeat alone does not mean a dead host — a process paused by a debugger,
|
|
24
|
+
* a laptop resumed from sleep, or a machine under load all produce one. Taking
|
|
25
|
+
* the lease from a LIVE owner is the split-brain the lease exists to prevent,
|
|
26
|
+
* so the pid must also be gone.
|
|
27
|
+
*
|
|
28
|
+
* Exported and shared because a second caller now needs the same question
|
|
29
|
+
* answered without acquiring anything: a surface deciding whether a host is
|
|
30
|
+
* actually there. Two spellings of "is the owner dead" would eventually
|
|
31
|
+
* disagree, and the two answers are "take over" and "connect".
|
|
32
|
+
*/
|
|
33
|
+
export declare function isLeaseOwnerGone(input: {
|
|
34
|
+
heartbeatAt: number;
|
|
35
|
+
pid: number;
|
|
36
|
+
at: number;
|
|
37
|
+
staleAfterMs?: number;
|
|
38
|
+
isProcessAlive?: (pid: number) => boolean;
|
|
39
|
+
}): boolean;
|
|
40
|
+
export declare function inspectLocalAgentHostLease(rootDirectory: string): Promise<LocalAgentHostLeaseDescriptor | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Reads connection material for another trusted main/host process owned by the
|
|
43
|
+
* same OS user. Callers must keep the token outside renderers, logs, and IPC.
|
|
44
|
+
*/
|
|
45
|
+
export declare function readLocalAgentHostLeaseConnection(rootDirectory: string): Promise<LocalAgentHostLeaseConnectionInfo | null>;
|
|
46
|
+
export declare function readLeaseRecord(path: string): Promise<LocalAgentHostLeaseRecord>;
|
|
47
|
+
export declare function parseLeaseRecord(value: string): LocalAgentHostLeaseRecord;
|
|
48
|
+
export declare function publicDescriptor(record: LocalAgentHostLeaseRecord): LocalAgentHostLeaseDescriptor;
|
|
49
|
+
export declare function defaultProcessAlive(pid: number): boolean;
|
|
50
|
+
//# sourceMappingURL=localLease.d.ts.map
|