melchizedek-agents 1.1.0 → 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.
Files changed (118) hide show
  1. package/.env.example +10 -0
  2. package/AGENT_SETUP.md +3 -1
  3. package/CHANGELOG.md +232 -0
  4. package/DOCUMENTATION.md +91 -16
  5. package/ONBOARDING.md +339 -0
  6. package/QUICKSTART.md +15 -2
  7. package/README.md +5 -2
  8. package/config/agents/examples/council.yaml +1 -1
  9. package/config/agents/examples/critic.yaml +8 -8
  10. package/config/agents/examples/image_production.yaml +4 -3
  11. package/config/agents/syndicate.schema.json +65 -7
  12. package/config/agents/syndicateSchema.yaml +18 -6
  13. package/config/agents/templates/intake_extractor.yaml +3 -2
  14. package/config/agents/templates/systems_operator.yaml +5 -2
  15. package/dist/lib/a2a/app.d.ts +12 -0
  16. package/dist/lib/a2a/app.js +22 -11
  17. package/dist/lib/a2a/executor.js +14 -6
  18. package/dist/lib/a2a/oauthSetup.d.ts +16 -0
  19. package/dist/lib/a2a/oauthSetup.js +57 -3
  20. package/dist/lib/agentDialect.d.ts +18 -8
  21. package/dist/lib/agentDialect.js +36 -19
  22. package/dist/lib/compile.d.ts +38 -7
  23. package/dist/lib/compile.js +57 -37
  24. package/dist/lib/compileNative.d.ts +11 -3
  25. package/dist/lib/compileNative.js +18 -1
  26. package/dist/lib/doctor.d.ts +76 -0
  27. package/dist/lib/doctor.js +159 -7
  28. package/dist/lib/loadSyndicate.d.ts +9 -0
  29. package/dist/lib/models/capabilities.d.ts +9 -0
  30. package/dist/lib/models/capabilities.js +24 -0
  31. package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
  32. package/dist/lib/models/chatCompletionsAdapter.js +19 -5
  33. package/dist/lib/models/claudeAdapter.js +4 -0
  34. package/dist/lib/models/contract.d.ts +5 -2
  35. package/dist/lib/models/gateway.d.ts +2 -1
  36. package/dist/lib/models/gateway.js +4 -2
  37. package/dist/lib/models/gatewayAdapter.d.ts +7 -1
  38. package/dist/lib/models/gatewayAdapter.js +8 -0
  39. package/dist/lib/models/geminiAdapter.js +8 -2
  40. package/dist/lib/models/genaiMapping.d.ts +8 -4
  41. package/dist/lib/models/genaiMapping.js +23 -9
  42. package/dist/lib/models/gptAdapter.d.ts +4 -2
  43. package/dist/lib/models/gptAdapter.js +11 -4
  44. package/dist/lib/models/grokAdapter.d.ts +3 -1
  45. package/dist/lib/models/grokAdapter.js +11 -3
  46. package/dist/lib/models/kimiAdapter.d.ts +2 -1
  47. package/dist/lib/models/kimiAdapter.js +3 -3
  48. package/dist/lib/models/reasoning.d.ts +30 -2
  49. package/dist/lib/models/reasoning.js +56 -5
  50. package/dist/lib/onboarding.d.ts +134 -0
  51. package/dist/lib/onboarding.js +539 -0
  52. package/dist/lib/runtime/approvals.d.ts +2 -0
  53. package/dist/lib/runtime/approvals.js +4 -3
  54. package/dist/lib/runtime/credentials.d.ts +6 -0
  55. package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
  56. package/dist/lib/runtime/native/agentLoop.js +2 -0
  57. package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
  58. package/dist/lib/runtime/native/checkpoint.js +13 -19
  59. package/dist/lib/runtime/native/delegate.d.ts +82 -1
  60. package/dist/lib/runtime/native/delegate.js +157 -2
  61. package/dist/lib/runtime/native/interrupts.d.ts +88 -5
  62. package/dist/lib/runtime/native/interrupts.js +220 -30
  63. package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
  64. package/dist/lib/runtime/syndicateTurn.js +295 -62
  65. package/dist/lib/runtime/turnControl.d.ts +41 -0
  66. package/dist/lib/session/transcript.d.ts +9 -0
  67. package/dist/lib/session/transcript.js +20 -1
  68. package/dist/lib/skills.d.ts +18 -0
  69. package/dist/lib/skills.js +73 -1
  70. package/dist/lib/storage/postgres/index.d.ts +6 -1
  71. package/dist/lib/storage/postgres/index.js +1 -1
  72. package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
  73. package/dist/lib/storage/postgres/taskQueue.js +10 -3
  74. package/dist/lib/syndicateSchema.d.ts +38 -1
  75. package/dist/lib/syndicateSchema.js +25 -18
  76. package/dist/lib/tools/auth.d.ts +13 -1
  77. package/dist/lib/tools/auth.js +10 -2
  78. package/dist/lib/tools/credentialHosts.d.ts +88 -0
  79. package/dist/lib/tools/credentialHosts.js +165 -0
  80. package/dist/lib/tools/credentialUses.d.ts +92 -0
  81. package/dist/lib/tools/credentialUses.js +112 -0
  82. package/dist/lib/tools/mcpToolFactory.js +1 -1
  83. package/dist/lib/tools/oauthConsent.js +8 -1
  84. package/dist/lib/tools/oauthHosts.d.ts +2 -0
  85. package/dist/lib/tools/oauthHosts.js +2 -2
  86. package/dist/lib/tools/oauthTools.d.ts +10 -1
  87. package/dist/lib/tools/oauthTools.js +29 -3
  88. package/dist/lib/tools/openapiTools.d.ts +7 -0
  89. package/dist/lib/tools/openapiTools.js +49 -3
  90. package/dist/lib/tools/taskTools.d.ts +32 -2
  91. package/dist/lib/tools/taskTools.js +93 -33
  92. package/dist/lib/workflow/turn.d.ts +22 -3
  93. package/dist/lib/workflow/turn.js +151 -31
  94. package/dist/lib/workflow.d.ts +10 -7
  95. package/dist/lib/workflow.js +10 -7
  96. package/dist/scripts/a2a_server.d.ts +2 -2
  97. package/dist/scripts/a2a_server.js +11 -2
  98. package/dist/scripts/assistant_worker.d.ts +2 -0
  99. package/dist/scripts/assistant_worker.js +13 -4
  100. package/dist/scripts/doctor.d.ts +4 -2
  101. package/dist/scripts/doctor.js +6 -4
  102. package/dist/scripts/setup.d.ts +23 -0
  103. package/dist/scripts/setup.js +154 -0
  104. package/dist/scripts/skills_install.d.ts +3 -1
  105. package/dist/scripts/skills_install.js +17 -2
  106. package/dist/scripts/yaml_codemod.d.ts +12 -2
  107. package/dist/scripts/yaml_codemod.js +115 -33
  108. package/package.json +4 -1
  109. package/skills/README.md +11 -2
  110. package/skills/briefs/_shared.md +1 -1
  111. package/skills/briefs/melchizedek-models.md +4 -2
  112. package/skills/briefs/skills-readme.md +3 -1
  113. package/skills/melchizedek-models/SKILL.md +6 -2
  114. package/skills/melchizedek-onboard/SKILL.md +49 -0
  115. package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
  116. package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
  117. package/skills/melchizedek-onboard-local/SKILL.md +41 -0
  118. package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
