melchizedek-agents 1.1.0 → 1.3.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 -0
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +373 -0
- package/DOCUMENTATION.md +193 -21
- package/ONBOARDING.md +378 -0
- package/QUICKSTART.md +15 -2
- package/README.md +26 -3
- package/config/agents/examples/connectors.yaml +82 -0
- package/config/agents/examples/council.yaml +1 -1
- package/config/agents/examples/critic.yaml +8 -8
- package/config/agents/examples/image_production.yaml +4 -3
- package/config/agents/syndicate.schema.json +389 -33
- package/config/agents/syndicateSchema.yaml +18 -6
- package/config/agents/templates/intake_extractor.yaml +3 -2
- package/config/agents/templates/systems_operator.yaml +5 -2
- package/dist/lib/a2a/app.d.ts +12 -0
- package/dist/lib/a2a/app.js +25 -11
- package/dist/lib/a2a/executor.js +14 -6
- package/dist/lib/a2a/oauthSetup.d.ts +16 -0
- package/dist/lib/a2a/oauthSetup.js +77 -8
- package/dist/lib/agentDialect.d.ts +18 -8
- package/dist/lib/agentDialect.js +36 -19
- package/dist/lib/chatgpt/adapter.d.ts +50 -0
- package/dist/lib/chatgpt/adapter.js +107 -0
- package/dist/lib/chatgpt/cli.d.ts +23 -0
- package/dist/lib/chatgpt/cli.js +77 -0
- package/dist/lib/chatgpt/oauth.d.ts +103 -0
- package/dist/lib/chatgpt/oauth.js +448 -0
- package/dist/lib/chatgpt/state.d.ts +98 -0
- package/dist/lib/chatgpt/state.js +145 -0
- package/dist/lib/chatgpt/store.d.ts +24 -0
- package/dist/lib/chatgpt/store.js +94 -0
- package/dist/lib/compile.d.ts +38 -7
- package/dist/lib/compile.js +97 -62
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +18 -1
- package/dist/lib/dispatch.js +6 -2
- package/dist/lib/doctor.d.ts +107 -0
- package/dist/lib/doctor.js +217 -7
- package/dist/lib/loadSyndicate.d.ts +15 -0
- package/dist/lib/mcp/server.d.ts +195 -0
- package/dist/lib/mcp/server.js +564 -0
- package/dist/lib/memory/supabaseMemoryService.js +2 -2
- package/dist/lib/models/adapterResolver.js +6 -1
- package/dist/lib/models/capabilities.d.ts +9 -0
- package/dist/lib/models/capabilities.js +24 -0
- 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 +3 -2
- package/dist/lib/models/gateway.js +8 -4
- 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/onboarding.d.ts +135 -0
- package/dist/lib/onboarding.js +578 -0
- package/dist/lib/runtime/approvals.d.ts +2 -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 +5 -2
- package/dist/lib/runtime/native/agentLoop.js +2 -0
- package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
- package/dist/lib/runtime/native/checkpoint.js +13 -19
- package/dist/lib/runtime/native/delegate.d.ts +82 -1
- package/dist/lib/runtime/native/delegate.js +157 -2
- package/dist/lib/runtime/native/interrupts.d.ts +88 -5
- package/dist/lib/runtime/native/interrupts.js +220 -30
- package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
- package/dist/lib/runtime/syndicateTurn.js +324 -77
- package/dist/lib/runtime/turnControl.d.ts +41 -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 +6 -1
- package/dist/lib/storage/postgres/index.js +1 -1
- package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
- package/dist/lib/storage/postgres/taskQueue.js +10 -3
- package/dist/lib/syndicateSchema.d.ts +184 -9
- package/dist/lib/syndicateSchema.js +143 -29
- package/dist/lib/tools/auth.d.ts +13 -1
- package/dist/lib/tools/auth.js +10 -2
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialUses.d.ts +98 -0
- package/dist/lib/tools/credentialUses.js +120 -0
- package/dist/lib/tools/generateImageTool.js +2 -1
- package/dist/lib/tools/inspectImageTool.js +1 -1
- package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
- package/dist/lib/tools/mcpToolFactory.js +72 -26
- package/dist/lib/tools/mcpTransports.d.ts +12 -0
- package/dist/lib/tools/mcpTransports.js +11 -0
- package/dist/lib/tools/oauthConsent.d.ts +18 -3
- package/dist/lib/tools/oauthConsent.js +57 -15
- package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
- package/dist/lib/tools/oauthDiscovery.js +616 -0
- package/dist/lib/tools/oauthHosts.d.ts +2 -0
- package/dist/lib/tools/oauthHosts.js +2 -2
- package/dist/lib/tools/oauthTools.d.ts +54 -13
- package/dist/lib/tools/oauthTools.js +106 -23
- package/dist/lib/tools/openapiTools.d.ts +7 -0
- package/dist/lib/tools/openapiTools.js +49 -3
- package/dist/lib/tools/taskTools.d.ts +32 -2
- package/dist/lib/tools/taskTools.js +93 -33
- package/dist/lib/tools/xApiSearchTool.js +2 -2
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +10 -7
- package/dist/lib/workflow.js +10 -7
- package/dist/scripts/a2a_server.d.ts +2 -2
- package/dist/scripts/a2a_server.js +19 -2
- package/dist/scripts/assistant_worker.d.ts +2 -0
- package/dist/scripts/assistant_worker.js +24 -5
- package/dist/scripts/doctor.d.ts +4 -2
- package/dist/scripts/doctor.js +6 -4
- package/dist/scripts/mcp_server.d.ts +12 -0
- package/dist/scripts/mcp_server.js +345 -0
- package/dist/scripts/setup.d.ts +25 -0
- package/dist/scripts/setup.js +181 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/syndicate_chat.js +12 -3
- package/dist/scripts/yaml_codemod.d.ts +12 -2
- package/dist/scripts/yaml_codemod.js +115 -33
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-models.md +4 -2
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek-models/SKILL.md +6 -2
- package/skills/melchizedek-onboard/SKILL.md +71 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +47 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -104,6 +104,38 @@
|
|
|
104
104
|
* asked at the end of the path. The walk stops there: a pause inside a
|
|
105
105
|
* workflow node's own delegation does not run (lib/workflow/agentNode.ts).
|
|
106
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
|
+
*
|
|
107
139
|
* ADK stays out of this file: an ADK tool an agent still lists is asked
|
|
108
140
|
* whether it gates through its own checkRequireConfirmation, by shape.
|
|
109
141
|
*/
|
|
@@ -111,10 +143,11 @@ import { isDeepStrictEqual } from 'node:util';
|
|
|
111
143
|
import { nativeToolOf } from '../../models/schemaNormalize.js';
|
|
112
144
|
import { instructionToolOf, isTool, toolOf } from '../../tools/tool.js';
|
|
113
145
|
import { inputRequestFrom } from '../../workflowConfig.js';
|
|
114
|
-
import { pendingApproval } from '../approvals.js';
|
|
146
|
+
import { approvalRequestOf, pendingApproval } from '../approvals.js';
|
|
147
|
+
import { pendingConsent } from '../credentials.js';
|
|
115
148
|
import { getFunctionCalls, getFunctionResponses } from '../events.js';
|
|
116
149
|
import { ASK_USER, pendingQuestion } from '../questions.js';
|
|
117
|
-
import { childAppName, legacyChild } from './delegate.js';
|
|
150
|
+
import { childAppName, entryAppName, legacyChild, legacyEntry } from './delegate.js';
|
|
118
151
|
import { REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, REQUEST_INPUT_CALL, isSegmentPrefix } from './history.js';
|
|
119
152
|
import { isToolset } from './request.js';
|
|
120
153
|
/** A confirmation that does not bind to its pinned call: ADK's IntentMismatchError, by name and text. */
|
|
@@ -386,9 +419,9 @@ async function pauseBelow(key, caller, call, depth, seen) {
|
|
|
386
419
|
if (!child)
|
|
387
420
|
return undefined;
|
|
388
421
|
const here = [caller, name];
|
|
389
|
-
const walked =
|
|
422
|
+
const walked = await workflowPauseIn(key, child, name);
|
|
390
423
|
if (walked) {
|
|
391
|
-
const path = [
|
|
424
|
+
const path = [caller, ...walked.path];
|
|
392
425
|
return {
|
|
393
426
|
path,
|
|
394
427
|
callIds: [id],
|
|
@@ -402,50 +435,206 @@ async function pauseBelow(key, caller, call, depth, seen) {
|
|
|
402
435
|
const question = pendingQuestion(child.events);
|
|
403
436
|
if (question && question.node === name)
|
|
404
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 } };
|
|
405
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)));
|
|
406
513
|
if (!first)
|
|
407
514
|
return undefined;
|
|
408
|
-
const path =
|
|
515
|
+
const path = down(first.path);
|
|
409
516
|
return {
|
|
410
517
|
path,
|
|
411
|
-
callIds: [id, ...first.callIds],
|
|
412
518
|
...(first.approval ? { approval: { ...first.approval, path } } : {}),
|
|
413
519
|
...(first.question ? { question: { ...first.question, path } } : {}),
|
|
520
|
+
...(first.consent ? { consent: { ...first.consent, path } } : {}),
|
|
414
521
|
};
|
|
415
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('.'));
|
|
416
525
|
/**
|
|
417
526
|
* The pause a nested workflow's walk holds in its child session (ADR 0111):
|
|
418
527
|
* the workflow's own pause record, the last event, authored by the
|
|
419
528
|
* workflow (`name`) at its own path with no content, names the open
|
|
420
529
|
* interrupts; the approval request or ask_user question among them is the
|
|
421
|
-
* pause, and the node that raised it the asker.
|
|
422
|
-
* holds no paused walk of
|
|
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.
|
|
423
535
|
*/
|
|
424
|
-
function workflowPause(events, name) {
|
|
536
|
+
function workflowPause(events, name, id) {
|
|
425
537
|
const record = events[events.length - 1];
|
|
426
538
|
if (!record || record.author !== name || (record.content?.parts ?? []).length > 0 || record.nodeInfo?.path !== name)
|
|
427
539
|
return undefined;
|
|
428
|
-
const open = new Set(record.longRunningToolIds ?? []);
|
|
540
|
+
const open = new Set((record.longRunningToolIds ?? []).filter((x) => id === undefined || x === id));
|
|
429
541
|
if (open.size === 0)
|
|
430
542
|
return undefined;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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))
|
|
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')
|
|
441
548
|
continue;
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
}
|
|
445
558
|
}
|
|
446
559
|
}
|
|
447
560
|
return undefined;
|
|
448
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
|
+
}
|
|
449
638
|
/**
|
|
450
639
|
* Every pause waiting inside a delegated call still open in `events` (one
|
|
451
640
|
* session's events), latest call first: each open call whose child session
|
|
@@ -462,18 +651,19 @@ export async function delegatedPauses(key, events, author, depth = 0, seen = new
|
|
|
462
651
|
}
|
|
463
652
|
return out;
|
|
464
653
|
}
|
|
465
|
-
/** The interrupt a delegated pause waits on: the approval request's id, or the
|
|
654
|
+
/** The interrupt a delegated pause waits on: the approval request's id, the question's, or the consent request's. */
|
|
466
655
|
export function interruptIdOf(pause) {
|
|
467
|
-
return pause.approval?.id ?? pause.question?.id;
|
|
656
|
+
return pause.approval?.id ?? pause.question?.id ?? pause.consent?.id;
|
|
468
657
|
}
|
|
469
658
|
/**
|
|
470
659
|
* The delegated calls `agent` left open that the latest user message
|
|
471
660
|
* answers (ADR 0110): the message's approval decisions and question
|
|
472
661
|
* answers (function responses to `adk_request_confirmation` or `ask_user`)
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
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.
|
|
477
667
|
*/
|
|
478
668
|
export async function resumedDelegations(agent, scope, sessions) {
|
|
479
669
|
const events = scope.session.events;
|
|
@@ -485,7 +675,7 @@ export async function resumedDelegations(agent, scope, sessions) {
|
|
|
485
675
|
return undefined;
|
|
486
676
|
const answerParts = (latest.content?.parts ?? []).filter((p) => {
|
|
487
677
|
const r = p.functionResponse;
|
|
488
|
-
return !!r?.id && (r.name === REQUEST_CONFIRMATION_CALL || r.name === ASK_USER);
|
|
678
|
+
return !!r?.id && (r.name === REQUEST_CONFIRMATION_CALL || r.name === ASK_USER || r.name === REQUEST_CREDENTIAL_CALL);
|
|
489
679
|
});
|
|
490
680
|
if (answerParts.length === 0)
|
|
491
681
|
return undefined;
|
|
@@ -158,7 +158,8 @@ export interface SyndicateTurnOptions {
|
|
|
158
158
|
* the native runtime. The store is pinned to `appName`; a tool reads the
|
|
159
159
|
* grant of `userId` only. With `consent`, a call whose provider the user
|
|
160
160
|
* has not granted pauses the turn input-required (`result.consent`), and
|
|
161
|
-
* the next message after the grant resumes it
|
|
161
|
+
* the next message after the grant resumes it; inside a delegated
|
|
162
|
+
* subagent too, with `consent.path` set (ADR 0118). Omitted: tools have no
|
|
162
163
|
* `accessToken`, as before.
|
|
163
164
|
*/
|
|
164
165
|
toolCredentials?: ToolCredentials;
|
|
@@ -229,7 +230,8 @@ export interface SyndicateTurnResult {
|
|
|
229
230
|
input?: PendingInput;
|
|
230
231
|
/** The OAuth grant a paused call waits for, when status is input-required
|
|
231
232
|
* (ADR 0085): the person opens `consent.authUri`; their next message after
|
|
232
|
-
* the grant is stored resumes the call.
|
|
233
|
+
* the grant is stored resumes the call. `consent.path` is set for a call
|
|
234
|
+
* inside a delegated subagent (ADR 0118). */
|
|
233
235
|
consent?: PendingConsent;
|
|
234
236
|
/** The text the user receives (relay fallback and guards applied). */
|
|
235
237
|
text: string;
|