@ai-workbench/remote-agent 0.8.13 → 0.8.15
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 +10 -8
- package/dist/acp.d.ts +12 -7
- package/dist/acp.js +65 -41
- package/dist/broker-runtime.js +46 -20
- package/dist/broker.d.ts +4 -2
- package/dist/broker.js +18 -9
- package/dist/claude-local.d.ts +2 -1
- package/dist/claude-local.js +62 -35
- package/dist/close-diagnostics.d.ts +56 -0
- package/dist/close-diagnostics.js +104 -0
- package/dist/conversation-details.js +67 -9
- package/dist/employee-endpoint.d.ts +3 -1
- package/dist/employee-endpoint.js +8 -6
- package/dist/employee-exec.d.ts +7 -4
- package/dist/employee-exec.js +39 -29
- package/dist/employee-mcp.d.ts +6 -3
- package/dist/employee-mcp.js +75 -39
- package/dist/employee-presentation.d.ts +6 -0
- package/dist/employee-presentation.js +16 -0
- package/dist/employee-provider.js +59 -36
- package/dist/employee-shell.d.ts +2 -0
- package/dist/employee-shell.js +17 -5
- package/dist/employee-ssh.d.ts +2 -0
- package/dist/employee-ssh.js +6 -4
- package/dist/employee-tools.d.ts +3 -1
- package/dist/employee-tools.js +6 -5
- package/dist/index.d.ts +18 -4
- package/dist/index.js +183 -55
- package/dist/model-catalog.d.ts +9 -1
- package/dist/model-catalog.js +63 -5
- package/dist/model-configuration.d.ts +14 -1
- package/dist/model-configuration.js +122 -89
- package/dist/model-presentation.d.ts +13 -0
- package/dist/model-presentation.js +18 -0
- package/dist/presentation.js +2 -1
- package/dist/provider.d.ts +8 -2
- package/dist/recovery.d.ts +4 -0
- package/dist/rpc-namespace.d.ts +2 -2
- package/dist/rpc-namespace.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { NeedError } from "./remote.js";
|
|
3
|
+
/** Only typed codes, never exception messages, causes, stderr or RPC data. */
|
|
4
|
+
export function closeCode(error, fallback = 'CLOSE_REASON_UNKNOWN') {
|
|
5
|
+
return error instanceof NeedError && /^[A-Z][A-Z0-9_]*$/.test(error.code) ? error.code : fallback;
|
|
6
|
+
}
|
|
7
|
+
/** Retain trusted typed codes through wrappers; never traverse error text or invoke getters. */
|
|
8
|
+
export function closeErrorCodes(error, fallback = 'CLOSE_REASON_UNKNOWN') {
|
|
9
|
+
const queue = [error], seen = new Set(), codes = new Set();
|
|
10
|
+
while (queue.length) {
|
|
11
|
+
const next = queue.pop();
|
|
12
|
+
if (!(next instanceof NeedError || next instanceof AggregateError) || seen.has(next))
|
|
13
|
+
continue;
|
|
14
|
+
seen.add(next);
|
|
15
|
+
if (next instanceof NeedError) {
|
|
16
|
+
const code = Object.getOwnPropertyDescriptor(next, 'code');
|
|
17
|
+
if (code && 'value' in code && typeof code.value === 'string' && /^[A-Z][A-Z0-9_]*$/.test(code.value))
|
|
18
|
+
codes.add(code.value);
|
|
19
|
+
}
|
|
20
|
+
const cause = Object.getOwnPropertyDescriptor(next, 'cause');
|
|
21
|
+
if (cause && 'value' in cause)
|
|
22
|
+
queue.push(cause.value);
|
|
23
|
+
if (next instanceof AggregateError) {
|
|
24
|
+
const errors = Object.getOwnPropertyDescriptor(next, 'errors');
|
|
25
|
+
if (errors && 'value' in errors && Array.isArray(errors.value))
|
|
26
|
+
for (const child of errors.value)
|
|
27
|
+
queue.push(child);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return codes.size ? [...codes] : [fallback];
|
|
31
|
+
}
|
|
32
|
+
export function closeFailure(code, message, requires, codes) {
|
|
33
|
+
return Object.assign(new NeedError(code, message, requires), { cause: new AggregateError([...new Set(codes)].map(value => new NeedError(value, '关闭收尾代码 ' + value, '按安全代码核对')), 'CLOSE_AND_DIAGNOSTIC_FAILURES') });
|
|
34
|
+
}
|
|
35
|
+
/** Assign order before abort/kill; persist through the existing asynchronous D19 outbox. */
|
|
36
|
+
export class CloseDiagnostics {
|
|
37
|
+
sink;
|
|
38
|
+
parentId;
|
|
39
|
+
state;
|
|
40
|
+
id = randomUUID();
|
|
41
|
+
sequence = 0;
|
|
42
|
+
pending = new Map();
|
|
43
|
+
firstValue;
|
|
44
|
+
started = performance.now();
|
|
45
|
+
stage = 'runtime-admission';
|
|
46
|
+
constructor(sink, parentId, state = {}) {
|
|
47
|
+
this.sink = sink;
|
|
48
|
+
this.parentId = parentId;
|
|
49
|
+
this.state = state;
|
|
50
|
+
}
|
|
51
|
+
async run(operation, work) { this.pending.set(operation, (this.pending.get(operation) ?? 0) + 1); try {
|
|
52
|
+
return await work();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
const count = this.pending.get(operation) - 1;
|
|
56
|
+
if (count)
|
|
57
|
+
this.pending.set(operation, count);
|
|
58
|
+
else
|
|
59
|
+
this.pending.delete(operation);
|
|
60
|
+
} }
|
|
61
|
+
get recordingFailureCode() { return this.state.failure ? closeCode(this.state.failure.error, 'CLOSE_RECORD_FAILED') : undefined; }
|
|
62
|
+
get first() { return this.firstValue; }
|
|
63
|
+
bind(sink) { this.sink = sink; }
|
|
64
|
+
/** A delayed resource callback retains its original accepted detail channel. */
|
|
65
|
+
capture() { const sink = this.sink; return cause => this.recordUsing(sink, cause); }
|
|
66
|
+
child(sink = this.sink) { const child = new CloseDiagnostics(sink, this.id, this.state); child.stage = this.stage; return child; }
|
|
67
|
+
record(cause) { this.recordUsing(this.sink, cause); }
|
|
68
|
+
recordUsing(sink, cause) {
|
|
69
|
+
const first = this.sequence === 0;
|
|
70
|
+
const metadata = { ...cause.changedFields ? { changedFields: Object.freeze([...cause.changedFields]) } : {}, changes: Object.freeze((cause.changes ?? [{ field: 'observer.close-sequence', before: this.sequence, after: this.sequence + 1 }]).map(change => Object.freeze({ field: change.field, before: change.before, after: change.after }))), layer: cause.layer, initiator: cause.initiator, reason: cause.reason, ...cause.action ? { action: cause.action } : {}, ...cause.operation ? { operation: cause.operation } : {}, ...cause.exitCode !== undefined ? { exitCode: cause.exitCode } : {}, ...cause.signal !== undefined ? { signal: cause.signal } : {}, ...cause.remoteReason ? { remoteReason: cause.remoteReason } : {} };
|
|
71
|
+
const payload = Object.freeze({ ...metadata, pendingOperations: Object.freeze([...this.pending.keys()]), closeId: this.id, ...this.parentId ? { parentCloseId: this.parentId } : {}, closeSequence: ++this.sequence, first, stage: this.stage, clock: 'client-monotonic', elapsedMs: performance.now() - this.started });
|
|
72
|
+
this.firstValue ??= payload;
|
|
73
|
+
try {
|
|
74
|
+
sink({ kind: 'session', severity: 'warning', code: first ? 'REMOTE_CLOSE_FIRST' : 'REMOTE_CLOSE_NEXT', summary: first ? '首次观察到关闭(不等同根因)' : '后续关闭或清理', payload });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.state.failure ??= { error };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
assertRecorded() { if (this.state.failure)
|
|
81
|
+
throw new NeedError('CONVERSATION_DETAILS_FAILED', '关闭原因保存未完成;首次观察原因 ' + String(this.firstValue?.reason ?? 'CLOSE_REASON_UNKNOWN') + '。', '关闭诊断保存代码 ' + this.recordingFailureCode + ';不将保存失败当作原始关闭原因'); }
|
|
82
|
+
}
|
|
83
|
+
/** Run after owned cleanup; expose both the primary error code and persistence failure. */
|
|
84
|
+
export async function flushCloseDiagnostics(diagnostics, flush, primary) {
|
|
85
|
+
const reporting = [];
|
|
86
|
+
try {
|
|
87
|
+
await flush?.();
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
reporting.push(...closeErrorCodes(error, 'CLOSE_FLUSH_FAILED'));
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
diagnostics.assertRecorded();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
reporting.push(diagnostics.recordingFailureCode ?? closeCode(error, 'CLOSE_RECORD_FAILED'));
|
|
97
|
+
}
|
|
98
|
+
if (reporting.length) {
|
|
99
|
+
const primaryCodes = primary ? closeErrorCodes(primary.error, 'REMOTE_CLEANUP_UNCONFIRMED') : [];
|
|
100
|
+
throw closeFailure('CONVERSATION_DETAILS_FAILED', '关闭诊断保存未完成;原关闭与清理结果需核对。', '首次关闭 ' + String(diagnostics.first?.reason ?? 'CLOSE_REASON_UNKNOWN') + ';清理 ' + (primaryCodes.join(',') || 'closed') + ';保存 ' + reporting.join(','), [...primaryCodes, ...reporting]);
|
|
101
|
+
}
|
|
102
|
+
if (primary)
|
|
103
|
+
throw primary.error;
|
|
104
|
+
}
|
|
@@ -283,6 +283,10 @@ function modelConfigurationStructure(value, level = 'root') {
|
|
|
283
283
|
return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
284
284
|
if (typeof child === 'string' && strings[level].includes(key))
|
|
285
285
|
return [key, child];
|
|
286
|
+
if (level === 'root' && key === 'resolvedModelSource' && child === 'host-user-catalog')
|
|
287
|
+
return [key, child];
|
|
288
|
+
if (level === 'root' && key === 'sessionModelReported' && child === false)
|
|
289
|
+
return [key, false];
|
|
286
290
|
if (level === 'root' && key === 'status' && child === 'remote-configured')
|
|
287
291
|
return [key, child];
|
|
288
292
|
if (level === 'actual' && typeof child === 'boolean' && ['confirmed', 'effortAvailable'].includes(key))
|
|
@@ -331,6 +335,58 @@ function employeeStructure(value) {
|
|
|
331
335
|
return [key, opaqueStructure(child)];
|
|
332
336
|
}));
|
|
333
337
|
}
|
|
338
|
+
/** Closure metadata has no arbitrary exception or transport payload fields. */
|
|
339
|
+
function closeStructure(value) {
|
|
340
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
341
|
+
return opaqueStructure(value);
|
|
342
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
343
|
+
if (key === 'changes' && Array.isArray(child))
|
|
344
|
+
return [key, child.map(change => {
|
|
345
|
+
if (!change || typeof change !== 'object' || Array.isArray(change))
|
|
346
|
+
return opaqueStructure(change);
|
|
347
|
+
const field = change.field;
|
|
348
|
+
const known = typeof field === 'string' && (/^(?:observer.close-sequence|executor.ended|transport.closed|transport.termination-requested|provider.ended|broker.ended|broker.attached|broker.lease-aborted|lease.aborted|lease.closed|mcp.ended|mcp.closing|mcp.poisoned|shell.settled|shell.stop-code|tools.aborted|tools.stop-code|acp.close-requested|adapter.turn-aborted|adapter.detached|adapter.shutting-down|workspace.binding-id|workspace.source|workspace.status|workspace.match-count|network.phase|catalog.api-replaced|catalog.capability|catalog.signature-sha256|connection.instance-id|connection.epoch|connection.phase|connection.need-code)$/.test(field) || /^catalog\.(?:codex|claude)\.(?:ok|error-code|instance-id|epoch|provider|target-sha256|metadata-sha256)$/.test(field));
|
|
349
|
+
if (!known)
|
|
350
|
+
return opaqueStructure(change);
|
|
351
|
+
const scalar = (value) => value === null || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value) || typeof value === 'string' && /^[a-zA-Z0-9_.:@-]+$/.test(value) ? value : opaqueStructure(value);
|
|
352
|
+
return Object.fromEntries(Object.entries(change).map(([name, value]) => [name, name === 'field' ? field : ['before', 'after'].includes(name) ? scalar(value) : opaqueStructure(value)]));
|
|
353
|
+
})];
|
|
354
|
+
if (key === 'pendingOperations' && Array.isArray(child))
|
|
355
|
+
return [key, child.map(field => typeof field === 'string' && /^[a-z][a-z0-9._/-]*$/.test(field) ? field : opaqueStructure(field))];
|
|
356
|
+
if (key === 'changedFields' && Array.isArray(child))
|
|
357
|
+
return [key, child.map(field => typeof field === 'string' && /^[a-z][a-z-]*$/.test(field) ? field : opaqueStructure(field))];
|
|
358
|
+
if (['closeId', 'parentCloseId'].includes(key) && typeof child === 'string' && /^[a-f0-9-]+$/.test(child))
|
|
359
|
+
return [key, child];
|
|
360
|
+
if (['layer', 'initiator', 'action', 'operation', 'stage', 'clock'].includes(key) && typeof child === 'string' && /^[a-z][a-z0-9._/-]*$/.test(child))
|
|
361
|
+
return [key, child];
|
|
362
|
+
if (key === 'reason' && typeof child === 'string' && /^[A-Z][A-Z0-9_]*$/.test(child))
|
|
363
|
+
return [key, child];
|
|
364
|
+
if (key === 'remoteReason' && child === 'unavailable' || key === 'first' && typeof child === 'boolean')
|
|
365
|
+
return [key, child];
|
|
366
|
+
if (['closeSequence', 'elapsedMs'].includes(key) && typeof child === 'number' && Number.isFinite(child) && child >= 0)
|
|
367
|
+
return [key, child];
|
|
368
|
+
if (key === 'exitCode' && (child === null || typeof child === 'number' && Number.isSafeInteger(child)))
|
|
369
|
+
return [key, child];
|
|
370
|
+
if (key === 'signal' && (child === null || typeof child === 'string' && /^SIG[A-Z]+$/.test(child)))
|
|
371
|
+
return [key, child];
|
|
372
|
+
return [key, opaqueStructure(child)];
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
function catalogScopeStructure(value, nested = false) {
|
|
376
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value) || isSanitizedRedaction(value))
|
|
377
|
+
return opaqueStructure(value);
|
|
378
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
379
|
+
if (!nested && key === 'reason' && child === 'CATALOG_PRESENTATION_CHANGED')
|
|
380
|
+
return [key, child];
|
|
381
|
+
if (!nested && ['before', 'after'].includes(key))
|
|
382
|
+
return [key, catalogScopeStructure(child, true)];
|
|
383
|
+
if (nested && key === 'presentationSha256' && (child === null || typeof child === 'string' && /^[a-f0-9]{64}$/.test(child)))
|
|
384
|
+
return [key, child];
|
|
385
|
+
if (nested && key === 'needCode' && (child === null || typeof child === 'string' && /^[A-Z][A-Z0-9_]*$/.test(child)))
|
|
386
|
+
return [key, child];
|
|
387
|
+
return [key, opaqueStructure(child)];
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
334
390
|
export function sanitizeObservation(observation, protectModelMaterial = false) {
|
|
335
391
|
const payload = snapshotPayload(observation.payload, new Set(), protectModelMaterial && observation.kind !== 'commands');
|
|
336
392
|
return freezeDeep({
|
|
@@ -338,15 +394,17 @@ export function sanitizeObservation(observation, protectModelMaterial = false) {
|
|
|
338
394
|
code: sanitizeDisplayLabel(observation.code), summary: sanitizeDisplayLabel(observation.summary),
|
|
339
395
|
...(observation.method === undefined ? {} : { method: sanitizeDisplayLabel(observation.method) }),
|
|
340
396
|
payload: !protectModelMaterial ? payload
|
|
341
|
-
: observation.kind === 'session' && observation.code === '
|
|
342
|
-
: observation.kind === 'session' &&
|
|
343
|
-
: observation.kind === '
|
|
344
|
-
: observation.kind === '
|
|
345
|
-
: observation.kind === '
|
|
346
|
-
: observation.kind === '
|
|
347
|
-
: observation.kind === '
|
|
348
|
-
: observation.kind === '
|
|
349
|
-
:
|
|
397
|
+
: observation.kind === 'session' && observation.code === 'CATALOG_SCOPE_INFORMATION' ? catalogScopeStructure(payload)
|
|
398
|
+
: observation.kind === 'session' && observation.code === 'CODEX_CONTEXT_SESSION_REPLACED' ? contextTransitionStructure(payload)
|
|
399
|
+
: observation.kind === 'session' && ['MODEL_CONFIGURATION_CONFIRMED', 'MODEL_CONFIGURATION_FAILED', 'MODEL_CONFIGURATION_INFORMATION', 'MODEL_CONFIGURATION_CHANGED_DURING_TURN', 'TURN_GATE_INFORMATION'].includes(observation.code) ? modelConfigurationStructure(payload)
|
|
400
|
+
: observation.kind === 'session' && ['REMOTE_CLOSE_FIRST', 'REMOTE_CLOSE_NEXT'].includes(observation.code) ? closeStructure(payload)
|
|
401
|
+
: observation.kind === 'commands' ? catalogStructure(payload)
|
|
402
|
+
: observation.kind === 'usage' ? usageStructure(payload)
|
|
403
|
+
: observation.kind === 'session' && observation.code === 'REMOTE_STAGE_TIMING' ? timingStructure(payload)
|
|
404
|
+
: observation.kind === 'session' && ['CLAUDE_EMPLOYEE_OPERATION', 'CLAUDE_EMPLOYEE_PROCESS', 'CLAUDE_EMPLOYEE_REVOKED', 'CLAUDE_EMPLOYEE_SCOPE'].includes(observation.code) ? employeeStructure(payload)
|
|
405
|
+
: observation.kind === 'need' ? payload
|
|
406
|
+
: observation.kind === 'tool' || ['SDK_ITEM', 'SDK_TOOL', 'ACP_SESSION_UPDATE', 'ACP_IDLE_UPDATE'].includes(observation.code) ? toolStructure(payload)
|
|
407
|
+
: opaqueStructure(payload),
|
|
350
408
|
});
|
|
351
409
|
}
|
|
352
410
|
function validatedScope(scope) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CloseCause, type CloseDiagnostics } from './close-diagnostics.ts';
|
|
1
2
|
import type { ConnectApi as LApi, LocalWorkspaceContextRequest } from '@ai-workbench/connect/api';
|
|
2
3
|
import type { ConnectTarget } from './provider.ts';
|
|
3
4
|
import type { DetailObservation } from './conversation-details.ts';
|
|
@@ -66,6 +67,7 @@ export interface EmployeeWorkspace {
|
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
export interface EmployeeLeaseOptions {
|
|
70
|
+
closeDiagnostics?: CloseDiagnostics;
|
|
69
71
|
getConnect: () => EmployeeConnectApi | undefined;
|
|
70
72
|
getNetwork: () => EmployeeNetworkApi | undefined;
|
|
71
73
|
readWorkspace: () => Promise<EmployeeWorkspace>;
|
|
@@ -86,6 +88,6 @@ export interface EmployeeLease {
|
|
|
86
88
|
request: Readonly<LocalWorkspaceContextRequest>;
|
|
87
89
|
signal: AbortSignal;
|
|
88
90
|
validate(callerSignal?: AbortSignal): Promise<EmployeeEndpointTarget>;
|
|
89
|
-
close(): Promise<void>;
|
|
91
|
+
close(cause?: CloseCause): Promise<void>;
|
|
90
92
|
}
|
|
91
93
|
export declare function openEmployeeLease(options: EmployeeLeaseOptions, initialSignal?: AbortSignal): Promise<EmployeeLease>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { closeCode } from "./close-diagnostics.js";
|
|
1
2
|
import { lstat, realpath } from 'node:fs/promises';
|
|
2
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
4
|
import { sameTarget, unwrap } from "./provider.js";
|
|
@@ -41,7 +42,7 @@ export async function openEmployeeLease(options, initialSignal) {
|
|
|
41
42
|
throw fail();
|
|
42
43
|
const request = { instanceId: connection.instanceId, epoch: connection.epoch, workspaceId: workspace.workspaceId, expectedBindingId: items[0].bindingId };
|
|
43
44
|
let target, closed = false, diagnosticFailure;
|
|
44
|
-
const invalidate = (code) => { if (lifetime.signal.aborted)
|
|
45
|
+
const invalidate = (code, initiator = 'lease.validate') => { options.closeDiagnostics?.record({ layer: 'endpoint', initiator, reason: code, action: 'invalidate', changes: [{ field: 'lease.aborted', before: lifetime.signal.aborted, after: true }] }); if (lifetime.signal.aborted)
|
|
45
46
|
return; try {
|
|
46
47
|
options.detail({ kind: 'session', severity: 'warning', code: 'CLAUDE_EMPLOYEE_REVOKED', summary: '本机旧绑定已停止派发,在途结果按实际确认;不会重放', payload: { reason: code, workspaceId: request.workspaceId, bindingId: request.expectedBindingId } });
|
|
47
48
|
}
|
|
@@ -59,14 +60,14 @@ export async function openEmployeeLease(options, initialSignal) {
|
|
|
59
60
|
throw fail();
|
|
60
61
|
lifetime.signal.throwIfAborted();
|
|
61
62
|
};
|
|
62
|
-
const changed = () => { try {
|
|
63
|
+
const changed = (initiator) => { try {
|
|
63
64
|
current();
|
|
64
65
|
}
|
|
65
66
|
catch {
|
|
66
|
-
invalidate('EMPLOYEE_CONTEXT_CHANGED');
|
|
67
|
+
invalidate('EMPLOYEE_CONTEXT_CHANGED', initiator);
|
|
67
68
|
} };
|
|
68
|
-
offs.push(api.onConnectionState(changed), api.onWorkspaceLocationState(changed), network.onState(event => { if (event?.kind === 'lost' || network.getState().phase !== 'online')
|
|
69
|
-
invalidate('EMPLOYEE_ENDPOINT_UNAVAILABLE'); }));
|
|
69
|
+
offs.push(api.onConnectionState(() => changed('lease.connection-change')), api.onWorkspaceLocationState(() => changed('lease.workspace-change')), network.onState(event => { if (event?.kind === 'lost' || network.getState().phase !== 'online')
|
|
70
|
+
invalidate('EMPLOYEE_ENDPOINT_UNAVAILABLE', 'lease.network-loss'); }));
|
|
70
71
|
async function validate(callerSignal) {
|
|
71
72
|
if (diagnosticFailure)
|
|
72
73
|
throw diagnosticFailure;
|
|
@@ -124,7 +125,7 @@ export async function openEmployeeLease(options, initialSignal) {
|
|
|
124
125
|
}
|
|
125
126
|
try {
|
|
126
127
|
await validate(initialSignal);
|
|
127
|
-
return { target: structuredClone(target), request: Object.freeze({ ...request }), signal: lifetime.signal, validate, close: async () => { if (!closed) {
|
|
128
|
+
return { target: structuredClone(target), request: Object.freeze({ ...request }), signal: lifetime.signal, validate, close: async (cause = { layer: 'endpoint', initiator: 'lease.close', reason: 'EMPLOYEE_EXECUTOR_CLOSED', action: 'close' }) => { options.closeDiagnostics?.record({ ...cause, changes: cause.changes ?? [{ field: 'lease.closed', before: closed, after: true }] }); if (!closed) {
|
|
128
129
|
closed = true;
|
|
129
130
|
lifetime.abort(fail('EMPLOYEE_EXECUTOR_CLOSED'));
|
|
130
131
|
for (const off of offs)
|
|
@@ -133,6 +134,7 @@ export async function openEmployeeLease(options, initialSignal) {
|
|
|
133
134
|
throw diagnosticFailure; } };
|
|
134
135
|
}
|
|
135
136
|
catch (error) {
|
|
137
|
+
options.closeDiagnostics?.record({ layer: 'endpoint', initiator: 'lease.open-failure', reason: closeCode(error), action: 'failure' });
|
|
136
138
|
for (const off of offs)
|
|
137
139
|
off();
|
|
138
140
|
lifetime.abort();
|
package/dist/employee-exec.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CloseCause, type CloseDiagnostics, type CloseObserver } from './close-diagnostics.ts';
|
|
1
2
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
3
|
import { NeedError } from './remote.ts';
|
|
3
4
|
export type EmployeeMessage = Record<string, unknown>;
|
|
@@ -5,10 +6,11 @@ export interface EmployeeExecutionTransport {
|
|
|
5
6
|
write(message: EmployeeMessage, signal?: AbortSignal): Promise<void>;
|
|
6
7
|
read(signal?: AbortSignal): Promise<EmployeeMessage | null>;
|
|
7
8
|
/** Closing a carrier never proves that an employee command completed. */
|
|
8
|
-
close(): void | Promise<void>;
|
|
9
|
+
close(cause?: CloseCause): void | Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
export interface EmployeeExecutionOptions {
|
|
11
12
|
bindingId: string;
|
|
13
|
+
closeDiagnostics?: CloseDiagnostics;
|
|
12
14
|
acpMaxMessageBytes: number;
|
|
13
15
|
/** Rechecks the current L CAS/directory/account and N/S five-tuple. No cached permit. */
|
|
14
16
|
validate(signal: AbortSignal): Promise<void>;
|
|
@@ -42,12 +44,13 @@ export declare class EmployeeExecution {
|
|
|
42
44
|
private gate;
|
|
43
45
|
private stop;
|
|
44
46
|
private closeTransport;
|
|
45
|
-
invalidate(error?: NeedError): void;
|
|
47
|
+
invalidate(error?: NeedError, cause?: CloseCause): void;
|
|
46
48
|
private cleanup;
|
|
47
|
-
dispose(): Promise<void>;
|
|
49
|
+
dispose(cause?: CloseCause): Promise<void>;
|
|
48
50
|
handle(params: unknown, signal?: AbortSignal): Promise<EmployeeExecutionResult>;
|
|
51
|
+
private handleRequest;
|
|
49
52
|
private open;
|
|
50
53
|
private read;
|
|
51
54
|
}
|
|
52
55
|
/** NDJSON carrier with byte bounds and backpressure derived solely from acpMaxMessageBytes. */
|
|
53
|
-
export declare function createEmployeeProcessTransport(child: ChildProcessWithoutNullStreams, acpMaxMessageBytes: number, stderrCodes?: readonly string[]): EmployeeExecutionTransport;
|
|
56
|
+
export declare function createEmployeeProcessTransport(child: ChildProcessWithoutNullStreams, acpMaxMessageBytes: number, stderrCodes?: readonly string[], observe?: CloseObserver): EmployeeExecutionTransport;
|
package/dist/employee-exec.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { closeCode } from "./close-diagnostics.js";
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { NeedError } from "./remote.js";
|
|
3
4
|
const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
@@ -80,7 +81,8 @@ export class EmployeeExecution {
|
|
|
80
81
|
active() { if (this.ended)
|
|
81
82
|
throw this.error ?? need('EMPLOYEE_EXEC_CLOSED'); }
|
|
82
83
|
async gate(signal) { this.active(); cancelled(signal); await waiting(this.options.validate(signal), signal); this.active(); cancelled(signal); }
|
|
83
|
-
stop(error) {
|
|
84
|
+
stop(error, cause = { layer: 'executor', initiator: 'executor.stop', reason: closeCode(error, 'EMPLOYEE_EXEC_CLOSED'), action: 'close' }, observe = this.options.closeDiagnostics?.capture()) {
|
|
85
|
+
observe?.({ ...cause, changes: cause.changes ?? [{ field: 'executor.ended', before: this.ended, after: true }] });
|
|
84
86
|
if (this.ended)
|
|
85
87
|
return;
|
|
86
88
|
this.ended = true;
|
|
@@ -89,12 +91,13 @@ export class EmployeeExecution {
|
|
|
89
91
|
const io = this.io;
|
|
90
92
|
this.io = undefined;
|
|
91
93
|
if (io)
|
|
92
|
-
this.closeTransport(io);
|
|
94
|
+
this.closeTransport(io, undefined, observe);
|
|
93
95
|
}
|
|
94
|
-
closeTransport(io) {
|
|
96
|
+
closeTransport(io, cause = { layer: 'executor', initiator: 'executor.transport-cleanup', reason: 'EXECUTOR_CLEANUP', action: 'close' }, observe = this.options.closeDiagnostics?.capture()) {
|
|
97
|
+
observe?.(cause);
|
|
95
98
|
let closing;
|
|
96
99
|
try {
|
|
97
|
-
closing = Promise.resolve(io.close());
|
|
100
|
+
closing = Promise.resolve(io.close(cause));
|
|
98
101
|
}
|
|
99
102
|
catch {
|
|
100
103
|
closing = Promise.reject(need('EMPLOYEE_EXEC_UNKNOWN'));
|
|
@@ -103,28 +106,32 @@ export class EmployeeExecution {
|
|
|
103
106
|
// Revocation starts cleanup without a caller waiting yet; retain rejection for close/dispose.
|
|
104
107
|
void this.closing.catch(() => { });
|
|
105
108
|
}
|
|
106
|
-
invalidate(error = need('EMPLOYEE_CONTEXT_CHANGED')) { this.stop(error); }
|
|
109
|
+
invalidate(error = need('EMPLOYEE_CONTEXT_CHANGED'), cause) { this.stop(error, cause ?? { layer: 'executor', initiator: 'executor.invalidate', reason: error.code, action: 'invalidate' }); }
|
|
107
110
|
async cleanup() { await this.factoryReceipt; await this.closing; }
|
|
108
|
-
async dispose() { this.stop(); await this.cleanup(); }
|
|
111
|
+
async dispose(cause = { layer: 'executor', initiator: 'executor.dispose', reason: 'EXECUTOR_DISPOSE', action: 'dispose' }) { this.stop(undefined, cause); await this.cleanup(); }
|
|
109
112
|
async handle(params, signal) {
|
|
110
|
-
const request = this.parse(params);
|
|
113
|
+
const request = this.parse(params), observe = this.options.closeDiagnostics?.capture();
|
|
114
|
+
const run = () => this.handleRequest(request, signal, observe);
|
|
115
|
+
return this.options.closeDiagnostics ? this.options.closeDiagnostics.run('employee-exec.' + request.operation, run) : run();
|
|
116
|
+
}
|
|
117
|
+
async handleRequest(request, signal, observe) {
|
|
111
118
|
cancelled(signal);
|
|
112
119
|
if (request.operation === 'close') {
|
|
113
|
-
this.stop();
|
|
120
|
+
this.stop(undefined, { layer: 'executor', initiator: 'remote.employee-exec.close', reason: 'REMOTE_REASON_UNAVAILABLE', remoteReason: 'unavailable', operation: 'close', action: 'close' }, observe);
|
|
114
121
|
await this.cleanup();
|
|
115
122
|
return { version: 1, closed: true, result: 'UNKNOWN' };
|
|
116
123
|
}
|
|
117
124
|
this.active();
|
|
118
125
|
const current = signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
|
|
119
126
|
if (request.operation === 'open')
|
|
120
|
-
return this.open(current);
|
|
127
|
+
return this.open(current, observe);
|
|
121
128
|
if (request.operation === 'read')
|
|
122
|
-
return this.read(current);
|
|
129
|
+
return this.read(current, observe);
|
|
123
130
|
const value = encoded(request.message, this.options.acpMaxMessageBytes);
|
|
124
131
|
const size = value.line.byteLength;
|
|
125
132
|
if (this.queuedWriteBytes + size > this.options.acpMaxMessageBytes) {
|
|
126
133
|
const error = need('ACP_MESSAGE_TOO_LARGE');
|
|
127
|
-
this.stop(error);
|
|
134
|
+
this.stop(error, { layer: 'executor', initiator: 'executor.write-capacity', reason: error.code, operation: 'write', action: 'failure' }, observe);
|
|
128
135
|
throw error;
|
|
129
136
|
}
|
|
130
137
|
this.queuedWriteBytes += size;
|
|
@@ -140,7 +147,7 @@ export class EmployeeExecution {
|
|
|
140
147
|
}
|
|
141
148
|
catch (error) {
|
|
142
149
|
const known = this.error ?? failure(error);
|
|
143
|
-
this.stop(known);
|
|
150
|
+
this.stop(known, { layer: 'executor', initiator: 'executor.write-failure', reason: known.code, operation: 'write', action: 'failure' }, observe);
|
|
144
151
|
throw known;
|
|
145
152
|
}
|
|
146
153
|
finally {
|
|
@@ -150,7 +157,7 @@ export class EmployeeExecution {
|
|
|
150
157
|
this.writeTail = operation.catch(() => { });
|
|
151
158
|
return operation;
|
|
152
159
|
}
|
|
153
|
-
async open(signal) {
|
|
160
|
+
async open(signal, observe) {
|
|
154
161
|
if (this.opening || this.executionId)
|
|
155
162
|
throw need('EMPLOYEE_EXEC_ALREADY_OPEN');
|
|
156
163
|
this.opening = true;
|
|
@@ -158,7 +165,7 @@ export class EmployeeExecution {
|
|
|
158
165
|
await this.gate(signal);
|
|
159
166
|
// A factory may finish after revoke/abort. Its late executor belongs to this binding and must close.
|
|
160
167
|
const created = Promise.resolve().then(() => { cancelled(signal); return this.options.transport(signal); }).then(async (io) => { if (this.ended || signal.aborted) {
|
|
161
|
-
this.closeTransport(io);
|
|
168
|
+
this.closeTransport(io, { layer: 'executor', initiator: 'executor.late-factory', reason: this.error?.code ?? 'ABORTED', operation: 'open', action: 'close' }, observe);
|
|
162
169
|
await this.closing;
|
|
163
170
|
throw this.error ?? need('ABORTED');
|
|
164
171
|
} this.io = io; return io; });
|
|
@@ -176,14 +183,14 @@ export class EmployeeExecution {
|
|
|
176
183
|
}
|
|
177
184
|
catch (error) {
|
|
178
185
|
const known = this.error ?? failure(error);
|
|
179
|
-
this.stop(known);
|
|
186
|
+
this.stop(known, { layer: 'executor', initiator: 'executor.open-failure', reason: known.code, operation: 'open', action: 'failure' }, observe);
|
|
180
187
|
throw known;
|
|
181
188
|
}
|
|
182
189
|
finally {
|
|
183
190
|
this.opening = false;
|
|
184
191
|
}
|
|
185
192
|
}
|
|
186
|
-
async read(signal) {
|
|
193
|
+
async read(signal, observe) {
|
|
187
194
|
if (this.pendingRead)
|
|
188
195
|
throw need('EMPLOYEE_EXEC_READ_BUSY');
|
|
189
196
|
this.pendingRead = true;
|
|
@@ -201,7 +208,7 @@ export class EmployeeExecution {
|
|
|
201
208
|
}
|
|
202
209
|
await this.gate(signal);
|
|
203
210
|
if (message === null) {
|
|
204
|
-
this.stop();
|
|
211
|
+
this.stop(undefined, { layer: 'executor', initiator: 'executor.read-eof', reason: 'EXECUTOR_EOF', operation: 'read', action: 'eof' }, observe);
|
|
205
212
|
return { version: 1, messages: [], closed: true };
|
|
206
213
|
}
|
|
207
214
|
const value = encoded(message, this.options.acpMaxMessageBytes);
|
|
@@ -215,7 +222,7 @@ export class EmployeeExecution {
|
|
|
215
222
|
if (signal.aborted && !this.ended) {
|
|
216
223
|
if (consumed) {
|
|
217
224
|
const unknown = need('EMPLOYEE_EXEC_UNKNOWN');
|
|
218
|
-
this.stop(unknown);
|
|
225
|
+
this.stop(unknown, { layer: 'executor', initiator: 'executor.read-consumed-cancel', reason: unknown.code, operation: 'read', action: 'abort' }, observe);
|
|
219
226
|
throw unknown;
|
|
220
227
|
}
|
|
221
228
|
throw need('ABORTED');
|
|
@@ -223,7 +230,7 @@ export class EmployeeExecution {
|
|
|
223
230
|
if (this.ended && !this.error)
|
|
224
231
|
return { version: 1, messages: [], closed: true };
|
|
225
232
|
const known = failure(error);
|
|
226
|
-
this.stop(known);
|
|
233
|
+
this.stop(known, { layer: 'executor', initiator: 'executor.read-failure', reason: known.code, operation: 'read', action: 'failure' }, observe);
|
|
227
234
|
throw known;
|
|
228
235
|
}
|
|
229
236
|
finally {
|
|
@@ -232,7 +239,7 @@ export class EmployeeExecution {
|
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
241
|
/** NDJSON carrier with byte bounds and backpressure derived solely from acpMaxMessageBytes. */
|
|
235
|
-
export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderrCodes = []) {
|
|
242
|
+
export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderrCodes = [], observe) {
|
|
236
243
|
capacity(acpMaxMessageBytes);
|
|
237
244
|
let partial = Buffer.alloc(0), queuedBytes = 0, writingBytes = 0, closed = false, reason;
|
|
238
245
|
let processClosed = false, terminationRequested = false;
|
|
@@ -247,6 +254,7 @@ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderr
|
|
|
247
254
|
const settle = () => { if (!pending)
|
|
248
255
|
return; const reader = pending; pending = undefined; reader.clear(); reason ? reader.reject(reason) : reader.resolve(null); };
|
|
249
256
|
const terminate = () => {
|
|
257
|
+
observe?.({ layer: 'relay', initiator: 'ssh.terminate', reason: 'TRANSPORT_CLEANUP', action: 'terminate', changes: [{ field: 'transport.termination-requested', before: terminationRequested, after: processClosed || terminationRequested ? terminationRequested : true }] });
|
|
250
258
|
if (processClosed || terminationRequested)
|
|
251
259
|
return;
|
|
252
260
|
terminationRequested = true;
|
|
@@ -267,7 +275,8 @@ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderr
|
|
|
267
275
|
receipt.reject(need('EMPLOYEE_EXEC_UNKNOWN'));
|
|
268
276
|
}
|
|
269
277
|
};
|
|
270
|
-
const finish = (error, kill = false) => {
|
|
278
|
+
const finish = (error, kill = false, cause = { layer: 'relay', initiator: 'ssh.protocol-failure', reason: closeCode(error, 'TRANSPORT_CLOSED'), action: 'failure' }) => {
|
|
279
|
+
observe?.({ ...cause, changes: cause.changes ?? [{ field: 'transport.closed', before: closed, after: true }] });
|
|
271
280
|
if (!closed) {
|
|
272
281
|
closed = true;
|
|
273
282
|
reason = error;
|
|
@@ -353,9 +362,10 @@ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderr
|
|
|
353
362
|
}
|
|
354
363
|
});
|
|
355
364
|
child.stderr.resume();
|
|
356
|
-
child.stdin.on('error', () => finish(need(diagnosticCode ?? 'EMPLOYEE_EXEC_UNKNOWN'), true));
|
|
357
|
-
child.once('error', () => finish(need('EMPLOYEE_EXEC_UNKNOWN'), true));
|
|
358
|
-
child.
|
|
365
|
+
child.stdin.on('error', () => finish(need(diagnosticCode ?? 'EMPLOYEE_EXEC_UNKNOWN'), true, { layer: 'relay', initiator: 'ssh.stdin-error', reason: diagnosticCode ?? 'EMPLOYEE_EXEC_UNKNOWN', action: 'failure' }));
|
|
366
|
+
child.once('error', () => finish(need('EMPLOYEE_EXEC_UNKNOWN'), true, { layer: 'relay', initiator: 'ssh.process-error', reason: 'EMPLOYEE_EXEC_UNKNOWN', action: 'failure' }));
|
|
367
|
+
child.prependOnceListener('close', (code, signal) => observe?.({ layer: 'relay', initiator: 'ssh.process.close', reason: diagnosticCode ?? (code === 0 && !signal ? 'PROCESS_EXIT' : 'EMPLOYEE_EXEC_UNKNOWN'), action: 'exit', exitCode: code, signal }));
|
|
368
|
+
child.once('close', (code, signal) => { processClosed = true; finish(partial.length ? need('EMPLOYEE_EXEC_PROTOCOL_INVALID') : code === 0 && !signal ? undefined : need(diagnosticCode ?? 'EMPLOYEE_EXEC_UNKNOWN'), false, { layer: 'relay', initiator: 'ssh.process-close-settle', reason: diagnosticCode ?? (code === 0 && !signal ? 'PROCESS_EXIT' : 'EMPLOYEE_EXEC_UNKNOWN'), action: 'exit', exitCode: code, signal }); receipt.resolve(); });
|
|
359
369
|
return {
|
|
360
370
|
write: async (message, signal) => {
|
|
361
371
|
cancelled(signal);
|
|
@@ -364,7 +374,7 @@ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderr
|
|
|
364
374
|
const value = encoded(message, acpMaxMessageBytes);
|
|
365
375
|
if (writingBytes + value.line.byteLength > acpMaxMessageBytes) {
|
|
366
376
|
const error = need('ACP_MESSAGE_TOO_LARGE');
|
|
367
|
-
finish(error, true);
|
|
377
|
+
finish(error, true, { layer: 'relay', initiator: 'ssh.write-capacity', reason: error.code, operation: 'write', action: 'failure' });
|
|
368
378
|
throw error;
|
|
369
379
|
}
|
|
370
380
|
writingBytes += value.line.byteLength;
|
|
@@ -374,19 +384,19 @@ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderr
|
|
|
374
384
|
return false; done = true; writingBytes -= value.line.byteLength; writes.delete(fail); signal?.removeEventListener('abort', abort); return true; };
|
|
375
385
|
const fail = (error) => { if (cleanup())
|
|
376
386
|
reject(error); };
|
|
377
|
-
const abort = () => { const error = need('EMPLOYEE_EXEC_UNKNOWN'); finish(error, true); fail(error); };
|
|
387
|
+
const abort = () => { const error = need('EMPLOYEE_EXEC_UNKNOWN'); finish(error, true, { layer: 'relay', initiator: 'ssh.write-abort', reason: error.code, operation: 'write', action: 'abort' }); fail(error); };
|
|
378
388
|
writes.add(fail);
|
|
379
389
|
signal?.addEventListener('abort', abort, { once: true });
|
|
380
390
|
try {
|
|
381
391
|
child.stdin.write(value.line, error => { if (error) {
|
|
382
|
-
finish(need('EMPLOYEE_EXEC_UNKNOWN'), true);
|
|
392
|
+
finish(need('EMPLOYEE_EXEC_UNKNOWN'), true, { layer: 'relay', initiator: 'ssh.write-callback', reason: 'EMPLOYEE_EXEC_UNKNOWN', operation: 'write', action: 'failure' });
|
|
383
393
|
fail(need('EMPLOYEE_EXEC_UNKNOWN'));
|
|
384
394
|
}
|
|
385
395
|
else if (cleanup())
|
|
386
396
|
resolve(); });
|
|
387
397
|
}
|
|
388
398
|
catch {
|
|
389
|
-
finish(need('EMPLOYEE_EXEC_UNKNOWN'), true);
|
|
399
|
+
finish(need('EMPLOYEE_EXEC_UNKNOWN'), true, { layer: 'relay', initiator: 'ssh.write-call', reason: 'EMPLOYEE_EXEC_UNKNOWN', operation: 'write', action: 'failure' });
|
|
390
400
|
fail(need('EMPLOYEE_EXEC_UNKNOWN'));
|
|
391
401
|
}
|
|
392
402
|
if (signal?.aborted)
|
|
@@ -418,6 +428,6 @@ export function createEmployeeProcessTransport(child, acpMaxMessageBytes, stderr
|
|
|
418
428
|
abort();
|
|
419
429
|
});
|
|
420
430
|
},
|
|
421
|
-
close: () => { finish(undefined, true); queue.length = 0; queuedBytes = 0; return receipt.promise; }
|
|
431
|
+
close: (cause = { layer: 'relay', initiator: 'ssh.transport-close', reason: 'TRANSPORT_CLOSE', action: 'close' }) => { finish(undefined, true, cause); queue.length = 0; queuedBytes = 0; return receipt.promise; }
|
|
422
432
|
};
|
|
423
433
|
}
|
package/dist/employee-mcp.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CloseCause, type CloseDiagnostics, type CloseObserver } from './close-diagnostics.ts';
|
|
1
2
|
import { type Socket } from 'node:net';
|
|
2
3
|
import { type JSONRPCMessage, type Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
@@ -10,6 +11,7 @@ export declare const EMPLOYEE_MCP_TOOLS: Tool[];
|
|
|
10
11
|
export declare class EmployeeSocketTransport implements Transport {
|
|
11
12
|
private socket;
|
|
12
13
|
private maxBytes;
|
|
14
|
+
private observeClose?;
|
|
13
15
|
onclose?: () => void;
|
|
14
16
|
onerror?: (error: Error) => void;
|
|
15
17
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
@@ -19,13 +21,14 @@ export declare class EmployeeSocketTransport implements Transport {
|
|
|
19
21
|
get error(): Error | undefined;
|
|
20
22
|
private readonly lifetime;
|
|
21
23
|
readonly signal: AbortSignal;
|
|
22
|
-
constructor(socket: Socket, maxBytes: number);
|
|
24
|
+
constructor(socket: Socket, maxBytes: number, observeClose?: CloseObserver | undefined);
|
|
23
25
|
private failed;
|
|
24
26
|
start(): Promise<void>;
|
|
25
27
|
send(message: JSONRPCMessage): Promise<void>;
|
|
26
|
-
close(): Promise<void>;
|
|
28
|
+
close(cause?: CloseCause): Promise<void>;
|
|
27
29
|
}
|
|
28
30
|
export interface EmployeeMcpOptions {
|
|
31
|
+
closeDiagnostics?: CloseDiagnostics;
|
|
29
32
|
lease: EmployeeLease;
|
|
30
33
|
maxBytes: number;
|
|
31
34
|
killGraceMs: number;
|
|
@@ -37,6 +40,6 @@ export interface EmployeeMcpOptions {
|
|
|
37
40
|
export interface EmployeeMcp {
|
|
38
41
|
routing: ReturnType<typeof employeeRouting>;
|
|
39
42
|
signal: AbortSignal;
|
|
40
|
-
close(): Promise<void>;
|
|
43
|
+
close(cause?: CloseCause): Promise<void>;
|
|
41
44
|
}
|
|
42
45
|
export declare function openEmployeeMcp(options: EmployeeMcpOptions): Promise<EmployeeMcp>;
|