@animalabs/agent-framework 0.19.0 → 0.20.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/README.md +29 -0
- package/dist/src/agent.d.ts +22 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +128 -10
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/types.d.ts +1 -1
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts +10 -1
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js +27 -2
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/code-execution/tool-definition.d.ts +21 -0
- package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
- package/dist/src/code-execution/tool-definition.js +34 -0
- package/dist/src/code-execution/tool-definition.js.map +1 -1
- package/dist/src/framework.d.ts +153 -2
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1169 -76
- package/dist/src/framework.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-grant.d.ts +1 -1
- package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
- package/dist/src/mcpl/capability-grant.js +49 -5
- package/dist/src/mcpl/capability-grant.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +59 -2
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +106 -6
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +3 -0
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +3 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +318 -0
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -0
- package/dist/src/mcpl/push-coalescer.js +709 -0
- package/dist/src/mcpl/push-coalescer.js.map +1 -0
- package/dist/src/mcpl/push-handler.d.ts +7 -2
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +43 -4
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +18 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +44 -1
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts +71 -0
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -0
- package/dist/src/mcpl/tool-classes.js +170 -0
- package/dist/src/mcpl/tool-classes.js.map +1 -0
- package/dist/src/mcpl/tool-glob.d.ts +16 -0
- package/dist/src/mcpl/tool-glob.d.ts.map +1 -0
- package/dist/src/mcpl/tool-glob.js +41 -0
- package/dist/src/mcpl/tool-glob.js.map +1 -0
- package/dist/src/mcpl/tool-lifecycle.d.ts +253 -0
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -0
- package/dist/src/mcpl/tool-lifecycle.js +592 -0
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +12 -0
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +33 -1
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +66 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js +6 -0
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/modules/discord/index.js +1 -1
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +85 -0
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +1019 -24
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +202 -0
- package/dist/src/modules/history/semantic.d.ts.map +1 -0
- package/dist/src/modules/history/semantic.js +418 -0
- package/dist/src/modules/history/semantic.js.map +1 -0
- package/dist/src/modules/index.d.ts +2 -1
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +82 -0
- package/dist/src/tool-result-guard.d.ts.map +1 -0
- package/dist/src/tool-result-guard.js +306 -0
- package/dist/src/tool-result-guard.js.map +1 -0
- package/dist/src/types/agent.d.ts +14 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +21 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +17 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/channel-registry-routing.test.js +59 -0
- package/dist/test/channel-registry-routing.test.js.map +1 -1
- package/dist/test/code-execution.test.js +139 -0
- package/dist/test/code-execution.test.js.map +1 -1
- package/dist/test/helpers/coalescing-fixture.d.ts +72 -0
- package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -0
- package/dist/test/helpers/coalescing-fixture.js +145 -0
- package/dist/test/helpers/coalescing-fixture.js.map +1 -0
- package/dist/test/helpers/coalescing-kill-child.d.ts +2 -0
- package/dist/test/helpers/coalescing-kill-child.d.ts.map +1 -0
- package/dist/test/helpers/coalescing-kill-child.js +24 -0
- package/dist/test/helpers/coalescing-kill-child.js.map +1 -0
- package/dist/test/history-module-realstore.test.d.ts +2 -0
- package/dist/test/history-module-realstore.test.d.ts.map +1 -0
- package/dist/test/history-module-realstore.test.js +363 -0
- package/dist/test/history-module-realstore.test.js.map +1 -0
- package/dist/test/history-module-semantic.test.d.ts +2 -0
- package/dist/test/history-module-semantic.test.d.ts.map +1 -0
- package/dist/test/history-module-semantic.test.js +213 -0
- package/dist/test/history-module-semantic.test.js.map +1 -0
- package/dist/test/history-module-ux.test.d.ts +2 -0
- package/dist/test/history-module-ux.test.d.ts.map +1 -0
- package/dist/test/history-module-ux.test.js +304 -0
- package/dist/test/history-module-ux.test.js.map +1 -0
- package/dist/test/history-module.test.js +25 -1
- package/dist/test/history-module.test.js.map +1 -1
- package/dist/test/history-semantic-review-findings.test.d.ts +2 -0
- package/dist/test/history-semantic-review-findings.test.d.ts.map +1 -0
- package/dist/test/history-semantic-review-findings.test.js +338 -0
- package/dist/test/history-semantic-review-findings.test.js.map +1 -0
- package/dist/test/history-semantic-sync-converges.test.d.ts +2 -0
- package/dist/test/history-semantic-sync-converges.test.d.ts.map +1 -0
- package/dist/test/history-semantic-sync-converges.test.js +109 -0
- package/dist/test/history-semantic-sync-converges.test.js.map +1 -0
- package/dist/test/mcpl-capability-grant.test.js +3 -1
- package/dist/test/mcpl-capability-grant.test.js.map +1 -1
- package/dist/test/mcpl-child-env.test.d.ts +2 -0
- package/dist/test/mcpl-child-env.test.d.ts.map +1 -0
- package/dist/test/mcpl-child-env.test.js +69 -0
- package/dist/test/mcpl-child-env.test.js.map +1 -0
- package/dist/test/mcpl-coalescing.test.d.ts +2 -0
- package/dist/test/mcpl-coalescing.test.d.ts.map +1 -0
- package/dist/test/mcpl-coalescing.test.js +1050 -0
- package/dist/test/mcpl-coalescing.test.js.map +1 -0
- package/dist/test/mcpl-list-status.test.js +4 -0
- package/dist/test/mcpl-list-status.test.js.map +1 -1
- package/dist/test/mcpl-tool-class-listing.test.d.ts +2 -0
- package/dist/test/mcpl-tool-class-listing.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-class-listing.test.js +147 -0
- package/dist/test/mcpl-tool-class-listing.test.js.map +1 -0
- package/dist/test/mcpl-tool-classes.test.d.ts +7 -0
- package/dist/test/mcpl-tool-classes.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-classes.test.js +80 -0
- package/dist/test/mcpl-tool-classes.test.js.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +2 -0
- package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js +237 -0
- package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +11 -0
- package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js +307 -0
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -0
- package/dist/test/mcpl-tool-lifecycle.test.d.ts +11 -0
- package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-lifecycle.test.js +270 -0
- package/dist/test/mcpl-tool-lifecycle.test.js.map +1 -0
- package/dist/test/silent-heartbeat.test.d.ts +2 -0
- package/dist/test/silent-heartbeat.test.d.ts.map +1 -0
- package/dist/test/silent-heartbeat.test.js +154 -0
- package/dist/test/silent-heartbeat.test.js.map +1 -0
- package/dist/test/tool-result-guard.test.d.ts +2 -0
- package/dist/test/tool-result-guard.test.d.ts.map +1 -0
- package/dist/test/tool-result-guard.test.js +1081 -0
- package/dist/test/tool-result-guard.test.js.map +1 -0
- package/dist/test/trunk-channel-routing.test.js +67 -0
- package/dist/test/trunk-channel-routing.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/agent.ts +128 -10
- package/src/api/types.ts +2 -0
- package/src/code-execution/py-runner.ts +34 -2
- package/src/code-execution/tool-definition.ts +46 -0
- package/src/framework.ts +1201 -80
- package/src/index.ts +13 -0
- package/src/mcpl/capability-grant.ts +54 -6
- package/src/mcpl/channel-registry.ts +115 -7
- package/src/mcpl/index.ts +29 -0
- package/src/mcpl/push-coalescer.ts +821 -0
- package/src/mcpl/push-handler.ts +40 -4
- package/src/mcpl/server-connection.ts +48 -2
- package/src/mcpl/tool-classes.ts +188 -0
- package/src/mcpl/tool-glob.ts +36 -0
- package/src/mcpl/tool-lifecycle.ts +730 -0
- package/src/mcpl/transport.ts +35 -1
- package/src/mcpl/types.ts +69 -1
- package/src/modules/discord/index.ts +1 -1
- package/src/modules/history/index.ts +1089 -24
- package/src/modules/history/semantic.ts +473 -0
- package/src/modules/index.ts +2 -1
- package/src/tool-result-guard.ts +318 -0
- package/src/types/agent.ts +15 -0
- package/src/types/framework.ts +23 -0
- package/src/types/trace.ts +17 -0
package/src/index.ts
CHANGED
|
@@ -93,6 +93,19 @@ export type { McplServerConfig } from './mcpl/index.js';
|
|
|
93
93
|
export { McplServerConnection } from './mcpl/index.js';
|
|
94
94
|
export type { McplHostCapabilities } from './mcpl/index.js';
|
|
95
95
|
|
|
96
|
+
// MCPL tool lifecycle (RFC-007) and tool classes (RFC-008): the types a host
|
|
97
|
+
// needs to write McplServerConfig.toolLifecycle policy and class tables, and
|
|
98
|
+
// the portable pattern matcher both use.
|
|
99
|
+
export { TOOL_CLASSES, DEFAULT_INPUT_CLASSES, globMatch } from './mcpl/index.js';
|
|
100
|
+
export type {
|
|
101
|
+
ToolClass,
|
|
102
|
+
ToolClassSource,
|
|
103
|
+
ToolLifecycleConfig,
|
|
104
|
+
ToolLifecycleNarrowing,
|
|
105
|
+
ToolLifecycleParams,
|
|
106
|
+
ToolObserveRule,
|
|
107
|
+
} from './mcpl/index.js';
|
|
108
|
+
|
|
96
109
|
// Per-channel conversation routing
|
|
97
110
|
export { ConversationRouter, DEFAULT_CLOSURE_PROMPT } from './mcpl/index.js';
|
|
98
111
|
export type {
|
|
@@ -48,6 +48,9 @@ const VOCABULARY: CapNode = {
|
|
|
48
48
|
modelInfo: null,
|
|
49
49
|
inferenceRequest: { streaming: null },
|
|
50
50
|
inferenceLifecycle: null,
|
|
51
|
+
// RFC-007: metadata observation and argument observation are separate
|
|
52
|
+
// leaves, separately granted (observation never bundles content).
|
|
53
|
+
toolLifecycle: { observe: null, inputs: null },
|
|
51
54
|
contextHooks: {
|
|
52
55
|
beforeInference: {
|
|
53
56
|
observe: null,
|
|
@@ -217,8 +220,36 @@ export function capabilityPatternMatches(pattern: string, path: string): boolean
|
|
|
217
220
|
// The grant
|
|
218
221
|
// ============================================================================
|
|
219
222
|
|
|
220
|
-
/** §13.4: denied unless explicitly enabled by host config.
|
|
221
|
-
|
|
223
|
+
/** §13.4: denied unless explicitly enabled by host config. RFC-007 adds both
|
|
224
|
+
* tool-lifecycle leaves: observing other servers' tool use (and, above all,
|
|
225
|
+
* their arguments) is an operator decision, never a default. */
|
|
226
|
+
const DENY_BY_DEFAULT: readonly string[] = [
|
|
227
|
+
'contextHooks.beforeInference.inject.system',
|
|
228
|
+
'toolLifecycle.observe',
|
|
229
|
+
'toolLifecycle.inputs',
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* RFC-007: stating a `toolLifecycle.observe` / `.inputs` policy block in the
|
|
234
|
+
* server's config is itself the explicit grant of that path — the operator
|
|
235
|
+
* who wrote a narrowing meant to grant what it narrows.
|
|
236
|
+
*/
|
|
237
|
+
function configGrants(config: Pick<McplServerConfig, 'toolLifecycle'>, path: string): boolean {
|
|
238
|
+
const key = path === 'toolLifecycle.observe' ? 'observe' : path === 'toolLifecycle.inputs' ? 'inputs' : null;
|
|
239
|
+
if (!key) return false;
|
|
240
|
+
const block = (config.toolLifecycle as Record<string, unknown> | undefined)?.[key];
|
|
241
|
+
if (block === undefined) return false;
|
|
242
|
+
// Only a policy OBJECT is a grant. `observe: false` (or null, or a string)
|
|
243
|
+
// must never read as "granted with no narrowing".
|
|
244
|
+
if (!isPlainObject(block)) {
|
|
245
|
+
console.error(
|
|
246
|
+
`[mcpl] toolLifecycle.${key} is ${JSON.stringify(block)}, not a policy object — ` +
|
|
247
|
+
`not granted by this block (RFC-007 §4.1)`,
|
|
248
|
+
);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
222
253
|
|
|
223
254
|
export class CapabilityGrant {
|
|
224
255
|
/** Exact granted paths (already expanded — no patterns stored). */
|
|
@@ -286,7 +317,7 @@ export class CapabilityGrant {
|
|
|
286
317
|
*/
|
|
287
318
|
export function computeGrant(
|
|
288
319
|
capabilities: McplCapabilities | null,
|
|
289
|
-
config: Pick<McplServerConfig, 'enabledCapabilities'>,
|
|
320
|
+
config: Pick<McplServerConfig, 'enabledCapabilities' | 'toolLifecycle'>,
|
|
290
321
|
opts?: {
|
|
291
322
|
/** §5.1: `tools` is sourced from the OUTER standard MCP
|
|
292
323
|
* `capabilities.tools` — pass its presence here. The experimental
|
|
@@ -304,16 +335,33 @@ export function computeGrant(
|
|
|
304
335
|
!!config.enabledCapabilities?.some((pat) => capabilityPatternMatches(pat, path));
|
|
305
336
|
|
|
306
337
|
for (const path of advertised) {
|
|
307
|
-
if (DENY_BY_DEFAULT.includes(path) && !explicitlyEnabled(path)) {
|
|
338
|
+
if (DENY_BY_DEFAULT.includes(path) && !explicitlyEnabled(path) && !configGrants(config, path)) {
|
|
308
339
|
denied.push(path);
|
|
309
340
|
console.error(
|
|
310
|
-
`[mcpl] "${path}" advertised but DENIED BY DEFAULT (SPEC §13.4) — ` +
|
|
311
|
-
`grant it explicitly via enabledCapabilities if intended`,
|
|
341
|
+
`[mcpl] "${path}" advertised but DENIED BY DEFAULT (${path.startsWith('toolLifecycle.') ? 'RFC-007' : 'SPEC §13.4'}) — ` +
|
|
342
|
+
`grant it explicitly via enabledCapabilities${path.startsWith('toolLifecycle.') ? ' or a toolLifecycle policy block' : ''} if intended`,
|
|
312
343
|
);
|
|
313
344
|
continue;
|
|
314
345
|
}
|
|
315
346
|
granted.add(path);
|
|
316
347
|
}
|
|
317
348
|
|
|
349
|
+
// RFC-007 §4.1/§4.3 diagnostics — loud, because each is a grant that
|
|
350
|
+
// silently delivers less than the operator probably meant.
|
|
351
|
+
if (granted.has('toolLifecycle.inputs')) {
|
|
352
|
+
if (!granted.has('toolLifecycle.observe')) {
|
|
353
|
+
console.error('[mcpl] toolLifecycle.inputs granted without toolLifecycle.observe — delivers nothing (RFC-007 §4.1)');
|
|
354
|
+
}
|
|
355
|
+
const n = config.toolLifecycle?.inputs;
|
|
356
|
+
const hasTerm = isPlainObject(n) && ((Array.isArray(n.tools) && n.tools.length > 0)
|
|
357
|
+
|| n.classes === 'default' || (Array.isArray(n.classes) && n.classes.length > 0));
|
|
358
|
+
if (!hasTerm) {
|
|
359
|
+
console.error(
|
|
360
|
+
'[mcpl] toolLifecycle.inputs granted with no tools/classes narrowing — treated as narrowed to NO tools; ' +
|
|
361
|
+
'inputs is never unconditional (RFC-007 §4.3)',
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
318
366
|
return new CapabilityGrant(granted, denied.sort());
|
|
319
367
|
}
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
ChannelsIncomingParams,
|
|
27
27
|
ChannelsIncomingResult,
|
|
28
28
|
ChannelIncomingMessageResult,
|
|
29
|
+
ChannelIncomingMessage,
|
|
29
30
|
ChannelsPublishParams,
|
|
30
31
|
ChannelsOpenResult,
|
|
31
32
|
ChannelHistoryRequest,
|
|
@@ -36,6 +37,7 @@ import type { McplServerRegistry } from './server-registry.js';
|
|
|
36
37
|
import type { FeatureSetManager } from './feature-set-manager.js';
|
|
37
38
|
import type { ToolDefinition, ToolResult, ProcessEvent } from '../types/index.js';
|
|
38
39
|
import { expandCoreTags } from './tags.js';
|
|
40
|
+
import { validateCoalescedContent } from './push-coalescer.js';
|
|
39
41
|
import { CapabilityGrant } from './capability-grant.js';
|
|
40
42
|
|
|
41
43
|
// ============================================================================
|
|
@@ -452,6 +454,17 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
|
|
|
452
454
|
// ============================================================================
|
|
453
455
|
|
|
454
456
|
interface ChannelRegistryOptions {
|
|
457
|
+
/**
|
|
458
|
+
* RFC-006: an admitted `channels/incoming` message carrying `coalesce`, with
|
|
459
|
+
* the event the ordinary path would have queued. The handler decides
|
|
460
|
+
* replace / append / withdraw and returns the per-message result. Throws a
|
|
461
|
+
* `CoalesceError` for a malformed or unauthorized occurrence.
|
|
462
|
+
*/
|
|
463
|
+
handleCoalescedIncoming?: (
|
|
464
|
+
serverId: string,
|
|
465
|
+
message: ChannelIncomingMessage,
|
|
466
|
+
event: McplChannelIncomingEvent,
|
|
467
|
+
) => Promise<ChannelIncomingMessageResult>;
|
|
455
468
|
/** Chronicle store used for durable desired channel lifecycle state. */
|
|
456
469
|
store?: JsStore;
|
|
457
470
|
/** Callback to determine whether an incoming message should trigger inference. */
|
|
@@ -643,6 +656,7 @@ export class ChannelRegistry {
|
|
|
643
656
|
/** One-time migration inputs from the retired recipe auto-open policy. */
|
|
644
657
|
private legacyPolicies = new Map<string, 'auto' | 'manual' | string[]>();
|
|
645
658
|
private migratedLegacyPolicies = new Set<string>();
|
|
659
|
+
private handleCoalescedIncoming?: ChannelRegistryOptions['handleCoalescedIncoming'];
|
|
646
660
|
|
|
647
661
|
constructor(
|
|
648
662
|
serverRegistry: McplServerRegistry,
|
|
@@ -658,6 +672,7 @@ export class ChannelRegistry {
|
|
|
658
672
|
) => void;
|
|
659
673
|
},
|
|
660
674
|
) {
|
|
675
|
+
this.handleCoalescedIncoming = options?.handleCoalescedIncoming;
|
|
661
676
|
this.serverRegistry = serverRegistry;
|
|
662
677
|
this.featureSetManager = featureSetManager;
|
|
663
678
|
this.pushEventFn = pushEventFn;
|
|
@@ -838,14 +853,23 @@ export class ChannelRegistry {
|
|
|
838
853
|
* Converts each message's content, pushes McplChannelIncomingEvent to the
|
|
839
854
|
* queue, and responds with per-message results.
|
|
840
855
|
*/
|
|
841
|
-
handleIncoming(
|
|
856
|
+
async handleIncoming(
|
|
842
857
|
serverId: string,
|
|
843
858
|
params: ChannelsIncomingParams,
|
|
844
859
|
responder?: Responder,
|
|
845
|
-
): void {
|
|
860
|
+
): Promise<void> {
|
|
846
861
|
const results: ChannelIncomingMessageResult[] = [];
|
|
847
862
|
|
|
848
863
|
for (const message of params.messages) {
|
|
864
|
+
if (!message || typeof message.channelId !== 'string' || !message.channelId
|
|
865
|
+
|| typeof message.messageId !== 'string' || !message.messageId) {
|
|
866
|
+
results.push({
|
|
867
|
+
messageId: typeof message?.messageId === 'string' ? message.messageId : '',
|
|
868
|
+
accepted: false,
|
|
869
|
+
reason: message?.coalesce !== undefined ? 'coalesce_invalid' : 'invalid channel/message identity',
|
|
870
|
+
});
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
849
873
|
// §14.5 FIRST, before ANY semantic processing: admission against the
|
|
850
874
|
// actually-registered channel precedes tag expansion and content
|
|
851
875
|
// conversion — decoding an unregistered sender's payload (including
|
|
@@ -890,6 +914,17 @@ export class ChannelRegistry {
|
|
|
890
914
|
|
|
891
915
|
// ACCEPTED from here down: semantic processing only for admitted
|
|
892
916
|
// messages. §16.3 core-tag closure, then content conversion.
|
|
917
|
+
const coalesced = message.coalesce !== undefined && !!this.handleCoalescedIncoming;
|
|
918
|
+
if (coalesced) {
|
|
919
|
+
// RFC-006 §13: malformed content on a coalesced item is that item's
|
|
920
|
+
// failure, not the batch's — check the shape before converting.
|
|
921
|
+
try {
|
|
922
|
+
validateCoalescedContent(message.content);
|
|
923
|
+
} catch (error) {
|
|
924
|
+
results.push({ messageId: message.messageId, accepted: false, reason: 'coalesce_invalid' });
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
893
928
|
if (message.tags) message.tags = expandCoreTags(message.tags);
|
|
894
929
|
const convertedContent: ContentBlock[] = message.content.map(convertBlock);
|
|
895
930
|
|
|
@@ -897,16 +932,19 @@ export class ChannelRegistry {
|
|
|
897
932
|
// deliberately after §14.5 validation: a rejected message from an
|
|
898
933
|
// unregistered channel must not retarget outbound speech (the locus is
|
|
899
934
|
// exactly the authority a self-attested channel would be stealing).
|
|
900
|
-
|
|
901
|
-
this
|
|
902
|
-
|
|
903
|
-
|
|
935
|
+
// A coalesced item is "accepted" only once the coalescer admits it, so
|
|
936
|
+
// for those this runs after the hook (below).
|
|
937
|
+
const markAccepted = () => {
|
|
938
|
+
this.defaultPublishChannel = message.channelId;
|
|
939
|
+
this.defaultPublishMessageId = message.messageId;
|
|
940
|
+
this.defaultPublishThreadId = message.threadId;
|
|
904
941
|
// A server sending channels/incoming is authoritative evidence that
|
|
905
942
|
// the transport is actually open. This repairs transient status only;
|
|
906
943
|
// durable desired state still changes exclusively through lifecycle
|
|
907
944
|
// operations.
|
|
908
945
|
this.channels.get(incomingKey)!.open = true;
|
|
909
|
-
}
|
|
946
|
+
};
|
|
947
|
+
if (!coalesced) markAccepted();
|
|
910
948
|
|
|
911
949
|
// Determine whether to trigger inference
|
|
912
950
|
let triggerInference = true;
|
|
@@ -945,6 +983,26 @@ export class ChannelRegistry {
|
|
|
945
983
|
triggerInference,
|
|
946
984
|
};
|
|
947
985
|
|
|
986
|
+
if (coalesced) {
|
|
987
|
+
// RFC-006 §14.3: a coalesced message is admitted like any other and
|
|
988
|
+
// then handed, with the event the ordinary path would have queued, to
|
|
989
|
+
// the coalescer, which replaces, appends or withdraws. Malformed
|
|
990
|
+
// `coalesce` is a per-message failure; siblings are unaffected.
|
|
991
|
+
try {
|
|
992
|
+
const result = await this.handleCoalescedIncoming!(serverId, message, event);
|
|
993
|
+
if (result.accepted) markAccepted();
|
|
994
|
+
results.push(result);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
const err = error as Error & { code?: number };
|
|
997
|
+
results.push({
|
|
998
|
+
messageId: message.messageId,
|
|
999
|
+
accepted: false,
|
|
1000
|
+
reason: err.code === -32602 ? 'coalesce_invalid' : err.message,
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
948
1006
|
// Push to the processing queue
|
|
949
1007
|
// Cast through unknown because McplChannelIncomingEvent matches the
|
|
950
1008
|
// CustomEvent `${string}:${string}` type pattern but lacks an index signature.
|
|
@@ -1155,6 +1213,18 @@ export class ChannelRegistry {
|
|
|
1155
1213
|
* Get the descriptor for a channel by its channelId (first match across
|
|
1156
1214
|
* servers). Used by the conversation router for DM classification.
|
|
1157
1215
|
*/
|
|
1216
|
+
/**
|
|
1217
|
+
* RFC-006 §3.2: is `channelId` registered BY `serverId` through a server
|
|
1218
|
+
* declaration (channels/register, channels/changed) or an authorized open?
|
|
1219
|
+
* A placeholder minted from a push's `origin` (ensureChannelRegistered)
|
|
1220
|
+
* does not count: a channel id appearing in an untrusted field is not a
|
|
1221
|
+
* registration.
|
|
1222
|
+
*/
|
|
1223
|
+
isDeclaredChannel(serverId: string, channelId: string): boolean {
|
|
1224
|
+
const entry = this.channels.get(`${serverId}:${channelId}`);
|
|
1225
|
+
return !!entry && !(entry.descriptor.metadata as { lazyRegistered?: boolean } | undefined)?.lazyRegistered;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1158
1228
|
getDescriptor(channelId: string): ChannelDescriptor | undefined {
|
|
1159
1229
|
return this.findChannelEntry(channelId)?.descriptor;
|
|
1160
1230
|
}
|
|
@@ -2375,6 +2445,44 @@ export class ChannelRegistry {
|
|
|
2375
2445
|
return { error: `no channel matches "${trimmed}"`, ...(near.length ? { candidates: near } : {}) };
|
|
2376
2446
|
}
|
|
2377
2447
|
|
|
2448
|
+
/**
|
|
2449
|
+
* The `>>` target to show an agent for a channel: one whitespace-free token
|
|
2450
|
+
* that `resolveProseTarget()` maps back to this same channel. The prefix
|
|
2451
|
+
* grammar takes the target as the first non-whitespace run, so a label with
|
|
2452
|
+
* a space can't be quoted verbatim: `>>#DM: alice` parses as target `#DM:`
|
|
2453
|
+
* plus body `alice …`, and `>>#fable (antra's server)` delivers
|
|
2454
|
+
* `(antra's server)` as text. Tried in order: `@name` for a DM, `#label`,
|
|
2455
|
+
* `#name` (label without its server suffix), the descriptor id.
|
|
2456
|
+
*
|
|
2457
|
+
* Undefined when there is no safe token: the channel isn't registered (on
|
|
2458
|
+
* `serverId`, when given), no candidate is whitespace-free and resolves back,
|
|
2459
|
+
* or the id is registered by more than one server. A resolved target names a
|
|
2460
|
+
* channel by id alone, and ids are unique only within a connection, so a
|
|
2461
|
+
* shared id could route the reply through the wrong server.
|
|
2462
|
+
*/
|
|
2463
|
+
proseTargetFor(channelId: string, serverId?: string): string | undefined {
|
|
2464
|
+
const sameId = [...this.channels.values()].filter((e) => e.descriptor.id === channelId);
|
|
2465
|
+
const entry = serverId ? sameId.find((e) => e.serverId === serverId) : sameId[0];
|
|
2466
|
+
if (!entry || sameId.length > 1) return undefined;
|
|
2467
|
+
const d = entry.descriptor;
|
|
2468
|
+
const label = d.label ?? '';
|
|
2469
|
+
const meta = d.metadata as { channelType?: string; recipientName?: string } | undefined;
|
|
2470
|
+
const isDm = meta?.channelType === 'dm' || label.toLowerCase().startsWith('dm: ') || d.id.includes(':dm:');
|
|
2471
|
+
const dmName = meta?.recipientName ?? (label.toLowerCase().startsWith('dm: ') ? label.slice(4) : undefined);
|
|
2472
|
+
const bare = label.replace(/^#/, '');
|
|
2473
|
+
const candidates = [
|
|
2474
|
+
...(isDm && dmName ? [`@${dmName}`] : []),
|
|
2475
|
+
...(bare ? [`#${bare}`, `#${bare.replace(/\s*\([^)]*\)\s*$/, '')}`] : []),
|
|
2476
|
+
d.id,
|
|
2477
|
+
];
|
|
2478
|
+
for (const c of candidates) {
|
|
2479
|
+
if (/\s/.test(c) || c === '#') continue;
|
|
2480
|
+
const r = this.resolveProseTarget(c);
|
|
2481
|
+
if ('channelId' in r && r.channelId === d.id) return c;
|
|
2482
|
+
}
|
|
2483
|
+
return undefined;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2378
2486
|
/**
|
|
2379
2487
|
* Open a channel because something was DELIVERED into it (explicit send
|
|
2380
2488
|
* tool or routed speech). Sending into a closed channel is not a thing:
|
package/src/mcpl/index.ts
CHANGED
|
@@ -118,6 +118,35 @@ export { ScopeManager, type ElevationHandler } from './scope-manager.js';
|
|
|
118
118
|
// Hook orchestration (beforeInference/afterInference fan-out)
|
|
119
119
|
export { HookOrchestrator } from './hook-orchestrator.js';
|
|
120
120
|
|
|
121
|
+
export {
|
|
122
|
+
ToolLifecycleEmitter,
|
|
123
|
+
parseToolObserveParams,
|
|
124
|
+
selectFields,
|
|
125
|
+
boundInput,
|
|
126
|
+
openingFor,
|
|
127
|
+
DEFAULT_MAX_INPUT_BYTES,
|
|
128
|
+
TOOL_OBSERVE_LIMITS,
|
|
129
|
+
type ToolPhase,
|
|
130
|
+
type ToolLifecycleParams,
|
|
131
|
+
type ToolObserveRule,
|
|
132
|
+
type ToolObserveMatch,
|
|
133
|
+
type ToolLifecycleConfig,
|
|
134
|
+
type ToolLifecycleNarrowing,
|
|
135
|
+
} from './tool-lifecycle.js';
|
|
136
|
+
export {
|
|
137
|
+
TOOL_CLASSES,
|
|
138
|
+
TOOL_CLASS_META_KEY,
|
|
139
|
+
DEFAULT_INPUT_CLASSES,
|
|
140
|
+
BUILTIN_TOOL_CLASSES,
|
|
141
|
+
parseDeclaredClasses,
|
|
142
|
+
resolveToolClass,
|
|
143
|
+
isToolClass,
|
|
144
|
+
type ToolClass,
|
|
145
|
+
type ToolClassSource,
|
|
146
|
+
type EffectiveToolClass,
|
|
147
|
+
} from './tool-classes.js';
|
|
148
|
+
export { globMatch } from './tool-glob.js';
|
|
149
|
+
|
|
121
150
|
// Push events (Section 9)
|
|
122
151
|
export { PushHandler } from './push-handler.js';
|
|
123
152
|
export type { McplPushEvent } from './push-handler.js';
|