melchizedek-agents 1.0.2 → 1.1.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 +19 -2
- package/CHANGELOG.md +333 -0
- package/DOCUMENTATION.md +99 -39
- package/README.md +1 -0
- 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 +6 -6
- package/config/agents/examples/critic.yaml +16 -16
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +33 -32
- 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 +1979 -0
- package/config/agents/syndicateSchema.yaml +109 -72
- 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 +61 -60
- 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 +21 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +15 -0
- package/dist/lib/a2a/app.js +53 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +34 -4
- package/dist/lib/a2a/oauthSetup.d.ts +90 -0
- package/dist/lib/a2a/oauthSetup.js +205 -0
- package/dist/lib/agentDialect.d.ts +66 -0
- package/dist/lib/agentDialect.js +134 -0
- package/dist/lib/compile.d.ts +10 -5
- package/dist/lib/compile.js +69 -37
- package/dist/lib/compileNative.js +2 -0
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +34 -1
- package/dist/lib/doctor.js +47 -3
- package/dist/lib/loadSyndicate.d.ts +45 -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 +16 -1
- package/dist/lib/models/capabilities.js +55 -1
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/runtime/approvals.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +18 -1
- package/dist/lib/runtime/native/agentLoop.js +105 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +164 -0
- package/dist/lib/runtime/native/checkpoint.js +365 -0
- package/dist/lib/runtime/native/delegate.d.ts +141 -18
- package/dist/lib/runtime/native/delegate.js +235 -39
- package/dist/lib/runtime/native/interrupts.d.ts +101 -2
- package/dist/lib/runtime/native/interrupts.js +201 -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 +7 -1
- package/dist/lib/runtime/syndicateTurn.js +74 -22
- package/dist/lib/runtime/turnControl.d.ts +15 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +7 -2
- package/dist/lib/storage/postgres/taskQueue.d.ts +14 -2
- package/dist/lib/storage/postgres/taskQueue.js +20 -3
- 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 +278 -0
- package/dist/lib/syndicateSchema.js +182 -15
- package/dist/lib/tools/auth.d.ts +7 -5
- package/dist/lib/tools/auth.js +12 -4
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthHosts.d.ts +71 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +233 -0
- package/dist/lib/tools/oauthTools.js +421 -0
- package/dist/lib/tools/openapiTools.d.ts +4 -6
- package/dist/lib/tools/openapiTools.js +40 -36
- package/dist/lib/tools/taskTools.d.ts +30 -5
- package/dist/lib/tools/taskTools.js +74 -10
- package/dist/lib/workflow.d.ts +8 -4
- package/dist/lib/workflow.js +8 -4
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +122 -1
- package/dist/scripts/assistant_worker.d.ts +10 -0
- package/dist/scripts/assistant_worker.js +69 -19
- package/dist/scripts/doctor.d.ts +4 -1
- package/dist/scripts/doctor.js +5 -2
- package/dist/scripts/yaml_codemod.d.ts +52 -0
- package/dist/scripts/yaml_codemod.js +570 -0
- package/package.json +11 -7
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +4 -3
- package/skills/briefs/melchizedek.md +1 -0
- 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 +6 -3
|
@@ -80,14 +80,42 @@
|
|
|
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
|
+
*
|
|
83
107
|
* ADK stays out of this file: an ADK tool an agent still lists is asked
|
|
84
108
|
* whether it gates through its own checkRequireConfirmation, by shape.
|
|
85
109
|
*/
|
|
86
110
|
import { isDeepStrictEqual } from 'node:util';
|
|
87
111
|
import { nativeToolOf } from '../../models/schemaNormalize.js';
|
|
88
112
|
import { instructionToolOf, isTool, toolOf } from '../../tools/tool.js';
|
|
113
|
+
import { inputRequestFrom } from '../../workflowConfig.js';
|
|
114
|
+
import { pendingApproval } from '../approvals.js';
|
|
89
115
|
import { getFunctionCalls, getFunctionResponses } from '../events.js';
|
|
90
|
-
import {
|
|
116
|
+
import { ASK_USER, pendingQuestion } from '../questions.js';
|
|
117
|
+
import { childAppName, legacyChild } from './delegate.js';
|
|
118
|
+
import { REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, REQUEST_INPUT_CALL, isSegmentPrefix } from './history.js';
|
|
91
119
|
import { isToolset } from './request.js';
|
|
92
120
|
/** A confirmation that does not bind to its pinned call: ADK's IntentMismatchError, by name and text. */
|
|
93
121
|
export class IntentMismatchError extends Error {
|
|
@@ -308,3 +336,175 @@ export async function grantedCalls(agent, scope) {
|
|
|
308
336
|
}
|
|
309
337
|
return undefined;
|
|
310
338
|
}
|
|
339
|
+
/** The deepest a chain of delegated pauses is followed: nested syndicates stop at 16 levels (lib/compile.ts). */
|
|
340
|
+
const MAX_DELEGATION_DEPTH = 16;
|
|
341
|
+
/** The engine's own long-running calls: never a delegation. */
|
|
342
|
+
const FRAMEWORK_CALLS = new Set([REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, ASK_USER]);
|
|
343
|
+
const hasUserText = (e) => e.author === 'user' && (e.content?.parts ?? []).some((p) => typeof p.text === 'string' && p.text.trim() !== '' && !p.thought);
|
|
344
|
+
/**
|
|
345
|
+
* The calls an agent made that are still open: no later event answers it,
|
|
346
|
+
* and the person has not written since. Only an agent's calls count (a
|
|
347
|
+
* call in a user-authored event is never one), and the engine's own
|
|
348
|
+
* framework calls are left out. `author`, when given, keeps one agent's
|
|
349
|
+
* calls. Latest event first, calls in call order.
|
|
350
|
+
*/
|
|
351
|
+
export function openCalls(events, author) {
|
|
352
|
+
const answered = new Set();
|
|
353
|
+
const open = [];
|
|
354
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
355
|
+
const event = events[i];
|
|
356
|
+
if (hasUserText(event))
|
|
357
|
+
break;
|
|
358
|
+
for (const r of getFunctionResponses(event))
|
|
359
|
+
if (r.id)
|
|
360
|
+
answered.add(r.id);
|
|
361
|
+
if (!event.author || event.author === 'user' || (author !== undefined && event.author !== author))
|
|
362
|
+
continue;
|
|
363
|
+
for (const call of getFunctionCalls(event)) {
|
|
364
|
+
if (!call.id || !call.name || FRAMEWORK_CALLS.has(call.name) || answered.has(call.id))
|
|
365
|
+
continue;
|
|
366
|
+
open.push({ author: event.author, call });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return open;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* What waits inside the delegated call `call` that `caller` left open: the
|
|
373
|
+
* child session under the call's name (where runSubagent runs it), and its
|
|
374
|
+
* own open approval request or question, else a pause inside a call it left
|
|
375
|
+
* open, one level down. Only the subagent's own requests count: a request
|
|
376
|
+
* or question authored by anyone else is none.
|
|
377
|
+
*/
|
|
378
|
+
async function pauseBelow(key, caller, call, depth, seen) {
|
|
379
|
+
const name = call.name;
|
|
380
|
+
const id = call.id;
|
|
381
|
+
if (depth >= MAX_DELEGATION_DEPTH || seen.has(name))
|
|
382
|
+
return undefined;
|
|
383
|
+
// Where runSubagent filed the call (ADR 0111), else where it filed it before.
|
|
384
|
+
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 });
|
|
385
|
+
const child = own ?? (await legacyChild(key.sessions, key, name, true));
|
|
386
|
+
if (!child)
|
|
387
|
+
return undefined;
|
|
388
|
+
const here = [caller, name];
|
|
389
|
+
const walked = workflowPause(child.events, name);
|
|
390
|
+
if (walked) {
|
|
391
|
+
const path = [...here, walked.asker];
|
|
392
|
+
return {
|
|
393
|
+
path,
|
|
394
|
+
callIds: [id],
|
|
395
|
+
...(walked.approval ? { approval: { ...walked.approval, path } } : {}),
|
|
396
|
+
...(walked.question ? { question: { ...walked.question, path } } : {}),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const approval = pendingApproval(child.events);
|
|
400
|
+
if (approval && approval.agent === name)
|
|
401
|
+
return { path: here, callIds: [id], approval: { ...approval, path: here } };
|
|
402
|
+
const question = pendingQuestion(child.events);
|
|
403
|
+
if (question && question.node === name)
|
|
404
|
+
return { path: here, callIds: [id], question: { ...question, path: here } };
|
|
405
|
+
const [first] = await delegatedPauses({ ...key, appName: child.appName, delegated: true }, child.events, name, depth + 1, new Set([...seen, name]));
|
|
406
|
+
if (!first)
|
|
407
|
+
return undefined;
|
|
408
|
+
const path = [caller, ...first.path];
|
|
409
|
+
return {
|
|
410
|
+
path,
|
|
411
|
+
callIds: [id, ...first.callIds],
|
|
412
|
+
...(first.approval ? { approval: { ...first.approval, path } } : {}),
|
|
413
|
+
...(first.question ? { question: { ...first.question, path } } : {}),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* The pause a nested workflow's walk holds in its child session (ADR 0111):
|
|
418
|
+
* the workflow's own pause record, the last event, authored by the
|
|
419
|
+
* workflow (`name`) at its own path with no content, names the open
|
|
420
|
+
* interrupts; the approval request or ask_user question among them is the
|
|
421
|
+
* pause, and the node that raised it the asker. Undefined when the session
|
|
422
|
+
* holds no paused walk of `name`'s.
|
|
423
|
+
*/
|
|
424
|
+
function workflowPause(events, name) {
|
|
425
|
+
const record = events[events.length - 1];
|
|
426
|
+
if (!record || record.author !== name || (record.content?.parts ?? []).length > 0 || record.nodeInfo?.path !== name)
|
|
427
|
+
return undefined;
|
|
428
|
+
const open = new Set(record.longRunningToolIds ?? []);
|
|
429
|
+
if (open.size === 0)
|
|
430
|
+
return undefined;
|
|
431
|
+
const approval = pendingApproval(events);
|
|
432
|
+
if (approval && open.has(approval.id) && approval.agent && approval.agent !== 'user')
|
|
433
|
+
return { asker: approval.agent, approval };
|
|
434
|
+
// The ask_user node's request the record names: the walk stores node inputs as user turns, so a later one may follow it.
|
|
435
|
+
for (let i = events.length - 2; i >= 0; i--) {
|
|
436
|
+
const event = events[i];
|
|
437
|
+
if (!event.author || event.author === 'user')
|
|
438
|
+
continue;
|
|
439
|
+
for (const call of getFunctionCalls(event)) {
|
|
440
|
+
if (call.name !== REQUEST_INPUT_CALL || !call.id || !open.has(call.id))
|
|
441
|
+
continue;
|
|
442
|
+
const question = inputRequestFrom(event.author, call);
|
|
443
|
+
if (question && question.id === call.id)
|
|
444
|
+
return { asker: event.author, question };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Every pause waiting inside a delegated call still open in `events` (one
|
|
451
|
+
* session's events), latest call first: each open call whose child session
|
|
452
|
+
* holds an open approval request or question, at any depth. `author`, when
|
|
453
|
+
* given, keeps one agent's calls. A call the person has written past is no
|
|
454
|
+
* longer open, and neither is anything waiting below it.
|
|
455
|
+
*/
|
|
456
|
+
export async function delegatedPauses(key, events, author, depth = 0, seen = new Set()) {
|
|
457
|
+
const out = [];
|
|
458
|
+
for (const { author: caller, call } of openCalls(events, author)) {
|
|
459
|
+
const pause = await pauseBelow(key, caller, call, depth, seen);
|
|
460
|
+
if (pause)
|
|
461
|
+
out.push(pause);
|
|
462
|
+
}
|
|
463
|
+
return out;
|
|
464
|
+
}
|
|
465
|
+
/** The interrupt a delegated pause waits on: the approval request's id, or the question's. */
|
|
466
|
+
export function interruptIdOf(pause) {
|
|
467
|
+
return pause.approval?.id ?? pause.question?.id;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The delegated calls `agent` left open that the latest user message
|
|
471
|
+
* answers (ADR 0110): the message's approval decisions and question
|
|
472
|
+
* answers (function responses to `adk_request_confirmation` or `ask_user`)
|
|
473
|
+
* matched, by interrupt id, to the pause below each open call. Undefined
|
|
474
|
+
* when the message answers none. The child binds the answer itself: an
|
|
475
|
+
* approval is checked against its pinned call there (approvedCalls), as at
|
|
476
|
+
* the top.
|
|
477
|
+
*/
|
|
478
|
+
export async function resumedDelegations(agent, scope, sessions) {
|
|
479
|
+
const events = scope.session.events;
|
|
480
|
+
let latest;
|
|
481
|
+
for (let i = events.length - 1; i >= 0 && !latest; i--)
|
|
482
|
+
if (events[i]?.author === 'user')
|
|
483
|
+
latest = events[i];
|
|
484
|
+
if (!latest)
|
|
485
|
+
return undefined;
|
|
486
|
+
const answerParts = (latest.content?.parts ?? []).filter((p) => {
|
|
487
|
+
const r = p.functionResponse;
|
|
488
|
+
return !!r?.id && (r.name === REQUEST_CONFIRMATION_CALL || r.name === ASK_USER);
|
|
489
|
+
});
|
|
490
|
+
if (answerParts.length === 0)
|
|
491
|
+
return undefined;
|
|
492
|
+
const pauses = await delegatedPauses({ sessions, userId: scope.session.userId, sessionId: scope.session.id, appName: scope.session.appName, delegated: scope.delegated === true }, events, agent.name);
|
|
493
|
+
if (pauses.length === 0)
|
|
494
|
+
return undefined;
|
|
495
|
+
const open = new Map(openCalls(events, agent.name).map(({ call }) => [call.id, call]));
|
|
496
|
+
const answers = new Map();
|
|
497
|
+
const waiting = [];
|
|
498
|
+
for (const pause of pauses) {
|
|
499
|
+
const callId = pause.callIds[0];
|
|
500
|
+
const parts = answerParts.filter((p) => p.functionResponse?.id === interruptIdOf(pause));
|
|
501
|
+
if (parts.length)
|
|
502
|
+
answers.set(callId, [...(answers.get(callId) ?? []), ...parts]);
|
|
503
|
+
else
|
|
504
|
+
waiting.push(callId);
|
|
505
|
+
}
|
|
506
|
+
if (answers.size === 0)
|
|
507
|
+
return undefined;
|
|
508
|
+
const calls = [...answers.keys()].map((id) => open.get(id)).filter((c) => !!c);
|
|
509
|
+
return { calls, answers, waiting: waiting.filter((id) => !answers.has(id)), tools: await toolsOf(agent, scope) };
|
|
510
|
+
}
|
|
@@ -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
|
|
@@ -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
|
|
@@ -66,6 +70,7 @@
|
|
|
66
70
|
* and defineTool, as registerTool refuses one (ADR 0107).
|
|
67
71
|
*/
|
|
68
72
|
import { contentsToMessages, reasoningOf, samplingOf, systemText, toolChoiceOf } from '../../models/genaiMapping.js';
|
|
73
|
+
import { outputSchemaBesideTools } from '../../models/capabilities.js';
|
|
69
74
|
import { providerForModel } from '../../models/providerMap.js';
|
|
70
75
|
import { contractToolDeclaration, nativeToolOf, toContractJsonSchema } from '../../models/schemaNormalize.js';
|
|
71
76
|
import { createToolContext, instructionToolOf, isTool, toolOf } from '../../tools/tool.js';
|
|
@@ -131,10 +136,26 @@ function vertexAi() {
|
|
|
131
136
|
return envFlag('GOOGLE_GENAI_USE_ENTERPRISE');
|
|
132
137
|
return envFlag('GOOGLE_GENAI_USE_VERTEXAI');
|
|
133
138
|
}
|
|
134
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* ADK's rule for whether the model takes an output schema beside tools in one
|
|
141
|
+
* request (Vertex AI, Gemini 2 and later). `mode: task` keeps it as it is,
|
|
142
|
+
* for the instruction line ADK's processor writes there.
|
|
143
|
+
*/
|
|
135
144
|
export function outputSchemaWithTools(model) {
|
|
136
145
|
return vertexAi() && isGemini2OrAbove(model);
|
|
137
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Whether the agent's output schema travels beside its tools as the
|
|
149
|
+
* provider's own structured-output field (ADR 0109): on every model the step
|
|
150
|
+
* may call, its own and its fallback_model, since a fallback answers the same
|
|
151
|
+
* request. ADK's Gemini rule still holds; the capability matrix's
|
|
152
|
+
* `structured_output_with_tools` adds Claude's current generations and
|
|
153
|
+
* OpenAI. Anywhere else the schema is the set_model_response tool.
|
|
154
|
+
*/
|
|
155
|
+
function schemaBesideTools(agent) {
|
|
156
|
+
const models = agent.fallbackModel ? [agent.model, agent.fallbackModel] : [agent.model];
|
|
157
|
+
return models.every((m) => outputSchemaWithTools(m) || outputSchemaBesideTools(m));
|
|
158
|
+
}
|
|
138
159
|
// ── Session state in an instruction (ADK's injectSessionState) ───────────────
|
|
139
160
|
const STATE_PREFIXES = ['app:', 'user:', 'temp:'];
|
|
140
161
|
const ARTIFACT_PREFIX = 'artifact.';
|
|
@@ -437,7 +458,10 @@ export async function buildModelRequest(agent, ctx) {
|
|
|
437
458
|
throw new Error('Response schema must be set via LlmAgent.output_schema.');
|
|
438
459
|
const listed = agent.tools ?? [];
|
|
439
460
|
const taskMode = agent.mode === 'task';
|
|
440
|
-
|
|
461
|
+
// An output schema beside tools: set_model_response unless the step's models take both in one request
|
|
462
|
+
// (ADR 0109). Task mode keeps ADK's rule: finish_task carries the schema there.
|
|
463
|
+
const besideTools = !!agent.outputSchema && listed.length > 0;
|
|
464
|
+
const schemaWithTools = besideTools && !(taskMode ? outputSchemaWithTools(model) : schemaBesideTools(agent));
|
|
441
465
|
// 1. Basic: the config, and the output schema where the model takes it (never in task mode: finish_task carries it).
|
|
442
466
|
if (agent.outputSchema && !schemaWithTools && !taskMode) {
|
|
443
467
|
cfg.responseSchema = agent.outputSchema;
|
|
@@ -40,10 +40,11 @@ import { SpanStatusCode, context, trace } from '@opentelemetry/api';
|
|
|
40
40
|
import { resolveAdapter } from '../../models/registry.js';
|
|
41
41
|
import { RUNTIME_SPAN_SCOPE } from '../../observability/lineage.js';
|
|
42
42
|
import { payloadPolicyFromEnv } from '../../observability/supabaseSpanExporter.js';
|
|
43
|
-
import {
|
|
43
|
+
import { startEngineSpan } from '../../observability/tracer.js';
|
|
44
44
|
/** The most a payload attribute holds, as for a failed call's (lib/observability/tracer.ts). */
|
|
45
45
|
const PAYLOAD_MAX_CHARS = 200_000;
|
|
46
|
-
|
|
46
|
+
/** A loop span in scope melchizedek.runtime; a non-recording one in a turn that opted out of tracing. */
|
|
47
|
+
const startRuntimeSpan = (name, options) => startEngineSpan(RUNTIME_SPAN_SCOPE, name, options);
|
|
47
48
|
/** JSON that never throws and never exceeds the cap. */
|
|
48
49
|
function payloadJson(value) {
|
|
49
50
|
let json;
|
|
@@ -85,8 +86,7 @@ function failed(span, error) {
|
|
|
85
86
|
}
|
|
86
87
|
/** The agent's run as an `agent.invoke <name>` span, opened when the run starts. */
|
|
87
88
|
export async function* traceAgentInvocation(agent, ctx, run) {
|
|
88
|
-
|
|
89
|
-
const span = runtimeTracer().startSpan(`agent.invoke ${agent.name}`, {
|
|
89
|
+
const span = startRuntimeSpan(`agent.invoke ${agent.name}`, {
|
|
90
90
|
attributes: {
|
|
91
91
|
'gen_ai.operation.name': 'invoke_agent',
|
|
92
92
|
'gen_ai.agent.name': agent.name,
|
|
@@ -116,8 +116,7 @@ export async function* traceAgentInvocation(agent, ctx, run) {
|
|
|
116
116
|
* provider that answered (the fallback's, when a fallback answered).
|
|
117
117
|
*/
|
|
118
118
|
export async function* traceModelCall(agent, ctx, step) {
|
|
119
|
-
|
|
120
|
-
const span = runtimeTracer().startSpan('model.call', {
|
|
119
|
+
const span = startRuntimeSpan('model.call', {
|
|
121
120
|
attributes: {
|
|
122
121
|
'gen_ai.operation.name': 'chat',
|
|
123
122
|
'gen_ai.agent.name': agent.name,
|
|
@@ -173,7 +172,7 @@ function recordModelCall(span, result, provider) {
|
|
|
173
172
|
span.setAttribute('llm.error_code', String(response.error.code));
|
|
174
173
|
return;
|
|
175
174
|
}
|
|
176
|
-
if (payloadPolicyFromEnv().mode === 'off')
|
|
175
|
+
if (!span.isRecording() || payloadPolicyFromEnv().mode === 'off')
|
|
177
176
|
return;
|
|
178
177
|
const { signal: _signal, ...request } = result.request;
|
|
179
178
|
span.setAttribute('llm.payload.request', payloadJson(request));
|
|
@@ -181,10 +180,9 @@ function recordModelCall(span, result, provider) {
|
|
|
181
180
|
}
|
|
182
181
|
/** One tool call as a `tool.execute <name>` span, under the agent's. */
|
|
183
182
|
export async function traceToolCall(call, tool, run) {
|
|
184
|
-
initializeTracing();
|
|
185
183
|
const name = call.name || '<unnamed>';
|
|
186
184
|
const description = tool && typeof tool === 'object' ? tool.description : undefined;
|
|
187
|
-
const span =
|
|
185
|
+
const span = startRuntimeSpan(`tool.execute ${name}`, {
|
|
188
186
|
attributes: {
|
|
189
187
|
'gen_ai.operation.name': 'execute_tool',
|
|
190
188
|
'gen_ai.tool.name': name,
|
|
@@ -221,8 +219,7 @@ export async function traceToolCall(call, tool, run) {
|
|
|
221
219
|
// attributes each model call to the node's agent, as on ADK.
|
|
222
220
|
/** The walk as a `workflow.invoke <name>` span; every node span opens under it. */
|
|
223
221
|
export async function traceWorkflowInvocation(workflow, ctx, run) {
|
|
224
|
-
|
|
225
|
-
const span = runtimeTracer().startSpan(`workflow.invoke ${workflow.name}`, {
|
|
222
|
+
const span = startRuntimeSpan(`workflow.invoke ${workflow.name}`, {
|
|
226
223
|
attributes: {
|
|
227
224
|
'gen_ai.operation.name': 'invoke_workflow',
|
|
228
225
|
'gen_ai.conversation.id': ctx.sessionId,
|
|
@@ -248,8 +245,7 @@ export async function traceWorkflowInvocation(workflow, ctx, run) {
|
|
|
248
245
|
* attempts the run made.
|
|
249
246
|
*/
|
|
250
247
|
export async function traceNodeExecution(node, run) {
|
|
251
|
-
|
|
252
|
-
const span = runtimeTracer().startSpan(`node.execute ${node.name}`, {
|
|
248
|
+
const span = startRuntimeSpan(`node.execute ${node.name}`, {
|
|
253
249
|
attributes: {
|
|
254
250
|
'gen_ai.operation.name': 'execute_node',
|
|
255
251
|
'adk.node.path': node.path,
|
|
@@ -274,9 +270,8 @@ export async function traceNodeExecution(node, run) {
|
|
|
274
270
|
}
|
|
275
271
|
/** A tool node's call as a `tool.execute <name>` span, under its node's (ToolNodeContext.traceCall). */
|
|
276
272
|
export async function traceToolNodeCall(call, tool, run) {
|
|
277
|
-
initializeTracing();
|
|
278
273
|
const description = tool && typeof tool === 'object' ? tool.description : undefined;
|
|
279
|
-
const span =
|
|
274
|
+
const span = startRuntimeSpan(`tool.execute ${call.name}`, {
|
|
280
275
|
attributes: {
|
|
281
276
|
'gen_ai.operation.name': 'execute_tool',
|
|
282
277
|
'gen_ai.tool.name': call.name,
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
* (lib/workflow.ts) and an approval (ADR 0028); a client handles all three
|
|
18
18
|
* by showing `result.input` (or the approval) and sending the next message.
|
|
19
19
|
*
|
|
20
|
-
* WHERE:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* WHERE: on any agent of a delegate or dispatch syndicate, as with approval
|
|
21
|
+
* gates. Inside a delegated subagent the question waits in the child's
|
|
22
|
+
* session, below the call its caller left open, and reaches the person
|
|
23
|
+
* with the agent path (`result.input.path`, ADR 0110); the next message is
|
|
24
|
+
* stored as the answer and carried down to the child
|
|
25
|
+
* (lib/runtime/native/interrupts.ts). Not inside a workflow node yet; the
|
|
26
|
+
* schema refuses it there.
|
|
24
27
|
*
|
|
25
28
|
* RESUME: the answer is an ordinary function response, so the loop resumes
|
|
26
29
|
* a question ADK opened before 1.0.0 as one it opened itself (ADR 0079); no
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
import type { TurnEvent } from './events.ts';
|
|
38
38
|
/** A conversation's address. Stores key it as `<appName>:<userId>:<sessionId>`. */
|
|
39
39
|
export interface SessionKey {
|
|
40
|
-
/** The syndicate (or, for a DELEGATE subagent's own row,
|
|
40
|
+
/** The syndicate (or, for a DELEGATE subagent's own row, its agent path: lib/runtime/native/delegate.ts childAppName). */
|
|
41
41
|
appName: string;
|
|
42
42
|
userId: string;
|
|
43
43
|
sessionId: string;
|
|
@@ -144,7 +144,13 @@ export interface SyndicateTurnOptions {
|
|
|
144
144
|
maxLlmCalls?: number;
|
|
145
145
|
/** Ask adapters for token-by-token partial events (the REPL's live view). */
|
|
146
146
|
streaming?: boolean;
|
|
147
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Root-span metadata. `false` disables tracing for the turn: it records no
|
|
149
|
+
* span, so no row reaches the ledger (adk_telemetry, adk_turns,
|
|
150
|
+
* adk_payloads), the console exporter, the in-process listeners or an OTLP
|
|
151
|
+
* endpoint, and the turn does not start the tracer. The step budget and
|
|
152
|
+
* the usage in the result are unaffected. Default: traced.
|
|
153
|
+
*/
|
|
148
154
|
trace?: TraceOptions | false;
|
|
149
155
|
events?: TurnEvents;
|
|
150
156
|
/**
|
|
@@ -63,10 +63,14 @@ import { isDispatchSyndicate, matchRouteOverride, resolveRoute } from '../dispat
|
|
|
63
63
|
import { collectGrounding, describeGrounding, newGroundingState, webSourcesLine } from '../grounding.js';
|
|
64
64
|
import { resolveGuards } from '../guards/index.js';
|
|
65
65
|
import { collectGuards, nestedLoader } from '../loadSyndicate.js';
|
|
66
|
+
import { context } from '@opentelemetry/api';
|
|
67
|
+
import core from '@opentelemetry/core';
|
|
68
|
+
const { suppressTracing } = core;
|
|
66
69
|
import { traceAgentRun } from '../observability/tracer.js';
|
|
67
70
|
import { ProjectedSessionService, renderTranscriptDigest } from '../session/transcript.js';
|
|
68
71
|
import { approvalDecisionIn, describeApproval, interruptedTurnStart, pendingApproval } from './approvals.js';
|
|
69
72
|
import { pendingQuestion, questionAnswerPart, questionFrom, turnStartOfCall } from './questions.js';
|
|
73
|
+
import { delegatedPauses } from './native/interrupts.js';
|
|
70
74
|
export { ASK_USER, pendingQuestion, questionAnswerPart } from './questions.js';
|
|
71
75
|
export { approvalResponsePart, describeApproval, pendingApproval } from './approvals.js';
|
|
72
76
|
import { RemoteA2AAgent, remoteContextId, remoteToolOutput } from '../a2a/remoteAgent.js';
|
|
@@ -274,9 +278,15 @@ export async function runSyndicateTurn(opts) {
|
|
|
274
278
|
maxLlmCalls: opts.maxLlmCalls ?? config.max_steps ?? DEFAULT_MAX_STEPS,
|
|
275
279
|
deadlineMs: opts.deadlineMs,
|
|
276
280
|
signal: opts.signal,
|
|
281
|
+
// `trace: false`: no span from anything the turn runs, to any sink.
|
|
282
|
+
untraced: opts.trace === false,
|
|
277
283
|
});
|
|
278
284
|
try {
|
|
279
|
-
|
|
285
|
+
const run = () => runWithTurnControl(control, () => runTurnInner(opts, control, runtime));
|
|
286
|
+
// A span a library opens through OpenTelemetry's own API (not the
|
|
287
|
+
// engine's, which read control.untraced) is suppressed too, while a
|
|
288
|
+
// registered tracer is there to read the suppression.
|
|
289
|
+
return await (control.untraced ? context.with(suppressTracing(context.active()), run) : run());
|
|
280
290
|
}
|
|
281
291
|
finally {
|
|
282
292
|
control.dispose();
|
|
@@ -360,16 +370,25 @@ async function runTurnInner(opts, control, _runtime) {
|
|
|
360
370
|
// A message answering an approval resumes the agent that asked; the answer
|
|
361
371
|
// must name the request still open in this conversation.
|
|
362
372
|
const decision = approvalDecisionIn(parts);
|
|
373
|
+
// Pauses inside delegated subagents (ADR 0110): what waits below a call an agent of this conversation left open.
|
|
374
|
+
const delegationKey = { sessions: sessionService, userId, sessionId, appName };
|
|
375
|
+
/** The first pause waiting below an open delegated call in this conversation, `author`'s calls only when given. */
|
|
376
|
+
const pauseBelow = async (events, author) => isWorkflowSyndicate(config) ? undefined : (await delegatedPauses(delegationKey, events, author))[0];
|
|
377
|
+
let resumingBelow;
|
|
363
378
|
let resuming;
|
|
364
379
|
if (decision) {
|
|
365
380
|
resuming = pendingApproval(existing?.events ?? []);
|
|
381
|
+
if (!resuming) {
|
|
382
|
+
resumingBelow = await pauseBelow(existing?.events ?? []);
|
|
383
|
+
resuming = resumingBelow?.approval;
|
|
384
|
+
}
|
|
366
385
|
if (!resuming || resuming.id !== decision.id) {
|
|
367
386
|
result.status = 'failed';
|
|
368
387
|
result.error = { code: 'NO_PENDING_APPROVAL', message: `No approval ${decision.id} is waiting in this conversation.` };
|
|
369
388
|
return finish();
|
|
370
389
|
}
|
|
371
390
|
// The log names the call, not its arguments: they are user content.
|
|
372
|
-
ev.log?.(`✓ Approval ${decision.approved ? 'granted' : 'refused'}: ${resuming.agent} → ${resuming.tool}`);
|
|
391
|
+
ev.log?.(`✓ Approval ${decision.approved ? 'granted' : 'refused'}: ${(resuming.path ?? [resuming.agent]).join(' → ')} → ${resuming.tool}`);
|
|
373
392
|
}
|
|
374
393
|
// ── Consent (lib/runtime/credentials.ts, ADR 0085) ────────────────────────
|
|
375
394
|
// While a call waits for an OAuth grant, the next message resumes it once
|
|
@@ -411,24 +430,30 @@ async function runTurnInner(opts, control, _runtime) {
|
|
|
411
430
|
// answer: it becomes that call's response, and the agent that asked
|
|
412
431
|
// resumes its own tool loop (the loop reads the response from its history;
|
|
413
432
|
// ADR 0079). A workflow's pauses are its walk's own business.
|
|
433
|
+
// A question asked inside a delegated subagent (ADR 0110) is answered the same way: the answer is stored here, and the
|
|
434
|
+
// open call that leads to the asker carries it down; `answering` names that call and the agent that made it.
|
|
414
435
|
let answering;
|
|
415
436
|
if (!decision && !granting && !isWorkflowSyndicate(config)) {
|
|
416
|
-
const question = pendingQuestion(existing?.events ?? []);
|
|
417
437
|
const plainText = parts.length > 0 && parts.every((p) => typeof p.text === 'string');
|
|
418
|
-
|
|
419
|
-
|
|
438
|
+
const own = plainText ? pendingQuestion(existing?.events ?? []) : undefined;
|
|
439
|
+
const below = plainText && !own ? await pauseBelow(existing?.events ?? []) : undefined;
|
|
440
|
+
const question = own ?? below?.question;
|
|
441
|
+
if (question) {
|
|
442
|
+
answering = below ? { agent: below.path[0], id: below.callIds[0] } : { agent: question.node, id: question.id };
|
|
420
443
|
parts = [questionAnswerPart(question.id, messageText)];
|
|
421
|
-
ev.log?.(`✓ Answer to ${question.node}'s question`);
|
|
444
|
+
ev.log?.(`✓ Answer to ${(question.path ?? [question.node]).join(' → ')}'s question`);
|
|
422
445
|
}
|
|
423
446
|
}
|
|
424
447
|
/** The agent a dispatch turn must resume, and the call its interrupted turn holds. */
|
|
425
|
-
const resumeTarget =
|
|
426
|
-
? { agent:
|
|
427
|
-
:
|
|
428
|
-
? { agent:
|
|
429
|
-
:
|
|
430
|
-
? {
|
|
431
|
-
:
|
|
448
|
+
const resumeTarget = resumingBelow
|
|
449
|
+
? { agent: resumingBelow.path[0], id: resumingBelow.callIds[0], why: 'resuming an approval' }
|
|
450
|
+
: resuming
|
|
451
|
+
? { agent: resuming.agent, id: resuming.id, why: 'resuming an approval' }
|
|
452
|
+
: granting
|
|
453
|
+
? { agent: granting.agent, id: granting.id, why: 'resuming after an authorization' }
|
|
454
|
+
: answering
|
|
455
|
+
? { ...answering, why: 'answering its question' }
|
|
456
|
+
: undefined;
|
|
432
457
|
/** After the answering run: is a call now waiting for a grant? */
|
|
433
458
|
const awaitingConsent = async (agentName) => {
|
|
434
459
|
if (!opts.toolCredentials?.consent)
|
|
@@ -454,11 +479,26 @@ async function runTurnInner(opts, control, _runtime) {
|
|
|
454
479
|
ev.log?.(`⏸ Input needed: ${describeInput(input)}`);
|
|
455
480
|
return finish();
|
|
456
481
|
};
|
|
482
|
+
/** After the answering run: does a pause wait inside a delegated call `agentName` left open (ADR 0110)? */
|
|
483
|
+
const pausedBelow = async (agentName) => {
|
|
484
|
+
const after = await sessionService.get({ appName, userId, sessionId });
|
|
485
|
+
const below = await pauseBelow(after?.events ?? [], agentName);
|
|
486
|
+
if (below?.approval)
|
|
487
|
+
return pause(below.approval);
|
|
488
|
+
if (below?.question) {
|
|
489
|
+
result.status = 'input-required';
|
|
490
|
+
result.input = below.question;
|
|
491
|
+
result.text = below.question.message;
|
|
492
|
+
ev.log?.(`⏸ Input needed: ${describeInput(below.question)} (${below.path.join(' → ')})`);
|
|
493
|
+
return finish();
|
|
494
|
+
}
|
|
495
|
+
return undefined;
|
|
496
|
+
};
|
|
457
497
|
const pause = (pending) => {
|
|
458
498
|
result.status = 'input-required';
|
|
459
499
|
result.approval = pending;
|
|
460
500
|
result.text = `Approval needed: ${describeApproval(pending)}.`;
|
|
461
|
-
ev.log?.(`⏸ Approval needed: ${pending.agent} → ${pending.tool}`);
|
|
501
|
+
ev.log?.(`⏸ Approval needed: ${(pending.path ?? [pending.agent]).join(' → ')} → ${pending.tool}`);
|
|
462
502
|
return finish();
|
|
463
503
|
};
|
|
464
504
|
/** Run ONE agent against ONE session, under the turn's controls. */
|
|
@@ -690,13 +730,15 @@ async function runTurnInner(opts, control, _runtime) {
|
|
|
690
730
|
userParts: parts,
|
|
691
731
|
// Resuming an approval or answering a question: the interrupted turn
|
|
692
732
|
// is replayed raw, so the loop finds the call the message answers.
|
|
693
|
-
sessions: new ProjectedSessionService(sessionService, routeCfg.name,
|
|
694
|
-
? { rawFrom: (events) =>
|
|
695
|
-
:
|
|
696
|
-
? { rawFrom: (events) =>
|
|
697
|
-
:
|
|
698
|
-
? { rawFrom: (events) => turnStartOfCall(events,
|
|
699
|
-
:
|
|
733
|
+
sessions: new ProjectedSessionService(sessionService, routeCfg.name, resumingBelow
|
|
734
|
+
? { rawFrom: (events) => turnStartOfCall(events, resumingBelow.callIds[0]) }
|
|
735
|
+
: resuming
|
|
736
|
+
? { rawFrom: (events) => interruptedTurnStart(events, resuming.id) }
|
|
737
|
+
: answering
|
|
738
|
+
? { rawFrom: (events) => turnStartOfCall(events, answering.id) }
|
|
739
|
+
: granting
|
|
740
|
+
? { rawFrom: (events) => turnStartOfCall(events, granting.id) }
|
|
741
|
+
: {}),
|
|
700
742
|
stage: 'dispatch',
|
|
701
743
|
route: resolution,
|
|
702
744
|
publishToolStatus: true,
|
|
@@ -709,11 +751,16 @@ async function runTurnInner(opts, control, _runtime) {
|
|
|
709
751
|
result.error = answer.error;
|
|
710
752
|
return finish();
|
|
711
753
|
}
|
|
712
|
-
if (!routeCfg.a2a_agent_url && agentGates(routeCfg)) {
|
|
754
|
+
if (!routeCfg.a2a_agent_url && (agentGates(routeCfg) || routeCfg.yaml_reference)) {
|
|
713
755
|
const pending = await awaitingApproval(routeCfg.name);
|
|
714
756
|
if (pending)
|
|
715
757
|
return pause(pending);
|
|
716
758
|
}
|
|
759
|
+
if (!routeCfg.a2a_agent_url) {
|
|
760
|
+
const below = await pausedBelow(routeCfg.name);
|
|
761
|
+
if (below)
|
|
762
|
+
return below;
|
|
763
|
+
}
|
|
717
764
|
if (!routeCfg.a2a_agent_url) {
|
|
718
765
|
const consent = await awaitingConsent(routeCfg.name);
|
|
719
766
|
if (consent)
|
|
@@ -835,6 +882,11 @@ async function runTurnInner(opts, control, _runtime) {
|
|
|
835
882
|
const consent = await awaitingConsent(config.orchestrator.name);
|
|
836
883
|
if (consent)
|
|
837
884
|
return consentPause(consent);
|
|
885
|
+
if (config.subagents?.length) {
|
|
886
|
+
const below = await pausedBelow(config.orchestrator.name);
|
|
887
|
+
if (below)
|
|
888
|
+
return below;
|
|
889
|
+
}
|
|
838
890
|
const askedOrchestrator = asked(answer);
|
|
839
891
|
if (askedOrchestrator)
|
|
840
892
|
return askedOrchestrator;
|