@animalabs/agent-framework 0.8.0 → 0.10.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 +11 -1
- 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 +184 -18
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1323 -246
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +73 -7
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +399 -22
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/types.d.ts +56 -0
- package/dist/src/gate/types.d.ts.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-grammar.d.ts +11 -0
- package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
- package/dist/src/mcpl/prose-grammar.js +21 -0
- package/dist/src/mcpl/prose-grammar.js.map +1 -1
- package/dist/src/mcpl/prose-stream-router.d.ts +11 -2
- package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
- package/dist/src/mcpl/prose-stream-router.js +73 -13
- 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/modules/workspace/index.d.ts +23 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +48 -0
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/sync.d.ts +11 -2
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +18 -8
- package/dist/src/modules/workspace/sync.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 +20 -0
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +26 -5
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/types/agent.d.ts +39 -1
- 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-passthrough.test.d.ts +15 -0
- package/dist/test/event-gate-passthrough.test.d.ts.map +1 -0
- package/dist/test/event-gate-passthrough.test.js +289 -0
- package/dist/test/event-gate-passthrough.test.js.map +1 -0
- 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/explicit-prose-routing.test.js +137 -7
- package/dist/test/explicit-prose-routing.test.js.map +1 -1
- package/dist/test/fixtures/awareness-barrier-mcpl-server.mjs +276 -0
- package/dist/test/fixtures/flaky-mcpl-server.mjs +41 -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/prose-stream-router.test.js +43 -0
- package/dist/test/prose-stream-router.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 +472 -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/test/xml-round-persist.test.d.ts +19 -0
- package/dist/test/xml-round-persist.test.d.ts.map +1 -0
- package/dist/test/xml-round-persist.test.js +235 -0
- package/dist/test/xml-round-persist.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/agent.ts +38 -4
- package/src/framework.ts +1488 -272
- package/src/gate/event-gate.ts +427 -24
- package/src/gate/types.ts +58 -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-grammar.ts +21 -0
- package/src/mcpl/prose-stream-router.ts +65 -14
- 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/modules/workspace/index.ts +48 -0
- package/src/modules/workspace/sync.ts +30 -7
- package/src/refusal-reactions.ts +37 -0
- package/src/tool-result-history.ts +27 -5
- package/src/types/agent.ts +40 -1
- package/src/types/framework.ts +17 -0
- package/src/types/trace.ts +22 -0
package/src/mcpl/types.ts
CHANGED
|
@@ -99,22 +99,32 @@ export interface McplResourceContent {
|
|
|
99
99
|
* Parsed from the server's `initialize` response.
|
|
100
100
|
*/
|
|
101
101
|
export interface McplCapabilities {
|
|
102
|
-
/** MCPL protocol version (e.g., "0.
|
|
102
|
+
/** MCPL protocol version (e.g., "0.5") */
|
|
103
103
|
version: string;
|
|
104
104
|
|
|
105
|
+
/** §17.2 canonical content digest; absent = manifest fixed at initialize. */
|
|
106
|
+
revision?: string;
|
|
107
|
+
|
|
105
108
|
/** Server supports push/event */
|
|
106
109
|
pushEvents?: boolean;
|
|
107
110
|
|
|
108
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Context hooks — §5.1 recursive shape. Boolean `true` at any level is
|
|
113
|
+
* shorthand for every leaf beneath it. `afterInference` is REMOVED in
|
|
114
|
+
* 0.5.0 (replaced by metadata-only inference/lifecycle, §10.5).
|
|
115
|
+
*/
|
|
109
116
|
contextHooks?: {
|
|
110
|
-
beforeInference?: boolean
|
|
111
|
-
|
|
117
|
+
beforeInference?: boolean | {
|
|
118
|
+
observe?: boolean;
|
|
119
|
+
inject?: { system?: boolean; beforeUser?: boolean; afterUser?: boolean };
|
|
120
|
+
};
|
|
112
121
|
};
|
|
113
122
|
|
|
114
|
-
/** Server-initiated inference
|
|
115
|
-
inferenceRequest?: {
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
/** Server-initiated inference. `true` = the capability with no refinements. */
|
|
124
|
+
inferenceRequest?: boolean | { streaming?: boolean };
|
|
125
|
+
|
|
126
|
+
/** Server consumes inference/lifecycle notifications (§10.5). */
|
|
127
|
+
inferenceLifecycle?: boolean;
|
|
118
128
|
|
|
119
129
|
/** Server supports model/info requests */
|
|
120
130
|
modelInfo?: boolean;
|
|
@@ -122,8 +132,8 @@ export interface McplCapabilities {
|
|
|
122
132
|
/** Declared feature sets (keyed by feature set name) */
|
|
123
133
|
featureSets?: Record<string, FeatureSetDeclaration>;
|
|
124
134
|
|
|
125
|
-
/** Channel capabilities */
|
|
126
|
-
channels?: McplChannelCapabilities;
|
|
135
|
+
/** Channel capabilities — §14.1 object of leaves, or `true` for all. */
|
|
136
|
+
channels?: boolean | McplChannelCapabilities;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
/**
|
|
@@ -134,23 +144,28 @@ export interface McplHostCapabilities {
|
|
|
134
144
|
version: string;
|
|
135
145
|
pushEvents?: boolean;
|
|
136
146
|
contextHooks?: {
|
|
137
|
-
beforeInference?: boolean
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
streaming?: boolean;
|
|
147
|
+
beforeInference?: boolean | {
|
|
148
|
+
observe?: boolean;
|
|
149
|
+
inject?: { system?: boolean; beforeUser?: boolean; afterUser?: boolean };
|
|
150
|
+
};
|
|
142
151
|
};
|
|
152
|
+
inferenceRequest?: boolean | { streaming?: boolean };
|
|
153
|
+
inferenceLifecycle?: boolean;
|
|
154
|
+
modelInfo?: boolean;
|
|
143
155
|
featureSets?: boolean;
|
|
144
|
-
channels?: McplChannelCapabilities;
|
|
156
|
+
channels?: boolean | McplChannelCapabilities;
|
|
145
157
|
}
|
|
146
158
|
|
|
147
|
-
/** Channel
|
|
159
|
+
/** Channel capability leaves (§14.1). `observe` was the pre-0.5 name for
|
|
160
|
+
* what split into `incoming` — removed, not aliased. */
|
|
148
161
|
export interface McplChannelCapabilities {
|
|
149
162
|
register?: boolean;
|
|
150
|
-
publish?: boolean;
|
|
151
|
-
observe?: boolean;
|
|
152
163
|
lifecycle?: boolean;
|
|
164
|
+
publish?: boolean;
|
|
165
|
+
incoming?: boolean;
|
|
153
166
|
streaming?: boolean;
|
|
167
|
+
acknowledge?: boolean;
|
|
168
|
+
typing?: boolean;
|
|
154
169
|
}
|
|
155
170
|
|
|
156
171
|
// ============================================================================
|
|
@@ -209,6 +224,15 @@ export interface McplServerConfig {
|
|
|
209
224
|
*/
|
|
210
225
|
accessProvider?: () => Promise<string | null>;
|
|
211
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Host-owned authority for the `host/command` admin surface (undo / hide /
|
|
229
|
+
* unstick). DEFAULT FALSE: no §6.2 capability path exists for it, and the
|
|
230
|
+
* grant cannot confer it — only this config, decided by the operator, can
|
|
231
|
+
* (PR #79 review blocker 9). The one legitimate fleet user is
|
|
232
|
+
* discord-mcpl's slash-command relay.
|
|
233
|
+
*/
|
|
234
|
+
allowHostCommands?: boolean;
|
|
235
|
+
|
|
212
236
|
/** Feature sets to enable on connect */
|
|
213
237
|
enabledFeatureSets?: string[];
|
|
214
238
|
|
|
@@ -233,6 +257,37 @@ export interface McplServerConfig {
|
|
|
233
257
|
*/
|
|
234
258
|
disabledTools?: string[];
|
|
235
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Capability allow-list: dotted paths as the server advertises them in its
|
|
262
|
+
* initialize response (`pushEvents`, `contextHooks.beforeInference`,
|
|
263
|
+
* `contextHooks.afterInference`, `inferenceRequest`, `modelInfo`,
|
|
264
|
+
* `channels`, `channels.streaming`, …). `*` matches one dot-segment
|
|
265
|
+
* (feature-set pattern rules), and a pattern matching a parent
|
|
266
|
+
* (`contextHooks`) covers every flag beneath it.
|
|
267
|
+
*
|
|
268
|
+
* If set, only advertised capabilities matching at least one pattern
|
|
269
|
+
* survive the handshake; everything else behaves as if the server had
|
|
270
|
+
* never advertised it. This is the host-side gate on hook fan-out: a
|
|
271
|
+
* server whose `contextHooks.afterInference` is masked never receives
|
|
272
|
+
* turn text, no matter what it self-advertises. Masked server-initiated
|
|
273
|
+
* capabilities (`pushEvents`, `inferenceRequest`, whole-`channels`) are
|
|
274
|
+
* also enforced inbound — the connection rejects those methods with
|
|
275
|
+
* CAPABILITY_DISABLED (-32002).
|
|
276
|
+
*
|
|
277
|
+
* `disabledCapabilities` takes precedence on conflict. `version` and
|
|
278
|
+
* `featureSets` are not maskable here — feature sets have their own
|
|
279
|
+
* enable/disable knobs above.
|
|
280
|
+
*/
|
|
281
|
+
enabledCapabilities?: string[];
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Capability deny-list (same paths and wildcard syntax as
|
|
285
|
+
* enabledCapabilities). Wins over enabledCapabilities on conflict.
|
|
286
|
+
* `disabledCapabilities: ['contextHooks.*']` is the one-line "this server
|
|
287
|
+
* gets no hook visibility" switch.
|
|
288
|
+
*/
|
|
289
|
+
disabledCapabilities?: string[];
|
|
290
|
+
|
|
236
291
|
/** Scope configurations per feature set */
|
|
237
292
|
scopes?: Record<string, ScopeConfig>;
|
|
238
293
|
|
|
@@ -352,16 +407,51 @@ export interface FeatureSetDeclaration {
|
|
|
352
407
|
* Spec Section 6.7.
|
|
353
408
|
*/
|
|
354
409
|
export interface FeatureSetsUpdateParams {
|
|
355
|
-
/**
|
|
410
|
+
/**
|
|
411
|
+
* The effective capability grant (§5.4) — the sole normative allowlist.
|
|
412
|
+
* Full §6.2 paths. Absence of a path is denial. In the Request form an
|
|
413
|
+
* absent FIELD is a grant of nothing (§5.3 as pinned 2026-08-02), never
|
|
414
|
+
* "no change".
|
|
415
|
+
*/
|
|
416
|
+
effectiveCapabilities?: string[];
|
|
417
|
+
|
|
418
|
+
/** Advertised-but-denied paths. Diagnostic only (§5.4) — MUST NOT
|
|
419
|
+
* participate in any authorization decision on either side. */
|
|
420
|
+
deniedCapabilities?: string[];
|
|
421
|
+
|
|
422
|
+
/** Feature sets to enable. Absent = no constraint (derivation governs);
|
|
423
|
+
* present = allowlist (§5.3 as pinned). */
|
|
356
424
|
enabled?: string[];
|
|
357
425
|
|
|
358
426
|
/** Feature sets to disable */
|
|
359
427
|
disabled?: string[];
|
|
360
|
-
|
|
361
|
-
/** Scope configurations per feature set */
|
|
362
|
-
scopes?: Record<string, ScopeConfig>;
|
|
363
428
|
}
|
|
364
429
|
|
|
430
|
+
/**
|
|
431
|
+
* featureSets/update result — the degradation receipt (§6.7). Consequence
|
|
432
|
+
* testimony, never policy authority: the host MUST NOT widen any grant in
|
|
433
|
+
* response to anything in here.
|
|
434
|
+
*/
|
|
435
|
+
export type FeatureSetsUpdateResult =
|
|
436
|
+
| {
|
|
437
|
+
accepted: true;
|
|
438
|
+
/** Omitted when nothing degraded (§6.7). */
|
|
439
|
+
mode?: 'degraded';
|
|
440
|
+
unavailableFeatures?: Array<{
|
|
441
|
+
featureSet: string;
|
|
442
|
+
missingCapabilities: string[];
|
|
443
|
+
effect: string;
|
|
444
|
+
}>;
|
|
445
|
+
notes?: string[];
|
|
446
|
+
}
|
|
447
|
+
| {
|
|
448
|
+
accepted: false;
|
|
449
|
+
/** REQUIRED on refusal (§6.7): the server names which applies. */
|
|
450
|
+
fallback: 'mcp-only' | 'close';
|
|
451
|
+
missingCapabilities?: string[];
|
|
452
|
+
reason?: string;
|
|
453
|
+
};
|
|
454
|
+
|
|
365
455
|
/**
|
|
366
456
|
* featureSets/changed params (Server → Host, Notification).
|
|
367
457
|
* Spec Section 6.7.
|
|
@@ -556,18 +646,48 @@ export interface PushEventResult {
|
|
|
556
646
|
* Distinct from membrane's ModelInfo which tracks requested vs actual model.
|
|
557
647
|
* Spec Section 10.1.
|
|
558
648
|
*/
|
|
649
|
+
/**
|
|
650
|
+
* inference/lifecycle params (Host → Server, Notification). SPEC §10.5.
|
|
651
|
+
*
|
|
652
|
+
* BEST-EFFORT: the host attempts exactly one terminal phase per `started`
|
|
653
|
+
* on every exit path it controls, but an unacknowledged Notification cannot
|
|
654
|
+
* guarantee delivery — consumers MUST dedupe by inferenceId and MUST retain
|
|
655
|
+
* a safety timeout. Carries no content: `modifiedResponse` and the blocking
|
|
656
|
+
* hook form are gone with context/afterInference.
|
|
657
|
+
*/
|
|
658
|
+
export interface InferenceLifecycleParams {
|
|
659
|
+
inferenceId: string;
|
|
660
|
+
conversationId: string;
|
|
661
|
+
turnIndex: number;
|
|
662
|
+
phase: 'started' | 'completed' | 'aborted' | 'failed';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** mcpl/manifestChanged params (Server → Host, Notification). §17.3. */
|
|
666
|
+
export interface ManifestChangedParams {
|
|
667
|
+
/** §17.2 canonical content digest. Untrusted; equality-only. */
|
|
668
|
+
revision: string;
|
|
669
|
+
/** Subset of the three §17.1 domains. A hint the host MAY ignore. */
|
|
670
|
+
domains: Array<'capabilities' | 'featureSets' | 'tagOntology'>;
|
|
671
|
+
}
|
|
672
|
+
|
|
559
673
|
export interface McplModelInfo {
|
|
560
674
|
/** Model identifier (e.g., "claude-opus-4-5-20251101") */
|
|
561
675
|
id: string;
|
|
562
676
|
|
|
563
|
-
/** Model vendor (e.g., "anthropic")
|
|
564
|
-
|
|
677
|
+
/** Model vendor (e.g., "anthropic"). Omitted when the host has no
|
|
678
|
+
* truthful source — never fabricated. */
|
|
679
|
+
vendor?: string;
|
|
565
680
|
|
|
566
|
-
/** Context window size in tokens
|
|
567
|
-
|
|
681
|
+
/** Context window size in tokens. Omitted when unknown (a numeric field
|
|
682
|
+
* cannot express "unknown", and a fabricated 200000 was false for
|
|
683
|
+
* residents configured at 300k/600k — PR #79 review). The §12.2
|
|
684
|
+
* model/info RESULT requires it, which is why this host does not
|
|
685
|
+
* advertise modelInfo; hook params carry honesty-over-completeness. */
|
|
686
|
+
contextWindow?: number;
|
|
568
687
|
|
|
569
|
-
/** Model capabilities (e.g., ["vision", "tools"])
|
|
570
|
-
|
|
688
|
+
/** Model capabilities (e.g., ["vision", "tools"]). Omitted when the
|
|
689
|
+
* host has no truthful source. */
|
|
690
|
+
capabilities?: string[];
|
|
571
691
|
}
|
|
572
692
|
|
|
573
693
|
/**
|
|
@@ -629,53 +749,6 @@ export interface BeforeInferenceResult {
|
|
|
629
749
|
contextInjections: McplContextInjection[];
|
|
630
750
|
}
|
|
631
751
|
|
|
632
|
-
/**
|
|
633
|
-
* context/afterInference params (Host → Server, Request or Notification).
|
|
634
|
-
* Spec Section 10.5.
|
|
635
|
-
*/
|
|
636
|
-
export interface AfterInferenceParams {
|
|
637
|
-
/** Inference identifier (matches beforeInference) */
|
|
638
|
-
inferenceId: string;
|
|
639
|
-
|
|
640
|
-
/** Persistent across turns */
|
|
641
|
-
conversationId: string;
|
|
642
|
-
|
|
643
|
-
/** 0-indexed turn number */
|
|
644
|
-
turnIndex: number;
|
|
645
|
-
|
|
646
|
-
/** Original user input */
|
|
647
|
-
userMessage: string | null;
|
|
648
|
-
|
|
649
|
-
/** Generated assistant response */
|
|
650
|
-
assistantMessage: string;
|
|
651
|
-
|
|
652
|
-
/** Model metadata */
|
|
653
|
-
model: McplModelInfo;
|
|
654
|
-
|
|
655
|
-
/** Token usage */
|
|
656
|
-
usage: {
|
|
657
|
-
inputTokens: number;
|
|
658
|
-
outputTokens: number;
|
|
659
|
-
cacheCreationTokens?: number;
|
|
660
|
-
cacheReadTokens?: number;
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* context/afterInference result (Server → Host, only for blocking hooks).
|
|
666
|
-
* Spec Section 10.5.
|
|
667
|
-
*/
|
|
668
|
-
export interface AfterInferenceResult {
|
|
669
|
-
/** Feature set that provided this response */
|
|
670
|
-
featureSet: string;
|
|
671
|
-
|
|
672
|
-
/** Modified response text (replaces assistantMessage if present) */
|
|
673
|
-
modifiedResponse?: string;
|
|
674
|
-
|
|
675
|
-
/** Arbitrary metadata */
|
|
676
|
-
metadata?: Record<string, unknown>;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
752
|
// ============================================================================
|
|
680
753
|
// Section 11 — Server-Initiated Inference
|
|
681
754
|
// ============================================================================
|
|
@@ -861,7 +934,14 @@ export interface ChannelsRegisterParams {
|
|
|
861
934
|
* channels/register result (Host → Server).
|
|
862
935
|
*/
|
|
863
936
|
export interface ChannelsRegisterResult {
|
|
937
|
+
/** Legacy pre-0.5 field: ids accepted. Kept for un-migrated servers. */
|
|
864
938
|
registered: string[];
|
|
939
|
+
/**
|
|
940
|
+
* §14.5 itemized results — one entry per SUBMITTED descriptor. 0.5
|
|
941
|
+
* servers key off this: a missing verdict is not a verdict, and a result
|
|
942
|
+
* without `results` is read by strict servers as accepting nothing.
|
|
943
|
+
*/
|
|
944
|
+
results: Array<{ id: string; accepted: boolean; reason?: string }>;
|
|
865
945
|
}
|
|
866
946
|
|
|
867
947
|
/**
|
|
@@ -1033,6 +1113,8 @@ export interface ChannelIncomingMessageResult {
|
|
|
1033
1113
|
messageId: string;
|
|
1034
1114
|
accepted: boolean;
|
|
1035
1115
|
conversationId?: string;
|
|
1116
|
+
/** §14.5: why a message was rejected (e.g. unknown/unregistered channel). */
|
|
1117
|
+
reason?: string;
|
|
1036
1118
|
}
|
|
1037
1119
|
|
|
1038
1120
|
// ============================================================================
|
|
@@ -1066,9 +1148,13 @@ export const McplMethod = {
|
|
|
1066
1148
|
// Push events (Server → Host)
|
|
1067
1149
|
PushEvent: 'push/event',
|
|
1068
1150
|
|
|
1069
|
-
// Context hooks (Host → Server)
|
|
1151
|
+
// Context hooks (Host → Server). context/afterInference is gone with the
|
|
1152
|
+
// rest of its surface: removed from the spec in 0.5.0 (§10.5, replaced by
|
|
1153
|
+
// inference/lifecycle), never sent by the runtime, and not part of the
|
|
1154
|
+
// package's public API. Pre-0.5 SERVERS' wire handlers for it are their
|
|
1155
|
+
// own compatibility surface, retired on the fleet-on-0.5 evidence
|
|
1156
|
+
// schedule, not here.
|
|
1070
1157
|
BeforeInference: 'context/beforeInference',
|
|
1071
|
-
AfterInference: 'context/afterInference',
|
|
1072
1158
|
|
|
1073
1159
|
// Server-initiated inference (Server → Host / Host → Server)
|
|
1074
1160
|
InferenceRequest: 'inference/request',
|
|
@@ -1077,6 +1163,18 @@ export const McplMethod = {
|
|
|
1077
1163
|
// Model info (Server → Host)
|
|
1078
1164
|
ModelInfo: 'model/info',
|
|
1079
1165
|
|
|
1166
|
+
// Inference lifecycle (Host → Server, Notification) — §10.5, replaces
|
|
1167
|
+
// context/afterInference. Metadata only; BEST-EFFORT delivery.
|
|
1168
|
+
InferenceLifecycle: 'inference/lifecycle',
|
|
1169
|
+
|
|
1170
|
+
// Server manifest changes (§17). manifestChanged is S→H Notification —
|
|
1171
|
+
// an opaque revision plus changed domains, NO payload, deliberately
|
|
1172
|
+
// ungated (§17.3: gating it would silence exactly the servers whose
|
|
1173
|
+
// grants just narrowed). manifest is H→S Request returning the complete
|
|
1174
|
+
// current experimental.mcpl object, never a delta.
|
|
1175
|
+
ManifestChanged: 'mcpl/manifestChanged',
|
|
1176
|
+
Manifest: 'mcpl/manifest',
|
|
1177
|
+
|
|
1080
1178
|
// Feature sets
|
|
1081
1179
|
FeatureSetsUpdate: 'featureSets/update',
|
|
1082
1180
|
FeatureSetsChanged: 'featureSets/changed',
|
|
@@ -1080,6 +1080,47 @@ export class WorkspaceModule implements Module {
|
|
|
1080
1080
|
return { data: blob };
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
|
+
/**
|
|
1084
|
+
* Read a file's raw bytes straight from the mount's filesystem, bypassing
|
|
1085
|
+
* the Chronicle tree.
|
|
1086
|
+
*
|
|
1087
|
+
* For material that is *passing through* rather than being kept: an upload,
|
|
1088
|
+
* a render being shipped somewhere, a file handed to a service. The store
|
|
1089
|
+
* path (`readBinary`) is append-only, so reading through it means every
|
|
1090
|
+
* byte is retained forever and the mount's `maxFileSize` guard applies —
|
|
1091
|
+
* correct for the resident's memory, wrong for egress, where paying a
|
|
1092
|
+
* permanent cost to send something once is the wrong trade.
|
|
1093
|
+
*
|
|
1094
|
+
* Same mount boundary and traversal guard as every other path here: only
|
|
1095
|
+
* declared mounts, nothing above their root. Deliberately does NOT sync,
|
|
1096
|
+
* register, or hash anything — it reads and returns.
|
|
1097
|
+
*/
|
|
1098
|
+
async readBinaryFromDisk(
|
|
1099
|
+
mountPrefixedPath: string,
|
|
1100
|
+
opts: { maxBytes?: number } = {},
|
|
1101
|
+
): Promise<{ data: Buffer; absolutePath: string } | { error: string }> {
|
|
1102
|
+
let mount: MountState;
|
|
1103
|
+
let relativePath: string;
|
|
1104
|
+
try {
|
|
1105
|
+
({ mount, relativePath } = this.parsePath(mountPrefixedPath));
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
1108
|
+
}
|
|
1109
|
+
const absolutePath = resolve(mount.config.path, relativePath);
|
|
1110
|
+
try {
|
|
1111
|
+
const info = await stat(absolutePath);
|
|
1112
|
+
if (!info.isFile()) return { error: `Not a file: ${mountPrefixedPath}` };
|
|
1113
|
+
if (opts.maxBytes !== undefined && info.size > opts.maxBytes) {
|
|
1114
|
+
return { error: `File too large: ${info.size} > ${opts.maxBytes} bytes` };
|
|
1115
|
+
}
|
|
1116
|
+
return { data: await readFile(absolutePath), absolutePath };
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
const code = (error as { code?: string }).code;
|
|
1119
|
+
if (code === 'ENOENT') return { error: `File not found on disk: ${mountPrefixedPath}` };
|
|
1120
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1083
1124
|
/**
|
|
1084
1125
|
* Parse a mount-prefixed path into (mountName, relativePath).
|
|
1085
1126
|
*/
|
|
@@ -1919,6 +1960,7 @@ export class WorkspaceModule implements Module {
|
|
|
1919
1960
|
private async handleSync(input: SyncInput): Promise<ToolResult> {
|
|
1920
1961
|
const store = this.getStore();
|
|
1921
1962
|
const allResults: Array<{ mount: string; synced: string[]; conflicts: ConflictInfo[] }> = [];
|
|
1963
|
+
const allSkipped: Array<{ mount: string; path: string; reason: string }> = [];
|
|
1922
1964
|
|
|
1923
1965
|
let mountsToSync: Array<{ name: string; mount: MountState }>;
|
|
1924
1966
|
if (input.mount) {
|
|
@@ -1951,6 +1993,11 @@ export class WorkspaceModule implements Module {
|
|
|
1951
1993
|
});
|
|
1952
1994
|
this.emitFsEvents(name, result.synced, result.conflicts);
|
|
1953
1995
|
}
|
|
1996
|
+
// Skips were previously computed and dropped, so "nothing synced" and
|
|
1997
|
+
// "your file was refused" looked identical from the outside. Say which.
|
|
1998
|
+
for (const s of result.skipped) {
|
|
1999
|
+
allSkipped.push({ mount: name, path: s.path, reason: s.reason });
|
|
2000
|
+
}
|
|
1954
2001
|
}
|
|
1955
2002
|
|
|
1956
2003
|
return {
|
|
@@ -1959,6 +2006,7 @@ export class WorkspaceModule implements Module {
|
|
|
1959
2006
|
results: allResults,
|
|
1960
2007
|
totalSynced: allResults.reduce((sum, r) => sum + r.synced.length, 0),
|
|
1961
2008
|
totalConflicts: allResults.reduce((sum, r) => sum + r.conflicts.length, 0),
|
|
2009
|
+
...(allSkipped.length > 0 ? { skipped: allSkipped } : {}),
|
|
1962
2010
|
},
|
|
1963
2011
|
};
|
|
1964
2012
|
}
|
|
@@ -40,6 +40,13 @@ export function hashContent(content: string | Buffer): string {
|
|
|
40
40
|
/**
|
|
41
41
|
* Check if content appears to be binary.
|
|
42
42
|
*/
|
|
43
|
+
export interface SkippedFile {
|
|
44
|
+
/** Relative path of the file that was not synced */
|
|
45
|
+
path: string;
|
|
46
|
+
/** Why, in words the resident can act on */
|
|
47
|
+
reason: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
export function isBinary(buffer: Buffer): boolean {
|
|
44
51
|
// Check for null bytes in first 8KB
|
|
45
52
|
const check = buffer.subarray(0, 8192);
|
|
@@ -68,8 +75,11 @@ export interface SyncResult {
|
|
|
68
75
|
synced: SyncedPath[];
|
|
69
76
|
/** Paths that conflicted (both agent and user modified) — filesystem wins */
|
|
70
77
|
conflicts: ConflictInfo[];
|
|
71
|
-
/** Paths that were skipped
|
|
72
|
-
|
|
78
|
+
/** Paths that were skipped, each with the reason — a silently skipped file
|
|
79
|
+
* is indistinguishable from one that synced cleanly, which is how a
|
|
80
|
+
* resident ends up believing a file is in their workspace when it never
|
|
81
|
+
* arrived. */
|
|
82
|
+
skipped: SkippedFile[];
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/**
|
|
@@ -93,7 +103,7 @@ export async function syncFromFs(
|
|
|
93
103
|
for (const relativePath of filesToSync) {
|
|
94
104
|
const absolutePath = safePath(mount.config.path, relativePath);
|
|
95
105
|
if (!absolutePath) {
|
|
96
|
-
result.skipped.push(relativePath);
|
|
106
|
+
result.skipped.push({ path: relativePath, reason: 'path resolves outside the mount' });
|
|
97
107
|
continue;
|
|
98
108
|
}
|
|
99
109
|
|
|
@@ -113,13 +123,23 @@ export async function syncFromFs(
|
|
|
113
123
|
const fileStat = await stat(absolutePath);
|
|
114
124
|
if (!fileStat.isFile()) continue;
|
|
115
125
|
if (fileStat.size > maxSize) {
|
|
116
|
-
result.skipped.push(
|
|
126
|
+
result.skipped.push({
|
|
127
|
+
path: relativePath,
|
|
128
|
+
reason: `too large: ${fileStat.size} bytes > maxFileSize ${maxSize}`,
|
|
129
|
+
});
|
|
117
130
|
continue;
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
const buffer = await readFile(absolutePath);
|
|
121
134
|
if (isBinary(buffer)) {
|
|
122
|
-
|
|
135
|
+
// Disk -> store sync is text-only, at ANY size: the tree stores text
|
|
136
|
+
// blobs. Binary that must live in the store has to be written through
|
|
137
|
+
// writeBinary; binary that is only passing through should be read with
|
|
138
|
+
// readBinaryFromDisk instead of synced at all.
|
|
139
|
+
result.skipped.push({
|
|
140
|
+
path: relativePath,
|
|
141
|
+
reason: 'binary file — disk sync stores text only (read it from disk, or write it via a binary-aware tool)',
|
|
142
|
+
});
|
|
123
143
|
continue;
|
|
124
144
|
}
|
|
125
145
|
|
|
@@ -159,8 +179,11 @@ export async function syncFromFs(
|
|
|
159
179
|
|
|
160
180
|
store.treeSet(mount.treeStateId, relativePath, entry);
|
|
161
181
|
result.synced.push({ path: relativePath, op: existing ? 'modified' : 'created' });
|
|
162
|
-
} catch {
|
|
163
|
-
result.skipped.push(
|
|
182
|
+
} catch (error) {
|
|
183
|
+
result.skipped.push({
|
|
184
|
+
path: relativePath,
|
|
185
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
186
|
+
});
|
|
164
187
|
}
|
|
165
188
|
}
|
|
166
189
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refusal-reaction markers — the single source of truth for the emoji the
|
|
3
|
+
* framework places on a message when inference is refused (see
|
|
4
|
+
* AgentFramework.reactToRefusal), exported so host composition can derive
|
|
5
|
+
* a protective suppression baseline from the exact set the framework emits.
|
|
6
|
+
*
|
|
7
|
+
* These annotations are placed by the framework on the resident's account
|
|
8
|
+
* and must never re-enter a resident's context as reaction events — that is
|
|
9
|
+
* the self-amplifying loop behind the 8/3 Mythos incident. The Discord
|
|
10
|
+
* adapter suppresses them when its operator config or the host-injected
|
|
11
|
+
* `DISCORD_SUPPRESSED_REACTIONS_BASELINE` names them; keeping the emitted
|
|
12
|
+
* set and the exported baseline as one constant is what makes drift between
|
|
13
|
+
* "what we stamp" and "what we suppress" structurally impossible, rather
|
|
14
|
+
* than a promise kept in two files.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Refusal category → Discord reaction emoji. Unknown categories get the
|
|
18
|
+
* fallback marker. */
|
|
19
|
+
export const REFUSAL_REACTIONS: Readonly<Record<string, string>> = {
|
|
20
|
+
bio: '☣️',
|
|
21
|
+
chem: '🧪',
|
|
22
|
+
nuclear: '☢️',
|
|
23
|
+
cyber: '💻',
|
|
24
|
+
reasoning_extraction: '🧠',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Marker used when the refusal category has no dedicated emoji. */
|
|
28
|
+
export const REFUSAL_REACTION_FALLBACK = '🛑';
|
|
29
|
+
|
|
30
|
+
/** Every marker the framework can emit — the category map plus the
|
|
31
|
+
* fallback, deduplicated, in stable declaration order. This IS the
|
|
32
|
+
* protective baseline: host composition serializes it (comma-joined) into
|
|
33
|
+
* `DISCORD_SUPPRESSED_REACTIONS_BASELINE` for adapters that render
|
|
34
|
+
* reactions. */
|
|
35
|
+
export const REFUSAL_REACTION_BASELINE: readonly string[] = [
|
|
36
|
+
...new Set([...Object.values(REFUSAL_REACTIONS), REFUSAL_REACTION_FALLBACK]),
|
|
37
|
+
];
|
|
@@ -16,14 +16,36 @@
|
|
|
16
16
|
|
|
17
17
|
import { safeSlice } from './safe-slice.js';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* House-safe default inline cap (chars) for tool results, error results, and
|
|
21
|
+
* background-script wake payloads (issue #89: a resident should never eat a
|
|
22
|
+
* 42k blob they didn't ask for). Durable per-residence value comes from
|
|
23
|
+
* `FrameworkConfig.toolResultInlineMaxChars`; a per-agent value from
|
|
24
|
+
* agent_settings `tool_result_inline_max_chars`.
|
|
25
|
+
*
|
|
26
|
+
* Raised 5000 → 24000 (2026-08-07) on production evidence rather than taste.
|
|
27
|
+
* The first residence to actually run the 5000 default spilled 48 results in
|
|
28
|
+
* 23h, 71% of them ordinary shell output from source review — `git diff`, test
|
|
29
|
+
* runs, `sed -n '450,590p'`-style excerpts, which routinely land at 5–15k. A
|
|
30
|
+
* 5000-char cap is tuned for conversation and turns every file read into a
|
|
31
|
+
* spill round-trip for a resident doing code work. Measured on that corpus:
|
|
32
|
+
* 20000 keeps 76% inline, 24000 keeps 88%, 32000 only reaches 90% — 24000 is
|
|
33
|
+
* the elbow. Genuinely large payloads (full histories, channel registries,
|
|
34
|
+
* broad greps) still spill, which is the point.
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS = 24000;
|
|
37
|
+
|
|
19
38
|
export function toolResultDataToHistoryString(data: unknown, maxChars?: number): string {
|
|
20
39
|
const fromArray = tryHistoryStringFromContentArray(data);
|
|
21
40
|
const str = fromArray ?? JSON.stringify(data);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
return maxChars ? truncateForHistory(str, maxChars) : str;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Bounded copy of an arbitrary string with the standard truncation notice. */
|
|
45
|
+
export function truncateForHistory(str: string, maxChars: number): string {
|
|
46
|
+
if (str.length <= maxChars) return str;
|
|
47
|
+
return safeSlice(str, 0, maxChars)
|
|
48
|
+
+ '\n\n[truncated — original was ' + str.length + ' chars]';
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
/**
|
package/src/types/agent.ts
CHANGED
|
@@ -48,6 +48,17 @@ export interface AgentConfig {
|
|
|
48
48
|
* restarts with recompiled (compressed) context. Default: 150000. */
|
|
49
49
|
maxStreamTokens?: number;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The model's PHYSICAL context window (provider hard cap, e.g. 200000).
|
|
53
|
+
* When set, the framework projects each continuation round's real size
|
|
54
|
+
* (cache-inclusive input of the prior round + the blocks about to be
|
|
55
|
+
* appended + reserve for response) and restarts the stream through a fresh
|
|
56
|
+
* compile instead of dispatching a request the provider will 400
|
|
57
|
+
* (issue #92: a legal compile can walk past the physical window mid-turn).
|
|
58
|
+
* Unset → no projection; only the maxStreamTokens restart applies.
|
|
59
|
+
*/
|
|
60
|
+
physicalWindowTokens?: number;
|
|
61
|
+
|
|
51
62
|
/** Per-agent context compile budget (input tokens). When unset, the
|
|
52
63
|
* ContextManager's built-in default (100k) applies. */
|
|
53
64
|
contextBudgetTokens?: number;
|
|
@@ -122,6 +133,31 @@ export interface AgentConfig {
|
|
|
122
133
|
* a poison turn. Default: off (a refusal just surfaces a marker + reaction).
|
|
123
134
|
*/
|
|
124
135
|
refusalHandling?: {
|
|
136
|
+
/**
|
|
137
|
+
* Same-model retries on a content-policy refusal, performed by MEMBRANE
|
|
138
|
+
* at the provider seam (passed through as `refusalRetries`). Default 0.
|
|
139
|
+
*
|
|
140
|
+
* Near the classifier's threshold a refusal is probabilistic rather than a
|
|
141
|
+
* property of the payload: identical bytes pass and refuse minutes apart
|
|
142
|
+
* (mythos, 2026-07-27 replay reps). So the first response is to ask again
|
|
143
|
+
* — no context surgery, no fallback model. Membrane replays the SAME
|
|
144
|
+
* request immediately and cache-warm, so only the discarded output tokens
|
|
145
|
+
* are real spend.
|
|
146
|
+
*
|
|
147
|
+
* Retrying in the framework instead was tried and removed: a requeue
|
|
148
|
+
* recompiles, so the attempts are correlated rather than fresh draws, and
|
|
149
|
+
* it cannot tell a streaming consumer to drop the abandoned attempt.
|
|
150
|
+
* Membrane announces each retry with a `retrying` stream event, which
|
|
151
|
+
* driveStream handles by discarding what was streamed.
|
|
152
|
+
*
|
|
153
|
+
* Retries are spent before the framework sees the refusal at all, so the
|
|
154
|
+
* escalation order is: membrane retries -> autoRewind (if on) -> the
|
|
155
|
+
* refusal reaction, which therefore means "the border is close" rather
|
|
156
|
+
* than firing on every near-threshold flip.
|
|
157
|
+
*
|
|
158
|
+
* Native tool mode only — see the XML-mode warning in Membrane.
|
|
159
|
+
*/
|
|
160
|
+
retries?: number;
|
|
125
161
|
/** Auto-rewind the triggering turn on refusal and retry. Default false. */
|
|
126
162
|
autoRewind?: boolean;
|
|
127
163
|
/** Max consecutive rewinds before giving up a turn. Default 3. */
|
|
@@ -141,8 +177,11 @@ export interface AgentConfig {
|
|
|
141
177
|
* (`>>#channel` / `>>@person` / `>>skip_reply`); unprefixed prose is never
|
|
142
178
|
* delivered — it bounces to a clipboard for a cheap prefixed resend.
|
|
143
179
|
* See docs/explicit-prose-routing.md.
|
|
180
|
+
* - 'hybrid': unprefixed prose keeps locus routing; `>>>destination` routes
|
|
181
|
+
* that publication envelope through the same authorized resolver.
|
|
182
|
+
* See docs/hybrid-prose-routing.md.
|
|
144
183
|
*/
|
|
145
|
-
proseRouting?: 'locus' | 'explicit';
|
|
184
|
+
proseRouting?: 'locus' | 'explicit' | 'hybrid';
|
|
146
185
|
}
|
|
147
186
|
|
|
148
187
|
/** The intentionally small set of agent settings that may change in-process. */
|
package/src/types/framework.ts
CHANGED
|
@@ -138,6 +138,23 @@ export interface FrameworkConfig {
|
|
|
138
138
|
*/
|
|
139
139
|
codeExecution?: CodeExecutionConfig;
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Durable inline cap (chars) for tool results — successes AND errors — and
|
|
143
|
+
* background-script wake payloads. Content over the cap is written to a
|
|
144
|
+
* workspace file under `tool-results/` (deterministic `<date>-<callId>.txt`
|
|
145
|
+
* name, overwritten on collision, retained until the workspace owner
|
|
146
|
+
* deletes it — never auto-GC'd) and replaced inline by a bounded preview
|
|
147
|
+
* plus the file reference; with no writable workspace the fallback is
|
|
148
|
+
* explicit plain truncation. Default 24000 (house-safe; issue #89). Must be
|
|
149
|
+
* >= 1000. A resident's own agent_settings value
|
|
150
|
+
* `tool_result_inline_max_chars` (durable, persisted in framework state)
|
|
151
|
+
* takes precedence over this for that agent; the EFFECTIVE cap for every
|
|
152
|
+
* source is min(desired, strategy bound) — desired values above the
|
|
153
|
+
* strategy-derived bound (maxMessageTokens * 4) are hard-clamped to it,
|
|
154
|
+
* with the clamp reported via agent_settings get.
|
|
155
|
+
*/
|
|
156
|
+
toolResultInlineMaxChars?: number;
|
|
157
|
+
|
|
141
158
|
/** Inference routing policy for server-initiated inference (optional). */
|
|
142
159
|
inferenceRouting?: InferenceRoutingPolicy;
|
|
143
160
|
|