melchizedek-agents 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +29 -2
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +524 -0
- package/DOCUMENTATION.md +178 -43
- package/ONBOARDING.md +339 -0
- package/QUICKSTART.md +15 -2
- package/README.md +6 -2
- package/config/agents/README.md +2 -2
- package/config/agents/examples/ares.yaml +6 -10
- package/config/agents/examples/assistant.yaml +6 -6
- package/config/agents/examples/augustin.yaml +9 -15
- package/config/agents/examples/cartographers.yaml +6 -6
- package/config/agents/examples/council.yaml +7 -7
- package/config/agents/examples/critic.yaml +24 -24
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +37 -35
- package/config/agents/examples/librarian.yaml +4 -4
- package/config/agents/examples/patient_advocate.yaml +6 -10
- package/config/agents/examples/pipeline.yaml +20 -19
- package/config/agents/examples/research.yaml +36 -43
- package/config/agents/examples/scribe.yaml +31 -32
- package/config/agents/examples/scriptorium.yaml +6 -6
- package/config/agents/examples/structured_critic.yaml +82 -0
- package/config/agents/examples/style_council.yaml +9 -11
- package/config/agents/examples/syndicate.yaml +3 -5
- package/config/agents/examples/tutor.yaml +2 -2
- package/config/agents/examples/weather.yaml +2 -2
- package/config/agents/syndicate.schema.json +859 -13
- package/config/agents/syndicateSchema.yaml +122 -73
- package/config/agents/templates/README.md +1 -1
- package/config/agents/templates/account_memory.yaml +4 -4
- package/config/agents/templates/case_desk.yaml +6 -6
- package/config/agents/templates/conversational.yaml +2 -2
- package/config/agents/templates/draft_review.yaml +6 -6
- package/config/agents/templates/intake_extractor.yaml +64 -62
- package/config/agents/templates/research_brief.yaml +6 -6
- package/config/agents/templates/research_desk.yaml +6 -6
- package/config/agents/templates/review_panel.yaml +8 -8
- package/config/agents/templates/support_triage.yaml +30 -29
- package/config/agents/templates/systems_operator.yaml +24 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +27 -0
- package/dist/lib/a2a/app.js +64 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +43 -5
- package/dist/lib/a2a/oauthSetup.d.ts +106 -0
- package/dist/lib/a2a/oauthSetup.js +259 -0
- package/dist/lib/agentDialect.d.ts +76 -0
- package/dist/lib/agentDialect.js +151 -0
- package/dist/lib/compile.d.ts +42 -6
- package/dist/lib/compile.js +97 -45
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +20 -1
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +110 -1
- package/dist/lib/doctor.js +205 -9
- package/dist/lib/loadSyndicate.d.ts +54 -2
- package/dist/lib/loadSyndicate.js +22 -2
- package/dist/lib/memory/namespace.d.ts +6 -5
- package/dist/lib/memory/namespace.js +6 -5
- package/dist/lib/memory/providers.js +3 -4
- package/dist/lib/models/capabilities.d.ts +25 -1
- package/dist/lib/models/capabilities.js +79 -1
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +2 -1
- package/dist/lib/models/gateway.js +4 -2
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/onboarding.d.ts +134 -0
- package/dist/lib/onboarding.js +539 -0
- package/dist/lib/runtime/approvals.d.ts +8 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +23 -3
- package/dist/lib/runtime/native/agentLoop.js +107 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +171 -0
- package/dist/lib/runtime/native/checkpoint.js +359 -0
- package/dist/lib/runtime/native/delegate.d.ts +222 -18
- package/dist/lib/runtime/native/delegate.js +390 -39
- package/dist/lib/runtime/native/interrupts.d.ts +184 -2
- package/dist/lib/runtime/native/interrupts.js +391 -1
- package/dist/lib/runtime/native/request.d.ts +13 -3
- package/dist/lib/runtime/native/request.js +28 -4
- package/dist/lib/runtime/native/telemetry.js +10 -15
- package/dist/lib/runtime/questions.d.ts +7 -4
- package/dist/lib/runtime/sessions.d.ts +1 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +11 -3
- package/dist/lib/runtime/syndicateTurn.js +353 -68
- package/dist/lib/runtime/turnControl.d.ts +56 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +12 -2
- package/dist/lib/storage/postgres/index.js +8 -3
- package/dist/lib/storage/postgres/taskQueue.d.ts +20 -4
- package/dist/lib/storage/postgres/taskQueue.js +29 -5
- package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
- package/dist/lib/storage/postgres/taskStore.js +74 -8
- package/dist/lib/syndicateSchema.d.ts +316 -1
- package/dist/lib/syndicateSchema.js +203 -29
- package/dist/lib/tools/auth.d.ts +20 -6
- package/dist/lib/tools/auth.js +21 -5
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/credentialUses.d.ts +92 -0
- package/dist/lib/tools/credentialUses.js +112 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthConsent.js +8 -1
- package/dist/lib/tools/oauthHosts.d.ts +73 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +242 -0
- package/dist/lib/tools/oauthTools.js +447 -0
- package/dist/lib/tools/openapiTools.d.ts +11 -6
- package/dist/lib/tools/openapiTools.js +88 -38
- package/dist/lib/tools/taskTools.d.ts +61 -6
- package/dist/lib/tools/taskTools.js +145 -21
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +14 -7
- package/dist/lib/workflow.js +14 -7
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +131 -1
- package/dist/scripts/assistant_worker.d.ts +12 -0
- package/dist/scripts/assistant_worker.js +81 -22
- package/dist/scripts/doctor.d.ts +7 -2
- package/dist/scripts/doctor.js +9 -4
- package/dist/scripts/setup.d.ts +23 -0
- package/dist/scripts/setup.js +154 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/yaml_codemod.d.ts +62 -0
- package/dist/scripts/yaml_codemod.js +652 -0
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +7 -4
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek/SKILL.md +1 -0
- package/skills/melchizedek-author/SKILL.md +7 -4
- package/skills/melchizedek-author/assets/minimal.yaml +13 -13
- package/skills/melchizedek-models/SKILL.md +11 -4
- package/skills/melchizedek-onboard/SKILL.md +49 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/a2a/oauthSetup.ts — tool credentials and the OAuth consent step from
|
|
3
|
+
* the environment, for the server binary (WS6-3d, ADR 0114).
|
|
4
|
+
*
|
|
5
|
+
* WHY this file exists:
|
|
6
|
+
* `createA2AApp({ toolCredentials })` takes a credential store and a
|
|
7
|
+
* consent step, and the YAML declares the grants (ADR 0112), but the
|
|
8
|
+
* `melchizedek-serve` binary built neither: a declared authorization-code
|
|
9
|
+
* grant worked only for an embedder who wrote the wiring. This module is
|
|
10
|
+
* that wiring, from four variables, so the binary and an embedder build it
|
|
11
|
+
* the same way:
|
|
12
|
+
*
|
|
13
|
+
* MELCHIZEDEK_CREDENTIAL_KEY the key that seals each user's tokens (ADR 0072)
|
|
14
|
+
* OAUTH_REDIRECT_URI the consent callback, as registered at every provider (ADR 0085)
|
|
15
|
+
* MELCHIZEDEK_OAUTH_HOSTS which hosts each provider's tokens may go to (ADR 0114)
|
|
16
|
+
* MELCHIZEDEK_CREDENTIAL_HOSTS which hosts each static credential variable may go to (ADR 0122)
|
|
17
|
+
* the client id and secret variables each YAML grant names
|
|
18
|
+
*
|
|
19
|
+
* The consent clients are built from the syndicates the server serves from
|
|
20
|
+
* files (`oauthClientsFor`), never from a registry row, so a stored YAML
|
|
21
|
+
* can name a provider the files declare but cannot define one.
|
|
22
|
+
*
|
|
23
|
+
* WHAT IT NEVER DOES: print, return or log a value. Problems name variables,
|
|
24
|
+
* providers and hosts only (secrets-hygiene).
|
|
25
|
+
*/
|
|
26
|
+
import { CREDENTIAL_KEY_ENV, credentialCipherFromEnv } from '../tools/credentialCipher.js';
|
|
27
|
+
import { credentialStore, memoryCredentialRows } from '../tools/credentialStore.js';
|
|
28
|
+
import { oauthConsent } from '../tools/oauthConsent.js';
|
|
29
|
+
import { OAUTH_HOSTS_ENV, oauthHosts } from '../tools/oauthHosts.js';
|
|
30
|
+
import { oauthClientsFor, oauthGrantHostProblems, oauthRefreshProviders, syndicateOAuthGrants, syndicateOAuthHostProblems } from '../tools/oauthTools.js';
|
|
31
|
+
import { CREDENTIAL_HOSTS_ENV, credentialHosts } from '../tools/credentialHosts.js';
|
|
32
|
+
import { syndicateCredentialHostProblems, syndicateCredentialUses, unboundCredentialEnvs } from '../tools/credentialUses.js';
|
|
33
|
+
/** The consent callback's URL, exactly as registered at every provider. */
|
|
34
|
+
export const OAUTH_REDIRECT_URI_ENV = 'OAUTH_REDIRECT_URI';
|
|
35
|
+
/** Who may complete a consent callback: `required` (the browser carries the user's identity) or `state` (the nonce alone). */
|
|
36
|
+
export const OAUTH_CALLBACK_IDENTITY_ENV = 'OAUTH_CALLBACK_IDENTITY';
|
|
37
|
+
/** OAUTH_CALLBACK_IDENTITY, checked: `required` (the default) or `state`. Throws on anything else. */
|
|
38
|
+
export function callbackIdentity(env = process.env) {
|
|
39
|
+
const raw = (env[OAUTH_CALLBACK_IDENTITY_ENV] ?? '').trim().toLowerCase() || 'required';
|
|
40
|
+
if (raw !== 'required' && raw !== 'state')
|
|
41
|
+
throw new Error(`${OAUTH_CALLBACK_IDENTITY_ENV} must be 'required' or 'state' (got '${raw.slice(0, 40)}')`);
|
|
42
|
+
return raw;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Why consent callbacks would all be refused under this environment, or
|
|
46
|
+
* null: a required caller identity that the configured authenticator cannot
|
|
47
|
+
* give a browser (only a gateway's header can: A2A_AUTH=header).
|
|
48
|
+
*/
|
|
49
|
+
export function callbackIdentityProblem(env = process.env) {
|
|
50
|
+
const authKind = (env.A2A_AUTH ?? 'secret').trim().toLowerCase() || 'secret';
|
|
51
|
+
if (callbackIdentity(env) !== 'required' || authKind === 'header')
|
|
52
|
+
return null;
|
|
53
|
+
return `the consent callback requires the browser to carry the user's identity, which A2A_AUTH=${authKind} cannot give a browser, so every callback is refused: serve behind a gateway (A2A_AUTH=header), or set ${OAUTH_CALLBACK_IDENTITY_ENV}=state to let the single-use state alone bind it`;
|
|
54
|
+
}
|
|
55
|
+
const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
|
|
56
|
+
/** Why `raw` cannot be the consent redirect URI, or null (the rule oauthConsent enforces). */
|
|
57
|
+
export function redirectUriProblem(raw) {
|
|
58
|
+
let url;
|
|
59
|
+
try {
|
|
60
|
+
url = new URL(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return `${OAUTH_REDIRECT_URI_ENV} is not a URL`;
|
|
64
|
+
}
|
|
65
|
+
if (!(url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname))))
|
|
66
|
+
return `${OAUTH_REDIRECT_URI_ENV} must use https (http only on a loopback host)`;
|
|
67
|
+
if (url.username || url.password)
|
|
68
|
+
return `${OAUTH_REDIRECT_URI_ENV} must not carry credentials`;
|
|
69
|
+
if (url.search || url.hash)
|
|
70
|
+
return `${OAUTH_REDIRECT_URI_ENV} must not carry a query or a fragment`;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** The MCP server a grant's tools call, when the grant is an MCP one. */
|
|
74
|
+
const serversOf = (use) => (use.tools.startsWith('mcp ') && use.tools.length > 4 ? [use.tools.slice(4)] : []);
|
|
75
|
+
/**
|
|
76
|
+
* Why this grant's hosts are refused by the allowlist in `env`, one line
|
|
77
|
+
* each (the MCP server, the token and authorization endpoints; an OpenAPI
|
|
78
|
+
* entry's servers are checked when it compiles). Names and hosts only.
|
|
79
|
+
*/
|
|
80
|
+
export function grantHostProblems(use, env = process.env) {
|
|
81
|
+
let allowlist;
|
|
82
|
+
try {
|
|
83
|
+
allowlist = oauthHosts(env) ?? null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return [`${OAUTH_HOSTS_ENV} is malformed`];
|
|
87
|
+
}
|
|
88
|
+
return oauthGrantHostProblems(use.oauth2, serversOf(use), allowlist);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* What is wrong with this environment for these declared grants, one line
|
|
92
|
+
* each, names only: the credential key, the redirect URI and the allowlist
|
|
93
|
+
* (each grant's own hosts: grantHostProblems). The doctor prints these; the
|
|
94
|
+
* server refuses to start on the ones that would make it unsafe.
|
|
95
|
+
*/
|
|
96
|
+
export function oauthEnvProblems(grants, env = process.env) {
|
|
97
|
+
const problems = [];
|
|
98
|
+
const authCode = [...new Set(grants.filter((g) => g.oauth2.grant === 'authorization_code').map((g) => g.oauth2.provider))];
|
|
99
|
+
const keySet = !!env[CREDENTIAL_KEY_ENV]?.trim();
|
|
100
|
+
const redirect = env[OAUTH_REDIRECT_URI_ENV]?.trim();
|
|
101
|
+
if (keySet) {
|
|
102
|
+
try {
|
|
103
|
+
credentialCipherFromEnv(env);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// parseCredentialKey's message names the variable and the shape, never the value.
|
|
107
|
+
problems.push(err instanceof Error ? err.message : `${CREDENTIAL_KEY_ENV} is malformed`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (redirect) {
|
|
111
|
+
const problem = redirectUriProblem(redirect);
|
|
112
|
+
if (problem)
|
|
113
|
+
problems.push(problem);
|
|
114
|
+
if (!keySet)
|
|
115
|
+
problems.push(`${OAUTH_REDIRECT_URI_ENV} is set but ${CREDENTIAL_KEY_ENV} is not: the consent step has nowhere to keep a grant`);
|
|
116
|
+
try {
|
|
117
|
+
const identity = callbackIdentityProblem(env);
|
|
118
|
+
if (identity && authCode.length)
|
|
119
|
+
problems.push(identity);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
problems.push(err instanceof Error ? err.message : `${OAUTH_CALLBACK_IDENTITY_ENV} is malformed`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (authCode.length) {
|
|
126
|
+
const who = authCode.join(', ');
|
|
127
|
+
if (!keySet)
|
|
128
|
+
problems.push(`authorization_code grants (${who}) need ${CREDENTIAL_KEY_ENV}: without it no user's token can be held, and their tools answer unavailable`);
|
|
129
|
+
if (!redirect)
|
|
130
|
+
problems.push(`authorization_code grants (${who}) need ${OAUTH_REDIRECT_URI_ENV}: without it a user who has not connected cannot be asked to`);
|
|
131
|
+
}
|
|
132
|
+
let allowlist;
|
|
133
|
+
try {
|
|
134
|
+
allowlist = oauthHosts(env);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
problems.push(err instanceof Error ? err.message : `${OAUTH_HOSTS_ENV} is malformed`);
|
|
138
|
+
}
|
|
139
|
+
if (authCode.length && allowlist === undefined && !problems.some((p) => p.startsWith(OAUTH_HOSTS_ENV))) {
|
|
140
|
+
problems.push(`authorization_code grants (${authCode.join(', ')}) need ${OAUTH_HOSTS_ENV}: without it the operator has bound no provider to its hosts, so they are refused`);
|
|
141
|
+
}
|
|
142
|
+
return problems;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The warning for credential variables no allowlist binds (ADR 0122), or
|
|
146
|
+
* null: with no credential host allowlist, each variable in `envs` (the
|
|
147
|
+
* ones the served YAML sends) still goes to whatever host the YAML names.
|
|
148
|
+
* Names only.
|
|
149
|
+
*/
|
|
150
|
+
export function unboundCredentialWarning(envs, env = process.env) {
|
|
151
|
+
let allowlist;
|
|
152
|
+
try {
|
|
153
|
+
allowlist = credentialHosts(env);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null; // A malformed allowlist is a problem, reported as one.
|
|
157
|
+
}
|
|
158
|
+
if (allowlist)
|
|
159
|
+
return null; // Configured: an unbound variable is refused, not warned about.
|
|
160
|
+
const unbound = [...new Set(envs)].sort();
|
|
161
|
+
if (!unbound.length)
|
|
162
|
+
return null;
|
|
163
|
+
return `${unbound.join(', ')} ${unbound.length === 1 ? 'is' : 'are'} sent to whatever host the YAML names: no ${CREDENTIAL_HOSTS_ENV} binds ${unbound.length === 1 ? 'it' : 'them'} to ${unbound.length === 1 ? 'its' : 'their'} hosts`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* What is wrong with the credential host allowlist for these uses, one line
|
|
167
|
+
* each, names and hosts only: a malformed allowlist, or each use it refuses
|
|
168
|
+
* (lib/tools/credentialUses.ts). Empty when none is configured.
|
|
169
|
+
*/
|
|
170
|
+
export function credentialHostEnvProblems(configs, env = process.env, load) {
|
|
171
|
+
let allowlist;
|
|
172
|
+
try {
|
|
173
|
+
allowlist = credentialHosts(env);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
return [err instanceof Error ? err.message : `${CREDENTIAL_HOSTS_ENV} is malformed`];
|
|
177
|
+
}
|
|
178
|
+
return syndicateCredentialHostProblems(configs, { allowlist: allowlist ?? null, ...(load ? { load } : {}) });
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The credential store and consent step from the environment. Throws, naming
|
|
182
|
+
* variables only, when the configuration would be unsafe or cannot work: a
|
|
183
|
+
* malformed key, a redirect URI without a key, a malformed redirect URI or
|
|
184
|
+
* allowlist, an authorization-code grant the allowlist does not permit, a
|
|
185
|
+
* grant whose client variables are unset.
|
|
186
|
+
*/
|
|
187
|
+
export function oauthServerSetup(options) {
|
|
188
|
+
const env = options.env ?? process.env;
|
|
189
|
+
const warnings = [];
|
|
190
|
+
const grants = syndicateOAuthGrants(options.configs, options.load);
|
|
191
|
+
const authCode = [...new Set(grants.filter((g) => g.oauth2.grant === 'authorization_code').map((g) => g.oauth2.provider))];
|
|
192
|
+
const redirect = env[OAUTH_REDIRECT_URI_ENV]?.trim() || undefined;
|
|
193
|
+
const cipher = credentialCipherFromEnv(env); // a malformed key throws here, at boot
|
|
194
|
+
const allowlist = oauthHosts(env); // a malformed allowlist throws here, at boot
|
|
195
|
+
const hostsLabel = allowlist ? `host allowlist for ${Object.keys(allowlist).join(', ')}` : `no host allowlist (${OAUTH_HOSTS_ENV}): authorization_code grants refused`;
|
|
196
|
+
if (redirect) {
|
|
197
|
+
const problem = redirectUriProblem(redirect);
|
|
198
|
+
if (problem)
|
|
199
|
+
throw new Error(problem);
|
|
200
|
+
}
|
|
201
|
+
// A served syndicate whose grant the allowlist refuses is refused here, at boot, as its route would refuse it.
|
|
202
|
+
const refused = syndicateOAuthHostProblems(options.configs, { ...(options.load ? { load: options.load } : {}), allowlist: allowlist ?? null });
|
|
203
|
+
if (refused.length)
|
|
204
|
+
throw new Error(`refusing an OAuth grant the operator's host allowlist does not permit:\n - ${refused.join('\n - ')}`);
|
|
205
|
+
// The credential host allowlist (ADR 0122): malformed throws here, at boot; a served file it refuses too.
|
|
206
|
+
const credentialList = credentialHosts(env);
|
|
207
|
+
const credentialRefused = syndicateCredentialHostProblems(options.configs, { ...(options.load ? { load: options.load } : {}), allowlist: credentialList ?? null });
|
|
208
|
+
if (credentialRefused.length)
|
|
209
|
+
throw new Error(`refusing a credential the operator's credential host allowlist does not permit:\n - ${credentialRefused.join('\n - ')}`);
|
|
210
|
+
const uses = syndicateCredentialUses(options.configs, options.load);
|
|
211
|
+
const unboundWarning = unboundCredentialWarning(uses.map((u) => u.env), env);
|
|
212
|
+
if (unboundWarning)
|
|
213
|
+
warnings.push(`${unboundWarning.charAt(0).toUpperCase()}${unboundWarning.slice(1)}.`);
|
|
214
|
+
const credentialSummary = credentialList
|
|
215
|
+
? `bound: ${Object.keys(credentialList).join(', ')} (${CREDENTIAL_HOSTS_ENV})`
|
|
216
|
+
: uses.length
|
|
217
|
+
? `no allowlist (${CREDENTIAL_HOSTS_ENV}): ${unboundCredentialEnvs(uses, undefined).join(', ')} unbound`
|
|
218
|
+
: `no allowlist (${CREDENTIAL_HOSTS_ENV}); no served file sends a static credential`;
|
|
219
|
+
if (!cipher) {
|
|
220
|
+
if (redirect)
|
|
221
|
+
throw new Error(`${OAUTH_REDIRECT_URI_ENV} is set but ${CREDENTIAL_KEY_ENV} is not: the consent step has nowhere to keep a grant. Set ${CREDENTIAL_KEY_ENV} (openssl rand -base64 32).`);
|
|
222
|
+
if (authCode.length)
|
|
223
|
+
warnings.push(`authorization_code grants (${authCode.join(', ')}) are declared but ${CREDENTIAL_KEY_ENV} is not set: their tools answer unavailable.`);
|
|
224
|
+
return { summary: `off (${CREDENTIAL_KEY_ENV} unset) · ${hostsLabel}`, credentialSummary, warnings };
|
|
225
|
+
}
|
|
226
|
+
const clients = oauthClientsFor(options.configs, { env, ...(options.load ? { load: options.load } : {}), allowlist: allowlist ?? null });
|
|
227
|
+
const providers = Object.keys(clients);
|
|
228
|
+
const store = credentialStore({
|
|
229
|
+
rows: options.rows ?? memoryCredentialRows(),
|
|
230
|
+
cipher,
|
|
231
|
+
providers: oauthRefreshProviders(clients, { ...(options.allowPrivate ? { allowPrivate: true } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) }),
|
|
232
|
+
...(options.audit ? { audit: options.audit } : {}),
|
|
233
|
+
});
|
|
234
|
+
if (!options.rows)
|
|
235
|
+
warnings.push(`Tool credentials are held in process memory: lost on restart, one instance only. Set DATABASE_URL to keep them sealed in Postgres.`);
|
|
236
|
+
const where = options.rows ? 'postgres' : 'process memory';
|
|
237
|
+
if (!redirect) {
|
|
238
|
+
if (providers.length)
|
|
239
|
+
warnings.push(`${OAUTH_REDIRECT_URI_ENV} is not set: a user who has not connected ${providers.join(', ')} cannot be asked to.`);
|
|
240
|
+
return { toolCredentials: { store }, summary: `sealed (key ${cipher.keyId}) in ${where}; no consent step · ${hostsLabel}`, credentialSummary, warnings };
|
|
241
|
+
}
|
|
242
|
+
if (!providers.length) {
|
|
243
|
+
warnings.push(`${OAUTH_REDIRECT_URI_ENV} is set but no served syndicate declares an authorization_code grant: no consent callback is mounted.`);
|
|
244
|
+
return { toolCredentials: { store }, summary: `sealed (key ${cipher.keyId}) in ${where}; no consent step · ${hostsLabel}`, credentialSummary, warnings };
|
|
245
|
+
}
|
|
246
|
+
const consent = oauthConsent({
|
|
247
|
+
providers: clients,
|
|
248
|
+
redirectUri: redirect,
|
|
249
|
+
credentials: store,
|
|
250
|
+
...(options.audit ? { audit: options.audit } : {}),
|
|
251
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
toolCredentials: { store, consent },
|
|
255
|
+
summary: `sealed (key ${cipher.keyId}) in ${where}; consent at ${new URL(redirect).pathname} for ${providers.join(', ')} · ${hostsLabel}`,
|
|
256
|
+
credentialSummary,
|
|
257
|
+
warnings,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/agentDialect.ts — the YAML v2 agent keys and the engine form they map to
|
|
3
|
+
* (ADR 0115).
|
|
4
|
+
*
|
|
5
|
+
* WHY this file exists:
|
|
6
|
+
* An agent's sampling and structured-output settings were spelled in
|
|
7
|
+
* Gemini's dialect (`generateContentConfig.temperature`, `outputSchema`),
|
|
8
|
+
* which is what the engine still reads (lib/runtime/native/request.ts
|
|
9
|
+
* buildModelRequest). YAML v2 spells them provider-neutrally (`sampling`,
|
|
10
|
+
* `output`, and `tool_choice` from ADR 0117) and adds per-provider
|
|
11
|
+
* instruction overrides (`model_overrides`).
|
|
12
|
+
* This module is the one place the two spellings meet:
|
|
13
|
+
*
|
|
14
|
+
* - `V2_SPELLINGS`: each v2 key path and the v1 path it replaces. The
|
|
15
|
+
* loader (lib/syndicateSchema.ts crossFieldProblems) refuses a file that
|
|
16
|
+
* sets both; `toEngineAgent` refuses a config built in code that does.
|
|
17
|
+
* - `toEngineAgent`: folds `sampling`, `output` and `tool_choice` into
|
|
18
|
+
* the engine form.
|
|
19
|
+
* validateSyndicateConfig applies it to every agent it returns, so every
|
|
20
|
+
* loader hands downstream the engine form; lib/compile.ts applies it
|
|
21
|
+
* again (idempotent) for configs built in code.
|
|
22
|
+
* - `v1SpellingsOf`: the deprecated v1 key paths a raw file still uses, for
|
|
23
|
+
* the loader's one-line deprecation warning (paths only, never values).
|
|
24
|
+
* - `instructionFor`: an agent's instruction with its `model_overrides`
|
|
25
|
+
* entry for the provider of the model it runs on applied.
|
|
26
|
+
*
|
|
27
|
+
* Internal: not in the lib/index.ts barrel nor the package exports map.
|
|
28
|
+
*/
|
|
29
|
+
import type { AgentYamlConfig } from './loadSyndicate.ts';
|
|
30
|
+
/** One v2 key and the v1 (engine) path it replaces. Both paths are relative to the agent. */
|
|
31
|
+
export interface V2Spelling {
|
|
32
|
+
readonly v2: readonly string[];
|
|
33
|
+
readonly v1: readonly string[];
|
|
34
|
+
/** The engine value for the v2 value, when the two are spelled differently. */
|
|
35
|
+
readonly toV1?: (value: unknown) => unknown;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `tool_choice` as Gemini's function-calling config, which the engine reads
|
|
39
|
+
* (lib/models/genaiMapping.ts toolChoiceOf): `auto` is AUTO, `none` NONE,
|
|
40
|
+
* `required` ANY, and `{ name }` ANY with that one name allowed (ADR 0117).
|
|
41
|
+
*/
|
|
42
|
+
export declare function functionCallingConfigOf(choice: unknown): Record<string, unknown>;
|
|
43
|
+
/** Every v2 key with a v1 equivalent; the mapper writes the v1 path. */
|
|
44
|
+
export declare const V2_SPELLINGS: readonly V2Spelling[];
|
|
45
|
+
/** The v2 agent keys, which an inline agent alone may carry (not a yaml_reference or a2a_agent_url subagent). */
|
|
46
|
+
export declare const V2_AGENT_KEYS: readonly ['sampling', 'output', 'tool_choice', 'model_overrides'];
|
|
47
|
+
/** The v1 spellings ADR 0115 deprecates, as agent keys. */
|
|
48
|
+
export declare const V1_AGENT_KEYS: readonly ['generateContentConfig', 'outputSchema'];
|
|
49
|
+
/** The v2/v1 pairs an agent sets both of: each is a conflict. */
|
|
50
|
+
export declare function conflictsOf(agent: unknown): V2Spelling[];
|
|
51
|
+
/** The loader's message for one conflict, reported against the v2 key path. */
|
|
52
|
+
export declare function conflictMessage(s: V2Spelling): string;
|
|
53
|
+
/**
|
|
54
|
+
* An agent in the engine form: `sampling`, `output` and `tool_choice` folded
|
|
55
|
+
* into `generateContentConfig` and `outputSchema` (the table above) and
|
|
56
|
+
* removed; `model_overrides` kept (lib/compile.ts applies it). The same
|
|
57
|
+
* object when the agent has none of the three, so it is idempotent. Throws
|
|
58
|
+
* when a v2 key and the v1 key it replaces are both set.
|
|
59
|
+
*/
|
|
60
|
+
export declare function toEngineAgent<T extends object>(agent: T): T;
|
|
61
|
+
/**
|
|
62
|
+
* The deprecated v1 key paths a raw syndicate uses, in file order:
|
|
63
|
+
* `orchestrator.generateContentConfig`, `subagents[2].outputSchema`, ...
|
|
64
|
+
* Paths only: the loader logs them, never the values under them.
|
|
65
|
+
*/
|
|
66
|
+
export declare function v1SpellingsOf(raw: unknown): string[];
|
|
67
|
+
/**
|
|
68
|
+
* An agent's base instruction for the model it runs on: its
|
|
69
|
+
* `model_overrides` entry for that model's provider replaces it
|
|
70
|
+
* (`instruction`) or is appended after a blank line (`instruction_append`).
|
|
71
|
+
* The base instruction when there is no entry or no model id. The skills
|
|
72
|
+
* index (lib/compile.ts withSkills) is appended after this.
|
|
73
|
+
*/
|
|
74
|
+
export declare function instructionFor(agent: Pick<AgentYamlConfig, 'model_overrides'> & {
|
|
75
|
+
instruction?: string;
|
|
76
|
+
}, modelId: string | undefined): string;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/agentDialect.ts — the YAML v2 agent keys and the engine form they map to
|
|
3
|
+
* (ADR 0115).
|
|
4
|
+
*
|
|
5
|
+
* WHY this file exists:
|
|
6
|
+
* An agent's sampling and structured-output settings were spelled in
|
|
7
|
+
* Gemini's dialect (`generateContentConfig.temperature`, `outputSchema`),
|
|
8
|
+
* which is what the engine still reads (lib/runtime/native/request.ts
|
|
9
|
+
* buildModelRequest). YAML v2 spells them provider-neutrally (`sampling`,
|
|
10
|
+
* `output`, and `tool_choice` from ADR 0117) and adds per-provider
|
|
11
|
+
* instruction overrides (`model_overrides`).
|
|
12
|
+
* This module is the one place the two spellings meet:
|
|
13
|
+
*
|
|
14
|
+
* - `V2_SPELLINGS`: each v2 key path and the v1 path it replaces. The
|
|
15
|
+
* loader (lib/syndicateSchema.ts crossFieldProblems) refuses a file that
|
|
16
|
+
* sets both; `toEngineAgent` refuses a config built in code that does.
|
|
17
|
+
* - `toEngineAgent`: folds `sampling`, `output` and `tool_choice` into
|
|
18
|
+
* the engine form.
|
|
19
|
+
* validateSyndicateConfig applies it to every agent it returns, so every
|
|
20
|
+
* loader hands downstream the engine form; lib/compile.ts applies it
|
|
21
|
+
* again (idempotent) for configs built in code.
|
|
22
|
+
* - `v1SpellingsOf`: the deprecated v1 key paths a raw file still uses, for
|
|
23
|
+
* the loader's one-line deprecation warning (paths only, never values).
|
|
24
|
+
* - `instructionFor`: an agent's instruction with its `model_overrides`
|
|
25
|
+
* entry for the provider of the model it runs on applied.
|
|
26
|
+
*
|
|
27
|
+
* Internal: not in the lib/index.ts barrel nor the package exports map.
|
|
28
|
+
*/
|
|
29
|
+
import { providerForModel } from './models/providerMap.js';
|
|
30
|
+
const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
31
|
+
/**
|
|
32
|
+
* `tool_choice` as Gemini's function-calling config, which the engine reads
|
|
33
|
+
* (lib/models/genaiMapping.ts toolChoiceOf): `auto` is AUTO, `none` NONE,
|
|
34
|
+
* `required` ANY, and `{ name }` ANY with that one name allowed (ADR 0117).
|
|
35
|
+
*/
|
|
36
|
+
export function functionCallingConfigOf(choice) {
|
|
37
|
+
if (choice === 'none')
|
|
38
|
+
return { mode: 'NONE' };
|
|
39
|
+
if (choice === 'required')
|
|
40
|
+
return { mode: 'ANY' };
|
|
41
|
+
if (isObj(choice) && typeof choice.name === 'string')
|
|
42
|
+
return { mode: 'ANY', allowedFunctionNames: [choice.name] };
|
|
43
|
+
return { mode: 'AUTO' };
|
|
44
|
+
}
|
|
45
|
+
/** Every v2 key with a v1 equivalent; the mapper writes the v1 path. */
|
|
46
|
+
export const V2_SPELLINGS = [
|
|
47
|
+
{ v2: ['sampling', 'temperature'], v1: ['generateContentConfig', 'temperature'] },
|
|
48
|
+
{ v2: ['sampling', 'top_p'], v1: ['generateContentConfig', 'topP'] },
|
|
49
|
+
{ v2: ['sampling', 'max_output_tokens'], v1: ['generateContentConfig', 'maxOutputTokens'] },
|
|
50
|
+
{ v2: ['sampling', 'stop'], v1: ['generateContentConfig', 'stopSequences'] },
|
|
51
|
+
{ v2: ['output', 'schema'], v1: ['outputSchema'] },
|
|
52
|
+
{ v2: ['output', 'mime'], v1: ['generateContentConfig', 'responseMimeType'] },
|
|
53
|
+
{ v2: ['tool_choice'], v1: ['generateContentConfig', 'toolConfig', 'functionCallingConfig'], toV1: functionCallingConfigOf },
|
|
54
|
+
];
|
|
55
|
+
/** The v2 agent keys, which an inline agent alone may carry (not a yaml_reference or a2a_agent_url subagent). */
|
|
56
|
+
export const V2_AGENT_KEYS = ['sampling', 'output', 'tool_choice', 'model_overrides'];
|
|
57
|
+
/** The v1 spellings ADR 0115 deprecates, as agent keys. */
|
|
58
|
+
export const V1_AGENT_KEYS = ['generateContentConfig', 'outputSchema'];
|
|
59
|
+
/** The value at `path` under `root`, or undefined. */
|
|
60
|
+
function at(root, path) {
|
|
61
|
+
let cur = root;
|
|
62
|
+
for (const seg of path) {
|
|
63
|
+
if (!isObj(cur))
|
|
64
|
+
return undefined;
|
|
65
|
+
cur = cur[seg];
|
|
66
|
+
}
|
|
67
|
+
return cur;
|
|
68
|
+
}
|
|
69
|
+
/** The v2/v1 pairs an agent sets both of: each is a conflict. */
|
|
70
|
+
export function conflictsOf(agent) {
|
|
71
|
+
return V2_SPELLINGS.filter((s) => at(agent, s.v2) !== undefined && at(agent, s.v1) !== undefined);
|
|
72
|
+
}
|
|
73
|
+
/** The loader's message for one conflict, reported against the v2 key path. */
|
|
74
|
+
export function conflictMessage(s) {
|
|
75
|
+
return `cannot be combined with ${s.v1.join('.')}; ${s.v2.join('.')} replaces it, so keep one (ADR 0115)`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* An agent in the engine form: `sampling`, `output` and `tool_choice` folded
|
|
79
|
+
* into `generateContentConfig` and `outputSchema` (the table above) and
|
|
80
|
+
* removed; `model_overrides` kept (lib/compile.ts applies it). The same
|
|
81
|
+
* object when the agent has none of the three, so it is idempotent. Throws
|
|
82
|
+
* when a v2 key and the v1 key it replaces are both set.
|
|
83
|
+
*/
|
|
84
|
+
export function toEngineAgent(agent) {
|
|
85
|
+
const a = agent;
|
|
86
|
+
if (a.sampling === undefined && a.output === undefined && a.tool_choice === undefined)
|
|
87
|
+
return agent;
|
|
88
|
+
const clash = conflictsOf(a);
|
|
89
|
+
if (clash.length) {
|
|
90
|
+
const name = typeof a.name === 'string' ? a.name : 'agent';
|
|
91
|
+
throw new Error(`${name}: ${clash.map((s) => `${s.v2.join('.')} cannot be combined with ${s.v1.join('.')}`).join('; ')}; the v2 key replaces it, so keep one (ADR 0115)`);
|
|
92
|
+
}
|
|
93
|
+
const { sampling: _sampling, output: _output, tool_choice: _toolChoice, ...rest } = a;
|
|
94
|
+
const out = { ...rest };
|
|
95
|
+
for (const s of V2_SPELLINGS) {
|
|
96
|
+
const raw = at(a, s.v2);
|
|
97
|
+
if (raw === undefined)
|
|
98
|
+
continue;
|
|
99
|
+
const value = s.toV1 ? s.toV1(raw) : raw;
|
|
100
|
+
// Copy each object on the v1 path, so the caller's config is never written to.
|
|
101
|
+
let parent = out;
|
|
102
|
+
for (const seg of s.v1.slice(0, -1)) {
|
|
103
|
+
const next = { ...(isObj(parent[seg]) ? parent[seg] : {}) };
|
|
104
|
+
parent[seg] = next;
|
|
105
|
+
parent = next;
|
|
106
|
+
}
|
|
107
|
+
parent[s.v1[s.v1.length - 1]] = value;
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The deprecated v1 key paths a raw syndicate uses, in file order:
|
|
113
|
+
* `orchestrator.generateContentConfig`, `subagents[2].outputSchema`, ...
|
|
114
|
+
* Paths only: the loader logs them, never the values under them.
|
|
115
|
+
*/
|
|
116
|
+
export function v1SpellingsOf(raw) {
|
|
117
|
+
if (!isObj(raw))
|
|
118
|
+
return [];
|
|
119
|
+
const agents = [['orchestrator', raw.orchestrator]];
|
|
120
|
+
if (Array.isArray(raw.subagents))
|
|
121
|
+
raw.subagents.forEach((sub, i) => agents.push([`subagents[${i}]`, sub]));
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const [path, agent] of agents) {
|
|
124
|
+
if (!isObj(agent))
|
|
125
|
+
continue;
|
|
126
|
+
for (const key of V1_AGENT_KEYS)
|
|
127
|
+
if (agent[key] !== undefined)
|
|
128
|
+
out.push(`${path}.${key}`);
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* An agent's base instruction for the model it runs on: its
|
|
134
|
+
* `model_overrides` entry for that model's provider replaces it
|
|
135
|
+
* (`instruction`) or is appended after a blank line (`instruction_append`).
|
|
136
|
+
* The base instruction when there is no entry or no model id. The skills
|
|
137
|
+
* index (lib/compile.ts withSkills) is appended after this.
|
|
138
|
+
*/
|
|
139
|
+
export function instructionFor(agent, modelId) {
|
|
140
|
+
const base = agent.instruction ?? '';
|
|
141
|
+
if (!agent.model_overrides || modelId === undefined)
|
|
142
|
+
return base;
|
|
143
|
+
const entry = agent.model_overrides[providerForModel(modelId)];
|
|
144
|
+
if (!entry)
|
|
145
|
+
return base;
|
|
146
|
+
if (entry.instruction !== undefined)
|
|
147
|
+
return entry.instruction;
|
|
148
|
+
if (entry.instruction_append !== undefined)
|
|
149
|
+
return base ? `${base}\n\n${entry.instruction_append}` : entry.instruction_append;
|
|
150
|
+
return base;
|
|
151
|
+
}
|
package/dist/lib/compile.d.ts
CHANGED
|
@@ -110,7 +110,30 @@ export type SpecTool = {
|
|
|
110
110
|
} | {
|
|
111
111
|
kind: 'workflow';
|
|
112
112
|
workflow: WorkflowSpec;
|
|
113
|
+
} | {
|
|
114
|
+
kind: 'dispatch';
|
|
115
|
+
dispatch: DispatchSpec;
|
|
113
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* A nested dispatch (plan-dispatch) syndicate (ADR 0120): it runs as it
|
|
119
|
+
* runs at the top, as a turn of its own on its own conversation (its
|
|
120
|
+
* classifier picks a route, the route answers), so what it compiles to is
|
|
121
|
+
* the file and the options its turn loads its routes with. The turn runner
|
|
122
|
+
* runs it (lib/runtime/syndicateTurn.ts, TurnControl.nestedDispatch), as a
|
|
123
|
+
* delegated call, a dispatch route or a workflow node.
|
|
124
|
+
*/
|
|
125
|
+
export interface DispatchSpec {
|
|
126
|
+
/** The entry's name, under which its caller lists it. */
|
|
127
|
+
name: string;
|
|
128
|
+
/** The entry's description: the tool's, when delegated to. */
|
|
129
|
+
description: string;
|
|
130
|
+
/** The nested syndicate, as loaded. */
|
|
131
|
+
config: SyndicateYamlConfig;
|
|
132
|
+
/** The file it came from. */
|
|
133
|
+
ref: string;
|
|
134
|
+
/** The options its routes compile with: the caller's, its nesting chain extended by `ref`. */
|
|
135
|
+
compile: CompileOptions;
|
|
136
|
+
}
|
|
114
137
|
/**
|
|
115
138
|
* A workflow syndicate, compiled: lib/compileNative.ts builds the walk's
|
|
116
139
|
* graph and agents from it. Nested as a subagent (a `yaml_reference` in
|
|
@@ -138,6 +161,11 @@ export interface WorkflowSpec {
|
|
|
138
161
|
yaml: SubagentYamlConfig;
|
|
139
162
|
workflow: WorkflowSpec;
|
|
140
163
|
}>;
|
|
164
|
+
/** Every node that is a `yaml_reference` to a dispatch syndicate, with its YAML entry: the walk runs it as the node, as its own turn (ADR 0120). */
|
|
165
|
+
dispatches?: Array<{
|
|
166
|
+
yaml: SubagentYamlConfig;
|
|
167
|
+
dispatch: DispatchSpec;
|
|
168
|
+
}>;
|
|
141
169
|
/** The registry entry for a tool node's tool name, or undefined (CompileOptions.onUnknownTool applies). */
|
|
142
170
|
resolveTool: (name: string) => unknown;
|
|
143
171
|
}
|
|
@@ -177,6 +205,8 @@ export interface AgentSpec {
|
|
|
177
205
|
context?: ContextConfig;
|
|
178
206
|
/** `mode: task`. */
|
|
179
207
|
mode?: 'task';
|
|
208
|
+
/** The syndicate's `max_concurrency`, on its orchestrator (ADR 0116): delegated calls one step runs at once. */
|
|
209
|
+
maxConcurrency?: number;
|
|
180
210
|
}
|
|
181
211
|
/**
|
|
182
212
|
* The spec of a workflow syndicate: every agent through compileSubagentSpec
|
|
@@ -184,20 +214,23 @@ export interface AgentSpec {
|
|
|
184
214
|
* lookup for its tool nodes. `name` and `description` name a nested one
|
|
185
215
|
* after its subagent entry (ADR 0098); `ref` is the file it came from.
|
|
186
216
|
*
|
|
187
|
-
* A nested workflow
|
|
188
|
-
*
|
|
189
|
-
*
|
|
217
|
+
* A nested workflow's pauses (an `ask_user` node, a gate) reach the turn
|
|
218
|
+
* wherever it runs (ADR 0111, ADR 0119). `delegated` is accepted for the
|
|
219
|
+
* callers that pass it and no longer changes what compiles.
|
|
190
220
|
*/
|
|
191
|
-
export declare function compileWorkflowSpec(config: SyndicateYamlConfig, opts?: CompileOptions, name?: string, description?: string, ref?: string): Promise<WorkflowSpec>;
|
|
221
|
+
export declare function compileWorkflowSpec(config: SyndicateYamlConfig, opts?: CompileOptions, name?: string, description?: string, ref?: string, delegated?: boolean): Promise<WorkflowSpec>;
|
|
192
222
|
/** Every agent spec a workflow runs, its nested workflow nodes' agents included, for one adapter lookup (nativeAdapterFor). */
|
|
193
223
|
export declare function workflowAgentSpecs(spec: WorkflowSpec): AgentSpec[];
|
|
194
|
-
/** A subagent entry, compiled: one agent,
|
|
224
|
+
/** A subagent entry, compiled: one agent, a nested workflow syndicate's whole graph (ADR 0098, ADR 0106), or a nested dispatch syndicate's own turn (ADR 0120). */
|
|
195
225
|
export type EntrySpec = {
|
|
196
226
|
kind: 'agent';
|
|
197
227
|
spec: AgentSpec;
|
|
198
228
|
} | {
|
|
199
229
|
kind: 'workflow';
|
|
200
230
|
workflow: WorkflowSpec;
|
|
231
|
+
} | {
|
|
232
|
+
kind: 'dispatch';
|
|
233
|
+
dispatch: DispatchSpec;
|
|
201
234
|
};
|
|
202
235
|
/**
|
|
203
236
|
* A subagent entry as what it runs: an inline agent or a nested syndicate's
|
|
@@ -205,8 +238,11 @@ export type EntrySpec = {
|
|
|
205
238
|
* as its whole graph under the entry's name and description. A delegated
|
|
206
239
|
* subagent, a dispatch route and a workflow node all compile through here,
|
|
207
240
|
* so the graph a file describes is the graph that runs (ADR 0106).
|
|
241
|
+
* A nested workflow pauses wherever it runs (ADR 0111, ADR 0119);
|
|
242
|
+
* `delegated` is accepted for the callers that pass it and no longer
|
|
243
|
+
* changes what compiles.
|
|
208
244
|
*/
|
|
209
|
-
export declare function compileEntrySpec(subCfg: SubagentYamlConfig, opts?: CompileOptions): Promise<EntrySpec>;
|
|
245
|
+
export declare function compileEntrySpec(subCfg: SubagentYamlConfig, opts?: CompileOptions, delegated?: boolean): Promise<EntrySpec>;
|
|
210
246
|
/**
|
|
211
247
|
* The names of a syndicate's subagent entries that are `yaml_reference`s to
|
|
212
248
|
* a workflow syndicate. Loads each reference once and compiles nothing, so a
|