@animalabs/agent-framework 0.28.0 → 0.30.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 +88 -25
- 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 +21 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +47 -7
- 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 +3 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +7 -3
- package/dist/src/api/server.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/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- 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 +846 -67
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4213 -924
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +46 -14
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +143 -53
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +174 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +565 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +7 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +118 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +266 -110
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +9 -0
- 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/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +187 -50
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +28 -12
- 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 +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.js +8 -6
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +133 -37
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +81 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +85 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +5 -55
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +63 -7
- 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 +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +382 -11
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1100 -119
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +101 -5
- 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 +48 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +142 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +136 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +52 -7
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/author-presentation.ts +81 -0
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- 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 +4362 -852
- package/src/gate/event-gate.ts +159 -52
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +612 -0
- package/src/index.ts +17 -4
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +306 -111
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +3 -1
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-coalescer.ts +19 -0
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +149 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +86 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +11 -7
- package/src/stored-injections.ts +7 -0
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +1252 -123
- package/src/tune-out/tools.ts +152 -11
- package/src/types/agent.ts +51 -9
- package/src/types/framework.ts +146 -0
- package/src/types/module.ts +23 -11
- package/src/types/trace.ts +150 -0
package/src/agent.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
1
2
|
import { heartbeatToolAlias } from './heartbeat/heartbeat.js';
|
|
2
3
|
import type { Membrane, NormalizedMessage, NormalizedRequest, ContentBlock, ThinkingBindingEntry, YieldingStream } from '@animalabs/membrane';
|
|
3
4
|
import { isAbortedResponse } from '@animalabs/membrane';
|
|
@@ -320,6 +321,11 @@ export class Agent {
|
|
|
320
321
|
/** Provider-specific request parameters forwarded unchanged by Membrane. */
|
|
321
322
|
readonly providerParams?: Record<string, unknown>;
|
|
322
323
|
readonly sameRoundThinkTextPolicy: AgentConfig['sameRoundThinkTextPolicy'];
|
|
324
|
+
/** The participant name this agent's turns are presented under (see AgentConfig.presentAs). */
|
|
325
|
+
readonly presentAs: string;
|
|
326
|
+
/** Stored participants presented as this agent's own turns: its name plus AgentConfig.selfParticipants. */
|
|
327
|
+
private readonly ownParticipants: ReadonlySet<string>;
|
|
328
|
+
private readonly deniedAtDispatch: ReadonlySet<string>;
|
|
323
329
|
|
|
324
330
|
private _state: AgentState = { status: 'idle' };
|
|
325
331
|
private _inferenceStartedAt = 0;
|
|
@@ -449,6 +455,9 @@ export class Agent {
|
|
|
449
455
|
this.prefillUserMessage = config.prefillUserMessage;
|
|
450
456
|
this.providerParams = config.providerParams;
|
|
451
457
|
this.sameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
|
|
458
|
+
this.presentAs = config.presentAs ?? config.name;
|
|
459
|
+
this.ownParticipants = new Set([config.name, ...(config.selfParticipants ?? [])]);
|
|
460
|
+
this.deniedAtDispatch = new Set(config.denyToolsAtDispatch ?? []);
|
|
452
461
|
this.maxStreamTokens = config.maxStreamTokens ?? 150_000;
|
|
453
462
|
// The window size converted as the projection's comparison (`projected > window`) converts it, so a string such
|
|
454
463
|
// as "200000" from a JSON or YAML config is its number, for every rule that reads it. A value whose number isn't
|
|
@@ -508,6 +517,36 @@ export class Agent {
|
|
|
508
517
|
return alias !== undefined && this.allowedTools.includes(alias);
|
|
509
518
|
}
|
|
510
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Whether a call to an advertised tool is refused at dispatch
|
|
522
|
+
* (see AgentConfig.denyToolsAtDispatch).
|
|
523
|
+
*/
|
|
524
|
+
deniesAtDispatch(toolName: string): boolean {
|
|
525
|
+
return this.deniedAtDispatch.has(toolName);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Whether a stored participant is this agent's own voice. */
|
|
529
|
+
isOwnParticipant(participant: string): boolean {
|
|
530
|
+
return this.ownParticipants.has(participant);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Role assignment for a request: every participant that is this agent's
|
|
535
|
+
* own voice — its name, and for a derived agent the agent it inherited
|
|
536
|
+
* from — is presented under one name, which the formatter maps to the
|
|
537
|
+
* assistant role. Stored authorship is untouched; this shapes the request
|
|
538
|
+
* only. For a plain agent (no selfParticipants, presentAs = name) the
|
|
539
|
+
* input is returned as is.
|
|
540
|
+
*/
|
|
541
|
+
private presentOwnTurns(messages: NormalizedMessage[]): NormalizedMessage[] {
|
|
542
|
+
if (this.ownParticipants.size === 1 && this.presentAs === this.name) return messages;
|
|
543
|
+
return messages.map((m) =>
|
|
544
|
+
this.ownParticipants.has(m.participant) && m.participant !== this.presentAs
|
|
545
|
+
? { ...m, participant: this.presentAs }
|
|
546
|
+
: m,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
511
550
|
/**
|
|
512
551
|
* Check if a source can trigger inference for this agent.
|
|
513
552
|
*/
|
|
@@ -1642,8 +1681,9 @@ export class Agent {
|
|
|
1642
1681
|
// What it holds of the stored injections is what the agent last saw.
|
|
1643
1682
|
this.noteRenderedInjections(compiled);
|
|
1644
1683
|
const { systemInjections } = compiled;
|
|
1645
|
-
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts)
|
|
1646
|
-
|
|
1684
|
+
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts);
|
|
1685
|
+
// the agent's own turns are then presented under its name (presentOwnTurns).
|
|
1686
|
+
const messages = this.presentOwnTurns(withoutTokenEstimates(compiled.messages));
|
|
1647
1687
|
// Read once, after the first compile, since results may have become
|
|
1648
1688
|
// ready while it ran; the refusal retry, assembled while the agent is
|
|
1649
1689
|
// inferring, sends the same ones.
|
|
@@ -1655,6 +1695,7 @@ export class Agent {
|
|
|
1655
1695
|
system: this.buildSystemPrompt(systemInjections),
|
|
1656
1696
|
tools: tools.length > 0 ? tools : undefined,
|
|
1657
1697
|
...this.requestSettings(),
|
|
1698
|
+
assistantParticipant: this.presentAs,
|
|
1658
1699
|
};
|
|
1659
1700
|
};
|
|
1660
1701
|
const request = await assemble();
|
|
@@ -1890,6 +1931,8 @@ export class Agent {
|
|
|
1890
1931
|
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts).
|
|
1891
1932
|
messages = withoutTokenEstimates(messages);
|
|
1892
1933
|
|
|
1934
|
+
messages = this.presentOwnTurns(messages);
|
|
1935
|
+
|
|
1893
1936
|
// Silent heartbeat ticks and replies to a [Continue] turn store no
|
|
1894
1937
|
// prompt row. Render each request-only turn before its response's first
|
|
1895
1938
|
// surviving row, so the wire formatter cannot merge the response into the
|
|
@@ -1900,7 +1943,7 @@ export class Agent {
|
|
|
1900
1943
|
// renders. "First" is per agent: a broadcast tick shares its eventId with
|
|
1901
1944
|
// every resident.
|
|
1902
1945
|
const index = this.indexRequestOnlyRows();
|
|
1903
|
-
const rendered = renderRequestOnlyRows(messages, index, this.
|
|
1946
|
+
const rendered = renderRequestOnlyRows(messages, index, this.presentAs);
|
|
1904
1947
|
messages = rendered.messages;
|
|
1905
1948
|
// A tick opens wherever its separator isn't already rendered: on its first
|
|
1906
1949
|
// request, and on a restart whose compile left none of the tick's rows,
|
|
@@ -1919,7 +1962,7 @@ export class Agent {
|
|
|
1919
1962
|
let continuePrompt: RequestOnlyPrompt | undefined;
|
|
1920
1963
|
if (openingTick) {
|
|
1921
1964
|
messages = [...messages, silentHeartbeatSeparatorTurn()];
|
|
1922
|
-
} else if (endsOnOwnMessage(messages, this.
|
|
1965
|
+
} else if (endsOnOwnMessage(messages, this.presentAs, injectionTail)) {
|
|
1923
1966
|
// Safety: ensure messages don't end with an assistant message.
|
|
1924
1967
|
// Some models reject trailing assistant messages ("prefill not supported"),
|
|
1925
1968
|
// and after context compression a stale assistant turn can end up last.
|
|
@@ -1969,7 +2012,7 @@ export class Agent {
|
|
|
1969
2012
|
cacheTtl: this.cacheTtl,
|
|
1970
2013
|
...(this.prefillUserMessage && { prefillUserMessage: this.prefillUserMessage }),
|
|
1971
2014
|
...(this.providerParams && { providerParams: this.providerParams }),
|
|
1972
|
-
assistantParticipant: this.
|
|
2015
|
+
assistantParticipant: this.presentAs,
|
|
1973
2016
|
};
|
|
1974
2017
|
}
|
|
1975
2018
|
|
|
@@ -2062,7 +2105,7 @@ export class Agent {
|
|
|
2062
2105
|
try {
|
|
2063
2106
|
const stored = cm.getAllMessages() as unknown as readonly StoredRowLike[];
|
|
2064
2107
|
if (this.requestOnlyIndex?.source === stored) return this.requestOnlyIndex.index;
|
|
2065
|
-
const index = indexRequestOnlyRows(stored, this.
|
|
2108
|
+
const index = indexRequestOnlyRows(stored, this.ownParticipants);
|
|
2066
2109
|
this.requestOnlyIndex = { source: stored, index };
|
|
2067
2110
|
return index;
|
|
2068
2111
|
} catch (error) {
|
|
@@ -2398,7 +2441,9 @@ export class Agent {
|
|
|
2398
2441
|
}
|
|
2399
2442
|
|
|
2400
2443
|
const injectedText = systemInjections.map((block) =>
|
|
2401
|
-
block.type === 'text'
|
|
2444
|
+
block.type === 'text'
|
|
2445
|
+
? block.text
|
|
2446
|
+
: `[${NOTICE_TAG.blockNotShown}: ${/^[aeiou]/i.test(block.type) ? 'an' : 'a'} ${block.type} block; the system prompt carries text only.]`);
|
|
2402
2447
|
|
|
2403
2448
|
return this.systemPrompt + '\n' + injectedText.join('\n');
|
|
2404
2449
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import WebSocket from 'ws';
|
|
15
|
-
import {
|
|
15
|
+
import { escapeLineSeparators, readLines } from '../line-reader.js';
|
|
16
16
|
import type { ApiRequest, ApiResponse, ApiEvent } from './types.js';
|
|
17
17
|
|
|
18
18
|
// ============================================================================
|
|
@@ -86,18 +86,7 @@ export class McpServer {
|
|
|
86
86
|
// Connect to WebSocket API
|
|
87
87
|
await this.connectWs();
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
const rl = createInterface({
|
|
91
|
-
input: process.stdin,
|
|
92
|
-
output: process.stdout,
|
|
93
|
-
terminal: false,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
rl.on('line', (line) => {
|
|
97
|
-
this.handleInput(line);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
rl.on('close', () => {
|
|
89
|
+
readLines(process.stdin, (line) => this.handleInput(line), () => {
|
|
101
90
|
this.close();
|
|
102
91
|
process.exit(0);
|
|
103
92
|
});
|
|
@@ -667,7 +656,7 @@ export class McpServer {
|
|
|
667
656
|
}
|
|
668
657
|
|
|
669
658
|
private send(message: JsonRpcResponse | JsonRpcNotification): void {
|
|
670
|
-
process.stdout.write(JSON.stringify(message) + '\n');
|
|
659
|
+
process.stdout.write(escapeLineSeparators(JSON.stringify(message)) + '\n');
|
|
671
660
|
}
|
|
672
661
|
|
|
673
662
|
close(): void {
|
package/src/api/server.ts
CHANGED
|
@@ -489,9 +489,13 @@ export class ApiServer {
|
|
|
489
489
|
if (!params.participant || !params.content) {
|
|
490
490
|
throw new Error('participant and content are required');
|
|
491
491
|
}
|
|
492
|
-
// ApiModule stores the message as one text
|
|
493
|
-
// refuses unless its text is a string
|
|
494
|
-
//
|
|
492
|
+
// ApiModule stores the participant as a name, and the message as one text
|
|
493
|
+
// block, which context-manager refuses unless its text is a string. So a
|
|
494
|
+
// participant or content that isn't text is refused here, where the
|
|
495
|
+
// caller hears it, rather than at dispatch.
|
|
496
|
+
if (typeof params.participant !== 'string') {
|
|
497
|
+
throw new Error('participant must be text');
|
|
498
|
+
}
|
|
495
499
|
if (typeof params.content !== 'string') {
|
|
496
500
|
throw new Error('content must be text');
|
|
497
501
|
}
|
|
@@ -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
|
+
}
|
package/src/auto-continue.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* row the next turn reads.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
9
|
+
|
|
8
10
|
/** The reason on a continuation's wake (InferenceRequest.reason). */
|
|
9
11
|
export const AUTO_CONTINUE_REASON = 'auto-continue';
|
|
10
12
|
|
|
@@ -33,11 +35,15 @@ export function autoContinueNotice(opts: {
|
|
|
33
35
|
stop: string;
|
|
34
36
|
last: boolean;
|
|
35
37
|
heldAfterSend?: boolean;
|
|
38
|
+
/** The run began with a heartbeat's turn, whose plain speech is held as drafts. */
|
|
39
|
+
heartbeat?: boolean;
|
|
36
40
|
}): string {
|
|
37
|
-
return `[continue] ${opts.agentName}, another turn starts now.` +
|
|
41
|
+
return `[${NOTICE_TAG.continue}] ${opts.agentName}, another turn starts now.` +
|
|
38
42
|
(opts.heldAfterSend
|
|
39
43
|
? ' Some or all of your closing words last turn were held, not sent: a send that turn may already have posted where they were going.'
|
|
40
44
|
: '') +
|
|
41
|
-
|
|
45
|
+
(opts.heartbeat
|
|
46
|
+
? ` If you're done, ${opts.stop}; plain words you write without it and with no destination are held as drafts, as on the heartbeat's turn, and an explicit send reaches a channel.`
|
|
47
|
+
: ` If you're done, ${opts.stop}; anything you write without it is sent as usual.`) +
|
|
42
48
|
(opts.last ? ' This is the last continuation in a row: after this turn you wait for the next event either way.' : '');
|
|
43
49
|
}
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* which ends with it, so nothing a script held stays after it.
|
|
5
5
|
*
|
|
6
6
|
* Every frame from the child is decoded and checked before the parent acts on
|
|
7
|
-
* it, since the child is untrusted. A frame that breaks the protocol, a
|
|
8
|
-
* that exits without saying how the script ended
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* call
|
|
7
|
+
* it, since the child is untrusted. A frame that breaks the protocol, or a
|
|
8
|
+
* child that exits without saying how the script ended when no stop was
|
|
9
|
+
* asked, ends the run as crashed. One that doesn't stop when asked is ended
|
|
10
|
+
* once the stop's grace has passed, and the run ends stopped, marked short.
|
|
11
|
+
* A tool call goes to `onToolCall`, the bridge to the framework's dispatcher,
|
|
12
|
+
* and its outcome goes into the run's call record at dispatch, as running
|
|
13
|
+
* until it's answered.
|
|
12
14
|
*
|
|
13
15
|
* What the host holds for a script is bounded: at most MAX_CALLS_IN_FLIGHT of
|
|
14
16
|
* its calls wait for answers at once, and at most MAX_CALLS_IN_FLIGHT are out
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* dispatcher a script's calls go through, and how to wake an agent.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import { NOTICE_TAG } from '../../notice-tags.js';
|
|
29
30
|
import { ChildCapacity, ScriptRun, type CallAnswer, type Ending, type ScriptRunOptions, type ToolAnswer } from './runner.js';
|
|
30
31
|
import type { CallSummary } from './calls.js';
|
|
31
32
|
import type { OutputBounds, OutputWindow } from './output.js';
|
|
@@ -478,10 +479,10 @@ function leftEndingText(scriptId: string, ending: Ending): string {
|
|
|
478
479
|
: ending.how === 'stopped'
|
|
479
480
|
? ending.why ? `was stopped: ${ending.why}` : 'was stopped at its limit'
|
|
480
481
|
: 'ended';
|
|
481
|
-
return `[script ${scriptId}] Your script ${how}. {"action": "wait", "script_id": "${scriptId}"} gives its result.`;
|
|
482
|
+
return `[${NOTICE_TAG.script} ${scriptId}] Your script ${how}. {"action": "wait", "script_id": "${scriptId}"} gives its result.`;
|
|
482
483
|
}
|
|
483
484
|
|
|
484
485
|
function wakeText(scriptId: string, payloadJson: string, n: number, max: number, ageMs: number): string {
|
|
485
486
|
const minutes = Math.round(ageMs / 60_000);
|
|
486
|
-
return `[script ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${payloadJson}`;
|
|
487
|
+
return `[${NOTICE_TAG.script} ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${payloadJson}`;
|
|
487
488
|
}
|
|
@@ -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';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presets — the historical agent kinds expressed as {@link AgentSpec}
|
|
3
|
+
* values. They are configurations of the common machinery, not engines:
|
|
4
|
+
* everything a preset decides is visible in the spec it returns, and a host
|
|
5
|
+
* may register a spec that matches none of them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentName, AgentSpec, ContextInheritance, ResultRoute } from './types.js';
|
|
9
|
+
|
|
10
|
+
/** Default idle bound for a bounded job with no explicit deadline (matches the run watchdog). */
|
|
11
|
+
export const DEFAULT_TASK_IDLE_TIMEOUT_MS = 15 * 60_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A resident: persistent, declared in configuration, reads the residents'
|
|
15
|
+
* shared message slot. The first one declared owns default delivery.
|
|
16
|
+
*/
|
|
17
|
+
export function residentSpec(name: AgentName, options: { primary: boolean }): AgentSpec {
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
kind: 'resident',
|
|
21
|
+
roles: {
|
|
22
|
+
defaultDelivery: options.primary,
|
|
23
|
+
receivesUntargeted: true,
|
|
24
|
+
receivesGateWakes: true,
|
|
25
|
+
ownsProviderScheduling: true,
|
|
26
|
+
},
|
|
27
|
+
lifetime: { kind: 'persistent' },
|
|
28
|
+
readsSharedSlot: true,
|
|
29
|
+
namespace: `agents/${name}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The subconscious: a persistent attention tenant of the resident it
|
|
35
|
+
* serves. It persists across policy epochs (cancelling a tune-out ends a
|
|
36
|
+
* channel's cadence, not this agent), observes the residents' slot with
|
|
37
|
+
* held traffic included, is never a broadcast recipient, and ends with its
|
|
38
|
+
* resident.
|
|
39
|
+
*/
|
|
40
|
+
export function subconsciousSpec(name: AgentName, serves: AgentName): AgentSpec {
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
kind: 'subconscious',
|
|
44
|
+
roles: {
|
|
45
|
+
defaultDelivery: false,
|
|
46
|
+
receivesUntargeted: false,
|
|
47
|
+
receivesGateWakes: false,
|
|
48
|
+
ownsProviderScheduling: true,
|
|
49
|
+
},
|
|
50
|
+
lifetime: { kind: 'persistent' },
|
|
51
|
+
spawnedBy: serves,
|
|
52
|
+
onParentEnd: 'end',
|
|
53
|
+
observes: [{ includeHeld: true }],
|
|
54
|
+
resultTo: { to: serves, as: 'message' },
|
|
55
|
+
readsSharedSlot: true,
|
|
56
|
+
namespace: `subconscious/${serves}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A conversation fork: derived from a template agent's context at bind
|
|
62
|
+
* time, bound to one channel, independent continuation, ends on idle.
|
|
63
|
+
*/
|
|
64
|
+
export function conversationForkSpec(
|
|
65
|
+
name: AgentName,
|
|
66
|
+
options: {
|
|
67
|
+
template: AgentName;
|
|
68
|
+
/** The channel it's bound to, on its server. */
|
|
69
|
+
homeChannel: { serverId: string; channelId: string };
|
|
70
|
+
idleTtlMs: number;
|
|
71
|
+
inherit?: Partial<ContextInheritance>;
|
|
72
|
+
},
|
|
73
|
+
): AgentSpec {
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
kind: 'conversation-fork',
|
|
77
|
+
roles: {
|
|
78
|
+
defaultDelivery: false,
|
|
79
|
+
receivesUntargeted: false,
|
|
80
|
+
// Woken by its own channel's debounced traffic through the host-wide gate.
|
|
81
|
+
receivesGateWakes: true,
|
|
82
|
+
ownsProviderScheduling: false,
|
|
83
|
+
},
|
|
84
|
+
lifetime: { kind: 'idle', idleTtlMs: options.idleTtlMs },
|
|
85
|
+
spawnedBy: options.template,
|
|
86
|
+
onParentEnd: 'orphan',
|
|
87
|
+
inherit: { from: options.template, mode: 'copy', ...options.inherit },
|
|
88
|
+
// A copy renames the template's turns as it copies them. A shared
|
|
89
|
+
// inheritance has no copy step, so the mapping is declared instead.
|
|
90
|
+
...(options.inherit?.mode === 'shared' ? { selfParticipants: [options.template] } : {}),
|
|
91
|
+
homeChannel: { serverId: options.homeChannel.serverId, channelId: options.homeChannel.channelId },
|
|
92
|
+
namespace: `conversations/${name}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface BoundedJobOptions {
|
|
97
|
+
/** Who created it. Absent for an independent job driven by the host. */
|
|
98
|
+
spawnedBy?: AgentName;
|
|
99
|
+
/** Where its completion returns. */
|
|
100
|
+
resultTo?: ResultRoute;
|
|
101
|
+
deadlineMs?: number;
|
|
102
|
+
idleTimeoutMs?: number;
|
|
103
|
+
maxTurns?: number;
|
|
104
|
+
namespace?: string;
|
|
105
|
+
metadata?: Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function boundedJob(name: AgentName, options: BoundedJobOptions): Omit<AgentSpec, 'kind'> {
|
|
109
|
+
return {
|
|
110
|
+
name,
|
|
111
|
+
roles: {
|
|
112
|
+
defaultDelivery: false,
|
|
113
|
+
receivesUntargeted: false,
|
|
114
|
+
receivesGateWakes: false,
|
|
115
|
+
ownsProviderScheduling: false,
|
|
116
|
+
},
|
|
117
|
+
lifetime: {
|
|
118
|
+
kind: 'task',
|
|
119
|
+
...(options.deadlineMs !== undefined ? { deadlineMs: options.deadlineMs } : {}),
|
|
120
|
+
idleTimeoutMs: options.idleTimeoutMs ?? DEFAULT_TASK_IDLE_TIMEOUT_MS,
|
|
121
|
+
...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
|
|
122
|
+
},
|
|
123
|
+
...(options.spawnedBy ? { spawnedBy: options.spawnedBy, onParentEnd: 'orphan' as const } : {}),
|
|
124
|
+
...(options.resultTo ? { resultTo: options.resultTo } : {}),
|
|
125
|
+
namespace: options.namespace ?? `subagent/${name}`,
|
|
126
|
+
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A fresh worker: new identity and context, one bounded job, a result route. */
|
|
131
|
+
export function workerSpec(name: AgentName, options: BoundedJobOptions = {}): AgentSpec {
|
|
132
|
+
return { ...boundedJob(name, options), kind: 'worker' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A task fork: the parent's context at a checkpoint, a private
|
|
137
|
+
* continuation, one bounded job, a return.
|
|
138
|
+
*/
|
|
139
|
+
export function taskForkSpec(
|
|
140
|
+
name: AgentName,
|
|
141
|
+
options: BoundedJobOptions & { parent: AgentName; inherit?: Partial<ContextInheritance> },
|
|
142
|
+
): AgentSpec {
|
|
143
|
+
return {
|
|
144
|
+
...boundedJob(name, { ...options, spawnedBy: options.spawnedBy ?? options.parent }),
|
|
145
|
+
kind: 'task-fork',
|
|
146
|
+
inherit: { from: options.parent, mode: 'copy', ...options.inherit },
|
|
147
|
+
...(options.inherit?.mode === 'shared' ? { selfParticipants: [options.parent] } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|