@@ -50,11 +50,18 @@
50
50
  * its model call is not checkpointed and runs again on resume.
51
51
  * - A dispatch route that is an agent answers through the transcript
52
52
  * projection (ProjectedSessionService, lib/session/transcript.ts), which
53
- * copies the session when the route opens it and applies only the
54
- * route's own appends to that copy. Restored events cannot reach that
55
- * copy through the store, so a checkpoint taken inside such a route is
56
- * not resumed: runDurableTurn starts that run fresh. A workflow route
57
- * writes its own child session and resumes.
53
+ * copies the session when the route opens it. The held opening session
54
+ * is empty at that open, so the copy is too; the route's append of the
55
+ * message is answered with the stored events, and the projection takes
56
+ * every event the real session gained, unprojected (the current turn),
57
+ * so the route resumes like a single agent. A workflow route writes its
58
+ * own child session and resumes. Checkpoints saved before this, which
59
+ * 1.1.0 set aside, restore the same way.
60
+ * - A task backend stores a checkpoint only up to its cap
61
+ * (TaskBackendOptions.checkpointMaxBytes, lib/tools/taskTools.ts,
62
+ * default 5 MiB of JSON); a save above it keeps the previous checkpoint
63
+ * and still answers whether the claim holds, so a huge run resumes from
64
+ * an older boundary.
58
65
  * - Each resumed attempt has its own step budget (max_steps, the loop's
59
66
  * call ceiling); attempts are bounded by the queue's MAX_ATTEMPTS.
60
67
  * - `temp:` state lasts one invocation and is never stored, so it does not
