@animalabs/agent-framework 0.31.0 → 0.33.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 +204 -14
- package/dist/src/admission/admit.d.ts +18 -0
- package/dist/src/admission/admit.d.ts.map +1 -1
- package/dist/src/admission/admit.js +16 -3
- package/dist/src/admission/admit.js.map +1 -1
- package/dist/src/agent.d.ts +185 -78
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +279 -102
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +50 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +4 -20
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +24 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/config-checks.d.ts +7 -0
- package/dist/src/config-checks.d.ts.map +1 -1
- package/dist/src/config-checks.js +14 -0
- package/dist/src/config-checks.js.map +1 -1
- package/dist/src/context-manager-errors.d.ts +25 -0
- package/dist/src/context-manager-errors.d.ts.map +1 -0
- package/dist/src/context-manager-errors.js +31 -0
- package/dist/src/context-manager-errors.js.map +1 -0
- package/dist/src/context-options.d.ts +67 -0
- package/dist/src/context-options.d.ts.map +1 -0
- package/dist/src/context-options.js +85 -0
- package/dist/src/context-options.js.map +1 -0
- package/dist/src/deferred-import.d.ts +93 -0
- package/dist/src/deferred-import.d.ts.map +1 -0
- package/dist/src/deferred-import.js +177 -0
- package/dist/src/deferred-import.js.map +1 -0
- package/dist/src/dendrite/audience.d.ts +223 -0
- package/dist/src/dendrite/audience.d.ts.map +1 -0
- package/dist/src/dendrite/audience.js +202 -0
- package/dist/src/dendrite/audience.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +5 -3
- package/dist/src/dendrite/index.d.ts.map +1 -1
- package/dist/src/dendrite/index.js +2 -1
- package/dist/src/dendrite/index.js.map +1 -1
- package/dist/src/dendrite/presets.d.ts +10 -5
- package/dist/src/dendrite/presets.d.ts.map +1 -1
- package/dist/src/dendrite/presets.js +17 -5
- package/dist/src/dendrite/presets.js.map +1 -1
- package/dist/src/dendrite/registry.d.ts +62 -7
- package/dist/src/dendrite/registry.d.ts.map +1 -1
- package/dist/src/dendrite/registry.js +179 -10
- package/dist/src/dendrite/registry.js.map +1 -1
- package/dist/src/dendrite/types.d.ts +91 -8
- package/dist/src/dendrite/types.d.ts.map +1 -1
- package/dist/src/foreign-rounds.d.ts +69 -0
- package/dist/src/foreign-rounds.d.ts.map +1 -0
- package/dist/src/foreign-rounds.js +152 -0
- package/dist/src/foreign-rounds.js.map +1 -0
- package/dist/src/framework.d.ts +1977 -820
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +7571 -3457
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +85 -15
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +421 -47
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/rule-scope.d.ts +30 -0
- package/dist/src/gate/rule-scope.d.ts.map +1 -0
- package/dist/src/gate/rule-scope.js +41 -0
- package/dist/src/gate/rule-scope.js.map +1 -0
- package/dist/src/gate/suppress-script-worker.d.ts +2 -0
- package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
- package/dist/src/gate/suppress-script-worker.js +103 -0
- package/dist/src/gate/suppress-script-worker.js.map +1 -0
- package/dist/src/gate/suppress-scripts.d.ts +105 -0
- package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
- package/dist/src/gate/suppress-scripts.js +403 -0
- package/dist/src/gate/suppress-scripts.js.map +1 -0
- package/dist/src/gate/suppression-review.d.ts +143 -0
- package/dist/src/gate/suppression-review.d.ts.map +1 -0
- package/dist/src/gate/suppression-review.js +463 -0
- package/dist/src/gate/suppression-review.js.map +1 -0
- package/dist/src/gate/suppression-stats.d.ts +125 -0
- package/dist/src/gate/suppression-stats.d.ts.map +1 -0
- package/dist/src/gate/suppression-stats.js +214 -0
- package/dist/src/gate/suppression-stats.js.map +1 -0
- package/dist/src/gate/types.d.ts +135 -7
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/gate/wake-onboarding.d.ts +119 -0
- package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
- package/dist/src/gate/wake-onboarding.js +277 -0
- package/dist/src/gate/wake-onboarding.js.map +1 -0
- package/dist/src/held-writes.d.ts +31 -23
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +44 -59
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- 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/jsonrpc-envelope.d.ts +69 -0
- package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
- package/dist/src/jsonrpc-envelope.js +66 -0
- package/dist/src/jsonrpc-envelope.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +131 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +290 -46
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +2 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +6 -6
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -22
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +105 -15
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -7
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +13 -10
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +134 -56
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.js +3 -0
- package/dist/src/mcpl/tool-classes.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +8 -2
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +24 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -18
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +45 -8
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +237 -86
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +59 -0
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +432 -67
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +6 -5
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +15 -22
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +1 -1
- package/dist/src/modules/workspace/index.d.ts +28 -7
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +76 -22
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/notice-tags.d.ts +10 -1
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +10 -1
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/operator-log.d.ts +36 -3
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js +2 -2
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/presentation.d.ts +746 -0
- package/dist/src/presentation.d.ts.map +1 -0
- package/dist/src/presentation.js +1435 -0
- package/dist/src/presentation.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +15 -11
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +16 -12
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/read-marks.d.ts +192 -0
- package/dist/src/read-marks.d.ts.map +1 -0
- package/dist/src/read-marks.js +242 -0
- package/dist/src/read-marks.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +91 -6
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +273 -29
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +22 -3
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +5 -9
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +40 -7
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/stored-injections.d.ts +2 -2
- package/dist/src/stored-injections.js +2 -2
- package/dist/src/stored-removal.d.ts +17 -4
- package/dist/src/stored-removal.d.ts.map +1 -1
- package/dist/src/stored-removal.js +29 -5
- package/dist/src/stored-removal.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +12 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +42 -8
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +1159 -79
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +3709 -363
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +114 -17
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +87 -27
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/tune-out/window-anchor.d.ts +143 -0
- package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
- package/dist/src/tune-out/window-anchor.js +202 -0
- package/dist/src/tune-out/window-anchor.js.map +1 -0
- package/dist/src/types/agent.d.ts +86 -31
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +2 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -21
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +180 -69
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +510 -36
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +32 -3
- package/src/agent.ts +323 -120
- package/src/api/mcp-server-core.ts +54 -17
- package/src/api/server.ts +4 -23
- package/src/code-execution/isolate/scripts.ts +28 -3
- package/src/config-checks.ts +14 -0
- package/src/context-manager-errors.ts +37 -0
- package/src/context-options.ts +96 -0
- package/src/deferred-import.ts +239 -0
- package/src/dendrite/audience.ts +315 -0
- package/src/dendrite/index.ts +14 -2
- package/src/dendrite/presets.ts +18 -6
- package/src/dendrite/registry.ts +209 -12
- package/src/dendrite/types.ts +92 -8
- package/src/foreign-rounds.ts +199 -0
- package/src/framework.ts +7814 -3585
- package/src/gate/event-gate.ts +428 -65
- package/src/gate/index.ts +3 -0
- package/src/gate/rule-scope.ts +45 -0
- package/src/gate/suppress-script-worker.ts +89 -0
- package/src/gate/suppress-scripts.ts +414 -0
- package/src/gate/suppression-review.ts +517 -0
- package/src/gate/suppression-stats.ts +314 -0
- package/src/gate/types.ts +141 -7
- package/src/gate/wake-onboarding.ts +309 -0
- package/src/held-writes.ts +49 -60
- package/src/index.ts +9 -2
- package/src/jsonrpc-envelope.ts +81 -0
- package/src/mcpl/channel-registry.ts +353 -85
- package/src/mcpl/checkpoint-manager.ts +3 -2
- package/src/mcpl/feature-set-manager.ts +2 -1
- package/src/mcpl/inbound-source.ts +2 -1
- package/src/mcpl/inference-router.ts +7 -4
- package/src/mcpl/push-coalescer.ts +193 -34
- package/src/mcpl/push-handler.ts +10 -5
- package/src/mcpl/server-connection.ts +141 -60
- package/src/mcpl/tool-classes.ts +3 -0
- package/src/mcpl/types.ts +8 -2
- package/src/module-registry.ts +73 -17
- package/src/modules/discord/index.ts +229 -81
- package/src/modules/history/index.ts +439 -70
- package/src/modules/history/semantic.ts +15 -21
- package/src/modules/workspace/disk-agreement.ts +1 -1
- package/src/modules/workspace/index.ts +85 -23
- package/src/notice-tags.ts +10 -1
- package/src/operator-log.ts +36 -3
- package/src/presentation.ts +1676 -0
- package/src/prose-drafts.ts +16 -12
- package/src/read-marks.ts +328 -0
- package/src/recovery/offline-branch.ts +346 -32
- package/src/recovery/recover-cli.ts +21 -3
- package/src/send-tools.ts +40 -7
- package/src/stored-injections.ts +2 -2
- package/src/stored-removal.ts +31 -7
- package/src/tool-result-guard.ts +41 -7
- package/src/tune-out/coordinator.ts +4089 -457
- package/src/tune-out/tools.ts +160 -43
- package/src/tune-out/window-anchor.ts +277 -0
- package/src/types/agent.ts +90 -31
- package/src/types/events.ts +2 -0
- package/src/types/framework.ts +43 -21
- package/src/types/module.ts +181 -68
- package/src/types/trace.ts +542 -40
- package/dist/src/reasoning-loss-notice.d.ts +0 -23
- package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
- package/dist/src/reasoning-loss-notice.js +0 -52
- package/dist/src/reasoning-loss-notice.js.map +0 -1
- package/dist/src/reasoning-loss.d.ts +0 -24
- package/dist/src/reasoning-loss.d.ts.map +0 -1
- package/dist/src/reasoning-loss.js +0 -102
- package/dist/src/reasoning-loss.js.map +0 -1
- package/src/reasoning-loss-notice.ts +0 -64
- package/src/reasoning-loss.ts +0 -120
|
@@ -16,6 +16,7 @@ import { maskNegotiatedCapabilities } from './capability-mask.js';
|
|
|
16
16
|
import { CapabilityGrant, expandAdvertisementShorthand } from './capability-grant.js';
|
|
17
17
|
import { CAPABILITY_DISABLED, boundedForLog, describeCause } from './errors.js';
|
|
18
18
|
import { logDetail, logValue, logValueRedacted, loggedCode } from './log-value.js';
|
|
19
|
+
import { jsonKind, readEnvelope } from '../jsonrpc-envelope.js';
|
|
19
20
|
import {
|
|
20
21
|
LEGACY_MCP_PROTOCOL_VERSION,
|
|
21
22
|
MODERN_MCP_PROTOCOL_VERSION,
|
|
@@ -367,11 +368,32 @@ interface HeldItem {
|
|
|
367
368
|
kept?: true;
|
|
368
369
|
}
|
|
369
370
|
|
|
370
|
-
/**
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
/** Whether a response that can't be matched to a request of ours, its id
|
|
372
|
+
* being neither a string nor a number or missing, is logged: only a
|
|
373
|
+
* JSON-RPC 2.0 error is, since that's how a server says it couldn't read a
|
|
374
|
+
* message of ours (JSON-RPC 2.0 §5). Anything else id-less, such as a
|
|
375
|
+
* server's own JSON log line with `"id": null`, is ignored silently, as it
|
|
376
|
+
* was before #475, so a server's log output can't fill the host's. */
|
|
377
|
+
function isJsonRpcError(response: object): boolean {
|
|
378
|
+
return (response as { jsonrpc?: unknown }).jsonrpc === '2.0' && Object.hasOwn(response, 'error');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** The log line for such an error. The host's own request ids are numbers,
|
|
382
|
+
* so it can't be matched to one. Once connected it may be the answer to a
|
|
383
|
+
* request of ours, which gets nothing from it and ends as any unanswered
|
|
384
|
+
* request does, by its timeout where it has one. (During the handshake an
|
|
385
|
+
* error with id null or no id answers initialize.) Its error member is the
|
|
386
|
+
* server's text, whatever JSON value it is, so it's printed redacted with
|
|
387
|
+
* `redact`, the server's redaction (logValueRedacted), as handleResponse
|
|
388
|
+
* prints a matched error's code and message. */
|
|
389
|
+
function unmatchedResponse(
|
|
390
|
+
serverId: string,
|
|
391
|
+
response: { id?: unknown; error?: unknown },
|
|
392
|
+
redact: (text: string) => string,
|
|
393
|
+
): string {
|
|
394
|
+
const which = Object.hasOwn(response, 'id') ? `whose id is ${jsonKind(response.id)}` : 'with no id';
|
|
395
|
+
return `[mcpl] ${serverId}: ignored a response ${which}, which can't be matched to a request of ours; ` +
|
|
396
|
+
`its error: ${logValueRedacted(response.error, redact)}`;
|
|
375
397
|
}
|
|
376
398
|
|
|
377
399
|
/**
|
|
@@ -700,13 +722,14 @@ export class McplServerConnection extends EventEmitter {
|
|
|
700
722
|
// Reconnect state (adapted from Anarchid/agent-framework@mcpl-module-proto)
|
|
701
723
|
private config: McplServerConfig | null = null;
|
|
702
724
|
/** The redactor of the server's text for `config`, made at its first error
|
|
703
|
-
* answer, by when `config`
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
* something new
|
|
725
|
+
* answer or response it can't match (handleResponse), by when `config`
|
|
726
|
+
* is set, and kept: a framework-launched child's `env` is a getter that
|
|
727
|
+
* reads the awareness ledger, so a redactor built per answer would read
|
|
728
|
+
* it per answer (#540's review, R1-2). An answer takes one build of it
|
|
729
|
+
* for its words, data and log line (`now()`), so it reads the ledger at
|
|
730
|
+
* most once. It builds again only after a build that failed, which
|
|
731
|
+
* withholds that answer's text, or once a dial notes something new
|
|
732
|
+
* (serverTextRedactor). */
|
|
710
733
|
private redactor: ServerTextRedactor | null = null;
|
|
711
734
|
private hostCapabilities: McplHostCapabilities | null = null;
|
|
712
735
|
private reconnectEnabled = false;
|
|
@@ -1358,15 +1381,35 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1358
1381
|
// initialize response, whatever its id: a server's own ids can start
|
|
1359
1382
|
// at 0, this handshake's. MCP lets a server ping before
|
|
1360
1383
|
// initialization completes, and the ping gets its empty result, as
|
|
1361
|
-
// on a connected transport
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1384
|
+
// on a connected transport, `"id": null` included
|
|
1385
|
+
// (src/jsonrpc-envelope.ts). Anything else it sends now is ignored,
|
|
1386
|
+
// an invalid request included. Of its responses, the one with
|
|
1387
|
+
// initialize's id answers it, and so does a JSON-RPC 2.0 error with
|
|
1388
|
+
// id null or with no id: a peer answers a request it couldn't read
|
|
1389
|
+
// that way (JSON-RPC 2.0 §5; MCP 2025-11-25, Basic), initialize is
|
|
1390
|
+
// the only request this host has out, and a peer never answers a
|
|
1391
|
+
// response. Such an error means the server can't read this host's
|
|
1392
|
+
// messages, so the handshake fails at once, with the error's message.
|
|
1393
|
+
// `"jsonrpc": "2.0"` keeps a server's own JSON log line with an
|
|
1394
|
+
// error key and id null, or no id, from failing it; one with
|
|
1395
|
+
// initialize's id is taken for the answer, whatever its jsonrpc. Of
|
|
1396
|
+
// the other responses, a JSON-RPC 2.0 error whose id is neither a
|
|
1397
|
+
// string nor a number is logged, as on a connected transport, and
|
|
1398
|
+
// any other is ignored.
|
|
1399
|
+
const envelope = readEnvelope(msg as unknown as Record<string, unknown>);
|
|
1400
|
+
if (envelope.kind === 'request' && envelope.method === 'ping') {
|
|
1401
|
+
transport.writeLine(JSON.stringify({ jsonrpc: '2.0', id: envelope.id, result: {} }));
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (envelope.kind !== 'response') return;
|
|
1405
|
+
const namesNoRequest = msg.id === null || !Object.hasOwn(msg, 'id');
|
|
1406
|
+
const answersInitialize = msg.id === initId || (namesNoRequest && msg.jsonrpc === '2.0' && Boolean(msg.error));
|
|
1407
|
+
if (!answersInitialize) {
|
|
1408
|
+
if (typeof msg.id !== 'string' && typeof msg.id !== 'number' && isJsonRpcError(msg)) {
|
|
1409
|
+
console.error(unmatchedResponse(config.id, msg, (text) => redactServerText(text, config)));
|
|
1366
1410
|
}
|
|
1367
1411
|
return;
|
|
1368
1412
|
}
|
|
1369
|
-
if (msg.id !== initId) return;
|
|
1370
1413
|
cleanup();
|
|
1371
1414
|
if (msg.error) {
|
|
1372
1415
|
// describeCause rather than the template's own conversion: the
|
|
@@ -1739,8 +1782,9 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1739
1782
|
* following `nextCursor` across pages (2024-11-05 pagination). Cursors are
|
|
1740
1783
|
* opaque strings, `''` included: only an absent or null `nextCursor` ends
|
|
1741
1784
|
* the inventory (a null one is how some servers say "no more", and main
|
|
1742
|
-
* read it as a complete page). A page that isn't well formed (no `tools` array,
|
|
1743
|
-
* that isn't a string
|
|
1785
|
+
* read it as a complete page). A page that isn't well formed (no `tools` array, an entry
|
|
1786
|
+
* that isn't an object with a string `name`, a cursor that isn't a string), or cursors
|
|
1787
|
+
* that don't terminate (a repeat, or more
|
|
1744
1788
|
* than {@link MAX_TOOL_LIST_PAGES} pages), are errors, never a partial or
|
|
1745
1789
|
* empty inventory published as complete. The read tap (RFC-014) is handed
|
|
1746
1790
|
* the complete inventory once, with the last page's `_meta`, as the last
|
|
@@ -1755,9 +1799,12 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1755
1799
|
let first: Record<string, unknown> | undefined;
|
|
1756
1800
|
const read: McpToolDefinition[] = [];
|
|
1757
1801
|
const revs = new Set<unknown>();
|
|
1802
|
+
const isTool = (entry: unknown): boolean =>
|
|
1803
|
+
!!entry && typeof entry === 'object' && typeof (entry as { name?: unknown }).name === 'string';
|
|
1758
1804
|
const onRead = (result: unknown) => {
|
|
1759
1805
|
if (!result || typeof result !== 'object' || !Array.isArray((result as { tools?: unknown }).tools)) return;
|
|
1760
1806
|
const r = result as { tools: McpToolDefinition[]; nextCursor?: unknown; _meta?: unknown };
|
|
1807
|
+
if (!r.tools.every(isTool)) return;
|
|
1761
1808
|
for (const t of r.tools) read.push(t);
|
|
1762
1809
|
const rev = (r._meta as Record<string, unknown> | undefined)?.['mcpl/rev'];
|
|
1763
1810
|
if (typeof rev === 'number') revs.add(rev);
|
|
@@ -1774,6 +1821,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1774
1821
|
if (!result || typeof result !== 'object' || !Array.isArray(result.tools)) {
|
|
1775
1822
|
throw malformed('no tools array');
|
|
1776
1823
|
}
|
|
1824
|
+
const unreadable = (result.tools as unknown[]).findIndex((entry) => !isTool(entry));
|
|
1825
|
+
if (unreadable !== -1) throw malformed(`entry ${unreadable + 1} isn't an object with a string name`);
|
|
1777
1826
|
first ??= result;
|
|
1778
1827
|
for (const t of result.tools as McpToolDefinition[]) tools.push(t);
|
|
1779
1828
|
if (result.nextCursor === undefined || result.nextCursor === null) {
|
|
@@ -2389,10 +2438,10 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2389
2438
|
// passed is the dead process's (afterBoundary).
|
|
2390
2439
|
const epoch = this.transportEpoch;
|
|
2391
2440
|
transport.on('line', (line: string) => {
|
|
2392
|
-
const msg = messageFromLine(this.id, line, transport)
|
|
2441
|
+
const msg = messageFromLine(this.id, line, transport);
|
|
2393
2442
|
if (!msg) return;
|
|
2394
2443
|
try {
|
|
2395
|
-
this.dispatchMessage(msg, epoch);
|
|
2444
|
+
this.dispatchMessage(msg as Record<string, unknown>, epoch);
|
|
2396
2445
|
} catch (error) {
|
|
2397
2446
|
const request = msg as { method?: unknown; id?: unknown };
|
|
2398
2447
|
this.reportInboundFailure(error, {
|
|
@@ -2404,25 +2453,47 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2404
2453
|
}
|
|
2405
2454
|
|
|
2406
2455
|
/** Contain synchronous dispatch failures; async RPC handlers own their promises. */
|
|
2407
|
-
private dispatchMessage(msg:
|
|
2408
|
-
//
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2456
|
+
private dispatchMessage(msg: Record<string, unknown>, epoch: number): void {
|
|
2457
|
+
// The envelope is read once, here (src/jsonrpc-envelope.ts says how).
|
|
2458
|
+
const envelope = readEnvelope(msg);
|
|
2459
|
+
switch (envelope.kind) {
|
|
2460
|
+
case 'response': {
|
|
2461
|
+
// Never answered, so a server that answers what it can't read, with
|
|
2462
|
+
// id null, can't start an exchange of errors with us. One read after
|
|
2463
|
+
// its transport's boundary is late (handleResponse).
|
|
2464
|
+
const late = epoch !== this.transportEpoch;
|
|
2465
|
+
// RFC-014: its hints apply as it's read, whether or not anyone still
|
|
2466
|
+
// waits for it. A dead transport's revs are its own, so a response
|
|
2467
|
+
// read after its boundary is never tapped.
|
|
2468
|
+
if (!late) this.tapResponse(msg as unknown as JsonRpcResponse);
|
|
2469
|
+
this.handleResponse(msg as unknown as JsonRpcResponse, late);
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
case 'neither':
|
|
2473
|
+
// Not JSON-RPC: ignored like the server's other unrelated output.
|
|
2474
|
+
return;
|
|
2475
|
+
case 'invalid': {
|
|
2476
|
+
// Answered on the transport it came on, as every request is. One
|
|
2477
|
+
// read after that transport's boundary is the dead process's and
|
|
2478
|
+
// isn't answered: sendErrorResponse logs the answer it didn't send.
|
|
2479
|
+
this.sendErrorResponse(envelope.id, envelope.error.code, envelope.error.message, undefined, epoch);
|
|
2480
|
+
if (epoch !== this.transportEpoch) return;
|
|
2481
|
+
const forMethod = envelope.method !== undefined ? ` for method ${logValue(envelope.method)}` : '';
|
|
2482
|
+
console.error(`[mcpl] ${this.id}: answered -32600 to an invalid request${forMethod}: ${envelope.problems}`);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2417
2485
|
}
|
|
2418
2486
|
|
|
2419
|
-
// It is an inbound request or notification from the server
|
|
2420
|
-
|
|
2487
|
+
// It is an inbound request or notification from the server, its method
|
|
2488
|
+
// a string. A request's id is a string, a number or null, and `"id": null`
|
|
2489
|
+
// is answered with id null; a notification has none.
|
|
2490
|
+
const request = msg as unknown as JsonRpcRequest;
|
|
2491
|
+
const requestId = envelope.kind === 'request' ? envelope.id : undefined;
|
|
2421
2492
|
|
|
2422
2493
|
// Read after its transport's boundary, it is the dead process's: the
|
|
2423
2494
|
// boundary's rule applies (afterBoundary).
|
|
2424
2495
|
if (epoch !== this.transportEpoch) {
|
|
2425
|
-
this.afterBoundary(request, epoch);
|
|
2496
|
+
this.afterBoundary(request, requestId, epoch);
|
|
2426
2497
|
return;
|
|
2427
2498
|
}
|
|
2428
2499
|
|
|
@@ -2432,7 +2503,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2432
2503
|
// paused plane mustn't hold that answer. A ping sent as a notification
|
|
2433
2504
|
// has nothing to answer. The handshake answers one too.
|
|
2434
2505
|
if (request.method === 'ping') {
|
|
2435
|
-
if (
|
|
2506
|
+
if (requestId !== undefined) this.sendResponse(requestId, {});
|
|
2436
2507
|
return;
|
|
2437
2508
|
}
|
|
2438
2509
|
|
|
@@ -2443,11 +2514,11 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2443
2514
|
if (request.method === 'push/event') this.tap({ kind: 'push', params: request.params });
|
|
2444
2515
|
else if (request.method === 'tools/hints' && this.readTap) {
|
|
2445
2516
|
this.tap({ kind: 'hints', params: request.params });
|
|
2446
|
-
if (
|
|
2517
|
+
if (requestId !== undefined) this.sendResponse(requestId, {});
|
|
2447
2518
|
return;
|
|
2448
2519
|
}
|
|
2449
2520
|
|
|
2450
|
-
const eventName =
|
|
2521
|
+
const eventName = Object.hasOwn(McplServerConnection.METHOD_TO_EVENT, request.method)
|
|
2451
2522
|
? McplServerConnection.METHOD_TO_EVENT[request.method] : undefined;
|
|
2452
2523
|
|
|
2453
2524
|
// §7 is removed in 0.5.0: scope/elevate is no longer a method. Answer
|
|
@@ -2459,8 +2530,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2459
2530
|
// routed, control-plane classified, and mutating declarations — a
|
|
2460
2531
|
// live ungated self-attestation path (PR #79 review blocker 4).
|
|
2461
2532
|
if (request.method === McplMethod.FeatureSetsChanged) {
|
|
2462
|
-
if (
|
|
2463
|
-
this.sendErrorResponse(
|
|
2533
|
+
if (requestId !== undefined) {
|
|
2534
|
+
this.sendErrorResponse(requestId, -32601, 'featureSets/changed removed in MCPL 0.5.0 (superseded by mcpl/manifestChanged, SPEC §17)');
|
|
2464
2535
|
} else {
|
|
2465
2536
|
console.error(`[mcpl] ${this.id}: discarded featureSets/changed — removed in 0.5.0 (§6.7/§17)`);
|
|
2466
2537
|
}
|
|
@@ -2468,8 +2539,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2468
2539
|
}
|
|
2469
2540
|
|
|
2470
2541
|
if (request.method === McplMethod.ScopeElevate) {
|
|
2471
|
-
if (
|
|
2472
|
-
this.sendErrorResponse(
|
|
2542
|
+
if (requestId !== undefined) {
|
|
2543
|
+
this.sendErrorResponse(requestId, -32601, 'scope/elevate removed in MCPL 0.5.0 (SPEC §7)');
|
|
2473
2544
|
}
|
|
2474
2545
|
return;
|
|
2475
2546
|
}
|
|
@@ -2481,20 +2552,19 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2481
2552
|
|
|
2482
2553
|
if (!eventName) {
|
|
2483
2554
|
// §6.6: a request that will never be answered MUST get an error, so a
|
|
2484
|
-
// request
|
|
2485
|
-
//
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
console.error(`[mcpl] ${this.id}: answered -32601 to a request for ${describeMethod(request.method)}, which this host doesn't handle`);
|
|
2555
|
+
// request whose method isn't one this host handles is answered -32601.
|
|
2556
|
+
// A notification nobody handles is ignored, as JSON-RPC asks.
|
|
2557
|
+
if (requestId !== undefined) {
|
|
2558
|
+
this.sendErrorResponse(requestId, -32601, 'Method not found');
|
|
2559
|
+
console.error(`[mcpl] ${this.id}: answered -32601 to a request for method ${logValue(request.method)}, which this host doesn't handle`);
|
|
2490
2560
|
}
|
|
2491
2561
|
return;
|
|
2492
2562
|
}
|
|
2493
2563
|
|
|
2494
2564
|
// Emit the typed event with params and (for requests) a respond callback
|
|
2495
|
-
if (
|
|
2565
|
+
if (requestId !== undefined) {
|
|
2496
2566
|
// Server expects a response — provide a respond helper
|
|
2497
|
-
this.dispatchInboundEvent(eventName, request.params, this.responderFor(
|
|
2567
|
+
this.dispatchInboundEvent(eventName, request.params, this.responderFor(requestId, epoch));
|
|
2498
2568
|
} else {
|
|
2499
2569
|
// Notification — no response expected
|
|
2500
2570
|
this.dispatchInboundEvent(eventName, request.params);
|
|
@@ -2504,9 +2574,10 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2504
2574
|
/**
|
|
2505
2575
|
* A request's responder, bound to the transport it came on (`epoch`): an
|
|
2506
2576
|
* answer, or a streamed inference's chunk, sent after that transport has
|
|
2507
|
-
* closed reaches nothing, never a server that didn't ask it.
|
|
2577
|
+
* closed reaches nothing, never a server that didn't ask it. A request
|
|
2578
|
+
* sent with id null is answered with id null.
|
|
2508
2579
|
*/
|
|
2509
|
-
private responderFor(id: string | number, epoch: number) {
|
|
2580
|
+
private responderFor(id: string | number | null, epoch: number) {
|
|
2510
2581
|
return {
|
|
2511
2582
|
id,
|
|
2512
2583
|
signal: epoch === this.transportEpoch ? this.transportEnded.signal : AbortSignal.abort(),
|
|
@@ -2531,17 +2602,17 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2531
2602
|
* it while its writer lives, with no grant to come, so it drops and logs
|
|
2532
2603
|
* all of it; hold's bounds cap what any other keeps.
|
|
2533
2604
|
*/
|
|
2534
|
-
private afterBoundary(request: JsonRpcRequest, epoch: number): void {
|
|
2535
|
-
const event =
|
|
2605
|
+
private afterBoundary(request: JsonRpcRequest, requestId: string | number | null | undefined, epoch: number): void {
|
|
2606
|
+
const event = Object.hasOwn(McplServerConnection.METHOD_TO_EVENT, request.method)
|
|
2536
2607
|
? McplServerConnection.METHOD_TO_EVENT[request.method] : undefined;
|
|
2537
2608
|
if (this.closedForGood || event === undefined || !McplServerConnection.WORLD_EVENTS.has(event)) {
|
|
2538
2609
|
console.error(
|
|
2539
2610
|
`[mcpl] ${this.id}: dropped a message read after its transport closed` +
|
|
2540
|
-
`${this.closedForGood ? ', with no transport to come' : ''}: ${
|
|
2611
|
+
`${this.closedForGood ? ', with no transport to come' : ''}: method ${logValue(request.method)}`,
|
|
2541
2612
|
);
|
|
2542
2613
|
return;
|
|
2543
2614
|
}
|
|
2544
|
-
this.hold(event,
|
|
2615
|
+
this.hold(event, requestId !== undefined ? [request.params, this.responderFor(requestId, epoch)] : [request.params], true);
|
|
2545
2616
|
}
|
|
2546
2617
|
|
|
2547
2618
|
/** Live delivery and delayed plane flushes share the same containment boundary. */
|
|
@@ -2596,7 +2667,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2596
2667
|
/** Tap a response to a hinted request (RFC-014): its result's hints, or a
|
|
2597
2668
|
* list's statement, apply now, awaited or not. */
|
|
2598
2669
|
private tapResponse(response: JsonRpcResponse): void {
|
|
2599
|
-
if (!this.readTap) return;
|
|
2670
|
+
if (!this.readTap || response.id === null) return;
|
|
2600
2671
|
const sent = this.hintedRequests.get(response.id);
|
|
2601
2672
|
if (!sent) return;
|
|
2602
2673
|
this.hintedRequests.delete(response.id);
|
|
@@ -2609,6 +2680,16 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2609
2680
|
* Handle a JSON-RPC response by resolving/rejecting the corresponding pending request.
|
|
2610
2681
|
*/
|
|
2611
2682
|
private handleResponse(response: JsonRpcResponse, late = false): void {
|
|
2683
|
+
// A response whose id is neither a string nor a number, or that has
|
|
2684
|
+
// none, can't be matched to a request of ours, on this transport or an
|
|
2685
|
+
// earlier one. It isn't an orphan, which is a late answer to one, so
|
|
2686
|
+
// it's ignored, and logged when it's a JSON-RPC 2.0 error.
|
|
2687
|
+
if (typeof response.id !== 'string' && typeof response.id !== 'number') {
|
|
2688
|
+
if (isJsonRpcError(response)) {
|
|
2689
|
+
console.error(unmatchedResponse(this.id, response, (this.redactor ??= serverTextRedactor(this.config ?? {})).now()));
|
|
2690
|
+
}
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2612
2693
|
// One read after its transport's boundary answers a request the close
|
|
2613
2694
|
// ended. It's matched against none of this connection's request records,
|
|
2614
2695
|
// since the next transport numbers its requests afresh, and goes the way
|
|
@@ -2706,7 +2787,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2706
2787
|
/**
|
|
2707
2788
|
* Send a successful JSON-RPC response back to the server.
|
|
2708
2789
|
*/
|
|
2709
|
-
private sendResponse(id: string | number, result: unknown, epoch = this.transportEpoch): void {
|
|
2790
|
+
private sendResponse(id: string | number | null, result: unknown, epoch = this.transportEpoch): void {
|
|
2710
2791
|
// A closed connection's epoch is past every request's, so a refusal
|
|
2711
2792
|
// while it is closed is logged too.
|
|
2712
2793
|
if (!this.sameTransport(id, epoch) || this.closed) return;
|
|
@@ -2717,7 +2798,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2717
2798
|
/** Whether a response to a request received on `epoch` may go out now.
|
|
2718
2799
|
* The id is the server's, so it's logged as a field; `answer`, which the
|
|
2719
2800
|
* caller has formatted for the log, ends the line. */
|
|
2720
|
-
private sameTransport(id: string | number, epoch: number, answer?: string): boolean {
|
|
2801
|
+
private sameTransport(id: string | number | null, epoch: number, answer?: string): boolean {
|
|
2721
2802
|
if (epoch === this.transportEpoch) return true;
|
|
2722
2803
|
console.error(
|
|
2723
2804
|
`[mcpl] ${this.id}: not answering request ${logValue(id)}: the transport it came on has closed${answer ? `, with ${answer}` : ''}`,
|
|
@@ -2729,7 +2810,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2729
2810
|
* Send a JSON-RPC error response back to the server.
|
|
2730
2811
|
*/
|
|
2731
2812
|
private sendErrorResponse(
|
|
2732
|
-
id: string | number,
|
|
2813
|
+
id: string | number | null,
|
|
2733
2814
|
code: number,
|
|
2734
2815
|
message: string,
|
|
2735
2816
|
data?: unknown,
|
package/src/mcpl/tool-classes.ts
CHANGED
|
@@ -87,8 +87,11 @@ export const BUILTIN_TOOL_CLASSES: Readonly<Record<string, readonly ToolClass[]>
|
|
|
87
87
|
wake: ['control'],
|
|
88
88
|
wake_add_rule: ['control'],
|
|
89
89
|
wake_remove_rule: ['control'],
|
|
90
|
+
wake_accept_suggestions: ['control'],
|
|
91
|
+
wake_decline: ['control'],
|
|
90
92
|
event_tags: ['control'],
|
|
91
93
|
prose_help: ['control'],
|
|
94
|
+
prose_locus: ['control'],
|
|
92
95
|
// Files and blobs in the agent's own stores.
|
|
93
96
|
save_recent_image: ['files'],
|
|
94
97
|
read_image: ['files'],
|
package/src/mcpl/types.ts
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
export interface JsonRpcRequest {
|
|
23
23
|
jsonrpc: '2.0';
|
|
24
24
|
method: string;
|
|
25
|
-
id
|
|
25
|
+
/** A request's id; null is allowed, though JSON-RPC 2.0 discourages it. */
|
|
26
|
+
id?: string | number | null;
|
|
26
27
|
params?: Record<string, unknown>;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -31,7 +32,8 @@ export interface JsonRpcRequest {
|
|
|
31
32
|
*/
|
|
32
33
|
export interface JsonRpcResponse {
|
|
33
34
|
jsonrpc: '2.0';
|
|
34
|
-
id
|
|
35
|
+
/** The request's id, or null when the request's own couldn't be read. */
|
|
36
|
+
id: string | number | null;
|
|
35
37
|
result?: unknown;
|
|
36
38
|
error?: JsonRpcError;
|
|
37
39
|
}
|
|
@@ -596,6 +598,10 @@ export interface FeatureSetDeclaration {
|
|
|
596
598
|
coreTags?: string[];
|
|
597
599
|
tags?: Record<string, unknown>;
|
|
598
600
|
keyed?: Record<string, unknown>;
|
|
601
|
+
/** Ordered suggested rules (RFC-001 §5.1). Advisory: applied only on
|
|
602
|
+
* acceptance (§5.2), which wake onboarding asks the agent for. */
|
|
603
|
+
suggestedTreatment?: unknown[];
|
|
604
|
+
/** @deprecated RFC-001 revision 1's name for suggestedTreatment; still read. */
|
|
599
605
|
defaultTreatment?: unknown[];
|
|
600
606
|
open?: boolean;
|
|
601
607
|
};
|
package/src/module-registry.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { JsStateRegistration, JsStore } from '@animalabs/chronicle';
|
|
2
2
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
3
3
|
import type {
|
|
4
|
+
ContextManager,
|
|
4
5
|
MessageId,
|
|
5
6
|
MessageMetadata,
|
|
6
7
|
MessageQuery,
|
|
7
8
|
ContextInjection,
|
|
9
|
+
StoredMessage,
|
|
10
|
+
WindowStatus,
|
|
8
11
|
} from '@animalabs/context-manager';
|
|
12
|
+
import { WriteOutcomeUnknownError } from './context-manager-errors.js';
|
|
9
13
|
import type {
|
|
10
14
|
Module,
|
|
11
15
|
ModuleContext,
|
|
@@ -25,7 +29,6 @@ import type {
|
|
|
25
29
|
ModuleContextInjection,
|
|
26
30
|
MessageQueryResult,
|
|
27
31
|
} from './types/index.js';
|
|
28
|
-
import { heldWriteId } from './held-writes.js';
|
|
29
32
|
import { boundedErrorMessage } from './error-message.js';
|
|
30
33
|
import type { Agent } from './agent.js';
|
|
31
34
|
import { DEFAULT_SHUTDOWN_TIMEOUT_MS, validateShutdownTimeout, waitForShutdown } from './shutdown.js';
|
|
@@ -150,6 +153,13 @@ export class ModuleRegistry {
|
|
|
150
153
|
message: string,
|
|
151
154
|
data?: Record<string, unknown>,
|
|
152
155
|
) => void;
|
|
156
|
+
/** The window lookups ModuleContext.windowStatusOf, observes, viewKeeps
|
|
157
|
+
* and agentNameOf give modules (#291), when the framework offers them.
|
|
158
|
+
* Readonly-public, as notifyOpsFn is. */
|
|
159
|
+
readonly windowStatusOfFn?: (id: MessageId, opts?: { forAgent?: string }) => WindowStatus;
|
|
160
|
+
readonly observesFn?: (observer: string, observed: string) => boolean;
|
|
161
|
+
readonly viewKeepsFn?: (row: StoredMessage, opts?: { forAgent?: string }) => boolean;
|
|
162
|
+
readonly agentNameOfFn?: (contextManager: ContextManager) => string | undefined;
|
|
153
163
|
|
|
154
164
|
constructor(
|
|
155
165
|
store: JsStore,
|
|
@@ -170,6 +180,14 @@ export class ModuleRegistry {
|
|
|
170
180
|
onTrace: (listener: TraceEventListener) => () => void;
|
|
171
181
|
callTool: (call: ToolCall) => Promise<ToolResult>;
|
|
172
182
|
notifyOps?: (kind: string, agentName: string, message: string, data?: Record<string, unknown>) => void;
|
|
183
|
+
/** Where a row stands with an agent's window (ModuleContext.windowStatusOf). */
|
|
184
|
+
windowStatusOf?: (id: MessageId, opts?: { forAgent?: string }) => WindowStatus;
|
|
185
|
+
/** Whether one agent observes another's own rows (ModuleContext.observes). */
|
|
186
|
+
observes?: (observer: string, observed: string) => boolean;
|
|
187
|
+
/** Whether an agent's view keeps a stored row (ModuleContext.viewKeeps). */
|
|
188
|
+
viewKeeps?: (row: StoredMessage, opts?: { forAgent?: string }) => boolean;
|
|
189
|
+
/** The live agent whose context a context manager is (ModuleContext.agentNameOf). */
|
|
190
|
+
agentNameOf?: (contextManager: ContextManager) => string | undefined;
|
|
173
191
|
}
|
|
174
192
|
) {
|
|
175
193
|
this.store = store;
|
|
@@ -184,6 +202,10 @@ export class ModuleRegistry {
|
|
|
184
202
|
this.onTraceFn = options.onTrace;
|
|
185
203
|
this.callToolFn = options.callTool;
|
|
186
204
|
this.notifyOpsFn = options.notifyOps;
|
|
205
|
+
this.windowStatusOfFn = options.windowStatusOf;
|
|
206
|
+
this.observesFn = options.observes;
|
|
207
|
+
this.viewKeepsFn = options.viewKeeps;
|
|
208
|
+
this.agentNameOfFn = options.agentNameOf;
|
|
187
209
|
this.dropRetiredPushFn = options.dropRetiredPush;
|
|
188
210
|
}
|
|
189
211
|
|
|
@@ -748,6 +770,12 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
748
770
|
private pushEventFn: (event: ProcessEvent, moduleName?: string) => void;
|
|
749
771
|
private onTraceFn: (listener: TraceEventListener) => () => void;
|
|
750
772
|
private callToolFn: (call: ToolCall) => Promise<ToolResult>;
|
|
773
|
+
/** Offered only when the registry was given the framework's lookups, so
|
|
774
|
+
* a module can tell a framework that has them from one that hasn't. */
|
|
775
|
+
readonly windowStatusOf?: (id: MessageId, opts?: { forAgent?: string }) => WindowStatus;
|
|
776
|
+
readonly observes?: (observer: string, observed: string) => boolean;
|
|
777
|
+
readonly viewKeeps?: (row: StoredMessage, opts?: { forAgent?: string }) => boolean;
|
|
778
|
+
readonly agentNameOf?: (contextManager: ContextManager) => string | undefined;
|
|
751
779
|
|
|
752
780
|
// External ID mapping stored in module state
|
|
753
781
|
private externalIdMap: Map<string, MessageId> = new Map();
|
|
@@ -794,6 +822,14 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
794
822
|
this.pushEventFn = pushEvent;
|
|
795
823
|
this.onTraceFn = onTrace;
|
|
796
824
|
this.callToolFn = callTool;
|
|
825
|
+
const windowStatusOf = registry.windowStatusOfFn;
|
|
826
|
+
if (windowStatusOf) this.windowStatusOf = (id, opts) => windowStatusOf(id, opts);
|
|
827
|
+
const observes = registry.observesFn;
|
|
828
|
+
if (observes) this.observes = (observer, observed) => observes(observer, observed);
|
|
829
|
+
const viewKeeps = registry.viewKeepsFn;
|
|
830
|
+
if (viewKeeps) this.viewKeeps = (row, opts) => viewKeeps(row, opts);
|
|
831
|
+
const agentNameOf = registry.agentNameOfFn;
|
|
832
|
+
if (agentNameOf) this.agentNameOf = (contextManager) => agentNameOf(contextManager);
|
|
797
833
|
|
|
798
834
|
// Load external ID map from state if exists
|
|
799
835
|
const state = this.getState<{ externalIdMap?: Record<string, string> }>();
|
|
@@ -951,15 +987,35 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
951
987
|
throw new Error(`A message for agent ${JSON.stringify(opts.forAgent)} can't carry metadata.external: `
|
|
952
988
|
+ "its external id would be looked up in the primary's window. Leave forAgent out, or the external id.");
|
|
953
989
|
}
|
|
954
|
-
|
|
990
|
+
let id: MessageId;
|
|
991
|
+
try {
|
|
992
|
+
id = this.addMessageFn(participant, content, metadata, opts);
|
|
993
|
+
} catch (error) {
|
|
994
|
+
// A write whose store threw where context-manager can't tell whether
|
|
995
|
+
// it landed (WriteOutcomeUnknownError, which the write is answered by)
|
|
996
|
+
// may be stored: its external id maps to '', as a write kept in memory
|
|
997
|
+
// does, so findMessageByExternalId looks it up in the store. A mapping
|
|
998
|
+
// that can't be persisted is logged, and the write's own error thrown.
|
|
999
|
+
if (metadata?.external && error instanceof WriteOutcomeUnknownError) {
|
|
1000
|
+
this.externalIdMap.set(`${metadata.external.source}:${metadata.external.id}`, '');
|
|
1001
|
+
try {
|
|
1002
|
+
this.persistExternalIdMap();
|
|
1003
|
+
} catch (persist) {
|
|
1004
|
+
console.error(`[module:${this.moduleName}] the external id of a write that may be stored could not be persisted:`, persist);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
throw error;
|
|
1008
|
+
}
|
|
955
1009
|
|
|
956
|
-
// Track external ID if provided
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
1010
|
+
// Track external ID if provided: the stored id the write was given
|
|
1011
|
+
// (#291 stores every write at once), or '' for one kept in memory (a
|
|
1012
|
+
// surgery's hold, or the wait for its agent's recovery), as earlier
|
|
1013
|
+
// releases kept '' for a write they held.
|
|
1014
|
+
// findMessageByExternalId finds such a write by its external id once it
|
|
1015
|
+
// is stored.
|
|
960
1016
|
if (metadata?.external) {
|
|
961
1017
|
const key = `${metadata.external.source}:${metadata.external.id}`;
|
|
962
|
-
this.externalIdMap.set(key,
|
|
1018
|
+
this.externalIdMap.set(key, id);
|
|
963
1019
|
// Persist the mapping
|
|
964
1020
|
this.persistExternalIdMap();
|
|
965
1021
|
}
|
|
@@ -991,15 +1047,16 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
991
1047
|
if (entry === undefined) return null;
|
|
992
1048
|
// A stored id answers while the store holds its row, and once the row is
|
|
993
1049
|
// gone the entry goes, as removeMessage drops it, except from a context
|
|
994
|
-
// whose module has stopped, which keeps its mapping as it was.
|
|
995
|
-
// write
|
|
996
|
-
//
|
|
997
|
-
//
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1050
|
+
// whose module has stopped, which keeps its mapping as it was. A '' here
|
|
1051
|
+
// names no row: a write kept in memory (a surgery's hold, or the wait for
|
|
1052
|
+
// its agent's recovery), or one an earlier release held, whose map it
|
|
1053
|
+
// persisted with ''. So the message is looked up in the store by its
|
|
1054
|
+
// external id, and the first row found is taken, as context-manager's
|
|
1055
|
+
// findByExternalId takes it, and its id becomes the entry (but in a
|
|
1056
|
+
// stopped module's context). With nothing found, '' stays: in a module's
|
|
1057
|
+
// start(), a write an earlier release left queued isn't stored yet, since
|
|
1058
|
+
// create() imports those after the modules start, and a write that shows
|
|
1059
|
+
// later finds its entry.
|
|
1003
1060
|
if (entry !== '') {
|
|
1004
1061
|
if (this.getMessageFn(entry)) return entry;
|
|
1005
1062
|
if (!this.retired) {
|
|
@@ -1009,7 +1066,6 @@ class ModuleContextImpl implements ModuleContext {
|
|
|
1009
1066
|
return null;
|
|
1010
1067
|
}
|
|
1011
1068
|
const row = this.queryMessagesFn({ source, externalIds: [externalId] }).messages[0];
|
|
1012
|
-
if (row?.held) return row.id;
|
|
1013
1069
|
if (!row) return null;
|
|
1014
1070
|
if (!this.retired) {
|
|
1015
1071
|
this.externalIdMap.set(key, row.id);
|