@animalabs/agent-framework 0.29.0 → 0.31.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 +71 -8
- package/dist/src/admission/admit.d.ts +90 -0
- package/dist/src/admission/admit.d.ts.map +1 -0
- package/dist/src/admission/admit.js +43 -0
- package/dist/src/admission/admit.js.map +1 -0
- package/dist/src/agent.d.ts +50 -9
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +125 -23
- 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 +10 -3
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/author-presentation.d.ts +50 -0
- package/dist/src/author-presentation.d.ts.map +1 -0
- package/dist/src/author-presentation.js +58 -0
- package/dist/src/author-presentation.js.map +1 -0
- package/dist/src/code-execution/isolate/render.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/render.js +7 -6
- package/dist/src/code-execution/isolate/render.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -4
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/dendrite/index.d.ts +6 -0
- package/dist/src/dendrite/index.d.ts.map +1 -0
- package/dist/src/dendrite/index.js +3 -0
- package/dist/src/dendrite/index.js.map +1 -0
- package/dist/src/dendrite/presets.d.ts +60 -0
- package/dist/src/dendrite/presets.d.ts.map +1 -0
- package/dist/src/dendrite/presets.js +117 -0
- package/dist/src/dendrite/presets.js.map +1 -0
- package/dist/src/dendrite/registry.d.ts +260 -0
- package/dist/src/dendrite/registry.d.ts.map +1 -0
- package/dist/src/dendrite/registry.js +742 -0
- package/dist/src/dendrite/registry.js.map +1 -0
- package/dist/src/dendrite/types.d.ts +392 -0
- package/dist/src/dendrite/types.d.ts.map +1 -0
- package/dist/src/dendrite/types.js +24 -0
- package/dist/src/dendrite/types.js.map +1 -0
- package/dist/src/framework.d.ts +806 -118
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4180 -1121
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +36 -21
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +223 -149
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -1
- package/dist/src/gate/recipe-reconcile.js +6 -5
- package/dist/src/gate/recipe-reconcile.js.map +1 -1
- package/dist/src/host-presentation.d.ts +4 -0
- package/dist/src/host-presentation.d.ts.map +1 -1
- package/dist/src/host-presentation.js +12 -10
- package/dist/src/host-presentation.js.map +1 -1
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/inference-bounds.d.ts +37 -0
- package/dist/src/inference-bounds.d.ts.map +1 -0
- package/dist/src/inference-bounds.js +84 -0
- package/dist/src/inference-bounds.js.map +1 -0
- package/dist/src/line-reader.d.ts +7 -1
- package/dist/src/line-reader.d.ts.map +1 -1
- package/dist/src/line-reader.js +17 -6
- package/dist/src/line-reader.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +84 -9
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +136 -58
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/errors.d.ts +1 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +3 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -2
- package/dist/src/mcpl/inbound-source.js +2 -2
- package/dist/src/mcpl/inference-router.d.ts +11 -1
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +65 -9
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +10 -7
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +85 -74
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +45 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +11 -7
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +21 -0
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +1 -2
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +3 -3
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +14 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +26 -22
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +1 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +16 -11
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +7 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +44 -21
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +283 -64
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +17 -1
- 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 +32 -4
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +189 -25
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +24 -16
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +5 -4
- package/dist/src/modules/sender-name.d.ts.map +1 -1
- package/dist/src/modules/sender-name.js +7 -5
- package/dist/src/modules/sender-name.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +16 -8
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +33 -12
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +6 -3
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +61 -15
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +9 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +50 -10
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +11 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +19 -8
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +8 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +7 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +7 -5
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/notice-tags.d.ts +2 -0
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +2 -0
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js +1 -9
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +4 -2
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +7 -4
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stored-injections.d.ts +4 -0
- package/dist/src/stored-injections.d.ts.map +1 -1
- package/dist/src/stored-injections.js +6 -0
- package/dist/src/stored-injections.js.map +1 -1
- package/dist/src/supplied-text.d.ts +2 -0
- package/dist/src/supplied-text.d.ts.map +1 -1
- package/dist/src/supplied-text.js +16 -7
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/tool-result-guard.js +1 -1
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +368 -16
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1224 -136
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +115 -7
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +55 -18
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +27 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +153 -15
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +42 -3
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +140 -3
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +138 -31
- package/src/api/mcp-server-core.ts +10 -3
- package/src/author-presentation.ts +81 -0
- package/src/code-execution/isolate/render.ts +7 -6
- package/src/code-execution/isolate/scripts.ts +5 -4
- package/src/dendrite/index.ts +32 -0
- package/src/dendrite/presets.ts +149 -0
- package/src/dendrite/registry.ts +808 -0
- package/src/dendrite/types.ts +397 -0
- package/src/framework.ts +4344 -1052
- package/src/gate/event-gate.ts +251 -150
- package/src/gate/recipe-reconcile.ts +7 -5
- package/src/host-presentation.ts +14 -10
- package/src/index.ts +16 -2
- package/src/inference-bounds.ts +111 -0
- package/src/line-reader.ts +22 -5
- package/src/mcpl/channel-registry.ts +170 -60
- package/src/mcpl/errors.ts +4 -0
- package/src/mcpl/inbound-source.ts +2 -2
- package/src/mcpl/inference-router.ts +67 -10
- package/src/mcpl/modern-connection.ts +83 -68
- package/src/mcpl/protocol-family.ts +37 -4
- package/src/mcpl/push-coalescer.ts +21 -7
- package/src/mcpl/references.ts +3 -4
- package/src/mcpl/server-connection.ts +30 -21
- package/src/mcpl/tool-signature.ts +18 -11
- package/src/mcpl/toolsets-module.ts +7 -0
- package/src/mcpl/transport.ts +261 -60
- package/src/mcpl/types.ts +18 -1
- package/src/module-registry.ts +178 -25
- package/src/modules/discord/discord-js-client.ts +33 -29
- package/src/modules/sender-name.ts +8 -5
- package/src/modules/workspace/disk-agreement.ts +39 -15
- package/src/modules/workspace/index.ts +65 -14
- package/src/modules/workspace/observe.ts +51 -10
- package/src/modules/workspace/reconcile.ts +22 -8
- package/src/modules/workspace/types.ts +5 -0
- package/src/modules/workspace/watcher.ts +8 -6
- package/src/notice-tags.ts +2 -0
- package/src/recovery/discord-awareness-outbox.ts +1 -11
- package/src/silent-heartbeat.ts +7 -4
- package/src/stored-injections.ts +7 -0
- package/src/supplied-text.ts +17 -6
- package/src/tool-result-guard.ts +1 -1
- package/src/tune-out/coordinator.ts +1374 -141
- package/src/tune-out/tools.ts +166 -13
- package/src/types/agent.ts +30 -1
- package/src/types/framework.ts +157 -15
- package/src/types/module.ts +42 -3
- package/src/types/trace.ts +154 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The admission stage (Dendrite §D; issue #232 §3.5).
|
|
3
|
+
*
|
|
4
|
+
* One decision per inbound event, taken before any side effect. Everything
|
|
5
|
+
* downstream — storage, the recipient's view, the wake, the speech locus,
|
|
6
|
+
* the gate's batching — consumes this decision instead of rediscovering the
|
|
7
|
+
* policy at its own stage. The function is pure over the policy inputs it is
|
|
8
|
+
* handed; the framework supplies them and applies the result.
|
|
9
|
+
*
|
|
10
|
+
* What is decided, in the proposal's words:
|
|
11
|
+
* 1. captured — always; an admitted event is stored where it arrived
|
|
12
|
+
* 2. recipients — who is eligible (explicit targets, else the untargeted set)
|
|
13
|
+
* 3. delivery — a body in the recipients' view, or a hold
|
|
14
|
+
* 4. wake — may it start a turn
|
|
15
|
+
* 5. locus — may it count as "the latest delivered inbound" (never
|
|
16
|
+
* under a hold); the channel registry records it under
|
|
17
|
+
* either `speech.locus` setting, and makes it the fallback
|
|
18
|
+
* locus only under `'latest-inbound'`
|
|
19
|
+
*
|
|
20
|
+
* A hold is a policy epoch over a channel (today: tune-out). Residents share
|
|
21
|
+
* one message slot, so a hold applies to the slot: every resident recipient
|
|
22
|
+
* gets the same disposition. A reader of the slot (the subconscious, or a
|
|
23
|
+
* reader fork) sees held traffic through its own view; that is not a
|
|
24
|
+
* delivery decision of this stage.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export interface InboundEnvelope {
|
|
28
|
+
/** Which MCPL lane carried it. */
|
|
29
|
+
lane: 'channel' | 'push';
|
|
30
|
+
serverId: string;
|
|
31
|
+
/** Endpoint-qualified channel id when the event has one (a push event
|
|
32
|
+
* without channel provenance — heartbeat, timer — has none). */
|
|
33
|
+
channelId?: string;
|
|
34
|
+
/** Platform message id, or the push event id. */
|
|
35
|
+
messageId: string;
|
|
36
|
+
authorId?: string;
|
|
37
|
+
tags?: string[];
|
|
38
|
+
/** `chat:addressed` (mention / reply / DM) or the server's equivalent. */
|
|
39
|
+
addressed: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The server-side veto: a per-server `shouldTriggerInference` said no, or
|
|
42
|
+
* the event is a materialized coalesced delivery whose wake already
|
|
43
|
+
* happened (`triggerInference: false`). A vetoed event wakes nobody and is
|
|
44
|
+
* not shown to the gate as a fresh event.
|
|
45
|
+
*/
|
|
46
|
+
vetoed: boolean;
|
|
47
|
+
/** Explicit recipients named by the event, if any. */
|
|
48
|
+
targetAgents?: string[];
|
|
49
|
+
/** What the gate sees: the text and the metadata shape it has always matched on. */
|
|
50
|
+
gateContent: string;
|
|
51
|
+
gateMetadata: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HoldRecord {
|
|
55
|
+
kind: 'tune-out';
|
|
56
|
+
epochId: string;
|
|
57
|
+
serverId: string;
|
|
58
|
+
channelId: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AdmissionPolicy {
|
|
62
|
+
/** The recipients an untargeted event reaches. */
|
|
63
|
+
untargetedRecipients(): string[];
|
|
64
|
+
/** The hold in force over this channel, if any. */
|
|
65
|
+
holdFor(serverId: string, channelId: string): HoldRecord | null;
|
|
66
|
+
/**
|
|
67
|
+
* The wake gate. `evaluate` may have side effects (debounce batching,
|
|
68
|
+
* counters, traces) and is called only for an event that may reach the
|
|
69
|
+
* recipients; `probe` is side-effect-free and answers "would the gate have
|
|
70
|
+
* woken them" for a held event, which the hold's own policy needs.
|
|
71
|
+
*/
|
|
72
|
+
gate?: {
|
|
73
|
+
evaluate(env: InboundEnvelope): boolean;
|
|
74
|
+
probe(env: InboundEnvelope): boolean;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AdmissionDecision {
|
|
79
|
+
recipients: string[];
|
|
80
|
+
/** Null = delivered into the recipients' view; otherwise held, with the record to stamp. */
|
|
81
|
+
held: HoldRecord | null;
|
|
82
|
+
/** Whether the recipients may be woken by this event. */
|
|
83
|
+
wake: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* The gate's verdict as a precondition for whatever reads the hold: true
|
|
86
|
+
* when the gate would (or did) wake the recipients. For a held event this
|
|
87
|
+
* comes from the side-effect-free probe; for a vetoed event it is false.
|
|
88
|
+
*/
|
|
89
|
+
gatePass: boolean;
|
|
90
|
+
/** Whether this arrival may count as the latest delivered inbound message. */
|
|
91
|
+
locus: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function admit(env: InboundEnvelope, policy: AdmissionPolicy): AdmissionDecision {
|
|
95
|
+
const recipients = env.targetAgents ?? policy.untargetedRecipients();
|
|
96
|
+
const held = env.channelId ? policy.holdFor(env.serverId, env.channelId) : null;
|
|
97
|
+
|
|
98
|
+
if (held) {
|
|
99
|
+
// Held: nothing downstream may act for the recipients. The gate is only
|
|
100
|
+
// asked what it would have done — no batch forms, no counter moves.
|
|
101
|
+
const gatePass = env.vetoed ? false : (policy.gate ? policy.gate.probe(env) : true);
|
|
102
|
+
return { recipients, held, wake: false, gatePass, locus: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (env.vetoed) {
|
|
106
|
+
return { recipients, held: null, wake: false, gatePass: false, locus: true };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Deliverable: now the gate may take its side effects (a debounce batch
|
|
110
|
+
// is the gate's own deferred wake and is consumed later by the same rule).
|
|
111
|
+
const gatePass = policy.gate ? policy.gate.evaluate(env) : true;
|
|
112
|
+
return { recipients, held: null, wake: gatePass, gatePass, locus: true };
|
|
113
|
+
}
|
package/src/agent.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { NOTICE_TAG } from './notice-tags.js';
|
|
|
2
2
|
import { heartbeatToolAlias } from './heartbeat/heartbeat.js';
|
|
3
3
|
import type { Membrane, NormalizedMessage, NormalizedRequest, ContentBlock, ThinkingBindingEntry, YieldingStream } from '@animalabs/membrane';
|
|
4
4
|
import { isAbortedResponse } from '@animalabs/membrane';
|
|
5
|
+
import { streamBounds, type InferenceBounds } from './inference-bounds.js';
|
|
5
6
|
import type { CacheWireReceipt, KvUnifiedRequestHooks } from './kv-unified-wire.js';
|
|
6
7
|
import { ToolResultGuard } from './tool-result-guard.js';
|
|
7
8
|
import { withoutTokenEstimates } from './thinking-token-stamp.js';
|
|
@@ -50,6 +51,9 @@ export interface ActivationRequestOptions {
|
|
|
50
51
|
* pass it (its errorPolicy is their retry layer); a direct caller of
|
|
51
52
|
* startStream keeps membrane's retries. */
|
|
52
53
|
membraneRetries?: boolean;
|
|
54
|
+
/** The framework's inferenceBounds, resolved: the stream gets their phase
|
|
55
|
+
* bounds and flat bound. None when left out. */
|
|
56
|
+
inferenceBounds?: InferenceBounds | null;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/** A compile's render stats, as context-manager's `getRenderStats()` types
|
|
@@ -162,6 +166,24 @@ function wireDigest(value: unknown): string {
|
|
|
162
166
|
const DEFAULT_CONTEXT_BUDGET_TOKENS = 100_000;
|
|
163
167
|
const DEFAULT_TRANSITION_PACE_TOKENS = 16_000;
|
|
164
168
|
const DEFAULT_SAME_ROUND_THINK_TEXT_POLICY: SameRoundThinkTextPolicy = 'public';
|
|
169
|
+
const TURN_INPUTS_PIN = 'agent-framework:turn-inputs';
|
|
170
|
+
|
|
171
|
+
export function assertThinkingBinding(setting: string, value: AgentConfig['thinkingBinding']): void {
|
|
172
|
+
if (holdsSettings(setting, value, '{ binds: true, dropBlock: true }')) {
|
|
173
|
+
assertBooleanSetting(
|
|
174
|
+
`${setting}.dropBlock`,
|
|
175
|
+
value.dropBlock,
|
|
176
|
+
'true asks the provider to drop a signed thinking block whose earlier conversation changed rather than refuse the request, ' +
|
|
177
|
+
'and false or leaving it out sends nothing, so the account\'s default applies',
|
|
178
|
+
);
|
|
179
|
+
assertBooleanSetting(
|
|
180
|
+
`${setting}.binds`,
|
|
181
|
+
value.binds,
|
|
182
|
+
'true says the model binds its signed thinking to the conversation before it, so each compile tells the context manager so, ' +
|
|
183
|
+
'and false or leaving it out compiles as before',
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
165
187
|
|
|
166
188
|
/**
|
|
167
189
|
* Refuse an agent config whose `promptCaching`, `toolResultGuard`,
|
|
@@ -190,20 +212,7 @@ export function assertAgentSwitches(config: AgentConfig): void {
|
|
|
190
212
|
'true asks the provider for extended thinking, and false or leaving it out doesn\'t',
|
|
191
213
|
);
|
|
192
214
|
}
|
|
193
|
-
|
|
194
|
-
assertBooleanSetting(
|
|
195
|
-
`${agent} thinkingBinding.dropBlock`,
|
|
196
|
-
config.thinkingBinding.dropBlock,
|
|
197
|
-
'true asks the provider to drop a signed thinking block whose earlier conversation changed rather than refuse the request, ' +
|
|
198
|
-
'and false or leaving it out sends nothing, so the account\'s default applies',
|
|
199
|
-
);
|
|
200
|
-
assertBooleanSetting(
|
|
201
|
-
`${agent} thinkingBinding.binds`,
|
|
202
|
-
config.thinkingBinding.binds,
|
|
203
|
-
'true says the model binds its signed thinking to the conversation before it, so each compile tells the context manager so, ' +
|
|
204
|
-
'and false or leaving it out compiles as before',
|
|
205
|
-
);
|
|
206
|
-
}
|
|
215
|
+
assertThinkingBinding(`${agent} thinkingBinding`, config.thinkingBinding);
|
|
207
216
|
if (holdsSettings(`${agent} refusalHandling`, config.refusalHandling, '{ autoRewind: true }')) {
|
|
208
217
|
assertBooleanSetting(
|
|
209
218
|
`${agent} refusalHandling.autoRewind`,
|
|
@@ -321,6 +330,11 @@ export class Agent {
|
|
|
321
330
|
/** Provider-specific request parameters forwarded unchanged by Membrane. */
|
|
322
331
|
readonly providerParams?: Record<string, unknown>;
|
|
323
332
|
readonly sameRoundThinkTextPolicy: AgentConfig['sameRoundThinkTextPolicy'];
|
|
333
|
+
/** The participant name this agent's turns are presented under (see AgentConfig.presentAs). */
|
|
334
|
+
readonly presentAs: string;
|
|
335
|
+
/** Stored participants presented as this agent's own turns: its name plus AgentConfig.selfParticipants. */
|
|
336
|
+
private readonly ownParticipants: ReadonlySet<string>;
|
|
337
|
+
private readonly deniedAtDispatch: ReadonlySet<string>;
|
|
324
338
|
|
|
325
339
|
private _state: AgentState = { status: 'idle' };
|
|
326
340
|
private _inferenceStartedAt = 0;
|
|
@@ -450,6 +464,9 @@ export class Agent {
|
|
|
450
464
|
this.prefillUserMessage = config.prefillUserMessage;
|
|
451
465
|
this.providerParams = config.providerParams;
|
|
452
466
|
this.sameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
|
|
467
|
+
this.presentAs = config.presentAs ?? config.name;
|
|
468
|
+
this.ownParticipants = new Set([config.name, ...(config.selfParticipants ?? [])]);
|
|
469
|
+
this.deniedAtDispatch = new Set(config.denyToolsAtDispatch ?? []);
|
|
453
470
|
this.maxStreamTokens = config.maxStreamTokens ?? 150_000;
|
|
454
471
|
// The window size converted as the projection's comparison (`projected > window`) converts it, so a string such
|
|
455
472
|
// as "200000" from a JSON or YAML config is its number, for every rule that reads it. A value whose number isn't
|
|
@@ -509,6 +526,36 @@ export class Agent {
|
|
|
509
526
|
return alias !== undefined && this.allowedTools.includes(alias);
|
|
510
527
|
}
|
|
511
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Whether a call to an advertised tool is refused at dispatch
|
|
531
|
+
* (see AgentConfig.denyToolsAtDispatch).
|
|
532
|
+
*/
|
|
533
|
+
deniesAtDispatch(toolName: string): boolean {
|
|
534
|
+
return this.deniedAtDispatch.has(toolName);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Whether a stored participant is this agent's own voice. */
|
|
538
|
+
isOwnParticipant(participant: string): boolean {
|
|
539
|
+
return this.ownParticipants.has(participant);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Role assignment for a request: every participant that is this agent's
|
|
544
|
+
* own voice — its name, and for a derived agent the agent it inherited
|
|
545
|
+
* from — is presented under one name, which the formatter maps to the
|
|
546
|
+
* assistant role. Stored authorship is untouched; this shapes the request
|
|
547
|
+
* only. For a plain agent (no selfParticipants, presentAs = name) the
|
|
548
|
+
* input is returned as is.
|
|
549
|
+
*/
|
|
550
|
+
private presentOwnTurns(messages: NormalizedMessage[]): NormalizedMessage[] {
|
|
551
|
+
if (this.ownParticipants.size === 1 && this.presentAs === this.name) return messages;
|
|
552
|
+
return messages.map((m) =>
|
|
553
|
+
this.ownParticipants.has(m.participant) && m.participant !== this.presentAs
|
|
554
|
+
? { ...m, participant: this.presentAs }
|
|
555
|
+
: m,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
512
559
|
/**
|
|
513
560
|
* Check if a source can trigger inference for this agent.
|
|
514
561
|
*/
|
|
@@ -1098,11 +1145,7 @@ export class Agent {
|
|
|
1098
1145
|
* A message above it may still be replaced or withdrawn in place by its
|
|
1099
1146
|
* sender, though it may already have been in one of the agent's requests.
|
|
1100
1147
|
* A live stream carries each round's own content and results, which it
|
|
1101
|
-
* stores at its tool boundaries, into its next request.
|
|
1102
|
-
* the messages injected at a boundary whose stream didn't say whether it
|
|
1103
|
-
* carries injections, as no membrane 0.5.x stream does: its native path
|
|
1104
|
-
* carries them and its XML path drops them, and either way they stay
|
|
1105
|
-
* above it.
|
|
1148
|
+
* stores at its tool boundaries, into its next request.
|
|
1106
1149
|
*/
|
|
1107
1150
|
private consumedWatermark: { branch: string; sequence: number } | null = null;
|
|
1108
1151
|
/** Counts marks (markContextConsumed's and applyCompileWatermark's), so a
|
|
@@ -1112,8 +1155,7 @@ export class Agent {
|
|
|
1112
1155
|
* The newest stored message a compile or a tool boundary may have put in
|
|
1113
1156
|
* front of the agent, per branch. It moves where the consumed watermark
|
|
1114
1157
|
* does, and sooner: when a committing compile takes its value, before it
|
|
1115
|
-
* awaits, and at every tool boundary that injects
|
|
1116
|
-
* stream says it carries injected messages. A message above it may still
|
|
1158
|
+
* awaits, and at every tool boundary that injects into its stream. A message above it may still
|
|
1117
1159
|
* have been shown, though: for one, a live stream carries each round's own
|
|
1118
1160
|
* content and results, stored at a boundary that injected nothing, into its
|
|
1119
1161
|
* next request. A compile that throws assembled nothing, and gives its move
|
|
@@ -1152,6 +1194,63 @@ export class Agent {
|
|
|
1152
1194
|
if (watermark) this.answeredWatermark = watermark;
|
|
1153
1195
|
}
|
|
1154
1196
|
|
|
1197
|
+
/**
|
|
1198
|
+
* Keep the stored rows from `first` through `last` raw for the turn now
|
|
1199
|
+
* opening, with a raw pin under one name per agent. Any pin of that name
|
|
1200
|
+
* already held is removed first, one a crash left included. Under a
|
|
1201
|
+
* strategy without pins nothing is pinned. A committing compile that
|
|
1202
|
+
* refuses as over budget while a pin of that name is held removes it and
|
|
1203
|
+
* compiles once more, and `unpinTurnInputs` removes it. Returns the pin's
|
|
1204
|
+
* id, or undefined when nothing was pinned.
|
|
1205
|
+
*
|
|
1206
|
+
* @internal For the framework's turn starts; not a host API.
|
|
1207
|
+
*/
|
|
1208
|
+
pinTurnInputs(first: MessageId, last: MessageId): string | undefined {
|
|
1209
|
+
if (!this.unpinTurnInputs()) return undefined;
|
|
1210
|
+
const cm = this.contextManager;
|
|
1211
|
+
if (typeof (cm.getStrategy() as { pinRange?: unknown }).pinRange !== 'function') return undefined;
|
|
1212
|
+
try {
|
|
1213
|
+
return cm.pinRange(first, last, { name: TURN_INPUTS_PIN });
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
console.error(`[turn-inputs] ${this.name}: could not pin the turn's inputs (${first}..${last}), so they may be compressed:`, error);
|
|
1216
|
+
return undefined;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/** Remove the turn's input pin `pinId`, or without one every pin of its
|
|
1221
|
+
* name. False when a removal threw.
|
|
1222
|
+
* @internal For the framework's turn starts and ends; not a host API. */
|
|
1223
|
+
unpinTurnInputs(pinId?: string): boolean {
|
|
1224
|
+
try {
|
|
1225
|
+
for (const pin of this.contextManager.listPins()) {
|
|
1226
|
+
if (pin.name === TURN_INPUTS_PIN && (pinId === undefined || pin.id === pinId)) this.contextManager.unpin(pin.id);
|
|
1227
|
+
}
|
|
1228
|
+
return true;
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
console.error(`[turn-inputs] ${this.name}: could not remove the turn's input pin:`, error);
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
private async compileUnpinningOnRefusal(compile: () => Promise<CompileResult>): Promise<CompileResult> {
|
|
1236
|
+
const pinned = this.holdsTurnInputsPin();
|
|
1237
|
+
try {
|
|
1238
|
+
return await compile();
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
if ((error as { name?: unknown } | null)?.name !== 'OverBudgetError' || !pinned || !this.unpinTurnInputs()) throw error;
|
|
1241
|
+
console.error(`[turn-inputs] ${this.name}: the compile refused with the turn's inputs pinned, so they are unpinned and it compiles again`);
|
|
1242
|
+
return compile();
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
private holdsTurnInputsPin(): boolean {
|
|
1247
|
+
try {
|
|
1248
|
+
return this.contextManager.listPins().some((pin) => pin.name === TURN_INPUTS_PIN);
|
|
1249
|
+
} catch {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1155
1254
|
/**
|
|
1156
1255
|
* Per destination (a server's channel, by its key), the newest consumed
|
|
1157
1256
|
* watermark the agent had read when it published there by a route the
|
|
@@ -1198,7 +1297,7 @@ export class Agent {
|
|
|
1198
1297
|
}
|
|
1199
1298
|
|
|
1200
1299
|
/** Mark everything currently stored as possibly shown (a tool boundary
|
|
1201
|
-
* that injected messages
|
|
1300
|
+
* that injected messages into its stream). Without
|
|
1202
1301
|
* a watermark the coalescer counts a message as shown, as it counts one
|
|
1203
1302
|
* read without a consumed watermark. */
|
|
1204
1303
|
markContextShown(): void {
|
|
@@ -1281,7 +1380,9 @@ export class Agent {
|
|
|
1281
1380
|
* run. It isn't told the agent's thinking binding (`thinkingBinding.binds`),
|
|
1282
1381
|
* so a context manager that keeps a thinking-binding record neither reads
|
|
1283
1382
|
* nor writes the record here, and what it returns keeps thinking whose
|
|
1284
|
-
* earlier conversation changed.
|
|
1383
|
+
* earlier conversation changed. Called while a turn's input pin is held
|
|
1384
|
+
* (pinTurnInputs), a refusal as over budget removes the pin and compiles
|
|
1385
|
+
* once more, as the turn's own compiles do. */
|
|
1285
1386
|
async compileContext(budget?: TokenBudget): Promise<CompileResult> {
|
|
1286
1387
|
// RFC-006 §3.3: consumption is determined at assembly — a compile that
|
|
1287
1388
|
// fails assembled nothing, so the watermark moves only on success.
|
|
@@ -1289,7 +1390,7 @@ export class Agent {
|
|
|
1289
1390
|
const marksBefore = this.watermarkMarks;
|
|
1290
1391
|
const shownBefore = this.shownWatermark;
|
|
1291
1392
|
if (watermark) this.shownWatermark = watermark;
|
|
1292
|
-
const compile = () => this.contextManager.compile(this.compileBudget(budget));
|
|
1393
|
+
const compile = () => this.compileUnpinningOnRefusal(() => this.contextManager.compile(this.compileBudget(budget)));
|
|
1293
1394
|
let result: CompileResult;
|
|
1294
1395
|
try {
|
|
1295
1396
|
result = budget ? await compile() : await this.compileAtLiveBudget(compile);
|
|
@@ -1545,7 +1646,8 @@ export class Agent {
|
|
|
1545
1646
|
// agent before the consumed watermark moves, once it returns.
|
|
1546
1647
|
const shownBefore = this.shownWatermark;
|
|
1547
1648
|
if (watermark) this.shownWatermark = watermark;
|
|
1548
|
-
const
|
|
1649
|
+
const select = () => this.contextManager.compile(this.compileBudget(budget), injections, opts as never);
|
|
1650
|
+
const compile = opts?.dryRun ? select : () => this.compileUnpinningOnRefusal(select);
|
|
1549
1651
|
let result: CompileResult;
|
|
1550
1652
|
try {
|
|
1551
1653
|
result = budget || opts?.dryRun ? await compile() : await this.compileAtLiveBudget(compile);
|
|
@@ -1643,8 +1745,9 @@ export class Agent {
|
|
|
1643
1745
|
// What it holds of the stored injections is what the agent last saw.
|
|
1644
1746
|
this.noteRenderedInjections(compiled);
|
|
1645
1747
|
const { systemInjections } = compiled;
|
|
1646
|
-
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts)
|
|
1647
|
-
|
|
1748
|
+
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts);
|
|
1749
|
+
// the agent's own turns are then presented under its name (presentOwnTurns).
|
|
1750
|
+
const messages = this.presentOwnTurns(withoutTokenEstimates(compiled.messages));
|
|
1648
1751
|
// Read once, after the first compile, since results may have become
|
|
1649
1752
|
// ready while it ran; the refusal retry, assembled while the agent is
|
|
1650
1753
|
// inferring, sends the same ones.
|
|
@@ -1656,6 +1759,7 @@ export class Agent {
|
|
|
1656
1759
|
system: this.buildSystemPrompt(systemInjections),
|
|
1657
1760
|
tools: tools.length > 0 ? tools : undefined,
|
|
1658
1761
|
...this.requestSettings(),
|
|
1762
|
+
assistantParticipant: this.presentAs,
|
|
1659
1763
|
};
|
|
1660
1764
|
};
|
|
1661
1765
|
const request = await assemble();
|
|
@@ -1891,6 +1995,8 @@ export class Agent {
|
|
|
1891
1995
|
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts).
|
|
1892
1996
|
messages = withoutTokenEstimates(messages);
|
|
1893
1997
|
|
|
1998
|
+
messages = this.presentOwnTurns(messages);
|
|
1999
|
+
|
|
1894
2000
|
// Silent heartbeat ticks and replies to a [Continue] turn store no
|
|
1895
2001
|
// prompt row. Render each request-only turn before its response's first
|
|
1896
2002
|
// surviving row, so the wire formatter cannot merge the response into the
|
|
@@ -1901,7 +2007,7 @@ export class Agent {
|
|
|
1901
2007
|
// renders. "First" is per agent: a broadcast tick shares its eventId with
|
|
1902
2008
|
// every resident.
|
|
1903
2009
|
const index = this.indexRequestOnlyRows();
|
|
1904
|
-
const rendered = renderRequestOnlyRows(messages, index, this.
|
|
2010
|
+
const rendered = renderRequestOnlyRows(messages, index, this.presentAs);
|
|
1905
2011
|
messages = rendered.messages;
|
|
1906
2012
|
// A tick opens wherever its separator isn't already rendered: on its first
|
|
1907
2013
|
// request, and on a restart whose compile left none of the tick's rows,
|
|
@@ -1920,7 +2026,7 @@ export class Agent {
|
|
|
1920
2026
|
let continuePrompt: RequestOnlyPrompt | undefined;
|
|
1921
2027
|
if (openingTick) {
|
|
1922
2028
|
messages = [...messages, silentHeartbeatSeparatorTurn()];
|
|
1923
|
-
} else if (endsOnOwnMessage(messages, this.
|
|
2029
|
+
} else if (endsOnOwnMessage(messages, this.presentAs, injectionTail)) {
|
|
1924
2030
|
// Safety: ensure messages don't end with an assistant message.
|
|
1925
2031
|
// Some models reject trailing assistant messages ("prefill not supported"),
|
|
1926
2032
|
// and after context compression a stale assistant turn can end up last.
|
|
@@ -1970,7 +2076,7 @@ export class Agent {
|
|
|
1970
2076
|
cacheTtl: this.cacheTtl,
|
|
1971
2077
|
...(this.prefillUserMessage && { prefillUserMessage: this.prefillUserMessage }),
|
|
1972
2078
|
...(this.providerParams && { providerParams: this.providerParams }),
|
|
1973
|
-
assistantParticipant: this.
|
|
2079
|
+
assistantParticipant: this.presentAs,
|
|
1974
2080
|
};
|
|
1975
2081
|
}
|
|
1976
2082
|
|
|
@@ -2063,7 +2169,7 @@ export class Agent {
|
|
|
2063
2169
|
try {
|
|
2064
2170
|
const stored = cm.getAllMessages() as unknown as readonly StoredRowLike[];
|
|
2065
2171
|
if (this.requestOnlyIndex?.source === stored) return this.requestOnlyIndex.index;
|
|
2066
|
-
const index = indexRequestOnlyRows(stored, this.
|
|
2172
|
+
const index = indexRequestOnlyRows(stored, this.ownParticipants);
|
|
2067
2173
|
this.requestOnlyIndex = { source: stored, index };
|
|
2068
2174
|
return index;
|
|
2069
2175
|
} catch (error) {
|
|
@@ -2169,6 +2275,7 @@ export class Agent {
|
|
|
2169
2275
|
// since membrane 0.7 its own schedules, ten minutes at a 529, would
|
|
2170
2276
|
// otherwise run inside each of the caller's attempts, out of its sight.
|
|
2171
2277
|
...(options.membraneRetries === false ? { retry: false } : {}),
|
|
2278
|
+
...streamBounds(options.inferenceBounds ?? null, request.config.maxTokens),
|
|
2172
2279
|
// Retry a content-policy refusal at the provider seam rather than at
|
|
2173
2280
|
// the framework level: membrane replays the SAME request immediately
|
|
2174
2281
|
// (cache-warm), where a framework-level requeue would recompile and
|
|
@@ -86,9 +86,16 @@ export class McpServer {
|
|
|
86
86
|
// Connect to WebSocket API
|
|
87
87
|
await this.connectWs();
|
|
88
88
|
|
|
89
|
-
readLines(process.stdin,
|
|
90
|
-
this.
|
|
91
|
-
|
|
89
|
+
readLines(process.stdin, {
|
|
90
|
+
line: (line) => this.handleInput(line),
|
|
91
|
+
end: () => {
|
|
92
|
+
this.close();
|
|
93
|
+
process.exit(0);
|
|
94
|
+
},
|
|
95
|
+
tooLong: (maxChars) => {
|
|
96
|
+
this.close();
|
|
97
|
+
process.stderr.write(`MCP server stopped: a line on stdin was longer than ${maxChars.toLocaleString('en-US')} UTF-16 code units\n`, () => process.exit(1));
|
|
98
|
+
},
|
|
92
99
|
});
|
|
93
100
|
|
|
94
101
|
// Handle SIGINT gracefully
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The author of a channel message, presented to the model.
|
|
3
|
+
*
|
|
4
|
+
* MCPL `channels/incoming` carries the author as a field, `author: {id,
|
|
5
|
+
* name}`, and the body alone as content (SPEC §14); the framework stores
|
|
6
|
+
* the message under the anonymous participant `user` with the author in
|
|
7
|
+
* `metadata.author`. The wire formatter writes the participant, so without
|
|
8
|
+
* this step the model reads `user: <text>` and who spoke is lost. Servers
|
|
9
|
+
* that pre-render `Name: body` into the text themselves (portal-mcpl) are
|
|
10
|
+
* left as they are: the rule is idempotent, so the name appears once
|
|
11
|
+
* whichever shape a server sends.
|
|
12
|
+
*
|
|
13
|
+
* Applied where the framework stores a `channels/incoming` message — the
|
|
14
|
+
* compiled view carries no metadata, so the name has to be in the stored
|
|
15
|
+
* text to reach the wire — and to a tune-out backlog line, which reads the
|
|
16
|
+
* metadata and must not name an author twice. The author stays in
|
|
17
|
+
* `metadata.author` as the structured record.
|
|
18
|
+
*/
|
|
19
|
+
import type { ContentBlock, MessageMetadata } from '@animalabs/membrane';
|
|
20
|
+
import { suppliedValue } from './supplied-text.js';
|
|
21
|
+
|
|
22
|
+
/** The participant the framework stores channel traffic under: a role, not a name. */
|
|
23
|
+
export const ANONYMOUS_PARTICIPANT = 'user';
|
|
24
|
+
|
|
25
|
+
type TextBlock = Extract<ContentBlock, { type: 'text' }>;
|
|
26
|
+
|
|
27
|
+
/** The author a stored message names, when it names one. */
|
|
28
|
+
export function authorNamed(metadata: Record<string, unknown> | MessageMetadata | undefined): string | undefined {
|
|
29
|
+
const name = (metadata?.author as { name?: unknown } | undefined)?.name;
|
|
30
|
+
return typeof name === 'string' && name.length > 0 ? name : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Whether `text` already begins with the author's own line: `Name:` then a space or the end. */
|
|
34
|
+
export function beginsWithAuthor(author: string, text: string): boolean {
|
|
35
|
+
if (!text.startsWith(author) || text.charAt(author.length) !== ':') return false;
|
|
36
|
+
const after = text.charAt(author.length + 1);
|
|
37
|
+
return after === '' || after === ' ' || after === '\n';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The author's name as a line of the framework's writes it: a value someone
|
|
42
|
+
* supplied, kept to one line with its notice openings marked (main's #852,
|
|
43
|
+
* suppliedValue), so a name can't open a line that reads as the framework's
|
|
44
|
+
* own notice.
|
|
45
|
+
*/
|
|
46
|
+
export function authorShown(author: string): string {
|
|
47
|
+
return suppliedValue(author);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Whether `text` begins with the author's line, as the author wrote their
|
|
51
|
+
* name or as the framework shows it (authorShown). */
|
|
52
|
+
export function beginsWithAuthorLine(author: string, text: string): boolean {
|
|
53
|
+
return beginsWithAuthor(author, text) || beginsWithAuthor(authorShown(author), text);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `author: text`, unless `text` begins that way already. */
|
|
57
|
+
export function authorLine(author: string, text: string): string {
|
|
58
|
+
return beginsWithAuthorLine(author, text) ? text : `${authorShown(author)}: ${text}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A message stored under the anonymous participant that names an author is
|
|
63
|
+
* presented with the author before its first text block — or, with no text
|
|
64
|
+
* block, as a leading `Name:` line before its media. Any other message, and
|
|
65
|
+
* one whose text already begins with the author's line, is returned as is.
|
|
66
|
+
* Never mutates its input.
|
|
67
|
+
*/
|
|
68
|
+
export function presentAuthor<M extends { participant: string; content: ContentBlock[]; metadata?: MessageMetadata | Record<string, unknown> }>(message: M): M {
|
|
69
|
+
if (message.participant !== ANONYMOUS_PARTICIPANT) return message;
|
|
70
|
+
const author = authorNamed(message.metadata);
|
|
71
|
+
if (author === undefined) return message;
|
|
72
|
+
const index = message.content.findIndex((b) => b.type === 'text');
|
|
73
|
+
if (index === -1) {
|
|
74
|
+
return { ...message, content: [{ type: 'text', text: `${authorShown(author)}:` } as TextBlock, ...message.content] };
|
|
75
|
+
}
|
|
76
|
+
const block = message.content[index] as TextBlock;
|
|
77
|
+
if (typeof block.text !== 'string' || beginsWithAuthorLine(author, block.text)) return message;
|
|
78
|
+
const content = [...message.content];
|
|
79
|
+
content[index] = { ...block, text: `${authorShown(author)}: ${block.text}` };
|
|
80
|
+
return { ...message, content };
|
|
81
|
+
}
|
|
@@ -9,6 +9,7 @@ import { fresh, toolRef, type CallSummary, type NamedCall } from './calls.js';
|
|
|
9
9
|
import type { Ending } from './runner.js';
|
|
10
10
|
import type { ScriptListing, ScriptReturn } from './scripts.js';
|
|
11
11
|
import { renderOutput } from './output.js';
|
|
12
|
+
import { quotedValue, suppliedValue } from '../../supplied-text.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The most of a script's return value, or of a failure's message or stack,
|
|
@@ -67,18 +68,18 @@ function endingLine(id: string, ending: Ending, lifetimeMs: number): string {
|
|
|
67
68
|
return `Script ${id} finished.`;
|
|
68
69
|
case 'failed':
|
|
69
70
|
// The run keeps the message cut to VALUE_CHARS, with room for the cut's marker.
|
|
70
|
-
return `Script ${id} failed: ${ending.error.name ? `${ending.error.name}: ` : ''}${ending.error.message}`;
|
|
71
|
+
return `Script ${id} failed: ${ending.error.name ? `${suppliedValue(ending.error.name)}: ` : ''}${suppliedValue(ending.error.message)}`;
|
|
71
72
|
case 'stopped': {
|
|
72
73
|
const why = ending.reason === 'agent'
|
|
73
74
|
? `Script ${id} was stopped, as you asked.`
|
|
74
75
|
: ending.why
|
|
75
|
-
? `Script ${id} was stopped: ${ending.why}.`
|
|
76
|
+
? `Script ${id} was stopped: ${suppliedValue(glimpse(ending.why, 300))}.`
|
|
76
77
|
: `Script ${id} was stopped at its limit of ${formatMs(lifetimeMs)}.`;
|
|
77
78
|
return ending.short ? `${why} Its process didn't stop by itself and was ended, so its output may be short.` : why;
|
|
78
79
|
}
|
|
79
80
|
case 'crashed':
|
|
80
81
|
// The detail is the host's account; what it means for the agent follows.
|
|
81
|
-
return `Script ${id} crashed: ${ending.detail}. A crash is the script's process failing, not an error its code threw: a fault in the host or its runtime, or something the script's own data asked of the runtime that it couldn't do, such as reading values nested thousands deep, or code nested hundreds deep. Running it again may work; if the same script crashes again, change what it does with its data or its code.`;
|
|
82
|
+
return `Script ${id} crashed: ${suppliedValue(ending.detail)}. A crash is the script's process failing, not an error its code threw: a fault in the host or its runtime, or something the script's own data asked of the runtime that it couldn't do, such as reading values nested thousands deep, or code nested hundreds deep. Running it again may work; if the same script crashes again, change what it does with its data or its code.`;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -107,7 +108,7 @@ function namedLine(call: NamedCall): string {
|
|
|
107
108
|
case 'timed-out':
|
|
108
109
|
return `- timed out in the script, and still running when the script ended, so it may still take effect: ${namedRef(call)}`;
|
|
109
110
|
case 'timed-out-answered':
|
|
110
|
-
return `- timed out in the script, and its answer came later: ${namedRef(call)}, answer ${
|
|
111
|
+
return `- timed out in the script, and its answer came later: ${namedRef(call)}, answer ${quotedValue(call.answer ?? '')}`;
|
|
111
112
|
case 'too-large':
|
|
112
113
|
return `- answered with a result the script couldn't receive, too large for its memory or nested too deep, so it got an error instead: ${namedRef(call)}`;
|
|
113
114
|
case 'unseen':
|
|
@@ -208,11 +209,11 @@ function listingState(s: ScriptListing, now: number): string {
|
|
|
208
209
|
case 'finished':
|
|
209
210
|
return `finished${when}`;
|
|
210
211
|
case 'failed':
|
|
211
|
-
return `failed${when}: ${e.error.name ? `${e.error.name}: ` : ''}${glimpse(e.error.message, 300)}`;
|
|
212
|
+
return `failed${when}: ${e.error.name ? `${suppliedValue(e.error.name)}: ` : ''}${suppliedValue(glimpse(e.error.message, 300))}`;
|
|
212
213
|
case 'crashed':
|
|
213
214
|
return `crashed${when}`;
|
|
214
215
|
case 'stopped':
|
|
215
|
-
return e.reason === 'agent' ? `stopped by you${when}` : e.why ? `stopped${when}: ${e.why}` : `stopped at its limit${when}`;
|
|
216
|
+
return e.reason === 'agent' ? `stopped by you${when}` : e.why ? `stopped${when}: ${suppliedValue(glimpse(e.why, 300))}` : `stopped at its limit${when}`;
|
|
216
217
|
default:
|
|
217
218
|
return 'ended';
|
|
218
219
|
}
|
|
@@ -32,6 +32,7 @@ import type { CallSummary } from './calls.js';
|
|
|
32
32
|
import type { OutputBounds, OutputWindow } from './output.js';
|
|
33
33
|
import type { PreludeTool } from './prelude.js';
|
|
34
34
|
import { glimpse } from './render.js';
|
|
35
|
+
import { quotedJson, suppliedValue } from '../../supplied-text.js';
|
|
35
36
|
|
|
36
37
|
/** The run or wait call watching a script: its turn, which the script's calls carry, and whether one of them ended that turn. */
|
|
37
38
|
export interface ScriptWatch {
|
|
@@ -473,16 +474,16 @@ export class AgentScripts {
|
|
|
473
474
|
|
|
474
475
|
function leftEndingText(scriptId: string, ending: Ending): string {
|
|
475
476
|
const how = ending.how === 'failed'
|
|
476
|
-
? `failed: ${ending.error.name ? `${ending.error.name}: ` : ''}${glimpse(ending.error.message, 300)}`
|
|
477
|
+
? `failed: ${ending.error.name ? `${suppliedValue(ending.error.name)}: ` : ''}${suppliedValue(glimpse(ending.error.message, 300))}`
|
|
477
478
|
: ending.how === 'crashed'
|
|
478
|
-
? `crashed: ${ending.detail}`
|
|
479
|
+
? `crashed: ${suppliedValue(ending.detail)}`
|
|
479
480
|
: ending.how === 'stopped'
|
|
480
|
-
? ending.why ? `was stopped: ${ending.why}` : 'was stopped at its limit'
|
|
481
|
+
? ending.why ? `was stopped: ${suppliedValue(glimpse(ending.why, 300))}` : 'was stopped at its limit'
|
|
481
482
|
: 'ended';
|
|
482
483
|
return `[${NOTICE_TAG.script} ${scriptId}] Your script ${how}. {"action": "wait", "script_id": "${scriptId}"} gives its result.`;
|
|
483
484
|
}
|
|
484
485
|
|
|
485
486
|
function wakeText(scriptId: string, payloadJson: string, n: number, max: number, ageMs: number): string {
|
|
486
487
|
const minutes = Math.round(ageMs / 60_000);
|
|
487
|
-
return `[${NOTICE_TAG.script} ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${payloadJson}`;
|
|
488
|
+
return `[${NOTICE_TAG.script} ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${quotedJson(payloadJson)}`;
|
|
488
489
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { AgentRegistry, AgentSpecError } from './registry.js';
|
|
2
|
+
export type { AgentRegistryOptions, RestoreOutcome, ReconcileOutcome } from './registry.js';
|
|
3
|
+
export {
|
|
4
|
+
residentSpec,
|
|
5
|
+
subconsciousSpec,
|
|
6
|
+
conversationForkSpec,
|
|
7
|
+
workerSpec,
|
|
8
|
+
taskForkSpec,
|
|
9
|
+
DEFAULT_TASK_IDLE_TIMEOUT_MS,
|
|
10
|
+
} from './presets.js';
|
|
11
|
+
export type { BoundedJobOptions } from './presets.js';
|
|
12
|
+
export type {
|
|
13
|
+
AgentName,
|
|
14
|
+
AgentKind,
|
|
15
|
+
AgentRoles,
|
|
16
|
+
AgentLifetime,
|
|
17
|
+
ActivationBounds,
|
|
18
|
+
ParentEndPolicy,
|
|
19
|
+
ResultRoute,
|
|
20
|
+
ObserveEdge,
|
|
21
|
+
AgentRelationships,
|
|
22
|
+
ContextInheritance,
|
|
23
|
+
AgentSpec,
|
|
24
|
+
AgentEndReason,
|
|
25
|
+
AgentRef,
|
|
26
|
+
ActivationRecord,
|
|
27
|
+
AgentRecord,
|
|
28
|
+
HeldMail,
|
|
29
|
+
RegistrySnapshot,
|
|
30
|
+
EndOutcome,
|
|
31
|
+
PolicyEpoch,
|
|
32
|
+
} from './types.js';
|