@@ -50,11 +50,18 @@
50
50
  * its model call is not checkpointed and runs again on resume.
51
51
  * - A dispatch route that is an agent answers through the transcript
52
52
  * projection (ProjectedSessionService, lib/session/transcript.ts), which
53
- * copies the session when the route opens it and applies only the
54
- * route's own appends to that copy. Restored events cannot reach that
55
- * copy through the store, so a checkpoint taken inside such a route is
56
- * not resumed: runDurableTurn starts that run fresh. A workflow route
57
- * writes its own child session and resumes.
53
+ * copies the session when the route opens it. The held opening session
54
+ * is empty at that open, so the copy is too; the route's append of the
55
+ * message is answered with the stored events, and the projection takes
56
+ * every event the real session gained, unprojected (the current turn),
57
+ * so the route resumes like a single agent. A workflow route writes its
58
+ * own child session and resumes. Checkpoints saved before this, which
59
+ * 1.1.0 set aside, restore the same way.
60
+ * - A task backend stores a checkpoint only up to its cap
61
+ * (TaskBackendOptions.checkpointMaxBytes, lib/tools/taskTools.ts,
62
+ * default 5 MiB of JSON); a save above it keeps the previous checkpoint
63
+ * and still answers whether the claim holds, so a huge run resumes from
64
+ * an older boundary.
58
65
  * - Each resumed attempt has its own step budget (max_steps, the loop's
59
66
  * call ceiling); attempts are bounded by the queue's MAX_ATTEMPTS.
60
67
  * - `temp:` state lasts one invocation and is never stored, so it does not
@@ -67,7 +74,6 @@
67
74
  * clears one; the caller does when the run finishes.
68
75
  */
69
76
  import { createHash, randomUUID } from 'node:crypto';
70
- import { isDispatchSyndicate } from '../../dispatch.js';
71
77
  import { getFunctionCalls, getFunctionResponses, parseTurnEvents } from '../events.js';
72
78
  import { InProcessSessionService } from '../sessions.js';
73
79
  import { runSyndicateTurn } from '../syndicateTurn.js';
