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.
- package/.env.example +10 -0
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +232 -0
- package/DOCUMENTATION.md +91 -16
- package/ONBOARDING.md +339 -0
- package/QUICKSTART.md +15 -2
- package/README.md +5 -2
- 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 +65 -7
- 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 +22 -11
- package/dist/lib/a2a/executor.js +14 -6
- package/dist/lib/a2a/oauthSetup.d.ts +16 -0
- package/dist/lib/a2a/oauthSetup.js +57 -3
- package/dist/lib/agentDialect.d.ts +18 -8
- package/dist/lib/agentDialect.js +36 -19
- package/dist/lib/compile.d.ts +38 -7
- package/dist/lib/compile.js +57 -37
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +18 -1
- package/dist/lib/doctor.d.ts +76 -0
- package/dist/lib/doctor.js +159 -7
- package/dist/lib/loadSyndicate.d.ts +9 -0
- 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 +2 -1
- package/dist/lib/models/gateway.js +4 -2
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/onboarding.d.ts +134 -0
- package/dist/lib/onboarding.js +539 -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 +295 -62
- 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 +38 -1
- package/dist/lib/syndicateSchema.js +25 -18
- 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 +92 -0
- package/dist/lib/tools/credentialUses.js +112 -0
- package/dist/lib/tools/mcpToolFactory.js +1 -1
- package/dist/lib/tools/oauthConsent.js +8 -1
- package/dist/lib/tools/oauthHosts.d.ts +2 -0
- package/dist/lib/tools/oauthHosts.js +2 -2
- package/dist/lib/tools/oauthTools.d.ts +10 -1
- package/dist/lib/tools/oauthTools.js +29 -3
- 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/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 +11 -2
- package/dist/scripts/assistant_worker.d.ts +2 -0
- package/dist/scripts/assistant_worker.js +13 -4
- package/dist/scripts/doctor.d.ts +4 -2
- package/dist/scripts/doctor.js +6 -4
- package/dist/scripts/setup.d.ts +23 -0
- package/dist/scripts/setup.js +154 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/yaml_codemod.d.ts +12 -2
- package/dist/scripts/yaml_codemod.js +115 -33
- package/package.json +4 -1
- 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 +49 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
|
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)
|
|
356
|
-
//
|
|
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
|
|
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
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
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 {};
|