@animalabs/agent-framework 0.6.5 → 0.6.7
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 +78 -0
- package/dist/src/agent.d.ts +56 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +372 -57
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server.js +0 -0
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +7 -2
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/canonical-json.d.ts +2 -0
- package/dist/src/canonical-json.d.ts.map +1 -0
- package/dist/src/canonical-json.js +23 -0
- package/dist/src/canonical-json.js.map +1 -0
- package/dist/src/framework.d.ts +160 -2
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3098 -167
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +34 -4
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +36 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +322 -76
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +1 -0
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +1 -0
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +39 -4
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +146 -28
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +43 -12
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js +1 -0
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +12 -1
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +661 -2
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +4 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts +99 -0
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -0
- package/dist/src/recovery/discord-awareness-outbox.js +334 -0
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +45 -0
- package/dist/src/recovery/offline-branch.d.ts.map +1 -0
- package/dist/src/recovery/offline-branch.js +362 -0
- package/dist/src/recovery/offline-branch.js.map +1 -0
- package/dist/src/recovery/recover-cli.d.ts +3 -0
- package/dist/src/recovery/recover-cli.d.ts.map +1 -0
- package/dist/src/recovery/recover-cli.js +129 -0
- package/dist/src/recovery/recover-cli.js.map +1 -0
- package/dist/src/types/agent.d.ts +37 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +1 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +14 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +31 -0
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +4 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/agent-runtime-settings.test.d.ts +2 -0
- package/dist/test/agent-runtime-settings.test.d.ts.map +1 -0
- package/dist/test/agent-runtime-settings.test.js +227 -0
- package/dist/test/agent-runtime-settings.test.js.map +1 -0
- package/dist/test/busy-agent-scheduler.test.d.ts +2 -0
- package/dist/test/busy-agent-scheduler.test.d.ts.map +1 -0
- package/dist/test/busy-agent-scheduler.test.js +62 -0
- package/dist/test/busy-agent-scheduler.test.js.map +1 -0
- package/dist/test/channel-lifecycle-provenance.test.d.ts +2 -0
- package/dist/test/channel-lifecycle-provenance.test.d.ts.map +1 -0
- package/dist/test/channel-lifecycle-provenance.test.js +47 -0
- package/dist/test/channel-lifecycle-provenance.test.js.map +1 -0
- package/dist/test/channel-registry-routing.test.js +5 -4
- package/dist/test/channel-registry-routing.test.js.map +1 -1
- package/dist/test/conversation-fork-injection-scoping.test.js +12 -1
- package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
- package/dist/test/discord-awareness-outbox.test.d.ts +2 -0
- package/dist/test/discord-awareness-outbox.test.d.ts.map +1 -0
- package/dist/test/discord-awareness-outbox.test.js +248 -0
- package/dist/test/discord-awareness-outbox.test.js.map +1 -0
- package/dist/test/event-gate.test.js +21 -1
- package/dist/test/event-gate.test.js.map +1 -1
- package/dist/test/helpers/mock-membrane.d.ts +13 -1
- package/dist/test/helpers/mock-membrane.d.ts.map +1 -1
- package/dist/test/helpers/mock-membrane.js +9 -2
- package/dist/test/helpers/mock-membrane.js.map +1 -1
- package/dist/test/maintenance-timer.test.js +103 -1
- package/dist/test/maintenance-timer.test.js.map +1 -1
- package/dist/test/mcpl-awareness-barrier.test.d.ts +2 -0
- package/dist/test/mcpl-awareness-barrier.test.d.ts.map +1 -0
- package/dist/test/mcpl-awareness-barrier.test.js +845 -0
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -0
- package/dist/test/mcpl-gate-roundtrip.test.js +138 -6
- package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
- package/dist/test/mcpl-reconnect-reregistration.test.js +22 -3
- package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
- package/dist/test/mcpl-request-timeout.test.js +15 -0
- package/dist/test/mcpl-request-timeout.test.js.map +1 -1
- package/dist/test/offline-recovery-branch.test.d.ts +2 -0
- package/dist/test/offline-recovery-branch.test.d.ts.map +1 -0
- package/dist/test/offline-recovery-branch.test.js +226 -0
- package/dist/test/offline-recovery-branch.test.js.map +1 -0
- package/dist/test/present-while-acting.test.d.ts +19 -0
- package/dist/test/present-while-acting.test.d.ts.map +1 -0
- package/dist/test/present-while-acting.test.js +295 -0
- package/dist/test/present-while-acting.test.js.map +1 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts +2 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts.map +1 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.js +1955 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.js.map +1 -0
- package/dist/test/primary-summary-refusal-fallback.test.d.ts +2 -0
- package/dist/test/primary-summary-refusal-fallback.test.d.ts.map +1 -0
- package/dist/test/primary-summary-refusal-fallback.test.js +753 -0
- package/dist/test/primary-summary-refusal-fallback.test.js.map +1 -0
- package/dist/test/same-round-think-policy.test.d.ts +2 -0
- package/dist/test/same-round-think-policy.test.d.ts.map +1 -0
- package/dist/test/same-round-think-policy.test.js +853 -0
- package/dist/test/same-round-think-policy.test.js.map +1 -0
- package/dist/test/trunk-channel-routing.test.js +38 -0
- package/dist/test/trunk-channel-routing.test.js.map +1 -1
- package/dist/test/workspace-read-image.test.d.ts +2 -0
- package/dist/test/workspace-read-image.test.d.ts.map +1 -0
- package/dist/test/workspace-read-image.test.js +518 -0
- package/dist/test/workspace-read-image.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/src/agent.ts +485 -63
- package/src/api/server.ts +8 -2
- package/src/canonical-json.ts +22 -0
- package/src/framework.ts +4096 -304
- package/src/gate/event-gate.ts +50 -4
- package/src/index.ts +20 -0
- package/src/mcpl/channel-registry.ts +396 -85
- package/src/mcpl/push-handler.ts +2 -0
- package/src/mcpl/server-connection.ts +173 -28
- package/src/mcpl/types.ts +49 -12
- package/src/modules/workspace/index.ts +759 -2
- package/src/modules/workspace/types.ts +5 -0
- package/src/recovery/discord-awareness-outbox.ts +459 -0
- package/src/recovery/offline-branch.ts +510 -0
- package/src/recovery/recover-cli.ts +130 -0
- package/src/types/agent.ts +44 -0
- package/src/types/events.ts +1 -1
- package/src/types/framework.ts +17 -0
- package/src/types/module.ts +33 -0
- package/src/types/trace.ts +4 -0
package/src/mcpl/push-handler.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface McplPushEvent {
|
|
|
35
35
|
eventId: string;
|
|
36
36
|
content: ContentBlock[];
|
|
37
37
|
origin?: Record<string, unknown>;
|
|
38
|
+
tags?: string[];
|
|
38
39
|
timestamp: string;
|
|
39
40
|
inferenceId: string;
|
|
40
41
|
triggerInference?: boolean;
|
|
@@ -217,6 +218,7 @@ export class PushHandler {
|
|
|
217
218
|
eventId: params.eventId,
|
|
218
219
|
content,
|
|
219
220
|
origin: params.origin,
|
|
221
|
+
tags: params.tags,
|
|
220
222
|
timestamp: params.timestamp,
|
|
221
223
|
inferenceId,
|
|
222
224
|
triggerInference,
|
|
@@ -31,6 +31,8 @@ import type {
|
|
|
31
31
|
ChannelsOpenResult,
|
|
32
32
|
ChannelsCloseParams,
|
|
33
33
|
ChannelsCloseResult,
|
|
34
|
+
ChannelsAcknowledgeParams,
|
|
35
|
+
ChannelsAcknowledgeResult,
|
|
34
36
|
ChannelsListResult,
|
|
35
37
|
ChannelsPublishParams,
|
|
36
38
|
ChannelsPublishResult,
|
|
@@ -67,6 +69,13 @@ interface PendingRequest {
|
|
|
67
69
|
timer?: ReturnType<typeof setTimeout>;
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
interface RequestOptions {
|
|
73
|
+
/** Override the connection-wide timeout for this request only. */
|
|
74
|
+
timeoutMs?: number;
|
|
75
|
+
/** Surface a late response even when it carries no checkpoint state. */
|
|
76
|
+
surfaceOrphanedResponse?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
/**
|
|
71
80
|
* McplServerConnection manages a single JSON-RPC 2.0 connection to an
|
|
72
81
|
* MCPL server over stdio. Use the static `connect()` factory to create
|
|
@@ -105,13 +114,20 @@ export class McplServerConnection extends EventEmitter {
|
|
|
105
114
|
|
|
106
115
|
private nextRequestId = 1;
|
|
107
116
|
private pendingRequests = new Map<string | number, PendingRequest>();
|
|
117
|
+
/** Metadata retained after selected request deadlines so a late response is
|
|
118
|
+
* observable even though its original promise has already been rejected. */
|
|
119
|
+
private orphanedRequests = new Map<string | number, { method: string }>();
|
|
108
120
|
private closed = false;
|
|
109
121
|
|
|
110
122
|
/** Per-request timeout in ms (0 disables). See McplServerConfig.requestTimeoutMs. */
|
|
111
123
|
private requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
|
|
112
124
|
|
|
113
|
-
// Event buffering:
|
|
114
|
-
|
|
125
|
+
// Event buffering: control-plane traffic can be released independently from
|
|
126
|
+
// inference-bearing data-plane traffic. This lets a server finish its own
|
|
127
|
+
// registration (and answer host tools/call requests) while startup/reconnect
|
|
128
|
+
// reconciliation keeps agent wakes behind a durable barrier.
|
|
129
|
+
private controlPlaneReady = false;
|
|
130
|
+
private dataPlaneReady = false;
|
|
115
131
|
private bufferedEvents: Array<{ event: string; args: unknown[] }> = [];
|
|
116
132
|
|
|
117
133
|
// Reconnect state (adapted from Anarchid/agent-framework@mcpl-module-proto)
|
|
@@ -160,26 +176,87 @@ export class McplServerConnection extends EventEmitter {
|
|
|
160
176
|
* the caller has attached listeners via wireMcplEvents).
|
|
161
177
|
*/
|
|
162
178
|
ready(): void {
|
|
163
|
-
this.
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
this.controlPlaneReady = true;
|
|
180
|
+
this.dataPlaneReady = true;
|
|
181
|
+
const pending = this.bufferedEvents;
|
|
182
|
+
this.bufferedEvents = [];
|
|
183
|
+
for (const { event, args } of pending) {
|
|
184
|
+
// Re-enter the gate for every buffered item. A control handler (notably
|
|
185
|
+
// tools/list_changed) may install a newer data-plane barrier while this
|
|
186
|
+
// snapshot is being flushed; later data items must observe that pause.
|
|
187
|
+
this.emit(event, ...args);
|
|
166
188
|
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Release only server-establishment and lifecycle/control traffic. Responses
|
|
193
|
+
* to host requests are never event-buffered, so tools/list and tools/call
|
|
194
|
+
* responses remain live as well. Inference-bearing requests stay queued in
|
|
195
|
+
* their original relative order until {@link ready} is called.
|
|
196
|
+
*/
|
|
197
|
+
readyControlPlane(): void {
|
|
198
|
+
this.controlPlaneReady = true;
|
|
199
|
+
const pending = this.bufferedEvents;
|
|
200
|
+
const deferred: Array<{ event: string; args: unknown[] }> = [];
|
|
167
201
|
this.bufferedEvents = [];
|
|
202
|
+
for (const item of pending) {
|
|
203
|
+
if (McplServerConnection.isControlPlaneEvent(item.event)) {
|
|
204
|
+
super.emit(item.event, ...item.args);
|
|
205
|
+
} else {
|
|
206
|
+
deferred.push(item);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Data events emitted by control handlers while the snapshot was flushing
|
|
210
|
+
// were appended to the new buffer. Preserve their order behind older data.
|
|
211
|
+
this.bufferedEvents = [...deferred, ...this.bufferedEvents];
|
|
168
212
|
}
|
|
169
213
|
|
|
170
214
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
215
|
+
* Close the data plane synchronously while leaving an already-released
|
|
216
|
+
* control plane live. Used before reconnect adoption and list-change
|
|
217
|
+
* reconciliation so no new inference event can beat barrier installation.
|
|
218
|
+
*/
|
|
219
|
+
pauseDataPlane(): void {
|
|
220
|
+
this.dataPlaneReady = false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Override emit to buffer server→host events until the corresponding plane
|
|
225
|
+
* is ready. Lifecycle events always pass through so reconnect can install a
|
|
226
|
+
* new data-plane gate before the fresh transport is exposed.
|
|
173
227
|
*/
|
|
174
228
|
override emit(event: string | symbol, ...args: unknown[]): boolean {
|
|
175
229
|
const name = typeof event === 'string' ? event : '';
|
|
176
|
-
if (
|
|
230
|
+
if (
|
|
231
|
+
McplServerConnection.isLifecycleEvent(name)
|
|
232
|
+
|| (McplServerConnection.isControlPlaneEvent(name)
|
|
233
|
+
? this.controlPlaneReady
|
|
234
|
+
: this.dataPlaneReady)
|
|
235
|
+
) {
|
|
177
236
|
return super.emit(event, ...args);
|
|
178
237
|
}
|
|
179
238
|
this.bufferedEvents.push({ event: name, args });
|
|
180
239
|
return true;
|
|
181
240
|
}
|
|
182
241
|
|
|
242
|
+
private static isControlPlaneEvent(event: string): boolean {
|
|
243
|
+
return event === 'stderr'
|
|
244
|
+
|| event === 'scope-elevate'
|
|
245
|
+
|| event === 'channels-register'
|
|
246
|
+
|| event === 'channels-changed'
|
|
247
|
+
|| event === 'feature-sets-changed'
|
|
248
|
+
|| event === 'tools-list-changed'
|
|
249
|
+
|| event === 'connect-failed'
|
|
250
|
+
|| event === 'reconnect-failed'
|
|
251
|
+
|| event === 'orphaned-response';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private static isLifecycleEvent(event: string): boolean {
|
|
255
|
+
return event === 'close'
|
|
256
|
+
|| event === 'error'
|
|
257
|
+
|| event === 'reconnect';
|
|
258
|
+
}
|
|
259
|
+
|
|
183
260
|
// ==========================================================================
|
|
184
261
|
// Static factory
|
|
185
262
|
// ==========================================================================
|
|
@@ -397,6 +474,14 @@ export class McplServerConnection extends EventEmitter {
|
|
|
397
474
|
return this.sendRequest(McplMethod.ChannelsClose, params as unknown as Record<string, unknown>) as Promise<ChannelsCloseResult>;
|
|
398
475
|
}
|
|
399
476
|
|
|
477
|
+
/** Send `channels/acknowledge` request and await result. */
|
|
478
|
+
sendChannelsAcknowledge(params: ChannelsAcknowledgeParams): Promise<ChannelsAcknowledgeResult> {
|
|
479
|
+
return this.sendRequest(
|
|
480
|
+
McplMethod.ChannelsAcknowledge,
|
|
481
|
+
params as unknown as Record<string, unknown>,
|
|
482
|
+
) as Promise<ChannelsAcknowledgeResult>;
|
|
483
|
+
}
|
|
484
|
+
|
|
400
485
|
/** Send `channels/list` request and await result. */
|
|
401
486
|
sendChannelsList(): Promise<ChannelsListResult> {
|
|
402
487
|
return this.sendRequest(McplMethod.ChannelsList, {}) as Promise<ChannelsListResult>;
|
|
@@ -463,6 +548,26 @@ export class McplServerConnection extends EventEmitter {
|
|
|
463
548
|
return this.sendRequest('tools/call', params) as Promise<McpToolCallResult>;
|
|
464
549
|
}
|
|
465
550
|
|
|
551
|
+
/**
|
|
552
|
+
* Send a tools/call with a mandatory request-local deadline. This is used by
|
|
553
|
+
* durable external-side-effect accounting, whose safety bound must remain in
|
|
554
|
+
* force even when the server's general requestTimeoutMs is configured as 0.
|
|
555
|
+
*/
|
|
556
|
+
sendToolsCallWithDeadline(
|
|
557
|
+
name: string,
|
|
558
|
+
args: Record<string, unknown>,
|
|
559
|
+
timeoutMs: number,
|
|
560
|
+
): Promise<McpToolCallResult> {
|
|
561
|
+
const params: Record<string, unknown> = { name, arguments: args };
|
|
562
|
+
const mandatoryTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
563
|
+
? Math.max(1, Math.floor(timeoutMs))
|
|
564
|
+
: 1;
|
|
565
|
+
return this.sendRequest('tools/call', params, {
|
|
566
|
+
timeoutMs: mandatoryTimeoutMs,
|
|
567
|
+
surfaceOrphanedResponse: true,
|
|
568
|
+
}) as Promise<McpToolCallResult>;
|
|
569
|
+
}
|
|
570
|
+
|
|
466
571
|
// ==========================================================================
|
|
467
572
|
// Lifecycle
|
|
468
573
|
// ==========================================================================
|
|
@@ -487,6 +592,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
487
592
|
pending.reject(new Error(`Connection to MCPL server "${this.id}" closed while awaiting response for ${pending.method} (id=${id})`));
|
|
488
593
|
}
|
|
489
594
|
this.pendingRequests.clear();
|
|
595
|
+
this.orphanedRequests.clear();
|
|
490
596
|
|
|
491
597
|
// Tear down the transport (kills the child / closes the socket). The
|
|
492
598
|
// transport's own 'close' event is short-circuited by `this.closed` in
|
|
@@ -498,6 +604,27 @@ export class McplServerConnection extends EventEmitter {
|
|
|
498
604
|
this.emit('close');
|
|
499
605
|
}
|
|
500
606
|
|
|
607
|
+
/**
|
|
608
|
+
* Tear down a live transport after an awareness-accounting failure without
|
|
609
|
+
* disabling configured auto-reconnect. The normal transport close handler
|
|
610
|
+
* rejects requests, emits lifecycle state, and schedules the next bounded
|
|
611
|
+
* reconnect attempt. With reconnect disabled this leaves the server closed.
|
|
612
|
+
*/
|
|
613
|
+
async reconnectAfterFailure(): Promise<void> {
|
|
614
|
+
this.pauseDataPlane();
|
|
615
|
+
if (this.closed) {
|
|
616
|
+
this.scheduleReconnect();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (this.transport) {
|
|
620
|
+
await this.transport.close();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
this.closed = true;
|
|
624
|
+
this.emit('close');
|
|
625
|
+
this.scheduleReconnect();
|
|
626
|
+
}
|
|
627
|
+
|
|
501
628
|
/**
|
|
502
629
|
* Schedule a background reconnection attempt.
|
|
503
630
|
*
|
|
@@ -541,16 +668,17 @@ export class McplServerConnection extends EventEmitter {
|
|
|
541
668
|
this.hostCapabilities,
|
|
542
669
|
);
|
|
543
670
|
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
671
|
+
// Close the data plane before the new transport can emit. The reconnect
|
|
672
|
+
// lifecycle event bypasses buffering; its framework listener installs the
|
|
673
|
+
// awareness barrier synchronously, then releases control traffic needed
|
|
674
|
+
// to establish the server while data remains held.
|
|
675
|
+
this.pauseDataPlane();
|
|
547
676
|
this.transport = transport;
|
|
548
677
|
this.capabilities = capabilities;
|
|
549
678
|
this.closed = false;
|
|
550
679
|
this.nextRequestId = 1;
|
|
551
680
|
this.pendingRequests.clear();
|
|
552
681
|
this.wireTransport(transport);
|
|
553
|
-
this.readyFlag = true;
|
|
554
682
|
|
|
555
683
|
console.error(`MCPL server "${this.id}" reconnected successfully`);
|
|
556
684
|
this.reconnectAttempts = 0;
|
|
@@ -578,7 +706,11 @@ export class McplServerConnection extends EventEmitter {
|
|
|
578
706
|
* the framework maps the rejection to an isError tool_result, so a hung tool
|
|
579
707
|
* surfaces as a normal tool error and the turn completes.
|
|
580
708
|
*/
|
|
581
|
-
private sendRequest(
|
|
709
|
+
private sendRequest(
|
|
710
|
+
method: string,
|
|
711
|
+
params: Record<string, unknown>,
|
|
712
|
+
options: RequestOptions = {},
|
|
713
|
+
): Promise<unknown> {
|
|
582
714
|
if (this.closed) {
|
|
583
715
|
return Promise.reject(new Error(`Cannot send request: connection to "${this.id}" is closed`));
|
|
584
716
|
}
|
|
@@ -593,18 +725,22 @@ export class McplServerConnection extends EventEmitter {
|
|
|
593
725
|
|
|
594
726
|
return new Promise((resolve, reject) => {
|
|
595
727
|
const pending: PendingRequest = { resolve, reject, method };
|
|
596
|
-
|
|
728
|
+
const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
|
|
729
|
+
if (timeoutMs > 0) {
|
|
597
730
|
pending.timer = setTimeout(() => {
|
|
598
731
|
this.pendingRequests.delete(id);
|
|
732
|
+
if (options.surfaceOrphanedResponse) {
|
|
733
|
+
this.orphanedRequests.set(id, { method });
|
|
734
|
+
}
|
|
599
735
|
reject(new Error(
|
|
600
736
|
`MCPL server "${this.id}" did not respond to ${method} (id=${id}) ` +
|
|
601
|
-
`within ${
|
|
602
|
-
`
|
|
603
|
-
`tool may still have completed server-side
|
|
604
|
-
`
|
|
605
|
-
`
|
|
737
|
+
`within ${timeoutMs}ms — the server may be hung. The response ` +
|
|
738
|
+
`outcome is unknown: the request was abandoned locally but was not ` +
|
|
739
|
+
`cancelled, so the tool may still have completed server-side; verify ` +
|
|
740
|
+
`state before retrying; a blind retry of a stateful/side-effecting ` +
|
|
741
|
+
`tool may duplicate it.`,
|
|
606
742
|
));
|
|
607
|
-
},
|
|
743
|
+
}, timeoutMs);
|
|
608
744
|
// Don't hold the event loop open for the watchdog alone.
|
|
609
745
|
pending.timer.unref?.();
|
|
610
746
|
}
|
|
@@ -712,23 +848,31 @@ export class McplServerConnection extends EventEmitter {
|
|
|
712
848
|
const pending = this.pendingRequests.get(response.id);
|
|
713
849
|
if (!pending) {
|
|
714
850
|
// Orphaned response — the request already timed out (or was settled) and
|
|
715
|
-
// was removed from pendingRequests.
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
//
|
|
720
|
-
|
|
721
|
-
|
|
851
|
+
// was removed from pendingRequests. A late STATEFUL result must be
|
|
852
|
+
// surfaced because dropping it silently diverges the checkpoint tree.
|
|
853
|
+
// Selected side-effecting calls are also surfaced even without state so
|
|
854
|
+
// their outcome-unknown receipt remains visible. We cannot safely
|
|
855
|
+
// re-inject either result because the original dispatch context is gone.
|
|
856
|
+
const orphaned = this.orphanedRequests.get(response.id);
|
|
857
|
+
this.orphanedRequests.delete(response.id);
|
|
722
858
|
const result = response.result;
|
|
723
859
|
if (result && typeof result === 'object') {
|
|
724
860
|
const r = result as { state?: unknown; checkpoint?: unknown };
|
|
725
|
-
if (r.state !== undefined || r.checkpoint !== undefined) {
|
|
861
|
+
if (orphaned || r.state !== undefined || r.checkpoint !== undefined) {
|
|
726
862
|
this.emit('orphaned-response', {
|
|
727
863
|
id: response.id,
|
|
864
|
+
method: orphaned?.method,
|
|
728
865
|
hadState: r.state !== undefined,
|
|
729
866
|
hadCheckpoint: r.checkpoint !== undefined,
|
|
730
867
|
});
|
|
731
868
|
}
|
|
869
|
+
} else if (orphaned) {
|
|
870
|
+
this.emit('orphaned-response', {
|
|
871
|
+
id: response.id,
|
|
872
|
+
method: orphaned.method,
|
|
873
|
+
hadState: false,
|
|
874
|
+
hadCheckpoint: false,
|
|
875
|
+
});
|
|
732
876
|
}
|
|
733
877
|
return; // Orphaned response — ignore
|
|
734
878
|
}
|
|
@@ -795,6 +939,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
795
939
|
);
|
|
796
940
|
}
|
|
797
941
|
this.pendingRequests.clear();
|
|
942
|
+
this.orphanedRequests.clear();
|
|
798
943
|
|
|
799
944
|
this.emit('close', info.code ?? null, info.signal ?? null);
|
|
800
945
|
|
package/src/mcpl/types.ts
CHANGED
|
@@ -284,18 +284,9 @@ export interface McplServerConfig {
|
|
|
284
284
|
toolPrefix?: string;
|
|
285
285
|
|
|
286
286
|
/**
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* should listen passively (e.g. a frontdesk clerk).
|
|
291
|
-
* - `'manual'` — registered channels are listed but not opened. The agent
|
|
292
|
-
* must call the `channel_open` tool to start receiving messages. Right
|
|
293
|
-
* for focused agents that should not be flooded with chatter from
|
|
294
|
-
* servers that advertise many public channels (e.g. Zulip's full stream
|
|
295
|
-
* list).
|
|
296
|
-
* - `string[]` — allow-list of channel ids; only matching channels are
|
|
297
|
-
* auto-opened. Ids are server-scoped (the raw id the server registers,
|
|
298
|
-
* e.g. `"zulip:tracker-miner-f"`).
|
|
287
|
+
* @deprecated One-time migration input for an installation's old auto,
|
|
288
|
+
* manual, or allow-list policy. Runtime lifecycle state is persisted in
|
|
289
|
+
* Chronicle and changed with channel_open/channel_close.
|
|
299
290
|
*/
|
|
300
291
|
channelSubscription?: 'auto' | 'manual' | string[];
|
|
301
292
|
}
|
|
@@ -797,6 +788,33 @@ export interface ChannelDescriptor {
|
|
|
797
788
|
|
|
798
789
|
/** Arbitrary metadata */
|
|
799
790
|
metadata?: Record<string, unknown>;
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Server bootstrap preference, consulted only when the host has no durable
|
|
794
|
+
* desired state for this channel.
|
|
795
|
+
*/
|
|
796
|
+
initiallyOpen?: boolean;
|
|
797
|
+
|
|
798
|
+
/** Optional generic capabilities exposed by this channel. */
|
|
799
|
+
capabilities?: ChannelCapabilities;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export interface ChannelCapabilities {
|
|
803
|
+
history?: {
|
|
804
|
+
maxMessages?: number;
|
|
805
|
+
supportsBeforeMessage?: boolean;
|
|
806
|
+
supportsSinceLastSeen?: boolean;
|
|
807
|
+
};
|
|
808
|
+
acknowledgment?: {
|
|
809
|
+
kind?: string;
|
|
810
|
+
supportsValue?: boolean;
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export interface ChannelHistoryRequest {
|
|
815
|
+
limit: number;
|
|
816
|
+
beforeMessageId?: string;
|
|
817
|
+
sinceLastSeen?: boolean;
|
|
800
818
|
}
|
|
801
819
|
|
|
802
820
|
/**
|
|
@@ -858,9 +876,12 @@ export interface ChannelsListResult {
|
|
|
858
876
|
* Spec Section 14.3.
|
|
859
877
|
*/
|
|
860
878
|
export interface ChannelsOpenParams {
|
|
879
|
+
/** Exact registered id. Preferred over type/address matching. */
|
|
880
|
+
channelId?: string;
|
|
861
881
|
type: string;
|
|
862
882
|
address?: Record<string, unknown>;
|
|
863
883
|
metadata?: Record<string, unknown>;
|
|
884
|
+
history?: ChannelHistoryRequest;
|
|
864
885
|
}
|
|
865
886
|
|
|
866
887
|
/**
|
|
@@ -868,6 +889,8 @@ export interface ChannelsOpenParams {
|
|
|
868
889
|
*/
|
|
869
890
|
export interface ChannelsOpenResult {
|
|
870
891
|
channel: ChannelDescriptor;
|
|
892
|
+
history?: ChannelIncomingMessage[];
|
|
893
|
+
historyTruncated?: boolean;
|
|
871
894
|
}
|
|
872
895
|
|
|
873
896
|
/**
|
|
@@ -885,6 +908,19 @@ export interface ChannelsCloseResult {
|
|
|
885
908
|
closed: boolean;
|
|
886
909
|
}
|
|
887
910
|
|
|
911
|
+
export interface ChannelsAcknowledgeParams {
|
|
912
|
+
channelId: string;
|
|
913
|
+
messageId: string;
|
|
914
|
+
intent: string;
|
|
915
|
+
value?: string;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
export interface ChannelsAcknowledgeResult {
|
|
919
|
+
acknowledged: boolean;
|
|
920
|
+
representation?: string;
|
|
921
|
+
reason?: string;
|
|
922
|
+
}
|
|
923
|
+
|
|
888
924
|
/**
|
|
889
925
|
* channels/outgoing/chunk params (Host → Server, Notification).
|
|
890
926
|
* For observers receiving moderated deltas.
|
|
@@ -1046,6 +1082,7 @@ export const McplMethod = {
|
|
|
1046
1082
|
ChannelsList: 'channels/list',
|
|
1047
1083
|
ChannelsOpen: 'channels/open',
|
|
1048
1084
|
ChannelsClose: 'channels/close',
|
|
1085
|
+
ChannelsAcknowledge: 'channels/acknowledge',
|
|
1049
1086
|
ChannelsOutgoingChunk: 'channels/outgoing/chunk',
|
|
1050
1087
|
ChannelsOutgoingComplete: 'channels/outgoing/complete',
|
|
1051
1088
|
ChannelsPublish: 'channels/publish',
|