melchizedek-agents 1.0.3 → 1.2.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/.env.example +29 -2
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +524 -0
- package/DOCUMENTATION.md +178 -43
- package/ONBOARDING.md +339 -0
- package/QUICKSTART.md +15 -2
- package/README.md +6 -2
- package/config/agents/README.md +2 -2
- package/config/agents/examples/ares.yaml +6 -10
- package/config/agents/examples/assistant.yaml +6 -6
- package/config/agents/examples/augustin.yaml +9 -15
- package/config/agents/examples/cartographers.yaml +6 -6
- package/config/agents/examples/council.yaml +7 -7
- package/config/agents/examples/critic.yaml +24 -24
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +37 -35
- package/config/agents/examples/librarian.yaml +4 -4
- package/config/agents/examples/patient_advocate.yaml +6 -10
- package/config/agents/examples/pipeline.yaml +20 -19
- package/config/agents/examples/research.yaml +36 -43
- package/config/agents/examples/scribe.yaml +31 -32
- package/config/agents/examples/scriptorium.yaml +6 -6
- package/config/agents/examples/structured_critic.yaml +82 -0
- package/config/agents/examples/style_council.yaml +9 -11
- package/config/agents/examples/syndicate.yaml +3 -5
- package/config/agents/examples/tutor.yaml +2 -2
- package/config/agents/examples/weather.yaml +2 -2
- package/config/agents/syndicate.schema.json +859 -13
- package/config/agents/syndicateSchema.yaml +122 -73
- package/config/agents/templates/README.md +1 -1
- package/config/agents/templates/account_memory.yaml +4 -4
- package/config/agents/templates/case_desk.yaml +6 -6
- package/config/agents/templates/conversational.yaml +2 -2
- package/config/agents/templates/draft_review.yaml +6 -6
- package/config/agents/templates/intake_extractor.yaml +64 -62
- package/config/agents/templates/research_brief.yaml +6 -6
- package/config/agents/templates/research_desk.yaml +6 -6
- package/config/agents/templates/review_panel.yaml +8 -8
- package/config/agents/templates/support_triage.yaml +30 -29
- package/config/agents/templates/systems_operator.yaml +24 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +27 -0
- package/dist/lib/a2a/app.js +64 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +43 -5
- package/dist/lib/a2a/oauthSetup.d.ts +106 -0
- package/dist/lib/a2a/oauthSetup.js +259 -0
- package/dist/lib/agentDialect.d.ts +76 -0
- package/dist/lib/agentDialect.js +151 -0
- package/dist/lib/compile.d.ts +42 -6
- package/dist/lib/compile.js +97 -45
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +20 -1
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +110 -1
- package/dist/lib/doctor.js +205 -9
- package/dist/lib/loadSyndicate.d.ts +54 -2
- package/dist/lib/loadSyndicate.js +22 -2
- package/dist/lib/memory/namespace.d.ts +6 -5
- package/dist/lib/memory/namespace.js +6 -5
- package/dist/lib/memory/providers.js +3 -4
- package/dist/lib/models/capabilities.d.ts +25 -1
- package/dist/lib/models/capabilities.js +79 -1
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +2 -1
- package/dist/lib/models/gateway.js +4 -2
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/onboarding.d.ts +134 -0
- package/dist/lib/onboarding.js +539 -0
- package/dist/lib/runtime/approvals.d.ts +8 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +23 -3
- package/dist/lib/runtime/native/agentLoop.js +107 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +171 -0
- package/dist/lib/runtime/native/checkpoint.js +359 -0
- package/dist/lib/runtime/native/delegate.d.ts +222 -18
- package/dist/lib/runtime/native/delegate.js +390 -39
- package/dist/lib/runtime/native/interrupts.d.ts +184 -2
- package/dist/lib/runtime/native/interrupts.js +391 -1
- package/dist/lib/runtime/native/request.d.ts +13 -3
- package/dist/lib/runtime/native/request.js +28 -4
- package/dist/lib/runtime/native/telemetry.js +10 -15
- package/dist/lib/runtime/questions.d.ts +7 -4
- package/dist/lib/runtime/sessions.d.ts +1 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +11 -3
- package/dist/lib/runtime/syndicateTurn.js +353 -68
- package/dist/lib/runtime/turnControl.d.ts +56 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +12 -2
- package/dist/lib/storage/postgres/index.js +8 -3
- package/dist/lib/storage/postgres/taskQueue.d.ts +20 -4
- package/dist/lib/storage/postgres/taskQueue.js +29 -5
- package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
- package/dist/lib/storage/postgres/taskStore.js +74 -8
- package/dist/lib/syndicateSchema.d.ts +316 -1
- package/dist/lib/syndicateSchema.js +203 -29
- package/dist/lib/tools/auth.d.ts +20 -6
- package/dist/lib/tools/auth.js +21 -5
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/credentialUses.d.ts +92 -0
- package/dist/lib/tools/credentialUses.js +112 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthConsent.js +8 -1
- package/dist/lib/tools/oauthHosts.d.ts +73 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +242 -0
- package/dist/lib/tools/oauthTools.js +447 -0
- package/dist/lib/tools/openapiTools.d.ts +11 -6
- package/dist/lib/tools/openapiTools.js +88 -38
- package/dist/lib/tools/taskTools.d.ts +61 -6
- package/dist/lib/tools/taskTools.js +145 -21
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +14 -7
- package/dist/lib/workflow.js +14 -7
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +131 -1
- package/dist/scripts/assistant_worker.d.ts +12 -0
- package/dist/scripts/assistant_worker.js +81 -22
- package/dist/scripts/doctor.d.ts +7 -2
- package/dist/scripts/doctor.js +9 -4
- package/dist/scripts/setup.d.ts +23 -0
- package/dist/scripts/setup.js +154 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/yaml_codemod.d.ts +62 -0
- package/dist/scripts/yaml_codemod.js +652 -0
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +7 -4
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek/SKILL.md +1 -0
- package/skills/melchizedek-author/SKILL.md +7 -4
- package/skills/melchizedek-author/assets/minimal.yaml +13 -13
- package/skills/melchizedek-models/SKILL.md +11 -4
- package/skills/melchizedek-onboard/SKILL.md +49 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -80,12 +80,72 @@
|
|
|
80
80
|
* builds its request. A later step finds the agent's own events last,
|
|
81
81
|
* so the call runs once.
|
|
82
82
|
*
|
|
83
|
+
* PAUSES INSIDE DELEGATED SUBAGENTS (WS6-2a, ADR 0110). A subagent runs in
|
|
84
|
+
* its own child session (delegate.ts); an approval request or question it
|
|
85
|
+
* leaves open ends its run paused, and its caller leaves the delegated
|
|
86
|
+
* call open (no response stored) and ends paused too, up to the turn.
|
|
87
|
+
* delegatedPauses finds such a pause from any session by walking down:
|
|
88
|
+
* each call its agent left open (openCalls) leads to the child session
|
|
89
|
+
* under the call's name, whose own open request or question is the pause,
|
|
90
|
+
* or whose own open call leads one level further. The answer travels back
|
|
91
|
+
* down the same way: before each step, the loop resumes the open call
|
|
92
|
+
* whose pause the latest user message answers (agentLoop.ts), and the
|
|
93
|
+
* child reads the same answer from its own session, as it would at the top.
|
|
94
|
+
*
|
|
95
|
+
* NESTED SYNDICATES (WS6-2b, ADR 0111). The child session of a call is the
|
|
96
|
+
* one runSubagent files it under: the agent path (delegate.ts childAppName),
|
|
97
|
+
* else, for a conversation stored before ADR 0111, the subagent's name
|
|
98
|
+
* alone (legacyChild). A nested delegate syndicate is its orchestrator
|
|
99
|
+
* there, so the walk goes on through its open calls. A nested workflow
|
|
100
|
+
* syndicate holds a paused walk there instead: its own pause record (the
|
|
101
|
+
* workflow's event naming every interrupt it left open, authored by the
|
|
102
|
+
* workflow under the call's name) makes an approval request an agent node
|
|
103
|
+
* raised, or an ask_user node's question, the pause, with the node that
|
|
104
|
+
* asked at the end of the path. The walk stops there: a pause inside a
|
|
105
|
+
* workflow node's own delegation does not run (lib/workflow/agentNode.ts).
|
|
106
|
+
*
|
|
107
|
+
* NESTED WORKFLOWS AS A ROUTE OR A NODE (ADR 0119). A workflow node that is
|
|
108
|
+
* a nested workflow raises its walk's open requests again on its caller's
|
|
109
|
+
* walk, on one event of its own (lib/workflow/turn.ts), so the caller's
|
|
110
|
+
* walk pauses on the same ids; deepestPause follows such a request down,
|
|
111
|
+
* by id, through each child session filed under entryAppName (its kind in
|
|
112
|
+
* the key: `route:`, `node:`, ADR 0120; else an older key, legacyEntry) to
|
|
113
|
+
* the node that asked. A dispatch route that is one leaves its pause
|
|
114
|
+
* record as the conversation's last event (routePause). A delegated nested
|
|
115
|
+
* workflow's node that is one is followed the same way.
|
|
116
|
+
*
|
|
117
|
+
* NESTED DISPATCH SYNDICATES (ADR 0120). One runs as a turn of its own on
|
|
118
|
+
* its own conversation (the child session of a delegated call, a route or
|
|
119
|
+
* a node), its routes answering there as a turn's routes answer in the
|
|
120
|
+
* turn's conversation. conversationPause finds what it waits on as the
|
|
121
|
+
* turn finds it at the top: a route's pause record, a route's open
|
|
122
|
+
* request, question or consent request, else a pause inside a call a route
|
|
123
|
+
* left open (filed `<conversation>/<route>/<subagent>`). pauseBelow falls
|
|
124
|
+
* to it when the subagent's own checks find nothing; nestedWorkflowPause
|
|
125
|
+
* when the child session is not a walk. The answer goes down the open
|
|
126
|
+
* call, the route's pause record or the node's raised request, and the
|
|
127
|
+
* nested turn reads it as a message at the top.
|
|
128
|
+
*
|
|
129
|
+
* CONSENT AND SKILL SCRIPTS INSIDE DELEGATED SUBAGENTS (ADR 0118). A skill
|
|
130
|
+
* script run asks for approval through the same `adk_request_confirmation`
|
|
131
|
+
* call a gated tool stores, so the walk and the resume above carry it with
|
|
132
|
+
* nothing of its own. An OAuth consent request (`adk_request_credential`)
|
|
133
|
+
* the subagent left open in its child session is a pause too: the walk
|
|
134
|
+
* reports it with the path (pendingConsent), the turn runner stores the
|
|
135
|
+
* grant's answer once the callback has stored the grant, and the open
|
|
136
|
+
* call carries that answer down as it carries an approval decision; the
|
|
137
|
+
* child's own grantedCalls binds it and runs its paused call again.
|
|
138
|
+
*
|
|
83
139
|
* ADK stays out of this file: an ADK tool an agent still lists is asked
|
|
84
140
|
* whether it gates through its own checkRequireConfirmation, by shape.
|
|
85
141
|
*/
|
|
86
142
|
import type { ToolConfirmation } from '../../tools/tool.ts';
|
|
87
|
-
import type {
|
|
88
|
-
import type {
|
|
143
|
+
import type { PendingInput } from '../../workflowConfig.ts';
|
|
144
|
+
import type { PendingApproval } from '../approvals.ts';
|
|
145
|
+
import type { PendingConsent } from '../credentials.ts';
|
|
146
|
+
import type { TurnEvent, TurnFunctionCall, TurnPart } from '../events.ts';
|
|
147
|
+
import type { Session, SessionService } from '../sessions.ts';
|
|
148
|
+
import type { EntryKind } from './delegate.ts';
|
|
89
149
|
import type { NativeAgent } from './request.ts';
|
|
90
150
|
/** Why a confirmation does not bind to the call it pins: ADK's IntentMismatchError reasons. */
|
|
91
151
|
export type IntentMismatchReason = 'untrusted_request' | 'malformed_request' | 'unknown_original_call' | 'unregistered_tool' | 'tool_name_mismatch' | 'arguments_mismatch' | 'confirmation_not_required';
|
|
@@ -106,6 +166,8 @@ interface Scope {
|
|
|
106
166
|
session: Session;
|
|
107
167
|
invocationId: string;
|
|
108
168
|
branch?: string;
|
|
169
|
+
/** The session is a delegated subagent's, filed under its agent path (delegate.ts childAppName). */
|
|
170
|
+
delegated?: boolean;
|
|
109
171
|
}
|
|
110
172
|
/**
|
|
111
173
|
* The pinned calls the latest user message approves or refuses, bound to
|
|
@@ -126,4 +188,124 @@ export interface GrantedCalls {
|
|
|
126
188
|
* AuthPreprocessor, the exchange left to the callback route).
|
|
127
189
|
*/
|
|
128
190
|
export declare function grantedCalls(agent: NativeAgent, scope: Scope): Promise<GrantedCalls | undefined>;
|
|
191
|
+
/**
|
|
192
|
+
* An approval request or a question waiting inside a delegated call: the
|
|
193
|
+
* open call that leads to it from the session it was found from, and on
|
|
194
|
+
* down, to the agent that asked.
|
|
195
|
+
*/
|
|
196
|
+
export interface DelegatedPause {
|
|
197
|
+
/** The agents from the one that made the open call down to the one that asked: `[caller, subagent, …, asker]`. */
|
|
198
|
+
path: string[];
|
|
199
|
+
/** The open subagent calls on that path, by id, the outermost first. */
|
|
200
|
+
callIds: string[];
|
|
201
|
+
/** The approval request, when the asker waits on one; its `path` is set. */
|
|
202
|
+
approval?: PendingApproval;
|
|
203
|
+
/** The question, when the asker waits on one; its `path` is set. */
|
|
204
|
+
question?: PendingInput;
|
|
205
|
+
/** The OAuth consent request, when the asker waits for a grant (ADR 0118); its `path` is set. */
|
|
206
|
+
consent?: PendingConsent;
|
|
207
|
+
}
|
|
208
|
+
/** Where a session's delegated calls opened their child sessions: the store, and the caller's user and session ids. */
|
|
209
|
+
export interface DelegationKey {
|
|
210
|
+
sessions: Pick<SessionService, 'get'>;
|
|
211
|
+
userId: string;
|
|
212
|
+
sessionId: string;
|
|
213
|
+
/**
|
|
214
|
+
* The app name of the session whose events are walked: its children are
|
|
215
|
+
* filed below it (delegate.ts childAppName, ADR 0111). Absent, only the
|
|
216
|
+
* children a conversation stored before ADR 0111 holds are found.
|
|
217
|
+
*/
|
|
218
|
+
appName?: string;
|
|
219
|
+
/** The session walked is a delegated subagent's, already filed under its agent path. */
|
|
220
|
+
delegated?: boolean;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The calls an agent made that are still open: no later event answers it,
|
|
224
|
+
* and the person has not written since. Only an agent's calls count (a
|
|
225
|
+
* call in a user-authored event is never one), and the engine's own
|
|
226
|
+
* framework calls are left out. `author`, when given, keeps one agent's
|
|
227
|
+
* calls. Latest event first, calls in call order.
|
|
228
|
+
*/
|
|
229
|
+
export declare function openCalls(events: readonly TurnEvent[], author?: string): Array<{
|
|
230
|
+
author: string;
|
|
231
|
+
call: TurnFunctionCall;
|
|
232
|
+
}>;
|
|
233
|
+
/** A pause a nested workflow's walk holds: the path from the workflow down to the node that asked, and what it asks. */
|
|
234
|
+
export interface NestedWorkflowPause {
|
|
235
|
+
/** From the workflow (the route, the node, the subagent) down to the node that asked: `[workflow, …, node]`. */
|
|
236
|
+
path: string[];
|
|
237
|
+
/** The approval request, its `agent` the node that asked and its `path` set. */
|
|
238
|
+
approval?: PendingApproval;
|
|
239
|
+
/** The question, its `node` the node that asked and its `path` set. */
|
|
240
|
+
question?: PendingInput;
|
|
241
|
+
/** The OAuth consent request, inside a nested dispatch syndicate's route (ADR 0120); its `path` set. */
|
|
242
|
+
consent?: PendingConsent;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The pause a nested workflow run as a dispatch route or a workflow node
|
|
246
|
+
* holds (ADR 0119): `name`'s child session below the session filed under
|
|
247
|
+
* `key.appName` (delegate.ts entryAppName), else the one filed under its
|
|
248
|
+
* name alone (legacyChild), and the paused walk there, followed down to the
|
|
249
|
+
* node that asked. With `id`, only that interrupt counts: a request a node
|
|
250
|
+
* raised again on its caller's walk names the one its own walk waits on.
|
|
251
|
+
*/
|
|
252
|
+
export declare function nestedWorkflowPause(key: DelegationKey & {
|
|
253
|
+
appName: string;
|
|
254
|
+
}, name: string, kind: EntryKind, id?: string, depth?: number): Promise<NestedWorkflowPause | undefined>;
|
|
255
|
+
/**
|
|
256
|
+
* A dispatch route that is a nested workflow, paused (ADR 0119): the
|
|
257
|
+
* conversation's last event is the route's pause record (authored by the
|
|
258
|
+
* route at its own path, no content, the walk's open interrupts in
|
|
259
|
+
* `longRunningToolIds`; lib/runtime/syndicateTurn.ts stores it), and the
|
|
260
|
+
* route's child session holds the paused walk under one of those ids.
|
|
261
|
+
* Anything stored after the record closes it. `routes`, when given, are the
|
|
262
|
+
* names a record may carry.
|
|
263
|
+
*/
|
|
264
|
+
export declare function routePause(key: DelegationKey & {
|
|
265
|
+
appName: string;
|
|
266
|
+
}, events: readonly TurnEvent[], routes?: ReadonlySet<string>, depth?: number): Promise<(NestedWorkflowPause & {
|
|
267
|
+
route: string;
|
|
268
|
+
}) | undefined>;
|
|
269
|
+
/**
|
|
270
|
+
* An approval request or question found in a workflow's own session, raised
|
|
271
|
+
* by a node that is a nested workflow (lib/workflow/turn.ts raises its
|
|
272
|
+
* walk's requests again on the node's event, ADR 0119): the one its walk
|
|
273
|
+
* waits on, with the path from the node down to the one that asked. Any
|
|
274
|
+
* other request comes back as it is.
|
|
275
|
+
*/
|
|
276
|
+
export declare function deepestPause<T extends PendingApproval | PendingInput>(key: DelegationKey & {
|
|
277
|
+
appName: string;
|
|
278
|
+
}, pending: T): Promise<T>;
|
|
279
|
+
/**
|
|
280
|
+
* Every pause waiting inside a delegated call still open in `events` (one
|
|
281
|
+
* session's events), latest call first: each open call whose child session
|
|
282
|
+
* holds an open approval request or question, at any depth. `author`, when
|
|
283
|
+
* given, keeps one agent's calls. A call the person has written past is no
|
|
284
|
+
* longer open, and neither is anything waiting below it.
|
|
285
|
+
*/
|
|
286
|
+
export declare function delegatedPauses(key: DelegationKey, events: readonly TurnEvent[], author?: string, depth?: number, seen?: ReadonlySet<string>): Promise<DelegatedPause[]>;
|
|
287
|
+
/** The interrupt a delegated pause waits on: the approval request's id, the question's, or the consent request's. */
|
|
288
|
+
export declare function interruptIdOf(pause: DelegatedPause): string | undefined;
|
|
289
|
+
/** The open delegated calls the latest user message resumes, and the ones still waiting after it. */
|
|
290
|
+
export interface ResumedDelegations {
|
|
291
|
+
/** The open calls whose pause the message answers, as the agent made them. */
|
|
292
|
+
calls: TurnFunctionCall[];
|
|
293
|
+
/** By call id: the message's parts that answer what waits below it, to be the child's next message. */
|
|
294
|
+
answers: Map<string, TurnPart[]>;
|
|
295
|
+
/** The open calls whose pause the message does not answer, by id. */
|
|
296
|
+
waiting: string[];
|
|
297
|
+
/** The agent's tools by name, as the loop runs calls against them. */
|
|
298
|
+
tools: Map<string, unknown>;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The delegated calls `agent` left open that the latest user message
|
|
302
|
+
* answers (ADR 0110): the message's approval decisions and question
|
|
303
|
+
* answers (function responses to `adk_request_confirmation` or `ask_user`)
|
|
304
|
+
* and grants (to `adk_request_credential`, ADR 0118) matched, by interrupt
|
|
305
|
+
* id, to the pause below each open call. Undefined when the message answers
|
|
306
|
+
* none. The child binds the answer itself: an approval is checked against
|
|
307
|
+
* its pinned call there (approvedCalls), a grant against its request
|
|
308
|
+
* (grantedCalls), as at the top.
|
|
309
|
+
*/
|
|
310
|
+
export declare function resumedDelegations(agent: NativeAgent, scope: Scope, sessions: Pick<SessionService, 'get'>): Promise<ResumedDelegations | undefined>;
|
|
129
311
|
export {};
|
|
@@ -80,14 +80,75 @@
|
|
|
80
80
|
* builds its request. A later step finds the agent's own events last,
|
|
81
81
|
* so the call runs once.
|
|
82
82
|
*
|
|
83
|
+
* PAUSES INSIDE DELEGATED SUBAGENTS (WS6-2a, ADR 0110). A subagent runs in
|
|
84
|
+
* its own child session (delegate.ts); an approval request or question it
|
|
85
|
+
* leaves open ends its run paused, and its caller leaves the delegated
|
|
86
|
+
* call open (no response stored) and ends paused too, up to the turn.
|
|
87
|
+
* delegatedPauses finds such a pause from any session by walking down:
|
|
88
|
+
* each call its agent left open (openCalls) leads to the child session
|
|
89
|
+
* under the call's name, whose own open request or question is the pause,
|
|
90
|
+
* or whose own open call leads one level further. The answer travels back
|
|
91
|
+
* down the same way: before each step, the loop resumes the open call
|
|
92
|
+
* whose pause the latest user message answers (agentLoop.ts), and the
|
|
93
|
+
* child reads the same answer from its own session, as it would at the top.
|
|
94
|
+
*
|
|
95
|
+
* NESTED SYNDICATES (WS6-2b, ADR 0111). The child session of a call is the
|
|
96
|
+
* one runSubagent files it under: the agent path (delegate.ts childAppName),
|
|
97
|
+
* else, for a conversation stored before ADR 0111, the subagent's name
|
|
98
|
+
* alone (legacyChild). A nested delegate syndicate is its orchestrator
|
|
99
|
+
* there, so the walk goes on through its open calls. A nested workflow
|
|
100
|
+
* syndicate holds a paused walk there instead: its own pause record (the
|
|
101
|
+
* workflow's event naming every interrupt it left open, authored by the
|
|
102
|
+
* workflow under the call's name) makes an approval request an agent node
|
|
103
|
+
* raised, or an ask_user node's question, the pause, with the node that
|
|
104
|
+
* asked at the end of the path. The walk stops there: a pause inside a
|
|
105
|
+
* workflow node's own delegation does not run (lib/workflow/agentNode.ts).
|
|
106
|
+
*
|
|
107
|
+
* NESTED WORKFLOWS AS A ROUTE OR A NODE (ADR 0119). A workflow node that is
|
|
108
|
+
* a nested workflow raises its walk's open requests again on its caller's
|
|
109
|
+
* walk, on one event of its own (lib/workflow/turn.ts), so the caller's
|
|
110
|
+
* walk pauses on the same ids; deepestPause follows such a request down,
|
|
111
|
+
* by id, through each child session filed under entryAppName (its kind in
|
|
112
|
+
* the key: `route:`, `node:`, ADR 0120; else an older key, legacyEntry) to
|
|
113
|
+
* the node that asked. A dispatch route that is one leaves its pause
|
|
114
|
+
* record as the conversation's last event (routePause). A delegated nested
|
|
115
|
+
* workflow's node that is one is followed the same way.
|
|
116
|
+
*
|
|
117
|
+
* NESTED DISPATCH SYNDICATES (ADR 0120). One runs as a turn of its own on
|
|
118
|
+
* its own conversation (the child session of a delegated call, a route or
|
|
119
|
+
* a node), its routes answering there as a turn's routes answer in the
|
|
120
|
+
* turn's conversation. conversationPause finds what it waits on as the
|
|
121
|
+
* turn finds it at the top: a route's pause record, a route's open
|
|
122
|
+
* request, question or consent request, else a pause inside a call a route
|
|
123
|
+
* left open (filed `<conversation>/<route>/<subagent>`). pauseBelow falls
|
|
124
|
+
* to it when the subagent's own checks find nothing; nestedWorkflowPause
|
|
125
|
+
* when the child session is not a walk. The answer goes down the open
|
|
126
|
+
* call, the route's pause record or the node's raised request, and the
|
|
127
|
+
* nested turn reads it as a message at the top.
|
|
128
|
+
*
|
|
129
|
+
* CONSENT AND SKILL SCRIPTS INSIDE DELEGATED SUBAGENTS (ADR 0118). A skill
|
|
130
|
+
* script run asks for approval through the same `adk_request_confirmation`
|
|
131
|
+
* call a gated tool stores, so the walk and the resume above carry it with
|
|
132
|
+
* nothing of its own. An OAuth consent request (`adk_request_credential`)
|
|
133
|
+
* the subagent left open in its child session is a pause too: the walk
|
|
134
|
+
* reports it with the path (pendingConsent), the turn runner stores the
|
|
135
|
+
* grant's answer once the callback has stored the grant, and the open
|
|
136
|
+
* call carries that answer down as it carries an approval decision; the
|
|
137
|
+
* child's own grantedCalls binds it and runs its paused call again.
|
|
138
|
+
*
|
|
83
139
|
* ADK stays out of this file: an ADK tool an agent still lists is asked
|
|
84
140
|
* whether it gates through its own checkRequireConfirmation, by shape.
|
|
85
141
|
*/
|
|
86
142
|
import { isDeepStrictEqual } from 'node:util';
|
|
87
143
|
import { nativeToolOf } from '../../models/schemaNormalize.js';
|
|
88
144
|
import { instructionToolOf, isTool, toolOf } from '../../tools/tool.js';
|
|
145
|
+
import { inputRequestFrom } from '../../workflowConfig.js';
|
|
146
|
+
import { approvalRequestOf, pendingApproval } from '../approvals.js';
|
|
147
|
+
import { pendingConsent } from '../credentials.js';
|
|
89
148
|
import { getFunctionCalls, getFunctionResponses } from '../events.js';
|
|
90
|
-
import {
|
|
149
|
+
import { ASK_USER, pendingQuestion } from '../questions.js';
|
|
150
|
+
import { childAppName, entryAppName, legacyChild, legacyEntry } from './delegate.js';
|
|
151
|
+
import { REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, REQUEST_INPUT_CALL, isSegmentPrefix } from './history.js';
|
|
91
152
|
import { isToolset } from './request.js';
|
|
92
153
|
/** A confirmation that does not bind to its pinned call: ADK's IntentMismatchError, by name and text. */
|
|
93
154
|
export class IntentMismatchError extends Error {
|
|
@@ -308,3 +369,332 @@ export async function grantedCalls(agent, scope) {
|
|
|
308
369
|
}
|
|
309
370
|
return undefined;
|
|
310
371
|
}
|
|
372
|
+
/** The deepest a chain of delegated pauses is followed: nested syndicates stop at 16 levels (lib/compile.ts). */
|
|
373
|
+
const MAX_DELEGATION_DEPTH = 16;
|
|
374
|
+
/** The engine's own long-running calls: never a delegation. */
|
|
375
|
+
const FRAMEWORK_CALLS = new Set([REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, ASK_USER]);
|
|
376
|
+
const hasUserText = (e) => e.author === 'user' && (e.content?.parts ?? []).some((p) => typeof p.text === 'string' && p.text.trim() !== '' && !p.thought);
|
|
377
|
+
/**
|
|
378
|
+
* The calls an agent made that are still open: no later event answers it,
|
|
379
|
+
* and the person has not written since. Only an agent's calls count (a
|
|
380
|
+
* call in a user-authored event is never one), and the engine's own
|
|
381
|
+
* framework calls are left out. `author`, when given, keeps one agent's
|
|
382
|
+
* calls. Latest event first, calls in call order.
|
|
383
|
+
*/
|
|
384
|
+
export function openCalls(events, author) {
|
|
385
|
+
const answered = new Set();
|
|
386
|
+
const open = [];
|
|
387
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
388
|
+
const event = events[i];
|
|
389
|
+
if (hasUserText(event))
|
|
390
|
+
break;
|
|
391
|
+
for (const r of getFunctionResponses(event))
|
|
392
|
+
if (r.id)
|
|
393
|
+
answered.add(r.id);
|
|
394
|
+
if (!event.author || event.author === 'user' || (author !== undefined && event.author !== author))
|
|
395
|
+
continue;
|
|
396
|
+
for (const call of getFunctionCalls(event)) {
|
|
397
|
+
if (!call.id || !call.name || FRAMEWORK_CALLS.has(call.name) || answered.has(call.id))
|
|
398
|
+
continue;
|
|
399
|
+
open.push({ author: event.author, call });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return open;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* What waits inside the delegated call `call` that `caller` left open: the
|
|
406
|
+
* child session under the call's name (where runSubagent runs it), and its
|
|
407
|
+
* own open approval request or question, else a pause inside a call it left
|
|
408
|
+
* open, one level down. Only the subagent's own requests count: a request
|
|
409
|
+
* or question authored by anyone else is none.
|
|
410
|
+
*/
|
|
411
|
+
async function pauseBelow(key, caller, call, depth, seen) {
|
|
412
|
+
const name = call.name;
|
|
413
|
+
const id = call.id;
|
|
414
|
+
if (depth >= MAX_DELEGATION_DEPTH || seen.has(name))
|
|
415
|
+
return undefined;
|
|
416
|
+
// Where runSubagent filed the call (ADR 0111), else where it filed it before.
|
|
417
|
+
const own = key.appName === undefined ? undefined : await key.sessions.get({ appName: childAppName({ appName: key.appName, delegated: key.delegated === true }, caller, name), userId: key.userId, sessionId: key.sessionId });
|
|
418
|
+
const child = own ?? (await legacyChild(key.sessions, key, name, true));
|
|
419
|
+
if (!child)
|
|
420
|
+
return undefined;
|
|
421
|
+
const here = [caller, name];
|
|
422
|
+
const walked = await workflowPauseIn(key, child, name);
|
|
423
|
+
if (walked) {
|
|
424
|
+
const path = [caller, ...walked.path];
|
|
425
|
+
return {
|
|
426
|
+
path,
|
|
427
|
+
callIds: [id],
|
|
428
|
+
...(walked.approval ? { approval: { ...walked.approval, path } } : {}),
|
|
429
|
+
...(walked.question ? { question: { ...walked.question, path } } : {}),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const approval = pendingApproval(child.events);
|
|
433
|
+
if (approval && approval.agent === name)
|
|
434
|
+
return { path: here, callIds: [id], approval: { ...approval, path: here } };
|
|
435
|
+
const question = pendingQuestion(child.events);
|
|
436
|
+
if (question && question.node === name)
|
|
437
|
+
return { path: here, callIds: [id], question: { ...question, path: here } };
|
|
438
|
+
// Only the subagent's own request counts (pendingConsent already skips a user-authored one).
|
|
439
|
+
const consent = pendingConsent(child.events);
|
|
440
|
+
if (consent && consent.agent === name)
|
|
441
|
+
return { path: here, callIds: [id], consent: { ...consent, path: here } };
|
|
442
|
+
const [first] = await delegatedPauses({ ...key, appName: child.appName, delegated: true }, child.events, name, depth + 1, new Set([...seen, name]));
|
|
443
|
+
if (first) {
|
|
444
|
+
const path = [caller, ...first.path];
|
|
445
|
+
return {
|
|
446
|
+
path,
|
|
447
|
+
callIds: [id, ...first.callIds],
|
|
448
|
+
...(first.approval ? { approval: { ...first.approval, path } } : {}),
|
|
449
|
+
...(first.question ? { question: { ...first.question, path } } : {}),
|
|
450
|
+
...(first.consent ? { consent: { ...first.consent, path } } : {}),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
// A nested dispatch syndicate delegated to (ADR 0120): its routes answer in the child session, as routes answer in a turn's.
|
|
454
|
+
const routed = await conversationPause(key, child, name, undefined, depth + 1, new Set([...seen, name]));
|
|
455
|
+
if (!routed)
|
|
456
|
+
return undefined;
|
|
457
|
+
const path = [caller, ...routed.path];
|
|
458
|
+
return {
|
|
459
|
+
path,
|
|
460
|
+
callIds: [id],
|
|
461
|
+
...(routed.approval ? { approval: { ...routed.approval, path } } : {}),
|
|
462
|
+
...(routed.question ? { question: { ...routed.question, path } } : {}),
|
|
463
|
+
...(routed.consent ? { consent: { ...routed.consent, path } } : {}),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/** The id a pause found below waits on. */
|
|
467
|
+
const idOf = (pause) => pause.approval?.id ?? pause.question?.id ?? pause.consent?.id;
|
|
468
|
+
/**
|
|
469
|
+
* What a nested dispatch syndicate's conversation (`child`, filed for the
|
|
470
|
+
* entry `name`) waits on (ADR 0120): what its own turn would report at the
|
|
471
|
+
* top. Its routes answer there as a turn's routes answer in the turn's
|
|
472
|
+
* conversation, so it is found as the turn finds it: a route's pause record
|
|
473
|
+
* (a nested workflow or a nested dispatch syndicate run as a route, followed
|
|
474
|
+
* down), a route's open approval request, question or consent request, else
|
|
475
|
+
* a pause inside a call a route left open, its child filed under the
|
|
476
|
+
* route's name (`<child>/<route>/<subagent>`, as a turn's route files its
|
|
477
|
+
* own). The path runs from `name` down to the agent that asked. With `id`,
|
|
478
|
+
* only that request counts. Calls `name` made itself are not a route's.
|
|
479
|
+
*/
|
|
480
|
+
async function conversationPause(key, child, name, id, depth, seen = new Set()) {
|
|
481
|
+
if (depth >= MAX_DELEGATION_DEPTH)
|
|
482
|
+
return undefined;
|
|
483
|
+
const at = { ...key, appName: child.appName, delegated: false };
|
|
484
|
+
const wanted = (found) => !!found && (id === undefined || found === id);
|
|
485
|
+
const down = (path) => [name, ...path];
|
|
486
|
+
const routed = await routePause(at, child.events, undefined, depth + 1);
|
|
487
|
+
if (routed && wanted(idOf(routed))) {
|
|
488
|
+
const path = down(routed.path);
|
|
489
|
+
return {
|
|
490
|
+
path,
|
|
491
|
+
...(routed.approval ? { approval: { ...routed.approval, path } } : {}),
|
|
492
|
+
...(routed.question ? { question: { ...routed.question, path } } : {}),
|
|
493
|
+
...(routed.consent ? { consent: { ...routed.consent, path } } : {}),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const approval = pendingApproval(child.events);
|
|
497
|
+
if (approval && approval.agent && approval.agent !== 'user' && approval.agent !== name && wanted(approval.id)) {
|
|
498
|
+
const path = down([approval.agent]);
|
|
499
|
+
return { path, approval: { ...approval, path } };
|
|
500
|
+
}
|
|
501
|
+
const question = pendingQuestion(child.events);
|
|
502
|
+
if (question && question.node && question.node !== 'user' && question.node !== name && wanted(question.id)) {
|
|
503
|
+
const path = down([question.node]);
|
|
504
|
+
return { path, question: { ...question, path } };
|
|
505
|
+
}
|
|
506
|
+
const consent = pendingConsent(child.events);
|
|
507
|
+
if (consent && consent.agent && consent.agent !== name && wanted(consent.id)) {
|
|
508
|
+
const path = down([consent.agent]);
|
|
509
|
+
return { path, consent: { ...consent, path } };
|
|
510
|
+
}
|
|
511
|
+
const below = await delegatedPauses(at, child.events, undefined, depth + 1, seen);
|
|
512
|
+
const first = below.find((p) => p.path[0] !== name && wanted(interruptIdOf(p)));
|
|
513
|
+
if (!first)
|
|
514
|
+
return undefined;
|
|
515
|
+
const path = down(first.path);
|
|
516
|
+
return {
|
|
517
|
+
path,
|
|
518
|
+
...(first.approval ? { approval: { ...first.approval, path } } : {}),
|
|
519
|
+
...(first.question ? { question: { ...first.question, path } } : {}),
|
|
520
|
+
...(first.consent ? { consent: { ...first.consent, path } } : {}),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/** A walk's session: its node events carry a dotted node path (`<workflow>.<node>`). */
|
|
524
|
+
const isWalk = (session) => session.events.some((e) => typeof e.nodeInfo?.path === 'string' && e.nodeInfo.path.includes('.'));
|
|
525
|
+
/**
|
|
526
|
+
* The pause a nested workflow's walk holds in its child session (ADR 0111):
|
|
527
|
+
* the workflow's own pause record, the last event, authored by the
|
|
528
|
+
* workflow (`name`) at its own path with no content, names the open
|
|
529
|
+
* interrupts; the approval request or ask_user question among them is the
|
|
530
|
+
* pause, and the node that raised it the asker. With `id`, only that
|
|
531
|
+
* interrupt counts. Undefined when the session holds no paused walk of
|
|
532
|
+
* `name`'s. A node that is itself a nested workflow raised its walk's
|
|
533
|
+
* requests again on its own event (lib/workflow/turn.ts, ADR 0119), so it
|
|
534
|
+
* is the asker here, and workflowPauseIn goes on down.
|
|
535
|
+
*/
|
|
536
|
+
function workflowPause(events, name, id) {
|
|
537
|
+
const record = events[events.length - 1];
|
|
538
|
+
if (!record || record.author !== name || (record.content?.parts ?? []).length > 0 || record.nodeInfo?.path !== name)
|
|
539
|
+
return undefined;
|
|
540
|
+
const open = new Set((record.longRunningToolIds ?? []).filter((x) => id === undefined || x === id));
|
|
541
|
+
if (open.size === 0)
|
|
542
|
+
return undefined;
|
|
543
|
+
// The latest open request the record names, an approval first: the walk stores node inputs as user turns, so a later one may follow it.
|
|
544
|
+
for (const kind of [REQUEST_CONFIRMATION_CALL, REQUEST_INPUT_CALL]) {
|
|
545
|
+
for (let i = events.length - 2; i >= 0; i--) {
|
|
546
|
+
const event = events[i];
|
|
547
|
+
if (!event.author || event.author === 'user')
|
|
548
|
+
continue;
|
|
549
|
+
for (const call of getFunctionCalls(event)) {
|
|
550
|
+
if (call.name !== kind || !call.id || !open.has(call.id))
|
|
551
|
+
continue;
|
|
552
|
+
if (kind === REQUEST_CONFIRMATION_CALL)
|
|
553
|
+
return { asker: event.author, approval: approvalRequestOf(event, call) };
|
|
554
|
+
const question = inputRequestFrom(event.author, call);
|
|
555
|
+
if (question && question.id === call.id)
|
|
556
|
+
return { asker: event.author, question };
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The pause the walk of `name` holds in `child` (its child session), with
|
|
564
|
+
* `id` the only interrupt that counts when given: the node that asked, or,
|
|
565
|
+
* when that node is itself a nested workflow (ADR 0119), the pause its own
|
|
566
|
+
* child session holds under the same id, one level further down.
|
|
567
|
+
*/
|
|
568
|
+
async function workflowPauseIn(key, child, name, id, depth = 0) {
|
|
569
|
+
const walked = workflowPause(child.events, name, id);
|
|
570
|
+
if (!walked)
|
|
571
|
+
return undefined;
|
|
572
|
+
const found = (walked.approval?.id ?? walked.question?.id);
|
|
573
|
+
const deeper = await nestedWorkflowPause({ ...key, appName: child.appName }, walked.asker, 'node', found, depth + 1);
|
|
574
|
+
const path = deeper ? [name, ...deeper.path] : [name, walked.asker];
|
|
575
|
+
const approval = deeper ? deeper.approval : walked.approval;
|
|
576
|
+
const question = deeper ? deeper.question : walked.question;
|
|
577
|
+
return { path, ...(approval ? { approval: { ...approval, path } } : {}), ...(question ? { question: { ...question, path } } : {}) };
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* The pause a nested workflow run as a dispatch route or a workflow node
|
|
581
|
+
* holds (ADR 0119): `name`'s child session below the session filed under
|
|
582
|
+
* `key.appName` (delegate.ts entryAppName), else the one filed under its
|
|
583
|
+
* name alone (legacyChild), and the paused walk there, followed down to the
|
|
584
|
+
* node that asked. With `id`, only that interrupt counts: a request a node
|
|
585
|
+
* raised again on its caller's walk names the one its own walk waits on.
|
|
586
|
+
*/
|
|
587
|
+
export async function nestedWorkflowPause(key, name, kind, id, depth = 0) {
|
|
588
|
+
if (depth >= MAX_DELEGATION_DEPTH)
|
|
589
|
+
return undefined;
|
|
590
|
+
const own = await key.sessions.get({ appName: entryAppName(key.appName, name, kind), userId: key.userId, sessionId: key.sessionId });
|
|
591
|
+
const child = own ?? (await legacyEntry(key.sessions, key, name, true));
|
|
592
|
+
if (!child)
|
|
593
|
+
return undefined;
|
|
594
|
+
const walked = await workflowPauseIn(key, child, name, id, depth);
|
|
595
|
+
if (walked || isWalk(child))
|
|
596
|
+
return walked;
|
|
597
|
+
// A nested dispatch syndicate run as the route or the node (ADR 0120): its own conversation, as its turn reads it.
|
|
598
|
+
return conversationPause(key, child, name, id, depth + 1);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* A dispatch route that is a nested workflow, paused (ADR 0119): the
|
|
602
|
+
* conversation's last event is the route's pause record (authored by the
|
|
603
|
+
* route at its own path, no content, the walk's open interrupts in
|
|
604
|
+
* `longRunningToolIds`; lib/runtime/syndicateTurn.ts stores it), and the
|
|
605
|
+
* route's child session holds the paused walk under one of those ids.
|
|
606
|
+
* Anything stored after the record closes it. `routes`, when given, are the
|
|
607
|
+
* names a record may carry.
|
|
608
|
+
*/
|
|
609
|
+
export async function routePause(key, events, routes, depth = 0) {
|
|
610
|
+
const record = events[events.length - 1];
|
|
611
|
+
const route = record?.author;
|
|
612
|
+
if (!record || !route || route === 'user' || (record.content?.parts ?? []).length > 0 || record.nodeInfo?.path !== route)
|
|
613
|
+
return undefined;
|
|
614
|
+
if (routes && !routes.has(route))
|
|
615
|
+
return undefined;
|
|
616
|
+
for (const id of record.longRunningToolIds ?? []) {
|
|
617
|
+
const pause = await nestedWorkflowPause(key, route, 'route', id, depth);
|
|
618
|
+
if (pause)
|
|
619
|
+
return { ...pause, route };
|
|
620
|
+
}
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* An approval request or question found in a workflow's own session, raised
|
|
625
|
+
* by a node that is a nested workflow (lib/workflow/turn.ts raises its
|
|
626
|
+
* walk's requests again on the node's event, ADR 0119): the one its walk
|
|
627
|
+
* waits on, with the path from the node down to the one that asked. Any
|
|
628
|
+
* other request comes back as it is.
|
|
629
|
+
*/
|
|
630
|
+
export async function deepestPause(key, pending) {
|
|
631
|
+
const raisedBy = 'agent' in pending ? pending.agent : pending.node;
|
|
632
|
+
if (!raisedBy || pending.path)
|
|
633
|
+
return pending;
|
|
634
|
+
const below = await nestedWorkflowPause(key, raisedBy, 'node', pending.id);
|
|
635
|
+
const found = 'agent' in pending ? below?.approval : below?.question;
|
|
636
|
+
return found ?? pending;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Every pause waiting inside a delegated call still open in `events` (one
|
|
640
|
+
* session's events), latest call first: each open call whose child session
|
|
641
|
+
* holds an open approval request or question, at any depth. `author`, when
|
|
642
|
+
* given, keeps one agent's calls. A call the person has written past is no
|
|
643
|
+
* longer open, and neither is anything waiting below it.
|
|
644
|
+
*/
|
|
645
|
+
export async function delegatedPauses(key, events, author, depth = 0, seen = new Set()) {
|
|
646
|
+
const out = [];
|
|
647
|
+
for (const { author: caller, call } of openCalls(events, author)) {
|
|
648
|
+
const pause = await pauseBelow(key, caller, call, depth, seen);
|
|
649
|
+
if (pause)
|
|
650
|
+
out.push(pause);
|
|
651
|
+
}
|
|
652
|
+
return out;
|
|
653
|
+
}
|
|
654
|
+
/** The interrupt a delegated pause waits on: the approval request's id, the question's, or the consent request's. */
|
|
655
|
+
export function interruptIdOf(pause) {
|
|
656
|
+
return pause.approval?.id ?? pause.question?.id ?? pause.consent?.id;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* The delegated calls `agent` left open that the latest user message
|
|
660
|
+
* answers (ADR 0110): the message's approval decisions and question
|
|
661
|
+
* answers (function responses to `adk_request_confirmation` or `ask_user`)
|
|
662
|
+
* and grants (to `adk_request_credential`, ADR 0118) matched, by interrupt
|
|
663
|
+
* id, to the pause below each open call. Undefined when the message answers
|
|
664
|
+
* none. The child binds the answer itself: an approval is checked against
|
|
665
|
+
* its pinned call there (approvedCalls), a grant against its request
|
|
666
|
+
* (grantedCalls), as at the top.
|
|
667
|
+
*/
|
|
668
|
+
export async function resumedDelegations(agent, scope, sessions) {
|
|
669
|
+
const events = scope.session.events;
|
|
670
|
+
let latest;
|
|
671
|
+
for (let i = events.length - 1; i >= 0 && !latest; i--)
|
|
672
|
+
if (events[i]?.author === 'user')
|
|
673
|
+
latest = events[i];
|
|
674
|
+
if (!latest)
|
|
675
|
+
return undefined;
|
|
676
|
+
const answerParts = (latest.content?.parts ?? []).filter((p) => {
|
|
677
|
+
const r = p.functionResponse;
|
|
678
|
+
return !!r?.id && (r.name === REQUEST_CONFIRMATION_CALL || r.name === ASK_USER || r.name === REQUEST_CREDENTIAL_CALL);
|
|
679
|
+
});
|
|
680
|
+
if (answerParts.length === 0)
|
|
681
|
+
return undefined;
|
|
682
|
+
const pauses = await delegatedPauses({ sessions, userId: scope.session.userId, sessionId: scope.session.id, appName: scope.session.appName, delegated: scope.delegated === true }, events, agent.name);
|
|
683
|
+
if (pauses.length === 0)
|
|
684
|
+
return undefined;
|
|
685
|
+
const open = new Map(openCalls(events, agent.name).map(({ call }) => [call.id, call]));
|
|
686
|
+
const answers = new Map();
|
|
687
|
+
const waiting = [];
|
|
688
|
+
for (const pause of pauses) {
|
|
689
|
+
const callId = pause.callIds[0];
|
|
690
|
+
const parts = answerParts.filter((p) => p.functionResponse?.id === interruptIdOf(pause));
|
|
691
|
+
if (parts.length)
|
|
692
|
+
answers.set(callId, [...(answers.get(callId) ?? []), ...parts]);
|
|
693
|
+
else
|
|
694
|
+
waiting.push(callId);
|
|
695
|
+
}
|
|
696
|
+
if (answers.size === 0)
|
|
697
|
+
return undefined;
|
|
698
|
+
const calls = [...answers.keys()].map((id) => open.get(id)).filter((c) => !!c);
|
|
699
|
+
return { calls, answers, waiting: waiting.filter((id) => !answers.has(id)), tools: await toolsOf(agent, scope) };
|
|
700
|
+
}
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
*
|
|
14
14
|
* THE REQUEST, in the order ADK built it:
|
|
15
15
|
* 1. The agent's generateContentConfig, and its output schema when it has
|
|
16
|
-
* no tools
|
|
17
|
-
*
|
|
16
|
+
* no tools, or when every model the step may call (its own and its
|
|
17
|
+
* fallback_model) takes a schema beside tools in one request: Claude
|
|
18
|
+
* from Opus 4.8, Sonnet 5 and Haiku 5.5 on, OpenAI, Gemini 2 and later
|
|
19
|
+
* on Vertex AI (outputSchemaBesideTools, lib/models/capabilities.ts,
|
|
20
|
+
* ADR 0109). That is how an orchestrator that delegates answers in a
|
|
21
|
+
* schema without a set_model_response call.
|
|
18
22
|
* 2. The system prompt, each piece joined to the last by a blank line:
|
|
19
23
|
* - the identity lines ("You are an agent. Your internal name is …"),
|
|
20
24
|
* unless the agent may transfer to no one (an output schema rules
|
|
@@ -112,6 +116,8 @@ export interface NativeAgent {
|
|
|
112
116
|
fallbackModel?: string;
|
|
113
117
|
/** `context:` (ADR 0033): compact the history into a summary past a token threshold. The loop runs it before each step (compaction.ts). */
|
|
114
118
|
context?: ContextConfig;
|
|
119
|
+
/** The syndicate's `max_concurrency` (ADR 0116): delegated calls one step runs at once. Default DEFAULT_MAX_CONCURRENCY (delegate.ts). */
|
|
120
|
+
maxConcurrency?: number;
|
|
115
121
|
}
|
|
116
122
|
/** What an instruction function reads. */
|
|
117
123
|
export interface InstructionContext {
|
|
@@ -166,7 +172,11 @@ export interface BuiltRequest {
|
|
|
166
172
|
export declare const SET_MODEL_RESPONSE = "set_model_response";
|
|
167
173
|
/** set_model_response as an own Tool: its answer is the arguments, as JSON text, and it ends the step. */
|
|
168
174
|
export declare function setModelResponseTool(outputSchema: Record<string, unknown>): Tool;
|
|
169
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* ADK's rule for whether the model takes an output schema beside tools in one
|
|
177
|
+
* request (Vertex AI, Gemini 2 and later). `mode: task` keeps it as it is,
|
|
178
|
+
* for the instruction line ADK's processor writes there.
|
|
179
|
+
*/
|
|
170
180
|
export declare function outputSchemaWithTools(model: string): boolean;
|
|
171
181
|
/**
|
|
172
182
|
* What ADK's runLlmAgentAsNode puts in a node agent's invocation context
|