@animalabs/agent-framework 0.8.0 → 0.9.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/dist/src/agent.d.ts +10 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +33 -3
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +171 -15
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1059 -224
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +11 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +66 -0
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-grant.d.ts +115 -0
- package/dist/src/mcpl/capability-grant.d.ts.map +1 -0
- package/dist/src/mcpl/capability-grant.js +291 -0
- package/dist/src/mcpl/capability-grant.js.map +1 -0
- package/dist/src/mcpl/capability-mask.d.ts +51 -0
- package/dist/src/mcpl/capability-mask.d.ts.map +1 -0
- package/dist/src/mcpl/capability-mask.js +190 -0
- package/dist/src/mcpl/capability-mask.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +3 -1
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +163 -36
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +8 -3
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js +28 -8
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/errors.d.ts +6 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +6 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +10 -1
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js +45 -4
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts +10 -14
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +58 -67
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +1 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/prose-stream-router.d.ts +9 -0
- package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
- package/dist/src/mcpl/prose-stream-router.js +16 -0
- package/dist/src/mcpl/prose-stream-router.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +20 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +93 -9
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +231 -32
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/server-registry.js +4 -3
- package/dist/src/mcpl/server-registry.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +24 -0
- package/dist/src/mcpl/tags.d.ts.map +1 -0
- package/dist/src/mcpl/tags.js +52 -0
- package/dist/src/mcpl/tags.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +10 -0
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +159 -64
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js +16 -2
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/refusal-reactions.d.ts +27 -0
- package/dist/src/refusal-reactions.d.ts.map +1 -0
- package/dist/src/refusal-reactions.js +35 -0
- package/dist/src/refusal-reactions.js.map +1 -0
- package/dist/src/tool-result-history.d.ts +10 -0
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +16 -5
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/types/agent.d.ts +35 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +16 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +24 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/channel-registry-routing.test.js +38 -10
- package/dist/test/channel-registry-routing.test.js.map +1 -1
- package/dist/test/channel-registry-typing.test.js +10 -1
- package/dist/test/channel-registry-typing.test.js.map +1 -1
- package/dist/test/checkpoint-attribution.test.js +168 -8
- package/dist/test/checkpoint-attribution.test.js.map +1 -1
- package/dist/test/conversation-fork-injection-scoping.test.js +8 -3
- package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
- package/dist/test/event-gate-selfwake.test.d.ts +15 -0
- package/dist/test/event-gate-selfwake.test.d.ts.map +1 -0
- package/dist/test/event-gate-selfwake.test.js +125 -0
- package/dist/test/event-gate-selfwake.test.js.map +1 -0
- package/dist/test/machine-close-provenance.test.js +6 -0
- package/dist/test/machine-close-provenance.test.js.map +1 -1
- package/dist/test/mcpl-awareness-barrier.test.js +10 -0
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
- package/dist/test/mcpl-capability-grant.test.d.ts +10 -0
- package/dist/test/mcpl-capability-grant.test.d.ts.map +1 -0
- package/dist/test/mcpl-capability-grant.test.js +109 -0
- package/dist/test/mcpl-capability-grant.test.js.map +1 -0
- package/dist/test/mcpl-capability-scoping.test.d.ts +2 -0
- package/dist/test/mcpl-capability-scoping.test.d.ts.map +1 -0
- package/dist/test/mcpl-capability-scoping.test.js +266 -0
- package/dist/test/mcpl-capability-scoping.test.js.map +1 -0
- package/dist/test/mcpl-gate-roundtrip.test.js +13 -0
- package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
- package/dist/test/mcpl-list-status.test.d.ts +2 -0
- package/dist/test/mcpl-list-status.test.d.ts.map +1 -0
- package/dist/test/mcpl-list-status.test.js +78 -0
- package/dist/test/mcpl-list-status.test.js.map +1 -0
- package/dist/test/mcpl-manifest-changed.test.d.ts +2 -0
- package/dist/test/mcpl-manifest-changed.test.d.ts.map +1 -0
- package/dist/test/mcpl-manifest-changed.test.js +177 -0
- package/dist/test/mcpl-manifest-changed.test.js.map +1 -0
- package/dist/test/mcpl-reconnect-events.test.js +27 -2
- package/dist/test/mcpl-reconnect-events.test.js.map +1 -1
- package/dist/test/mcpl-reconnect-reregistration.test.js +53 -17
- package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
- package/dist/test/mcpl-request-timeout.test.js +1 -1
- package/dist/test/mcpl-request-timeout.test.js.map +1 -1
- package/dist/test/physical-window-restart.test.d.ts +14 -0
- package/dist/test/physical-window-restart.test.d.ts.map +1 -0
- package/dist/test/physical-window-restart.test.js +222 -0
- package/dist/test/physical-window-restart.test.js.map +1 -0
- package/dist/test/present-while-acting.test.js +199 -1
- package/dist/test/present-while-acting.test.js.map +1 -1
- package/dist/test/refusal-reaction-baseline.test.d.ts +2 -0
- package/dist/test/refusal-reaction-baseline.test.d.ts.map +1 -0
- package/dist/test/refusal-reaction-baseline.test.js +74 -0
- package/dist/test/refusal-reaction-baseline.test.js.map +1 -0
- package/dist/test/state-scaling-e2e.test.js +7 -1
- package/dist/test/state-scaling-e2e.test.js.map +1 -1
- package/dist/test/state-scaling.test.js +9 -1
- package/dist/test/state-scaling.test.js.map +1 -1
- package/dist/test/tool-result-spill.test.d.ts +13 -0
- package/dist/test/tool-result-spill.test.d.ts.map +1 -0
- package/dist/test/tool-result-spill.test.js +463 -0
- package/dist/test/tool-result-spill.test.js.map +1 -0
- package/dist/test/trunk-channel-routing.test.js +45 -0
- package/dist/test/trunk-channel-routing.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/agent.ts +37 -3
- package/src/framework.ts +1219 -248
- package/src/gate/event-gate.ts +72 -0
- package/src/index.ts +1 -0
- package/src/mcpl/capability-grant.ts +319 -0
- package/src/mcpl/capability-mask.ts +228 -0
- package/src/mcpl/channel-registry.ts +167 -39
- package/src/mcpl/checkpoint-manager.ts +28 -8
- package/src/mcpl/errors.ts +7 -0
- package/src/mcpl/feature-set-manager.ts +50 -5
- package/src/mcpl/hook-orchestrator.ts +64 -86
- package/src/mcpl/index.ts +0 -2
- package/src/mcpl/prose-stream-router.ts +17 -0
- package/src/mcpl/push-handler.ts +18 -2
- package/src/mcpl/server-connection.ts +262 -34
- package/src/mcpl/server-registry.ts +4 -3
- package/src/mcpl/tags.ts +50 -0
- package/src/mcpl/transport.ts +11 -0
- package/src/mcpl/types.ts +176 -78
- package/src/refusal-reactions.ts +37 -0
- package/src/tool-result-history.ts +17 -5
- package/src/types/agent.ts +36 -0
- package/src/types/framework.ts +17 -0
- package/src/types/trace.ts +22 -0
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the bridge between the MCPL protocol and the agent-framework inference
|
|
5
5
|
* pipeline. It collects ContextInjection[] from MCPL servers via beforeInference
|
|
6
|
-
* and feeds them into context-manager's compile().
|
|
7
|
-
*
|
|
6
|
+
* and feeds them into context-manager's compile(). Turn boundaries are announced
|
|
7
|
+
* via metadata-only `inference/lifecycle` notifications (§10.5) —
|
|
8
|
+
* context/afterInference and its modifiedResponse are removed in 0.5.0.
|
|
8
9
|
*
|
|
9
10
|
* Design principles:
|
|
10
11
|
* - Fail-open: timeouts and errors never block inference
|
|
@@ -20,18 +21,24 @@ import type {
|
|
|
20
21
|
McplContextInjection,
|
|
21
22
|
BeforeInferenceParams,
|
|
22
23
|
BeforeInferenceResult,
|
|
23
|
-
|
|
24
|
-
AfterInferenceResult,
|
|
24
|
+
InferenceLifecycleParams,
|
|
25
25
|
} from './types.js';
|
|
26
26
|
import type { McplServerRegistry } from './server-registry.js';
|
|
27
27
|
import type { McplServerConnection } from './server-connection.js';
|
|
28
28
|
import type { FeatureSetManager } from './feature-set-manager.js';
|
|
29
|
+
import { CapabilityGrant } from './capability-grant.js';
|
|
29
30
|
|
|
30
31
|
/** Timeout for beforeInference per server (fail-open). */
|
|
31
32
|
const BEFORE_INFERENCE_TIMEOUT_MS = 5_000;
|
|
32
33
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
34
|
+
/** The three injection positions and their §6.2 capability paths. Position
|
|
35
|
+
* is the TYPED field on the injection — authorization never reads the
|
|
36
|
+
* response's featureSet (§5.4). */
|
|
37
|
+
const INJECT_LEAVES: ReadonlyArray<{ position: string; path: string }> = [
|
|
38
|
+
{ position: 'system', path: 'contextHooks.beforeInference.inject.system' },
|
|
39
|
+
{ position: 'beforeUser', path: 'contextHooks.beforeInference.inject.beforeUser' },
|
|
40
|
+
{ position: 'afterUser', path: 'contextHooks.beforeInference.inject.afterUser' },
|
|
41
|
+
];
|
|
35
42
|
|
|
36
43
|
/**
|
|
37
44
|
* Races a promise against a timeout. Rejects with a descriptive error on timeout.
|
|
@@ -141,7 +148,16 @@ export class HookOrchestrator {
|
|
|
141
148
|
* Fail-open: servers that time out or error are silently skipped.
|
|
142
149
|
*/
|
|
143
150
|
async beforeInference(params: BeforeInferenceParams): Promise<ContextInjection[]> {
|
|
144
|
-
|
|
151
|
+
// §5.4: the GRANT decides fan-out, not the raw advertisement — which
|
|
152
|
+
// also fixes selection for the recursive §5.1 shape, where
|
|
153
|
+
// `beforeInference: {observe: true, …}` is an object, not `true`. A
|
|
154
|
+
// server granted any inject.* leaf but not observe is still CALLED
|
|
155
|
+
// (§10.1 — the hook is how injection happens) and receives
|
|
156
|
+
// `userMessage: null` instead of the user's text.
|
|
157
|
+
const servers = this.registry.getAllServers().filter((s) =>
|
|
158
|
+
CapabilityGrant.of(s).has('contextHooks.beforeInference.observe')
|
|
159
|
+
|| INJECT_LEAVES.some((leaf) => CapabilityGrant.of(s).has(leaf.path)),
|
|
160
|
+
);
|
|
145
161
|
if (servers.length === 0) {
|
|
146
162
|
return [];
|
|
147
163
|
}
|
|
@@ -155,23 +171,20 @@ export class HookOrchestrator {
|
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
/**
|
|
158
|
-
* Fan out `
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
174
|
+
* Fan out `inference/lifecycle` (§10.5) — the metadata-only replacement
|
|
175
|
+
* for context/afterInference. Notifications, fire-and-forget, gated on the
|
|
176
|
+
* inferenceLifecycle grant. BEST-EFFORT by design: the host attempts one
|
|
177
|
+
* terminal per `started` on every exit path it controls; consumers dedupe
|
|
178
|
+
* by inferenceId and keep a safety timeout. Never blocks the turn.
|
|
163
179
|
*/
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return await this.fanOutAfterInference(servers, params);
|
|
173
|
-
} finally {
|
|
174
|
-
this._isInHook = false;
|
|
180
|
+
emitLifecycle(params: InferenceLifecycleParams): void {
|
|
181
|
+
for (const server of this.registry.getAllServers()) {
|
|
182
|
+
if (!CapabilityGrant.of(server).has('inferenceLifecycle')) continue;
|
|
183
|
+
try {
|
|
184
|
+
server.sendInferenceLifecycle(params);
|
|
185
|
+
} catch {
|
|
186
|
+
/* best-effort — a failed notify never disturbs the turn */
|
|
187
|
+
}
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
190
|
|
|
@@ -184,13 +197,21 @@ export class HookOrchestrator {
|
|
|
184
197
|
params: BeforeInferenceParams,
|
|
185
198
|
): Promise<ContextInjection[]> {
|
|
186
199
|
const results = await Promise.allSettled(
|
|
187
|
-
servers.map((server) =>
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
servers.map((server) => {
|
|
201
|
+
// §10.1: a server not granted observe MUST receive
|
|
202
|
+
// `userMessage: null` — the field is ABSENT authority, not merely
|
|
203
|
+
// discouraged — while the hook is still invoked so granted
|
|
204
|
+
// injection positions keep working (write-without-read).
|
|
205
|
+
const perServer: BeforeInferenceParams =
|
|
206
|
+
CapabilityGrant.of(server).has('contextHooks.beforeInference.observe')
|
|
207
|
+
? params
|
|
208
|
+
: { ...params, userMessage: null };
|
|
209
|
+
return withTimeout(
|
|
210
|
+
server.sendBeforeInference(perServer),
|
|
190
211
|
BEFORE_INFERENCE_TIMEOUT_MS,
|
|
191
212
|
`beforeInference to "${server.id}"`,
|
|
192
|
-
).then((result) => ({ server, result }))
|
|
193
|
-
),
|
|
213
|
+
).then((result) => ({ server, result }));
|
|
214
|
+
}),
|
|
194
215
|
);
|
|
195
216
|
|
|
196
217
|
const injections: ContextInjection[] = [];
|
|
@@ -203,7 +224,9 @@ export class HookOrchestrator {
|
|
|
203
224
|
|
|
204
225
|
const { server, result } = settled.value;
|
|
205
226
|
|
|
206
|
-
//
|
|
227
|
+
// Feature-set discipline (diagnostics; §5.4 forbids using the
|
|
228
|
+
// response-supplied featureSet as AUTHORIZATION — that is the
|
|
229
|
+
// position check below).
|
|
207
230
|
try {
|
|
208
231
|
this.featureSetManager.validateInbound(server.id, result.featureSet);
|
|
209
232
|
} catch {
|
|
@@ -211,9 +234,20 @@ export class HookOrchestrator {
|
|
|
211
234
|
continue;
|
|
212
235
|
}
|
|
213
236
|
|
|
214
|
-
//
|
|
237
|
+
// §5.4/§10.8: authorize EACH injection by its typed position against
|
|
238
|
+
// the grant CURRENT NOW, at response receipt — not when the request
|
|
239
|
+
// was sent. A revocation that landed while the hook was in flight is
|
|
240
|
+
// enforced here without extra machinery.
|
|
215
241
|
if (result.contextInjections && result.contextInjections.length > 0) {
|
|
216
242
|
for (const mcplInj of result.contextInjections) {
|
|
243
|
+
const leaf = INJECT_LEAVES.find((l) => l.position === mcplInj.position);
|
|
244
|
+
if (!leaf || !CapabilityGrant.of(server).has(leaf.path)) {
|
|
245
|
+
console.error(
|
|
246
|
+
`[mcpl] ${server.id}: dropped beforeInference injection at position ` +
|
|
247
|
+
`"${mcplInj.position}" — not in the effective grant (§10.8)`,
|
|
248
|
+
);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
217
251
|
injections.push(convertMcplInjection(mcplInj));
|
|
218
252
|
}
|
|
219
253
|
}
|
|
@@ -222,60 +256,4 @@ export class HookOrchestrator {
|
|
|
222
256
|
return injections;
|
|
223
257
|
}
|
|
224
258
|
|
|
225
|
-
// ==========================================================================
|
|
226
|
-
// Private: afterInference fan-out
|
|
227
|
-
// ==========================================================================
|
|
228
|
-
|
|
229
|
-
private async fanOutAfterInference(
|
|
230
|
-
servers: McplServerConnection[],
|
|
231
|
-
params: AfterInferenceParams,
|
|
232
|
-
): Promise<AfterInferenceResult | null> {
|
|
233
|
-
const blockingPromises: Promise<AfterInferenceResult | null>[] = [];
|
|
234
|
-
|
|
235
|
-
for (const server of servers) {
|
|
236
|
-
const isBlocking = this.isBlockingAfterInference(server);
|
|
237
|
-
|
|
238
|
-
if (isBlocking) {
|
|
239
|
-
// Send as request, await response with timeout
|
|
240
|
-
const promise = withTimeout(
|
|
241
|
-
server.sendAfterInference(params, true) as Promise<AfterInferenceResult>,
|
|
242
|
-
AFTER_INFERENCE_BLOCKING_TIMEOUT_MS,
|
|
243
|
-
`afterInference (blocking) to "${server.id}"`,
|
|
244
|
-
).catch((): null => {
|
|
245
|
-
// Fail-open: timeout or error — treat as no modification
|
|
246
|
-
return null;
|
|
247
|
-
});
|
|
248
|
-
blockingPromises.push(promise);
|
|
249
|
-
} else {
|
|
250
|
-
// Send as notification (fire-and-forget)
|
|
251
|
-
server.sendAfterInference(params, false);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (blockingPromises.length === 0) {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Await all blocking responses and return the first valid result
|
|
260
|
-
const results = await Promise.all(blockingPromises);
|
|
261
|
-
for (const result of results) {
|
|
262
|
-
if (result && result.modifiedResponse) {
|
|
263
|
-
return result;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Check if a server's afterInference capability is blocking.
|
|
272
|
-
*/
|
|
273
|
-
private isBlockingAfterInference(server: McplServerConnection): boolean {
|
|
274
|
-
const caps = server.capabilities;
|
|
275
|
-
if (!caps?.contextHooks?.afterInference) {
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
const afterCap = caps.contextHooks.afterInference;
|
|
279
|
-
return typeof afterCap === 'object' && afterCap !== null && 'blocking' in afterCap && afterCap.blocking === true;
|
|
280
|
-
}
|
|
281
259
|
}
|
package/src/mcpl/index.ts
CHANGED
|
@@ -59,6 +59,23 @@ export class ProseStreamRouter {
|
|
|
59
59
|
return this.accumulated;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Drop everything routed so far and return to the constructed state.
|
|
64
|
+
*
|
|
65
|
+
* For a membrane refusal retry (RetryingEvent): the abandoned attempt's
|
|
66
|
+
* prose must not survive into the new one, and `accumulated` feeds
|
|
67
|
+
* outgoing/complete — leaving it would finalize text the model never
|
|
68
|
+
* actually said in the attempt that stands.
|
|
69
|
+
*/
|
|
70
|
+
reset(): void {
|
|
71
|
+
this.target = this.opts.initialTarget;
|
|
72
|
+
this.state = this.opts.mode === 'locus' ? 'body' : 'line-start';
|
|
73
|
+
this.held = '';
|
|
74
|
+
this.token = '';
|
|
75
|
+
this.sawBang = false;
|
|
76
|
+
this.accumulated = new Map();
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
feed(delta: string): RoutedDelta[] {
|
|
63
80
|
const out: RoutedDelta[] = [];
|
|
64
81
|
for (const ch of delta) this.step(ch, out);
|
package/src/mcpl/push-handler.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
} from './types.js';
|
|
17
17
|
import type { FeatureSetManager } from './feature-set-manager.js';
|
|
18
18
|
import { McplFeatureSetError } from './feature-set-manager.js';
|
|
19
|
+
import { expandCoreTags } from './tags.js';
|
|
19
20
|
|
|
20
21
|
// ============================================================================
|
|
21
22
|
// McplPushEvent (the ProcessEvent shape pushed to the queue)
|
|
@@ -165,7 +166,18 @@ export class PushHandler {
|
|
|
165
166
|
params: PushEventParams,
|
|
166
167
|
responder?: Responder,
|
|
167
168
|
): void {
|
|
168
|
-
//
|
|
169
|
+
// §16.3: expand the normative chat:* core closure once, at entry, so
|
|
170
|
+
// every downstream consumer (wake matching, metadata, the queued event)
|
|
171
|
+
// sees the closed set. Producer `implies` edges are NOT consumed —
|
|
172
|
+
// advisory pending acceptance (§16.4). Tags were admitted before this
|
|
173
|
+
// point and grant nothing (§16.6).
|
|
174
|
+
if (params.tags) params.tags = expandCoreTags(params.tags);
|
|
175
|
+
// 1. Validate feature set. §6.6: rejection is diagnostics, not
|
|
176
|
+
// authorization, and MUST be a JSON-RPC error object — not a result
|
|
177
|
+
// carrying a failure flag. (The old `{accepted:false, reason}` result
|
|
178
|
+
// was AUDIT-001's finding: the error factories existed and were never
|
|
179
|
+
// invoked.) Falls back to the result shape only for a legacy responder
|
|
180
|
+
// with no error path.
|
|
169
181
|
try {
|
|
170
182
|
this.featureSetManager.validateInbound(serverId, params.featureSet);
|
|
171
183
|
} catch (err) {
|
|
@@ -175,7 +187,11 @@ export class PushHandler {
|
|
|
175
187
|
// Loud rejection — a rejected push event is an agent that silently
|
|
176
188
|
// never hears the message. (2026-07-09 diagnosability pass.)
|
|
177
189
|
console.error(`[push-event-rejected] server=${serverId} eventId=${params.eventId} reason=${reason}`);
|
|
178
|
-
|
|
190
|
+
if (err instanceof McplFeatureSetError && responder?.respondError) {
|
|
191
|
+
responder.respondError(err.code, reason, { featureSet: err.featureSet });
|
|
192
|
+
} else {
|
|
193
|
+
responder?.respond({ accepted: false, reason });
|
|
194
|
+
}
|
|
179
195
|
return;
|
|
180
196
|
}
|
|
181
197
|
|