@qilin-agent/agent 0.0.0-stage → 3.1.4
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/LICENSE +21 -0
- package/README.i18n.yaml +91 -0
- package/README.md +184 -2
- package/README.zh.md +185 -0
- package/lib/index.js +700 -0
- package/lib/types/archive-admission.d.ts +24 -0
- package/lib/types/archive-admission.js +35 -0
- package/lib/types/consumed-work.d.ts +39 -0
- package/lib/types/consumed-work.js +93 -0
- package/lib/types/dispatch.d.ts +111 -0
- package/lib/types/dispatch.js +95 -0
- package/lib/types/index.d.ts +379 -0
- package/lib/types/index.js +468 -0
- package/lib/types/model-selection.d.ts +50 -0
- package/lib/types/model-selection.js +95 -0
- package/lib/types/projection.d.ts +9 -0
- package/lib/types/projection.js +2 -0
- package/lib/types/runtime-types.d.ts +410 -0
- package/lib/types/runtime-types.js +8 -0
- package/lib/types/types.d.ts +95 -0
- package/lib/types/types.js +7 -0
- package/package.json +55 -3
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `turn` family of the Workspace registry's archive admission: whether a
|
|
3
|
+
* Session's own Agent is inside a turn, and its cancel when the Session is
|
|
4
|
+
* archived with its work. Installed by the registry's constructor, so it
|
|
5
|
+
* answers for every Agent the registry publishes.
|
|
6
|
+
*
|
|
7
|
+
* @module @qilin-agent/agent
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
10
|
+
import type { SessionId } from '@qilin-agent/session';
|
|
11
|
+
import type { Agent } from './types.ts';
|
|
12
|
+
/**
|
|
13
|
+
* Answer `workspace/session-activity` with the `turn` family while the
|
|
14
|
+
* Session's Agent is running (a turn waiting for an approval or an answer
|
|
15
|
+
* included), and `workspace/session-stop` by cancelling that turn the way
|
|
16
|
+
* the user's own stop does — `agent.cancel({ kind: 'user' })`, but without
|
|
17
|
+
* the stop button's `keepInbox`, so queued input is discarded with a logged
|
|
18
|
+
* inbox splice instead of waking the archived Session later. Nothing is
|
|
19
|
+
* awaited to settlement. Both listeners live as long as `ctx`'s fiber.
|
|
20
|
+
* @param ctx - the registry's registration context.
|
|
21
|
+
* @param lookup - the registry's live-Agent lookup by Session id.
|
|
22
|
+
*/
|
|
23
|
+
export declare function installTurnArchiveAdmission(ctx: Context, lookup: (sessionId: SessionId) => Agent | undefined): void;
|
|
24
|
+
//# sourceMappingURL=archive-admission.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `turn` family of the Workspace registry's archive admission: whether a
|
|
3
|
+
* Session's own Agent is inside a turn, and its cancel when the Session is
|
|
4
|
+
* archived with its work. Installed by the registry's constructor, so it
|
|
5
|
+
* answers for every Agent the registry publishes.
|
|
6
|
+
*
|
|
7
|
+
* @module @qilin-agent/agent
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Answer `workspace/session-activity` with the `turn` family while the
|
|
11
|
+
* Session's Agent is running (a turn waiting for an approval or an answer
|
|
12
|
+
* included), and `workspace/session-stop` by cancelling that turn the way
|
|
13
|
+
* the user's own stop does — `agent.cancel({ kind: 'user' })`, but without
|
|
14
|
+
* the stop button's `keepInbox`, so queued input is discarded with a logged
|
|
15
|
+
* inbox splice instead of waking the archived Session later. Nothing is
|
|
16
|
+
* awaited to settlement. Both listeners live as long as `ctx`'s fiber.
|
|
17
|
+
* @param ctx - the registry's registration context.
|
|
18
|
+
* @param lookup - the registry's live-Agent lookup by Session id.
|
|
19
|
+
*/
|
|
20
|
+
export function installTurnArchiveAdmission(ctx, lookup) {
|
|
21
|
+
ctx.on('workspace/session-activity', async ({ sessionId }, next) => {
|
|
22
|
+
const running = lookup(sessionId)?.status === 'running';
|
|
23
|
+
const rest = await next();
|
|
24
|
+
if (!running)
|
|
25
|
+
return rest;
|
|
26
|
+
const own = { kind: 'turn' };
|
|
27
|
+
return [own, ...rest];
|
|
28
|
+
});
|
|
29
|
+
ctx.on('workspace/session-stop', ({ sessionId }) => {
|
|
30
|
+
const agent = lookup(sessionId);
|
|
31
|
+
if (agent?.status === 'running')
|
|
32
|
+
agent.cancel({ kind: 'user' });
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=archive-admission.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How one agent log accounts for the work it consumed.
|
|
3
|
+
*
|
|
4
|
+
* The turn and step vocabulary alone cannot answer this. A turn that stops
|
|
5
|
+
* before its first step leaves a `turn/end` shaped exactly like the balanced
|
|
6
|
+
* no-op turns a rejection or an empty claim produces, so reading turns in
|
|
7
|
+
* isolation either credits cut-short work as finished or convicts every no-op.
|
|
8
|
+
* The missing fact is the inbox's own record: {@link Inbox} logs each mutation
|
|
9
|
+
* with `removedCount` and marks a cancellation `outcome: 'canceled'`, which
|
|
10
|
+
* separates a turn claiming its input from work being dropped unrun.
|
|
11
|
+
*
|
|
12
|
+
* @module @qilin-agent/agent/consumed-work
|
|
13
|
+
*/
|
|
14
|
+
import type { SessionEvent } from '@qilin-agent/session';
|
|
15
|
+
/** How one agent log accounts for the work it consumed. */
|
|
16
|
+
export interface ConsumedWork {
|
|
17
|
+
/**
|
|
18
|
+
* The latest closed turn that accounts for consumed work: one that entered a
|
|
19
|
+
* model step, or one that claimed inbox input and then failed, was stopped,
|
|
20
|
+
* or was rejected. Absent when no turn closed over any work.
|
|
21
|
+
*/
|
|
22
|
+
readonly end?: SessionEvent<'turn/end'>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether accepted work was cancelled out of the inbox, unrun, after that
|
|
25
|
+
* turn. This is the only account of input a cancellation took before any turn
|
|
26
|
+
* could open over it — no `turn/end` describes it.
|
|
27
|
+
*/
|
|
28
|
+
readonly droppedUnrun: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fold one agent log, or an owned suffix of one, into its account of consumed
|
|
32
|
+
* work. Single pass, and every input is the log itself: no caller has to sample
|
|
33
|
+
* live state before cancelling, so a cancellation issued by anyone — the owner's
|
|
34
|
+
* teardown, an ancestor's interrupt, an unloading plugin — reads the same.
|
|
35
|
+
* @param events - the log, or an owned suffix, to fold.
|
|
36
|
+
* @returns the accounting turn when one closed, and whether work was dropped unrun after it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function foldConsumedWork(events: readonly SessionEvent[]): ConsumedWork;
|
|
39
|
+
//# sourceMappingURL=consumed-work.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How one agent log accounts for the work it consumed.
|
|
3
|
+
*
|
|
4
|
+
* The turn and step vocabulary alone cannot answer this. A turn that stops
|
|
5
|
+
* before its first step leaves a `turn/end` shaped exactly like the balanced
|
|
6
|
+
* no-op turns a rejection or an empty claim produces, so reading turns in
|
|
7
|
+
* isolation either credits cut-short work as finished or convicts every no-op.
|
|
8
|
+
* The missing fact is the inbox's own record: {@link Inbox} logs each mutation
|
|
9
|
+
* with `removedCount` and marks a cancellation `outcome: 'canceled'`, which
|
|
10
|
+
* separates a turn claiming its input from work being dropped unrun.
|
|
11
|
+
*
|
|
12
|
+
* @module @qilin-agent/agent/consumed-work
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Whether a turn that consumed input but never reached a step ends in a way
|
|
16
|
+
* that accounts for that input. Only a `completed` end does not: it had
|
|
17
|
+
* nothing left to run once its claim was rewritten away. A `blocked` end is
|
|
18
|
+
* that input's ending too — the pre-step rejection that produced it discarded
|
|
19
|
+
* the claimed messages, so the work it took will never run.
|
|
20
|
+
* @param reason - the turn's recorded ending.
|
|
21
|
+
* @returns whether the ending accounts for the input the turn took.
|
|
22
|
+
*/
|
|
23
|
+
function accountsForClaim(reason) {
|
|
24
|
+
switch (reason.kind) {
|
|
25
|
+
case 'completed':
|
|
26
|
+
return false;
|
|
27
|
+
case 'blocked':
|
|
28
|
+
case 'aborted':
|
|
29
|
+
case 'interrupted':
|
|
30
|
+
case 'error':
|
|
31
|
+
return true;
|
|
32
|
+
/* v8 ignore next 5 -- unreachable: `max-tokens` requires a step and short-circuits
|
|
33
|
+
* before this call; `forked` exists only in constructor seed history, while production
|
|
34
|
+
* callers fold an operation-owned suffix. `TurnEndReasonMap` is merge-extensible, so a
|
|
35
|
+
* backend-added variant cannot be listed; an unnameable ending over consumed input must
|
|
36
|
+
* not read as success. */
|
|
37
|
+
default:
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fold one agent log, or an owned suffix of one, into its account of consumed
|
|
43
|
+
* work. Single pass, and every input is the log itself: no caller has to sample
|
|
44
|
+
* live state before cancelling, so a cancellation issued by anyone — the owner's
|
|
45
|
+
* teardown, an ancestor's interrupt, an unloading plugin — reads the same.
|
|
46
|
+
* @param events - the log, or an owned suffix, to fold.
|
|
47
|
+
* @returns the accounting turn when one closed, and whether work was dropped unrun after it.
|
|
48
|
+
*/
|
|
49
|
+
export function foldConsumedWork(events) {
|
|
50
|
+
const stepped = new Set();
|
|
51
|
+
const claimed = new Set();
|
|
52
|
+
let open;
|
|
53
|
+
let end;
|
|
54
|
+
let droppedUnrun = false;
|
|
55
|
+
for (const event of events) {
|
|
56
|
+
switch (event.type) {
|
|
57
|
+
case 'turn/start':
|
|
58
|
+
open = event.data.turn;
|
|
59
|
+
break;
|
|
60
|
+
case 'step/start':
|
|
61
|
+
stepped.add(event.data.turn);
|
|
62
|
+
break;
|
|
63
|
+
case 'agent/inbox/spliced': {
|
|
64
|
+
const { removedCount, outcome, inserted } = event.data;
|
|
65
|
+
if (removedCount === undefined)
|
|
66
|
+
break;
|
|
67
|
+
// A replacement keeps the work pending under a new identity, so only a
|
|
68
|
+
// cancellation that leaves nothing behind drops it.
|
|
69
|
+
if (outcome === 'canceled')
|
|
70
|
+
droppedUnrun ||= inserted.length === 0;
|
|
71
|
+
// Claims are the loop's own step-boundary reads, always inside a turn.
|
|
72
|
+
else if (open !== undefined)
|
|
73
|
+
claimed.add(open);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case 'turn/end': {
|
|
77
|
+
const { turn, reason } = event.data;
|
|
78
|
+
open = undefined;
|
|
79
|
+
if (stepped.delete(turn) || (claimed.delete(turn) && accountsForClaim(reason))) {
|
|
80
|
+
end = event;
|
|
81
|
+
// Anything dropped before this turn closed is what its own ending
|
|
82
|
+
// reports; only a later drop is still unaccounted for.
|
|
83
|
+
droppedUnrun = false;
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { ...end === undefined ? {} : { end }, droppedUnrun };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=consumed-work.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-scoped dispatch and prompt assembly helpers. The fused dispatcher
|
|
3
|
+
* {@link agentEvents} couples the agent subject to its scope carrier, so the
|
|
4
|
+
* scope key and the payload's `agent` cannot diverge; repeat dispatchers (the
|
|
5
|
+
* loop driver) build it once in the agent's constructor and reuse it.
|
|
6
|
+
* @module @qilin-agent/agent/dispatch
|
|
7
|
+
*/
|
|
8
|
+
import type { Context, Events } from '@qilin-agent/kylin';
|
|
9
|
+
import type { Scoped } from '@qilin-agent/scope';
|
|
10
|
+
import type { AssembleContext } from '@qilin-agent/system-prompt';
|
|
11
|
+
import type { Agent } from './types.ts';
|
|
12
|
+
/** Extract the parameter tuple from an event handler type (its `this` is not part of the tuple). */
|
|
13
|
+
type Params<F> = F extends (...args: infer P) => unknown ? P : never;
|
|
14
|
+
/** Extract the return type from an event handler type. */
|
|
15
|
+
type Return<F> = F extends (...args: never[]) => infer R ? R : never;
|
|
16
|
+
/**
|
|
17
|
+
* The event names whose subject is an agent: the handler's first parameter is
|
|
18
|
+
* a payload object carrying the `agent` subject AND the handler declares a
|
|
19
|
+
* `Scoped<Agent>` `this` (the scope-carrier contract). The `this` check keeps
|
|
20
|
+
* accidental payload-happens-to-carry-an-Agent events (or zero-arg events,
|
|
21
|
+
* whose parameter tuple would satisfy a bare rest-tuple check via callability)
|
|
22
|
+
* out of the fused-dispatch surface.
|
|
23
|
+
*/
|
|
24
|
+
export type AgentSubjectEvent = {
|
|
25
|
+
[K in keyof Events]: Events[K] extends (this: Scoped<Agent>, ...args: infer P) => unknown ? P extends [infer Payload, ...unknown[]] ? Payload extends {
|
|
26
|
+
agent: Agent;
|
|
27
|
+
} ? K : never : never : never;
|
|
28
|
+
}[keyof Events];
|
|
29
|
+
/** The full payload object of one agent-subject event. */
|
|
30
|
+
type PayloadOf<K extends AgentSubjectEvent> = Params<Events[K]> extends [infer Payload, ...unknown[]] ? Payload : never;
|
|
31
|
+
/** The event arguments AFTER the payload: the waterfall `next` when present. */
|
|
32
|
+
type Tail<K extends AgentSubjectEvent> = Params<Events[K]> extends [unknown, ...infer R] ? R : never;
|
|
33
|
+
/**
|
|
34
|
+
* The payload as emit-side callers pass it: the full payload minus the agent
|
|
35
|
+
* field, which the fused dispatcher injects so subject and scope key cannot
|
|
36
|
+
* diverge.
|
|
37
|
+
*/
|
|
38
|
+
type PayloadRest<K extends AgentSubjectEvent> = Omit<PayloadOf<K> & object, 'agent'>;
|
|
39
|
+
/**
|
|
40
|
+
* The fused dispatcher {@link agentEvents} returns: each method dispatches the
|
|
41
|
+
* named agent-subject event with the agent's scope carrier as `thisArg` and
|
|
42
|
+
* the agent itself injected into the payload.
|
|
43
|
+
*/
|
|
44
|
+
export interface AgentEventDispatch {
|
|
45
|
+
/**
|
|
46
|
+
* Fire-and-forget notification in the agent's scope. Every listener is
|
|
47
|
+
* invoked; synchronous throws and returned-promise rejections are logged and
|
|
48
|
+
* contained per listener, so a notification cannot veto lifecycle progress
|
|
49
|
+
* or starve a later observer.
|
|
50
|
+
* @param name - the agent-subject event to emit.
|
|
51
|
+
* @param payload - the event's payload fields; `agent` is injected.
|
|
52
|
+
*/
|
|
53
|
+
emit<K extends AgentSubjectEvent>(name: K, payload: PayloadRest<K>): void;
|
|
54
|
+
/**
|
|
55
|
+
* Awaited in-order dispatch (Cordis `serial`) in the agent's scope.
|
|
56
|
+
* @param name - the agent-subject event to dispatch.
|
|
57
|
+
* @param payload - the event's payload fields; `agent` is injected.
|
|
58
|
+
* @returns the serial chain's result (the first bail value, if any).
|
|
59
|
+
*/
|
|
60
|
+
serial<K extends AgentSubjectEvent>(name: K, payload: PayloadRest<K>): Promise<Awaited<Return<Events[K]>>>;
|
|
61
|
+
/**
|
|
62
|
+
* Around-middleware dispatch (Cordis `waterfall`) in the agent's scope. The
|
|
63
|
+
* declared event parameters already end with the `next` callback, so `rest`
|
|
64
|
+
* is exactly the event's arguments after the payload — the final element
|
|
65
|
+
* being the innermost `next` (the default the listener chain wraps).
|
|
66
|
+
* @param name - the agent-subject event to dispatch.
|
|
67
|
+
* @param payload - the event's payload fields; `agent` is injected.
|
|
68
|
+
* @param rest - the event's arguments after the payload (the `next` callback).
|
|
69
|
+
* @returns the waterfall's composed result.
|
|
70
|
+
*/
|
|
71
|
+
waterfall<K extends AgentSubjectEvent>(name: K, payload: PayloadRest<K>, ...rest: Tail<K>): Return<Events[K]>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build the fused scope carrier for one agent subject.
|
|
75
|
+
*
|
|
76
|
+
* The carrier is a stateless routing object. {@link agentEvents} accepts an
|
|
77
|
+
* existing carrier, so callers that dispatch repeatedly for the same agent
|
|
78
|
+
* (the loop driver) build it once in the agent's constructor and reuse it,
|
|
79
|
+
* keeping hot-path dispatches allocation-free.
|
|
80
|
+
* @param agent - the subject agent and scope key.
|
|
81
|
+
* @returns the carrier passed as the event dispatcher `this` value.
|
|
82
|
+
*/
|
|
83
|
+
export declare function agentCarrier(agent: Agent): Scoped<Agent>;
|
|
84
|
+
/**
|
|
85
|
+
* Build a dispatcher that couples the agent subject to its scope carrier.
|
|
86
|
+
* @param ctx - the context to dispatch through (any context of the app).
|
|
87
|
+
* @param agent - the subject agent; also the scope-carrier key.
|
|
88
|
+
* @param carrier - the scope carrier to dispatch through; defaults to
|
|
89
|
+
* {@link agentCarrier} for the agent. Pass a constructor-built carrier to
|
|
90
|
+
* avoid rebuilding it for every dispatch.
|
|
91
|
+
* @returns the fused dispatcher.
|
|
92
|
+
*/
|
|
93
|
+
export declare function agentEvents(ctx: Context, agent: Agent, carrier?: Scoped<Agent>): AgentEventDispatch;
|
|
94
|
+
/**
|
|
95
|
+
* Emit one contained agent notification without allocating a retained dispatcher.
|
|
96
|
+
* @param ctx - the context to dispatch through.
|
|
97
|
+
* @param agent - the subject agent and scope key.
|
|
98
|
+
* @param name - the agent-subject event to emit.
|
|
99
|
+
* @param payload - the event's payload fields; `agent` is injected.
|
|
100
|
+
*/
|
|
101
|
+
export declare function emitAgentEvent<K extends AgentSubjectEvent>(ctx: Context, agent: Agent, name: K, payload: PayloadRest<K>): void;
|
|
102
|
+
/**
|
|
103
|
+
* Build the prompt assembly context with agent and scope set together, so
|
|
104
|
+
* agent-scoped prompt and tool contributions cannot be silently omitted.
|
|
105
|
+
* @param agent - the agent the assembly is for.
|
|
106
|
+
* @param signal - the current turn's explicit control signal, when assembly belongs to a turn.
|
|
107
|
+
* @returns the context to pass to `assemble()`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function assembleContextFor(agent: Agent, signal?: AbortSignal): AssembleContext;
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-scoped dispatch and prompt assembly helpers. The fused dispatcher
|
|
3
|
+
* {@link agentEvents} couples the agent subject to its scope carrier, so the
|
|
4
|
+
* scope key and the payload's `agent` cannot diverge; repeat dispatchers (the
|
|
5
|
+
* loop driver) build it once in the agent's constructor and reuse it.
|
|
6
|
+
* @module @qilin-agent/agent/dispatch
|
|
7
|
+
*/
|
|
8
|
+
import { scopeTarget } from '@qilin-agent/scope';
|
|
9
|
+
/**
|
|
10
|
+
* Build the fused scope carrier for one agent subject.
|
|
11
|
+
*
|
|
12
|
+
* The carrier is a stateless routing object. {@link agentEvents} accepts an
|
|
13
|
+
* existing carrier, so callers that dispatch repeatedly for the same agent
|
|
14
|
+
* (the loop driver) build it once in the agent's constructor and reuse it,
|
|
15
|
+
* keeping hot-path dispatches allocation-free.
|
|
16
|
+
* @param agent - the subject agent and scope key.
|
|
17
|
+
* @returns the carrier passed as the event dispatcher `this` value.
|
|
18
|
+
*/
|
|
19
|
+
export function agentCarrier(agent) {
|
|
20
|
+
return scopeTarget(agent, agent);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a dispatcher that couples the agent subject to its scope carrier.
|
|
24
|
+
* @param ctx - the context to dispatch through (any context of the app).
|
|
25
|
+
* @param agent - the subject agent; also the scope-carrier key.
|
|
26
|
+
* @param carrier - the scope carrier to dispatch through; defaults to
|
|
27
|
+
* {@link agentCarrier} for the agent. Pass a constructor-built carrier to
|
|
28
|
+
* avoid rebuilding it for every dispatch.
|
|
29
|
+
* @returns the fused dispatcher.
|
|
30
|
+
*/
|
|
31
|
+
export function agentEvents(ctx, agent, carrier = agentCarrier(agent)) {
|
|
32
|
+
// The ordinary dispatch methods forward through Cordis' variadic mixins. The
|
|
33
|
+
// fused (carrier, name, payload, ...rest) tuple is provably a valid argument
|
|
34
|
+
// list for the matching thisArg overload, but TypeScript cannot relate the
|
|
35
|
+
// generic Tail<K> spread back to that overload's conditional parameter
|
|
36
|
+
// tuple — hence one contained, shape-preserving cast per method.
|
|
37
|
+
const fused = (payload) =>
|
|
38
|
+
// The dispatcher owns the subject injection; callers pass PayloadRest, so
|
|
39
|
+
// the fused record is exactly the declared payload. The spread comes
|
|
40
|
+
// first, so a structurally acceptable payload that happens to carry an
|
|
41
|
+
// `agent` field can never override the injected subject.
|
|
42
|
+
({ ...payload, agent });
|
|
43
|
+
return {
|
|
44
|
+
emit(name, payload) {
|
|
45
|
+
// Cordis emit invokes callbacks through Array.map: one synchronous throw
|
|
46
|
+
// starves later listeners, and returned promises are discarded. Agent
|
|
47
|
+
// notifications are non-vetoing, so resolve the same filtered callback
|
|
48
|
+
// set ourselves and contain both failure modes independently.
|
|
49
|
+
const args = [carrier, name, fused(payload)];
|
|
50
|
+
const callbacks = ctx.events.dispatch('emit', args);
|
|
51
|
+
for (const callback of callbacks) {
|
|
52
|
+
try {
|
|
53
|
+
const returned = callback(...args);
|
|
54
|
+
void Promise.resolve(returned).catch((error) => {
|
|
55
|
+
ctx.logger.warn(`agent event "${name}" listener rejected: ${String(error)}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
ctx.logger.warn(`agent event "${name}" listener threw: ${String(error)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async serial(name, payload) {
|
|
64
|
+
// oxlint-disable-next-line typescript/unbound-method -- the events mixin accessor returns a pre-bound function
|
|
65
|
+
const serial = ctx.serial;
|
|
66
|
+
return await serial(carrier, name, fused(payload));
|
|
67
|
+
},
|
|
68
|
+
waterfall(name, payload, ...rest) {
|
|
69
|
+
// oxlint-disable-next-line typescript/unbound-method -- the events mixin accessor returns a pre-bound function
|
|
70
|
+
const waterfall = ctx.waterfall;
|
|
71
|
+
return waterfall(carrier, name, fused(payload), ...rest);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Emit one contained agent notification without allocating a retained dispatcher.
|
|
77
|
+
* @param ctx - the context to dispatch through.
|
|
78
|
+
* @param agent - the subject agent and scope key.
|
|
79
|
+
* @param name - the agent-subject event to emit.
|
|
80
|
+
* @param payload - the event's payload fields; `agent` is injected.
|
|
81
|
+
*/
|
|
82
|
+
export function emitAgentEvent(ctx, agent, name, payload) {
|
|
83
|
+
agentEvents(ctx, agent).emit(name, payload);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build the prompt assembly context with agent and scope set together, so
|
|
87
|
+
* agent-scoped prompt and tool contributions cannot be silently omitted.
|
|
88
|
+
* @param agent - the agent the assembly is for.
|
|
89
|
+
* @param signal - the current turn's explicit control signal, when assembly belongs to a turn.
|
|
90
|
+
* @returns the context to pass to `assemble()`.
|
|
91
|
+
*/
|
|
92
|
+
export function assembleContextFor(agent, signal) {
|
|
93
|
+
return { agent, scope: agent, ...signal === undefined ? {} : { signal } };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=dispatch.js.map
|