@@ -311,17 +317,6 @@ class CheckpointingStore {
311
317
  export function checkpointingSessions(opts) {
312
318
  return new CheckpointingStore(opts);
313
319
  }
314
- /**
315
- * A dispatch route's checkpoint the store cannot resume: the run's own
316
- * session holds route events past the message, which the route reads
317
- * through the transcript projection (see LIMITS in the header).
318
- */
319
- function projectedRouteCheckpoint(config, cp, appName, userId) {
320
- if (!isDispatchSyndicate(config))
321
- return false;
322
- const own = cp.sessions.find((s) => s.appName === appName && s.userId === userId && s.id === cp.sessionId);
323
- return !!own && own.events.length > 1;
324
- }
325
320
  /**
326
321
  * runSyndicateTurn, durable: the run's sessions live in a fresh in-process
327
322
  * store, restored from the sink's checkpoint when it is this run's for this
@@ -331,8 +326,7 @@ function projectedRouteCheckpoint(config, cp, appName, userId) {
331
326
  */
332
327
  export async function runDurableTurn(opts) {
333
328
  const { runId, checkpoints, onLost, onSaveError, sessions: inner, signal, ...turn } = opts;
334
- const loaded = usableCheckpoint(await checkpoints.load(), runId, turn.parts);
335
- const from = loaded && !projectedRouteCheckpoint(turn.config, loaded, turn.appName, turn.userId) ? loaded : null;
329
+ const from = usableCheckpoint(await checkpoints.load(), runId, turn.parts);
336
330
  const sessionId = from ? from.sessionId : randomUUID();
337
331
  const controller = new AbortController();
338
332
  const forward = () => controller.abort(signal?.reason);
@@ -57,6 +57,10 @@
57
57
  * agent reads it (an approval runs or refuses its pinned call, a question's
58
58
  * answer is the call's response), finishes, and its answer is the open
59
59
  * call's response. A child that pauses again leaves the call open again.
60
+ * A skill script's approval is the same pause, and so is an OAuth consent
61
+ * request (ADR 0118): the child gets the caller's consent step, its flows
62
+ * bound to the caller's app (consentPinnedTo), and a grant's answer travels
63
+ * down the open call as a decision does.
60
64
  *
61
65
  * CALLS TO SUBAGENTS IN ONE STEP RUN CONCURRENTLY (ADR 0116), under the
62
66
  * caller's `max_concurrency` (the syndicate's root key, default
@@ -100,7 +104,7 @@
100
104
  */
101
105
  import type { ModelAdapter } from '../../models/contract.ts';
102
106
  import type { Tool, ToolContext } from '../../tools/tool.ts';
103
- import type { TurnEvent, TurnPart } from '../events.ts';
107
+ import type { TurnEvent, TurnFunctionCall, TurnPart } from '../events.ts';
104
108
  import type { Session, SessionService } from '../sessions.ts';
105
109
  import type { AgentLoopContext, AgentLoopEnd } from './agentLoop.ts';
106
110
  import type { NativeAgent } from './request.ts';
@@ -141,6 +145,12 @@ export interface WorkflowSubagentEnd {
141
145
  interruptIds: readonly string[];
142
146
  };
143
147
  stopped?: boolean;
148
+ /**
149
+ * The request calls it paused on, by id, when they are not in its own
150
+ * session: a nested dispatch syndicate's (ADR 0120) may wait in a route's
151
+ * delegation below it. A workflow node raises these again on its caller's walk.
152
+ */
153
+ requests?: TurnFunctionCall[];
144
154
  }
145
155
  /** A workflow syndicate delegated to as a subagent (ADR 0098): its name and description, and the walk one call runs. */
146
156
  export interface WorkflowSubagent {
@@ -228,6 +238,27 @@ export declare function childAppName(parent: {
228
238
  appName: string;
229
239
  delegated?: boolean;
230
240
  }, caller: string, name: string): string;
241
+ /**
242
+ * What runs a child session filed below a route or a node (ADR 0120): a
243
+ * dispatch route, or a workflow node. Its segment carries the kind, so a
244
+ * route's or a node's session never shares a key with a delegated call's.
245
+ */
246
+ export type EntryKind = 'route' | 'node';
247
+ /**
248
+ * The app name a nested syndicate run as a dispatch route or a workflow
249
+ * node is filed under (ADR 0119, ADR 0120): the session that runs it, and
250
+ * the entry's name behind its kind (`<app>/route:<route>`,
251
+ * `<walk's app>/node:<node>`). Agent names are identifiers, so a `:` never
252
+ * occurs in one, and a delegated call's key (childAppName: names only)
253
+ * never equals a route's or a node's: a delegate orchestrator `X` calling
254
+ * `Y` files `<app>/X/Y`, a route `X` with a nested node `Y` files
255
+ * `<app>/route:X/node:Y`. F3 (ADR 0119) filed it under `<app>/<entry>`
256
+ * (entryAppNameF3) and ADR 0106 under the entry's name alone; entrySession
257
+ * and the pause walk still read both.
258
+ */
259
+ export declare function entryAppName(parentAppName: string, name: string, kind: EntryKind): string;
260
+ /** Where ADR 0119 filed a route's or a node's session before the kind segment (ADR 0120): `<app>/<entry>`. */
261
+ export declare function entryAppNameF3(parentAppName: string, name: string): string;
231
262
  /**
232
263
  * The session a delegated call ran in before ADR 0111, filed under the
233
264
  * subagent's name alone, when it is the one to continue (`continues`): the
@@ -239,6 +270,38 @@ export declare function legacyChild(sessions: Pick<SessionService, 'get'>, key:
239
270
  userId: string;
240
271
  sessionId: string;
241
272
  }, name: string, continues: boolean): Promise<Session | undefined>;
273
+ /**
274
+ * The session a route's or a node's entry ran in before the kind segment
275
+ * (ADR 0120), when it is the one to continue (`continues`): the one F3
276
+ * filed under `<app>/<entry>`, else the one ADR 0106 filed under the
277
+ * entry's name alone. Read only when none exists under entryAppName.
278
+ */
279
+ export declare function legacyEntry(sessions: Pick<SessionService, 'get'>, key: {
280
+ appName: string;
281
+ userId: string;
282
+ sessionId: string;
283
+ }, name: string, continues: boolean): Promise<Session | undefined>;
284
+ /**
285
+ * The child session a nested syndicate run as a dispatch route or a
286
+ * workflow node runs on (ADR 0119, ADR 0120): the one filed under
287
+ * entryAppName, else, when the entry ran in `parent` before (an event it
288
+ * authored there) or a pause was found waiting below it (`resuming`), the
289
+ * one filed under an older key (legacyEntry), else a new one from `state`.
290
+ */
291
+ export declare function entrySession(sessions: Pick<SessionService, 'get' | 'create'>, parent: {
292
+ appName: string;
293
+ userId: string;
294
+ sessionId: string;
295
+ events: readonly TurnEvent[];
296
+ }, name: string, kind: EntryKind, state: Record<string, unknown>, resuming?: boolean): Promise<Session>;
297
+ /**
298
+ * The consent step as a child run reaches it (ADR 0118): a flow it begins
299
+ * is bound to `appName`, the caller's app, so the callback stores the grant
300
+ * under the app the run's pinned credentials read (the root's), never under
301
+ * the child session's agent path. A grandchild pins its caller's pinned
302
+ * step again; the first pin is applied last, so the root's app wins.
303
+ */
304
+ export declare function consentPinnedTo(consent: NonNullable<AgentLoopContext['consent']>, appName: string): NonNullable<AgentLoopContext['consent']>;
242
305
  /**
243
306
  * Runs one delegated call: `agent` on its own session with the call's
244
307
  * `request` as its message, the child's state writes recorded in `context`.
@@ -271,3 +334,21 @@ export declare function runWorkflowSubagent(workflow: WorkflowSubagent, args: Re
271
334
  * does, a SubagentPause again when the walk pauses again.
272
335
  */
273
336
  export declare function resumeWorkflowSubagent(workflow: WorkflowSubagent, answer: TurnPart[], context: ToolContext, scope: DelegationScope): Promise<unknown>;
337
+ /** A nested dispatch syndicate as its entry runs it: its name, the loaded file, and the options its routes compile with (lib/compile.ts DispatchSpec). */
338
+ export interface NestedDispatchEntry {
339
+ name: string;
340
+ config: unknown;
341
+ compile: unknown;
342
+ }
343
+ /**
344
+ * Runs a nested dispatch syndicate as a walk (ADR 0120): its own turn, on
345
+ * the conversation its caller opened (`run`), through the turn runner the
346
+ * current turn carries (TurnControl.nestedDispatch), under the turn's
347
+ * controls. Completed, it yields one event, never stored, that carries the
348
+ * route's final text and the conversation's state writes: the caller reads
349
+ * the answer and records the writes from it, as from a walk's last event.
350
+ * Paused, it ends with the request's id, and the request call itself for a
351
+ * workflow node to raise again. Failed, it throws the turn's error. A
352
+ * delegated call, a dispatch route and a workflow node all run it.
353
+ */
354
+ export declare function nestedDispatchWalk(entry: NestedDispatchEntry, run: Pick<WorkflowSubagentRun, 'sessions' | 'appName' | 'userId' | 'sessionId' | 'userParts' | 'signal'>): AsyncGenerator<TurnEvent, WorkflowSubagentEnd | undefined>;
@@ -57,6 +57,10 @@
57
57
  * agent reads it (an approval runs or refuses its pinned call, a question's
58
58
  * answer is the call's response), finishes, and its answer is the open
59
59
  * call's response. A child that pauses again leaves the call open again.
60
+ * A skill script's approval is the same pause, and so is an OAuth consent
61
+ * request (ADR 0118): the child gets the caller's consent step, its flows
62
+ * bound to the caller's app (consentPinnedTo), and a grant's answer travels
63
+ * down the open call as a decision does.
60
64
  *
61
65
  * CALLS TO SUBAGENTS IN ONE STEP RUN CONCURRENTLY (ADR 0116), under the
62
66
  * caller's `max_concurrency` (the syndicate's root key, default
@@ -104,6 +108,7 @@ import { createTurnEvent } from '../events.js';
104
108
  import { TEMP_STATE_PREFIX } from '../sessions.js';
105
109
  import { REQUEST_INPUT_CALL } from './history.js';
106
110
  import { SelfCorrection } from './selfCorrection.js';
111
+ import { currentTurnControl } from '../turnControl.js';
107
112
  // ── The subagent tool ────────────────────────────────────────────────────────
108
113
  /** Where a subagent tool keeps the agent it runs. A global symbol, so a second copy of this module still finds it. */
109
114
  export const SUBAGENT = Symbol.for('melchizedek.subagent');
@@ -271,6 +276,25 @@ function answerOf(agent, last) {
271
276
  export function childAppName(parent, caller, name) {
272
277
  return parent.delegated ? `${parent.appName}/${name}` : `${parent.appName}/${caller}/${name}`;
273
278
  }
279
+ /**
280
+ * The app name a nested syndicate run as a dispatch route or a workflow
281
+ * node is filed under (ADR 0119, ADR 0120): the session that runs it, and
282
+ * the entry's name behind its kind (`<app>/route:<route>`,
283
+ * `<walk's app>/node:<node>`). Agent names are identifiers, so a `:` never
284
+ * occurs in one, and a delegated call's key (childAppName: names only)
285
+ * never equals a route's or a node's: a delegate orchestrator `X` calling
286
+ * `Y` files `<app>/X/Y`, a route `X` with a nested node `Y` files
287
+ * `<app>/route:X/node:Y`. F3 (ADR 0119) filed it under `<app>/<entry>`
288
+ * (entryAppNameF3) and ADR 0106 under the entry's name alone; entrySession
289
+ * and the pause walk still read both.
290
+ */
291
+ export function entryAppName(parentAppName, name, kind) {
292
+ return `${parentAppName}/${kind}:${name}`;
293
+ }
294
+ /** Where ADR 0119 filed a route's or a node's session before the kind segment (ADR 0120): `<app>/<entry>`. */
295
+ export function entryAppNameF3(parentAppName, name) {
296
+ return `${parentAppName}/${name}`;
297
+ }
274
298
  /**
275
299
  * The session a delegated call ran in before ADR 0111, filed under the
276
300
  * subagent's name alone, when it is the one to continue (`continues`): the
@@ -283,6 +307,32 @@ export async function legacyChild(sessions, key, name, continues) {
283
307
  return undefined;
284
308
  return sessions.get({ appName: name, userId: key.userId, sessionId: key.sessionId });
285
309
  }
310
+ /**
311
+ * The session a route's or a node's entry ran in before the kind segment
312
+ * (ADR 0120), when it is the one to continue (`continues`): the one F3
313
+ * filed under `<app>/<entry>`, else the one ADR 0106 filed under the
314
+ * entry's name alone. Read only when none exists under entryAppName.
315
+ */
316
+ export async function legacyEntry(sessions, key, name, continues) {
317
+ if (!continues)
318
+ return undefined;
319
+ return (await sessions.get({ appName: entryAppNameF3(key.appName, name), userId: key.userId, sessionId: key.sessionId })) ?? (await legacyChild(sessions, key, name, true));
320
+ }
321
+ /**
322
+ * The child session a nested syndicate run as a dispatch route or a
323
+ * workflow node runs on (ADR 0119, ADR 0120): the one filed under
324
+ * entryAppName, else, when the entry ran in `parent` before (an event it
325
+ * authored there) or a pause was found waiting below it (`resuming`), the
326
+ * one filed under an older key (legacyEntry), else a new one from `state`.
327
+ */
328
+ export async function entrySession(sessions, parent, name, kind, state, resuming = false) {
329
+ const key = { appName: entryAppName(parent.appName, name, kind), userId: parent.userId, sessionId: parent.sessionId };
330
+ const own = await sessions.get(key);
331
+ if (own)
332
+ return own;
333
+ const legacy = await legacyEntry(sessions, parent, name, resuming || parent.events.some((e) => e.author === name));
334
+ return legacy ?? (await sessions.create({ ...key, state }));
335
+ }
286
336
  /** Whether `caller` called `name` in `events` in a call other than `callId`: the subagent ran for it before. */
287
337
  function calledBefore(events, caller, name, callId) {
288
338
  return events.some((e) => e.author === caller && (e.content?.parts ?? []).some((p) => p.functionCall?.name === name && p.functionCall.id !== callId));
@@ -304,6 +354,19 @@ async function childSession(name, context, scope, resuming) {
304
354
  const legacy = await legacyChild(sessions, key, name, resuming || calledBefore(parent.events, scope.caller, name, context.functionCallId));
305
355
  return legacy ?? (await sessions.create({ ...key, state: { ...scope.stateBase, ...context.stateDelta } }));
306
356
  }
357
+ /**
358
+ * The consent step as a child run reaches it (ADR 0118): a flow it begins
359
+ * is bound to `appName`, the caller's app, so the callback stores the grant
360
+ * under the app the run's pinned credentials read (the root's), never under
361
+ * the child session's agent path. A grandchild pins its caller's pinned
362
+ * step again; the first pin is applied last, so the root's app wins.
363
+ */
364
+ export function consentPinnedTo(consent, appName) {
365
+ return {
366
+ has: (provider) => consent.has(provider),
367
+ begin: (binding) => consent.begin({ ...binding, appName }),
368
+ };
369
+ }
307
370
  /** Step 5: an event's state writes, `temp:` keys aside, into the call's state delta. */
308
371
  function recordState(event, context) {
309
372
  for (const [k, v] of Object.entries(event.actions?.stateDelta ?? {})) {
@@ -352,9 +415,10 @@ async function runChild(agent, content, context, scope, resuming) {
352
415
  // plugins, so a subagent's own errors are not retried (ADR 0075).
353
416
  selfCorrection: new SelfCorrection({ model_errors: 0, tool_errors: 0 }),
354
417
  ...(ctx.memory ? { memory: ctx.memory } : {}),
355
- // The parent's grants, pinned to the root's app (ADR 0072). Not its consent step: an OAuth
356
- // consent inside a subagent is not carried to the caller (ADR 0085, ADR 0110).
418
+ // The parent's grants, pinned to the root's app (ADR 0072), and its consent step: a call that
419
+ // needs a grant pauses the child, and the pause reaches the turn through the open call (ADR 0118).
357
420
  ...(ctx.credentials ? { credentials: ctx.credentials } : {}),
421
+ ...(ctx.consent ? { consent: consentPinnedTo(ctx.consent, ctx.session.appName) } : {}),
358
422
  ...(ctx.signal ? { signal: ctx.signal } : {}),
359
423
  ...(ctx.adapterFor ? { adapterFor: ctx.adapterFor } : {}),
360
424
  ...(ctx.log ? { log: ctx.log } : {}),
@@ -443,3 +507,94 @@ async function walkChild(workflow, userParts, context, scope, resuming) {
443
507
  }
444
508
  return answerOf({}, last);
445
509
  }
510
+ /** The engine's approval request call (lib/runtime/approvals.ts APPROVAL_REQUEST). */
511
+ const APPROVAL_REQUEST_CALL = 'adk_request_confirmation';
512
+ /**
513
+ * The message a nested dispatch syndicate's turn reads (ADR 0120): the
514
+ * parts as they came, except an answer to a question, which comes down as
515
+ * an `ask_user` reply (a delegated call's resume) or an `adk_request_input`
516
+ * reply (a workflow node's), and goes in as the plain text it was, as a
517
+ * person's answer reaches a turn at the top.
518
+ */
519
+ function dispatchParts(parts) {
520
+ return parts.map((p) => {
521
+ const r = p.functionResponse;
522
+ if (!r || (r.name !== ASK_USER_CALL && r.name !== REQUEST_INPUT_CALL))
523
+ return p;
524
+ const result = r.response?.result;
525
+ return { text: typeof result === 'string' ? result : JSON.stringify(result ?? '') };
526
+ });
527
+ }
528
+ /**
529
+ * The request call a paused nested dispatch syndicate's turn waits on, as a
530
+ * workflow node raises it again on its caller's walk: an approval request
531
+ * pinned to the gated call, or an input request carrying the question.
532
+ * Undefined for a consent request, which a walk cannot raise (ADR 0119).
533
+ */
534
+ function requestCallOf(end) {
535
+ const id = end.interruptId;
536
+ if (!id)
537
+ return undefined;
538
+ const approval = end.approval;
539
+ if (approval) {
540
+ return {
541
+ id,
542
+ name: APPROVAL_REQUEST_CALL,
543
+ args: { originalFunctionCall: { name: approval.tool ?? '', args: approval.args ?? {}, ...(approval.callId ? { id: approval.callId } : {}) }, toolConfirmation: { confirmed: false } },
544
+ };
545
+ }
546
+ const input = end.input;
547
+ if (input) {
548
+ return {
549
+ id,
550
+ name: REQUEST_INPUT_CALL,
551
+ args: {
552
+ interruptId: id,
553
+ message: input.message ?? '',
554
+ ...(input.payload !== undefined ? { payload: input.payload } : {}),
555
+ ...(input.schema !== undefined ? { response_schema: input.schema } : {}),
556
+ },
557
+ };
558
+ }
559
+ return undefined;
560
+ }
561
+ /**
562
+ * Runs a nested dispatch syndicate as a walk (ADR 0120): its own turn, on
563
+ * the conversation its caller opened (`run`), through the turn runner the
564
+ * current turn carries (TurnControl.nestedDispatch), under the turn's
565
+ * controls. Completed, it yields one event, never stored, that carries the
566
+ * route's final text and the conversation's state writes: the caller reads
567
+ * the answer and records the writes from it, as from a walk's last event.
568
+ * Paused, it ends with the request's id, and the request call itself for a
569
+ * workflow node to raise again. Failed, it throws the turn's error. A
570
+ * delegated call, a dispatch route and a workflow node all run it.
571
+ */
572
+ export async function* nestedDispatchWalk(entry, run) {
573
+ const runner = currentTurnControl()?.nestedDispatch;
574
+ if (!runner)
575
+ throw new Error(`'${entry.name}' is a nested dispatch syndicate, which runs only inside a syndicate turn (runSyndicateTurn).`);
576
+ const end = await runner({
577
+ config: entry.config,
578
+ compile: entry.compile,
579
+ name: entry.name,
580
+ sessions: run.sessions,
581
+ appName: run.appName,
582
+ userId: run.userId,
583
+ sessionId: run.sessionId,
584
+ parts: dispatchParts(run.userParts),
585
+ });
586
+ if (end.status === 'canceled' || run.signal?.aborted || currentTurnControl()?.stopReason)
587
+ return { stopped: true };
588
+ if (end.status === 'failed')
589
+ throw new Error(end.error?.message ?? `${entry.name} failed.`);
590
+ if (end.status === 'input-required' && end.interruptId) {
591
+ const request = requestCallOf(end);
592
+ return { run: { interruptIds: [end.interruptId] }, ...(request ? { requests: [request] } : {}) };
593
+ }
594
+ yield createTurnEvent({
595
+ author: entry.name,
596
+ content: { role: 'model', parts: [{ text: end.text }] },
597
+ ...(Object.keys(end.stateDelta).length ? { actions: { stateDelta: end.stateDelta } } : {}),
598
+ });
599
+ return { run: { interruptIds: [] } };
600
+ }
@@ -104,14 +104,48 @@
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
  */
110
142
  import type { ToolConfirmation } from '../../tools/tool.ts';
111
143
  import type { PendingInput } from '../../workflowConfig.ts';
112
144
  import type { PendingApproval } from '../approvals.ts';
145
+ import type { PendingConsent } from '../credentials.ts';
113
146
  import type { TurnEvent, TurnFunctionCall, TurnPart } from '../events.ts';
114
147
  import type { Session, SessionService } from '../sessions.ts';
148
+ import type { EntryKind } from './delegate.ts';
115
149
  import type { NativeAgent } from './request.ts';
116
150
  /** Why a confirmation does not bind to the call it pins: ADK's IntentMismatchError reasons. */
117
151
  export type IntentMismatchReason = 'untrusted_request' | 'malformed_request' | 'unknown_original_call' | 'unregistered_tool' | 'tool_name_mismatch' | 'arguments_mismatch' | 'confirmation_not_required';
@@ -168,6 +202,8 @@ export interface DelegatedPause {
168
202
  approval?: PendingApproval;
169
203
  /** The question, when the asker waits on one; its `path` is set. */
170
204
  question?: PendingInput;
205
+ /** The OAuth consent request, when the asker waits for a grant (ADR 0118); its `path` is set. */
206
+ consent?: PendingConsent;
171
207
  }
172
208
  /** Where a session's delegated calls opened their child sessions: the store, and the caller's user and session ids. */
173
209
  export interface DelegationKey {
@@ -194,6 +230,52 @@ export declare function openCalls(events: readonly TurnEvent[], author?: string)
194
230
  author: string;
195
231
  call: TurnFunctionCall;
196
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>;
197
279
  /**
198
280
  * Every pause waiting inside a delegated call still open in `events` (one
199
281
  * session's events), latest call first: each open call whose child session
@@ -202,7 +284,7 @@ export declare function openCalls(events: readonly TurnEvent[], author?: string)
202
284
  * longer open, and neither is anything waiting below it.
203
285
  */
204
286
  export declare function delegatedPauses(key: DelegationKey, events: readonly TurnEvent[], author?: string, depth?: number, seen?: ReadonlySet<string>): Promise<DelegatedPause[]>;
205
- /** The interrupt a delegated pause waits on: the approval request's id, or the question's. */
287
+ /** The interrupt a delegated pause waits on: the approval request's id, the question's, or the consent request's. */
206
288
  export declare function interruptIdOf(pause: DelegatedPause): string | undefined;
207
289
  /** The open delegated calls the latest user message resumes, and the ones still waiting after it. */
208
290
  export interface ResumedDelegations {
@@ -219,10 +301,11 @@ export interface ResumedDelegations {
219
301
  * The delegated calls `agent` left open that the latest user message
220
302
  * answers (ADR 0110): the message's approval decisions and question
221
303
  * answers (function responses to `adk_request_confirmation` or `ask_user`)
222
- * matched, by interrupt id, to the pause below each open call. Undefined
223
- * when the message answers none. The child binds the answer itself: an
224
- * approval is checked against its pinned call there (approvedCalls), as at
225
- * the top.
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.
226
309
  */
227
310
  export declare function resumedDelegations(agent: NativeAgent, scope: Scope, sessions: Pick<SessionService, 'get'>): Promise<ResumedDelegations | undefined>;
228
311
  export {};