melchizedek-agents 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +19 -0
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +373 -0
- package/DOCUMENTATION.md +193 -21
- package/ONBOARDING.md +378 -0
- package/QUICKSTART.md +15 -2
- package/README.md +26 -3
- package/config/agents/examples/connectors.yaml +82 -0
- package/config/agents/examples/council.yaml +1 -1
- package/config/agents/examples/critic.yaml +8 -8
- package/config/agents/examples/image_production.yaml +4 -3
- package/config/agents/syndicate.schema.json +389 -33
- package/config/agents/syndicateSchema.yaml +18 -6
- package/config/agents/templates/intake_extractor.yaml +3 -2
- package/config/agents/templates/systems_operator.yaml +5 -2
- package/dist/lib/a2a/app.d.ts +12 -0
- package/dist/lib/a2a/app.js +25 -11
- package/dist/lib/a2a/executor.js +14 -6
- package/dist/lib/a2a/oauthSetup.d.ts +16 -0
- package/dist/lib/a2a/oauthSetup.js +77 -8
- package/dist/lib/agentDialect.d.ts +18 -8
- package/dist/lib/agentDialect.js +36 -19
- package/dist/lib/chatgpt/adapter.d.ts +50 -0
- package/dist/lib/chatgpt/adapter.js +107 -0
- package/dist/lib/chatgpt/cli.d.ts +23 -0
- package/dist/lib/chatgpt/cli.js +77 -0
- package/dist/lib/chatgpt/oauth.d.ts +103 -0
- package/dist/lib/chatgpt/oauth.js +448 -0
- package/dist/lib/chatgpt/state.d.ts +98 -0
- package/dist/lib/chatgpt/state.js +145 -0
- package/dist/lib/chatgpt/store.d.ts +24 -0
- package/dist/lib/chatgpt/store.js +94 -0
- package/dist/lib/compile.d.ts +38 -7
- package/dist/lib/compile.js +97 -62
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +18 -1
- package/dist/lib/dispatch.js +6 -2
- package/dist/lib/doctor.d.ts +107 -0
- package/dist/lib/doctor.js +217 -7
- package/dist/lib/loadSyndicate.d.ts +15 -0
- package/dist/lib/mcp/server.d.ts +195 -0
- package/dist/lib/mcp/server.js +564 -0
- package/dist/lib/memory/supabaseMemoryService.js +2 -2
- package/dist/lib/models/adapterResolver.js +6 -1
- package/dist/lib/models/capabilities.d.ts +9 -0
- package/dist/lib/models/capabilities.js +24 -0
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +3 -2
- package/dist/lib/models/gateway.js +8 -4
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/onboarding.d.ts +135 -0
- package/dist/lib/onboarding.js +578 -0
- package/dist/lib/runtime/approvals.d.ts +2 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
- package/dist/lib/runtime/native/agentLoop.js +2 -0
- package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
- package/dist/lib/runtime/native/checkpoint.js +13 -19
- package/dist/lib/runtime/native/delegate.d.ts +82 -1
- package/dist/lib/runtime/native/delegate.js +157 -2
- package/dist/lib/runtime/native/interrupts.d.ts +88 -5
- package/dist/lib/runtime/native/interrupts.js +220 -30
- package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
- package/dist/lib/runtime/syndicateTurn.js +324 -77
- package/dist/lib/runtime/turnControl.d.ts +41 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +1 -1
- package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
- package/dist/lib/storage/postgres/taskQueue.js +10 -3
- package/dist/lib/syndicateSchema.d.ts +184 -9
- package/dist/lib/syndicateSchema.js +143 -29
- package/dist/lib/tools/auth.d.ts +13 -1
- package/dist/lib/tools/auth.js +10 -2
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialUses.d.ts +98 -0
- package/dist/lib/tools/credentialUses.js +120 -0
- package/dist/lib/tools/generateImageTool.js +2 -1
- package/dist/lib/tools/inspectImageTool.js +1 -1
- package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
- package/dist/lib/tools/mcpToolFactory.js +72 -26
- package/dist/lib/tools/mcpTransports.d.ts +12 -0
- package/dist/lib/tools/mcpTransports.js +11 -0
- package/dist/lib/tools/oauthConsent.d.ts +18 -3
- package/dist/lib/tools/oauthConsent.js +57 -15
- package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
- package/dist/lib/tools/oauthDiscovery.js +616 -0
- package/dist/lib/tools/oauthHosts.d.ts +2 -0
- package/dist/lib/tools/oauthHosts.js +2 -2
- package/dist/lib/tools/oauthTools.d.ts +54 -13
- package/dist/lib/tools/oauthTools.js +106 -23
- package/dist/lib/tools/openapiTools.d.ts +7 -0
- package/dist/lib/tools/openapiTools.js +49 -3
- package/dist/lib/tools/taskTools.d.ts +32 -2
- package/dist/lib/tools/taskTools.js +93 -33
- package/dist/lib/tools/xApiSearchTool.js +2 -2
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +10 -7
- package/dist/lib/workflow.js +10 -7
- package/dist/scripts/a2a_server.d.ts +2 -2
- package/dist/scripts/a2a_server.js +19 -2
- package/dist/scripts/assistant_worker.d.ts +2 -0
- package/dist/scripts/assistant_worker.js +24 -5
- package/dist/scripts/doctor.d.ts +4 -2
- package/dist/scripts/doctor.js +6 -4
- package/dist/scripts/mcp_server.d.ts +12 -0
- package/dist/scripts/mcp_server.js +345 -0
- package/dist/scripts/setup.d.ts +25 -0
- package/dist/scripts/setup.js +181 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/syndicate_chat.js +12 -3
- package/dist/scripts/yaml_codemod.d.ts +12 -2
- package/dist/scripts/yaml_codemod.js +115 -33
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-models.md +4 -2
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek-models/SKILL.md +6 -2
- package/skills/melchizedek-onboard/SKILL.md +71 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +47 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/chatgpt/state.ts — where a Sign in with ChatGPT credential lives, whether
|
|
3
|
+
* it is the OpenAI path, and the served-surface refusal (ADR 0126).
|
|
4
|
+
*
|
|
5
|
+
* WHY a leaf (node:fs, node:os, node:path only):
|
|
6
|
+
* The gateway's transport rule (lib/models/gateway.ts), the doctor, the
|
|
7
|
+
* resolver and every served bin ask the same two questions — is a sign-in
|
|
8
|
+
* stored, and does it carry OpenAI ids here — so the answer lives in one
|
|
9
|
+
* module with no adapter or SDK in its import graph.
|
|
10
|
+
*
|
|
11
|
+
* LOCAL ONLY:
|
|
12
|
+
* OpenAI's plan-usage flow is for open-source apps that run on the user's
|
|
13
|
+
* own machine (developers.openai.com/siwc/token-sharing-open-source). The
|
|
14
|
+
* A2A server, the worker and any other served surface call
|
|
15
|
+
* `refuseChatGptSignInOnServedSurface` at startup and stop while the
|
|
16
|
+
* sign-in would carry OpenAI ids; `markServedSurface` also makes the
|
|
17
|
+
* adapter refuse at call time, so a surface that skipped the startup check
|
|
18
|
+
* still never spends a person's ChatGPT plan on someone else's request.
|
|
19
|
+
*
|
|
20
|
+
* WHAT IT NEVER DOES: return or print a token. The status carries presence,
|
|
21
|
+
* times and the file's path.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
import { dirname, join, resolve } from 'node:path';
|
|
26
|
+
/** Where the credential file lives, when not the default (a path; never a value). */
|
|
27
|
+
export const CHATGPT_SIGNIN_FILE_ENV = 'MELCHIZEDEK_CHATGPT_SIGNIN_FILE';
|
|
28
|
+
/** `off` ignores a stored sign-in (a served process on a machine that also signed in). */
|
|
29
|
+
export const CHATGPT_SIGNIN_ENV = 'MELCHIZEDEK_CHATGPT_SIGNIN';
|
|
30
|
+
/** The scope that lets the token use the person's ChatGPT plan. */
|
|
31
|
+
export const PLAN_SCOPE = 'chatgpt.tokens.use.direct';
|
|
32
|
+
/** The credential file: MELCHIZEDEK_CHATGPT_SIGNIN_FILE, else ~/.melchizedek/chatgpt-signin.json. */
|
|
33
|
+
export function signInFile(env = process.env) {
|
|
34
|
+
const set = env[CHATGPT_SIGNIN_FILE_ENV]?.trim();
|
|
35
|
+
return set ? resolve(set) : join(homedir(), '.melchizedek', 'chatgpt-signin.json');
|
|
36
|
+
}
|
|
37
|
+
/** The git work tree a path sits inside, if any (an ancestor holding `.git`). */
|
|
38
|
+
export function insideGitWorkTree(file) {
|
|
39
|
+
let dir = dirname(resolve(file));
|
|
40
|
+
for (;;) {
|
|
41
|
+
if (existsSync(join(dir, '.git')))
|
|
42
|
+
return dir;
|
|
43
|
+
const parent = dirname(dir);
|
|
44
|
+
if (parent === dir)
|
|
45
|
+
return undefined;
|
|
46
|
+
dir = parent;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Whether a parsed value has the stored shape. */
|
|
50
|
+
export function isStoredSignIn(v) {
|
|
51
|
+
if (!v || typeof v !== 'object')
|
|
52
|
+
return false;
|
|
53
|
+
const o = v;
|
|
54
|
+
return o.version === 1 && typeof o.issuer === 'string' && typeof o.hostId === 'string';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read the credential file: undefined when absent; throws, naming the file
|
|
58
|
+
* and never its content, when it is readable by others or malformed.
|
|
59
|
+
*/
|
|
60
|
+
export function readStoredSignIn(file) {
|
|
61
|
+
if (!existsSync(file))
|
|
62
|
+
return undefined;
|
|
63
|
+
if (process.platform !== 'win32' && (statSync(file).mode & 0o077) !== 0) {
|
|
64
|
+
throw new Error(`${file} is readable by other users; run \`chmod 600 ${file}\` (or sign in again).`);
|
|
65
|
+
}
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(readFileSync(file, 'utf-8'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error(`${file} is not a Sign in with ChatGPT credential file; delete it and sign in again.`);
|
|
72
|
+
}
|
|
73
|
+
if (!isStoredSignIn(parsed))
|
|
74
|
+
throw new Error(`${file} is not a Sign in with ChatGPT credential file; delete it and sign in again.`);
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
/** Signed in: a refresh token, and the plan scope granted. */
|
|
78
|
+
export function isSignedIn(s) {
|
|
79
|
+
return !!s?.refreshToken && !!s.accessToken && (s.scopes ?? []).includes(PLAN_SCOPE);
|
|
80
|
+
}
|
|
81
|
+
/** The stored sign-in's status under this environment. */
|
|
82
|
+
export function chatGptSignInStatus(env = process.env) {
|
|
83
|
+
const file = signInFile(env);
|
|
84
|
+
const enabled = env[CHATGPT_SIGNIN_ENV]?.trim().toLowerCase() !== 'off';
|
|
85
|
+
const present = existsSync(file);
|
|
86
|
+
if (!present)
|
|
87
|
+
return { enabled, file, present, signedIn: false };
|
|
88
|
+
try {
|
|
89
|
+
const s = readStoredSignIn(file);
|
|
90
|
+
const signedIn = isSignedIn(s);
|
|
91
|
+
return { enabled, file, present, signedIn, ...(signedIn && s?.expiresAt ? { expiresAt: s.expiresAt } : {}) };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return { enabled, file, present, signedIn: false, problem: err instanceof Error ? err.message : String(err) };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether OpenAI ids would run on the sign-in here: it is enabled and signed
|
|
99
|
+
* in, no OPENAI_API_KEY is set, and OpenAI is on its own API (no
|
|
100
|
+
* OPENAI_PLATFORM other than direct, no OPENAI_BASE_URL proxy, which must
|
|
101
|
+
* never receive the token).
|
|
102
|
+
*/
|
|
103
|
+
export function chatGptSignInRoutesOpenAi(env = process.env) {
|
|
104
|
+
if (env.OPENAI_API_KEY?.trim())
|
|
105
|
+
return false;
|
|
106
|
+
const platform = env.OPENAI_PLATFORM?.trim().toLowerCase();
|
|
107
|
+
if (platform && platform !== 'direct')
|
|
108
|
+
return false;
|
|
109
|
+
if (env.OPENAI_BASE_URL?.trim())
|
|
110
|
+
return false;
|
|
111
|
+
const status = chatGptSignInStatus(env);
|
|
112
|
+
return status.enabled && status.signedIn;
|
|
113
|
+
}
|
|
114
|
+
// ── Local only ───────────────────────────────────────────────────────────────
|
|
115
|
+
let served;
|
|
116
|
+
/** Mark this process a served surface: the sign-in adapter refuses every call from here on. */
|
|
117
|
+
export function markServedSurface(surface) {
|
|
118
|
+
served ??= surface;
|
|
119
|
+
}
|
|
120
|
+
/** The served surface this process was marked as, if any. */
|
|
121
|
+
export function servedSurface() {
|
|
122
|
+
return served;
|
|
123
|
+
}
|
|
124
|
+
/** Test seam: forget the mark. */
|
|
125
|
+
export function resetServedSurfaceForTests() {
|
|
126
|
+
served = undefined;
|
|
127
|
+
}
|
|
128
|
+
/** The message a served surface stops with. */
|
|
129
|
+
export function servedSurfaceMessage(surface, env = process.env) {
|
|
130
|
+
return (`${surface} will not start: Sign in with ChatGPT is local only, and it is the OpenAI path here ` +
|
|
131
|
+
`(a sign-in is stored at ${signInFile(env)} and OPENAI_API_KEY is not set). ` +
|
|
132
|
+
`A served surface answers other people, and a ChatGPT plan pays only for its owner's own use on their own machine. ` +
|
|
133
|
+
`Set OPENAI_API_KEY (or Azure OpenAI), or set ${CHATGPT_SIGNIN_ENV}=off for this process, ` +
|
|
134
|
+
`or sign out with \`melchizedek-setup --chatgpt-signout\`.`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Called by every served surface at startup: marks the process served, and
|
|
138
|
+
* throws when the sign-in would carry OpenAI ids here. The A2A app, the A2A
|
|
139
|
+
* server bin and the worker call it; a new served surface calls it too.
|
|
140
|
+
*/
|
|
141
|
+
export function refuseChatGptSignInOnServedSurface(surface, env = process.env) {
|
|
142
|
+
markServedSurface(surface);
|
|
143
|
+
if (chatGptSignInRoutesOpenAi(env))
|
|
144
|
+
throw new Error(servedSurfaceMessage(surface, env));
|
|
145
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/chatgpt/store.ts — writing the Sign in with ChatGPT credential file and
|
|
3
|
+
* serializing its refreshes (ADR 0126).
|
|
4
|
+
*
|
|
5
|
+
* The file is the person's own: mode 600 in a mode-700 directory, outside any
|
|
6
|
+
* git work tree, replaced atomically (a new mode-600 file renamed over the
|
|
7
|
+
* old one). OpenAI's refresh tokens rotate, so two processes must not refresh
|
|
8
|
+
* the same session at once (developers.openai.com/siwc/token-sharing-open-source/
|
|
9
|
+
* profiles-and-sessions): a lock file beside the credential serializes them.
|
|
10
|
+
*/
|
|
11
|
+
import type { StoredSignIn } from './state.ts';
|
|
12
|
+
/** Write the credential file: mode 600, atomically, never inside a git work tree. */
|
|
13
|
+
export declare function writeStoredSignIn(file: string, value: StoredSignIn): void;
|
|
14
|
+
/** The stored sign-in with its tokens removed; the host id and the issued client id stay (OpenAI asks apps to reuse them). */
|
|
15
|
+
export declare function withoutTokens(s: StoredSignIn): StoredSignIn;
|
|
16
|
+
/** Remove the tokens from the file, keeping the registration. */
|
|
17
|
+
export declare function clearTokens(file: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Run `fn` holding the credential's lock file (created exclusively). A lock
|
|
20
|
+
* older than a minute is a crashed holder's and is taken over.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withSignInLock<T>(file: string, fn: () => Promise<T>, opts?: {
|
|
23
|
+
waitMs?: number;
|
|
24
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/chatgpt/store.ts — writing the Sign in with ChatGPT credential file and
|
|
3
|
+
* serializing its refreshes (ADR 0126).
|
|
4
|
+
*
|
|
5
|
+
* The file is the person's own: mode 600 in a mode-700 directory, outside any
|
|
6
|
+
* git work tree, replaced atomically (a new mode-600 file renamed over the
|
|
7
|
+
* old one). OpenAI's refresh tokens rotate, so two processes must not refresh
|
|
8
|
+
* the same session at once (developers.openai.com/siwc/token-sharing-open-source/
|
|
9
|
+
* profiles-and-sessions): a lock file beside the credential serializes them.
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, renameSync, rmSync, statSync, writeSync } from 'node:fs';
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
15
|
+
import { insideGitWorkTree, readStoredSignIn } from './state.js';
|
|
16
|
+
/** Write the credential file: mode 600, atomically, never inside a git work tree. */
|
|
17
|
+
export function writeStoredSignIn(file, value) {
|
|
18
|
+
const repo = insideGitWorkTree(file);
|
|
19
|
+
if (repo) {
|
|
20
|
+
throw new Error(`refusing to store a ChatGPT sign-in inside the git work tree ${repo}; set MELCHIZEDEK_CHATGPT_SIGNIN_FILE to a path outside it.`);
|
|
21
|
+
}
|
|
22
|
+
const dir = dirname(file);
|
|
23
|
+
if (!existsSync(dir))
|
|
24
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
25
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
26
|
+
const fd = openSync(tmp, 'wx', 0o600);
|
|
27
|
+
try {
|
|
28
|
+
writeSync(fd, `${JSON.stringify(value, null, 2)}\n`);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
closeSync(fd);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
if (process.platform !== 'win32')
|
|
35
|
+
chmodSync(tmp, 0o600);
|
|
36
|
+
renameSync(tmp, file);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
rmSync(tmp, { force: true });
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** The stored sign-in with its tokens removed; the host id and the issued client id stay (OpenAI asks apps to reuse them). */
|
|
44
|
+
export function withoutTokens(s) {
|
|
45
|
+
const { accessToken: _a, refreshToken: _r, expiresAt: _e, earliestRefreshAt: _n, scopes: _s, ...kept } = s;
|
|
46
|
+
return { ...kept, savedAt: new Date().toISOString() };
|
|
47
|
+
}
|
|
48
|
+
/** Remove the tokens from the file, keeping the registration. */
|
|
49
|
+
export function clearTokens(file) {
|
|
50
|
+
const s = readStoredSignIn(file);
|
|
51
|
+
if (s)
|
|
52
|
+
writeStoredSignIn(file, withoutTokens(s));
|
|
53
|
+
}
|
|
54
|
+
const LOCK_STALE_MS = 60_000;
|
|
55
|
+
const LOCK_WAIT_MS = 20_000;
|
|
56
|
+
/**
|
|
57
|
+
* Run `fn` holding the credential's lock file (created exclusively). A lock
|
|
58
|
+
* older than a minute is a crashed holder's and is taken over.
|
|
59
|
+
*/
|
|
60
|
+
export async function withSignInLock(file, fn, opts = {}) {
|
|
61
|
+
const lock = `${file}.lock`;
|
|
62
|
+
const dir = dirname(file);
|
|
63
|
+
if (!existsSync(dir))
|
|
64
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
65
|
+
const deadline = Date.now() + (opts.waitMs ?? LOCK_WAIT_MS);
|
|
66
|
+
for (;;) {
|
|
67
|
+
try {
|
|
68
|
+
closeSync(openSync(lock, 'wx', 0o600));
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err.code !== 'EEXIST')
|
|
73
|
+
throw err;
|
|
74
|
+
try {
|
|
75
|
+
if (Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS) {
|
|
76
|
+
rmSync(lock, { force: true });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (Date.now() > deadline)
|
|
84
|
+
throw new Error(`another process holds ${lock}; try again, or delete it if no melchizedek process is running.`);
|
|
85
|
+
await delay(100);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return await fn();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
rmSync(lock, { force: true });
|
|
93
|
+
}
|
|
94
|
+
}
|
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
|
}
|
|
@@ -186,21 +214,23 @@ export interface AgentSpec {
|
|
|
186
214
|
* lookup for its tool nodes. `name` and `description` name a nested one
|
|
187
215
|
* after its subagent entry (ADR 0098); `ref` is the file it came from.
|
|
188
216
|
*
|
|
189
|
-
* A nested workflow
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* by name there.
|
|
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.
|
|
193
220
|
*/
|
|
194
221
|
export declare function compileWorkflowSpec(config: SyndicateYamlConfig, opts?: CompileOptions, name?: string, description?: string, ref?: string, delegated?: boolean): Promise<WorkflowSpec>;
|
|
195
222
|
/** Every agent spec a workflow runs, its nested workflow nodes' agents included, for one adapter lookup (nativeAdapterFor). */
|
|
196
223
|
export declare function workflowAgentSpecs(spec: WorkflowSpec): AgentSpec[];
|
|
197
|
-
/** 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). */
|
|
198
225
|
export type EntrySpec = {
|
|
199
226
|
kind: 'agent';
|
|
200
227
|
spec: AgentSpec;
|
|
201
228
|
} | {
|
|
202
229
|
kind: 'workflow';
|
|
203
230
|
workflow: WorkflowSpec;
|
|
231
|
+
} | {
|
|
232
|
+
kind: 'dispatch';
|
|
233
|
+
dispatch: DispatchSpec;
|
|
204
234
|
};
|
|
205
235
|
/**
|
|
206
236
|
* A subagent entry as what it runs: an inline agent or a nested syndicate's
|
|
@@ -208,8 +238,9 @@ export type EntrySpec = {
|
|
|
208
238
|
* as its whole graph under the entry's name and description. A delegated
|
|
209
239
|
* subagent, a dispatch route and a workflow node all compile through here,
|
|
210
240
|
* so the graph a file describes is the graph that runs (ADR 0106).
|
|
211
|
-
*
|
|
212
|
-
*
|
|
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.
|
|
213
244
|
*/
|
|
214
245
|
export declare function compileEntrySpec(subCfg: SubagentYamlConfig, opts?: CompileOptions, delegated?: boolean): Promise<EntrySpec>;
|
|
215
246
|
/**
|
package/dist/lib/compile.js
CHANGED
|
@@ -145,10 +145,10 @@ export function examplesTool(examples) {
|
|
|
145
145
|
const tool = examplesInstructionTool(examples);
|
|
146
146
|
return tool ? [tool] : [];
|
|
147
147
|
}
|
|
148
|
-
async function resolveAgentTools(
|
|
149
|
-
const tools = [...resolveNamedTools(
|
|
148
|
+
async function resolveAgentTools(agent, opts) {
|
|
149
|
+
const tools = [...resolveNamedTools(agent.tools, opts.onUnknownTool), ...examplesTool(agent.examples)];
|
|
150
150
|
// OpenAPI operations become tools here, so require_approval can name them.
|
|
151
|
-
for (const entry of openapi ?? []) {
|
|
151
|
+
for (const entry of agent.openapi ?? []) {
|
|
152
152
|
const built = await buildOpenApiTools(entry);
|
|
153
153
|
opts.log?.(`openapi · ${relative(process.cwd(), entry.spec) || entry.spec}: ${built.map((t) => t.name).join(', ') || '(no operations)'}`);
|
|
154
154
|
for (const t of built) {
|
|
@@ -157,28 +157,43 @@ async function resolveAgentTools(toolNames, mcpServerUrl, opts, openapi, example
|
|
|
157
157
|
tools.push(t);
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
160
|
+
/** One server's tools, narrowed to the names the YAML allows (a server's list is its own to change). */
|
|
161
|
+
const offeredBy = async (label, url, allowedNames, oauth2, transport) => {
|
|
162
|
+
opts.log?.(`Loading MCP tools: ${label}`);
|
|
163
|
+
// An authorization_code server lists nothing at startup: its tools are the allowed names, each on its user's own connection (ADR 0112).
|
|
164
|
+
const offered = await createMcpTools(url, { ...(allowedNames ? { tools: allowedNames } : {}), ...(oauth2 ? { oauth2 } : {}), ...(transport ? { transport } : {}) });
|
|
165
|
+
if (!allowedNames)
|
|
166
|
+
return offered;
|
|
167
|
+
// A name the server does not offer is reported, and a gate on it fails the compile in gateTools.
|
|
168
|
+
const allowed = new Set(allowedNames);
|
|
169
|
+
const kept = offered.filter((t) => allowed.has(t.name));
|
|
170
|
+
const missing = [...allowed].filter((n) => !offered.some((t) => t.name === n));
|
|
171
|
+
if (missing.length)
|
|
172
|
+
opts.log?.(`MCP ${label} does not offer ${missing.map((n) => `'${n}'`).join(', ')}`);
|
|
173
|
+
const hidden = offered.length - kept.length;
|
|
174
|
+
if (hidden)
|
|
175
|
+
opts.log?.(`MCP ${label}: ${hidden} tool(s) not named for this agent are not exposed`);
|
|
176
|
+
return kept;
|
|
177
|
+
};
|
|
178
|
+
if (agent.mcp_server_url) {
|
|
179
|
+
// The single-server form: a name the agent already has keeps the agent's tool.
|
|
180
|
+
for (const mcpTool of await offeredBy(agent.mcp_server_url, agent.mcp_server_url, agent.mcp_tools, agent.mcp_auth?.oauth2, agent.mcp_transport)) {
|
|
178
181
|
if (!tools.some((t) => t.name === mcpTool.name))
|
|
179
182
|
tools.push(mcpTool);
|
|
180
183
|
}
|
|
181
184
|
}
|
|
185
|
+
// mcp_servers (ADR 0124): the loader refused a declared name twice; a
|
|
186
|
+
// server that answers with a name already taken is refused here too, so
|
|
187
|
+
// no server's tool can stand in for another's or the agent's own.
|
|
188
|
+
const servers = await Promise.all((agent.mcp_servers ?? []).map((server) => offeredBy(`${server.name} (${server.url})`, server.url, server.tools, server.auth?.oauth2, server.transport)));
|
|
189
|
+
for (const [i, offered] of servers.entries()) {
|
|
190
|
+
const server = agent.mcp_servers[i];
|
|
191
|
+
for (const mcpTool of offered) {
|
|
192
|
+
if (tools.some((t) => t?.name === mcpTool.name))
|
|
193
|
+
throw new Error(`mcp_servers ${server.name}: tool '${mcpTool.name}' collides with another of this agent's tools (ADR 0124)`);
|
|
194
|
+
tools.push(mcpTool);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
182
197
|
return tools;
|
|
183
198
|
}
|
|
184
199
|
/**
|
|
@@ -264,31 +279,44 @@ function nestedOptions(ref, opts) {
|
|
|
264
279
|
return { ...opts, [NESTING]: [...chain, ref] };
|
|
265
280
|
}
|
|
266
281
|
/**
|
|
267
|
-
* A nested `yaml_reference:` syndicate, loaded
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* route, which never runs nested, is refused;
|
|
274
|
-
* - a nested workflow's gates pause the walk, which reaches the caller
|
|
275
|
-
* only through a delegated call's open call (`delegated`): as a
|
|
276
|
-
* dispatch route or a workflow node, its pause cannot reach the turn
|
|
277
|
-
* yet, so they are refused there.
|
|
282
|
+
* A nested `yaml_reference:` syndicate, loaded. Every pause inside one
|
|
283
|
+
* reaches the turn wherever it runs (ADR 0110, ADR 0111, ADR 0119,
|
|
284
|
+
* ADR 0120): a nested delegate syndicate's through the open call, a nested
|
|
285
|
+
* workflow's through its walk, a nested dispatch syndicate's through its
|
|
286
|
+
* own turn, whose routes run as they run at the top. Nothing is refused
|
|
287
|
+
* here any more.
|
|
278
288
|
*/
|
|
279
|
-
function loadNestedSyndicate(ref, opts
|
|
289
|
+
function loadNestedSyndicate(ref, opts) {
|
|
280
290
|
opts.log?.(`Loading nested syndicate: ${ref}`);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
291
|
+
return (opts.loadNested ?? loadSyndicate)(ref);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* A nested dispatch syndicate as its entry runs it (ADR 0120). Its routes
|
|
295
|
+
* compile when its turn routes to one, as at the top, so a route that
|
|
296
|
+
* cannot compile fails that turn; the references below it are followed
|
|
297
|
+
* here (loaded, not compiled: no tool is resolved and no MCP server is
|
|
298
|
+
* reached), so a reference cycle or a depth past the limit is refused at
|
|
299
|
+
* load, by name, before any model call. A route may not carry the entry's
|
|
300
|
+
* name: the pause walk tells the nested syndicate from its routes by name.
|
|
301
|
+
*/
|
|
302
|
+
function compileDispatchEntry(subCfg, nested, nestedOpts) {
|
|
303
|
+
const ref = subCfg.yaml_reference;
|
|
304
|
+
for (const route of nested.subagents ?? []) {
|
|
305
|
+
if (route.name === subCfg.name) {
|
|
306
|
+
throw new Error(`${ref}: the route '${route.name}' has the name its caller gives the nested syndicate; rename the route or the entry.`);
|
|
286
307
|
}
|
|
287
308
|
}
|
|
288
|
-
|
|
289
|
-
|
|
309
|
+
followReferences(nested, nestedOpts);
|
|
310
|
+
return { name: subCfg.name, description: subCfg.description ?? '', config: nested, ref, compile: nestedOpts };
|
|
311
|
+
}
|
|
312
|
+
/** Loads every `yaml_reference` below `config`, depth first, through nestedOptions: a cycle or a chain past the limit throws by name. */
|
|
313
|
+
function followReferences(config, opts) {
|
|
314
|
+
for (const entry of config.subagents ?? []) {
|
|
315
|
+
if (!entry.yaml_reference || entry.a2a_agent_url)
|
|
316
|
+
continue;
|
|
317
|
+
const below = nestedOptions(entry.yaml_reference, opts);
|
|
318
|
+
followReferences(loadNestedSyndicate(entry.yaml_reference, below), below);
|
|
290
319
|
}
|
|
291
|
-
return nested;
|
|
292
320
|
}
|
|
293
321
|
/**
|
|
294
322
|
* The spec of a workflow syndicate: every agent through compileSubagentSpec
|
|
@@ -296,35 +324,34 @@ function loadNestedSyndicate(ref, opts, delegated = false) {
|
|
|
296
324
|
* lookup for its tool nodes. `name` and `description` name a nested one
|
|
297
325
|
* after its subagent entry (ADR 0098); `ref` is the file it came from.
|
|
298
326
|
*
|
|
299
|
-
* A nested workflow
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* by name there.
|
|
327
|
+
* A nested workflow's pauses (an `ask_user` node, a gate) reach the turn
|
|
328
|
+
* wherever it runs (ADR 0111, ADR 0119). `delegated` is accepted for the
|
|
329
|
+
* callers that pass it and no longer changes what compiles.
|
|
303
330
|
*/
|
|
304
331
|
export async function compileWorkflowSpec(config, opts = {}, name, description, ref, delegated = false) {
|
|
305
332
|
if (!isWorkflowSyndicate(config))
|
|
306
333
|
throw new Error(`${config.syndicate_name}: no workflow block`);
|
|
307
|
-
if (name && !delegated) {
|
|
308
|
-
const asking = Object.entries(config.workflow.nodes ?? {}).find(([, node]) => nodeKind(node) === 'ask_user');
|
|
309
|
-
if (asking) {
|
|
310
|
-
throw new Error(`${ref ?? config.syndicate_name}: the ask_user node '${asking[0]}' pauses for a person, which a workflow run as a dispatch route or a workflow node (${name}) cannot carry to the turn yet; delegate to the workflow as a subagent, run it as its own syndicate, or remove the node.`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
334
|
const workflowName = name || config.syndicate_name;
|
|
314
335
|
const agents = [];
|
|
315
336
|
const workflows = [];
|
|
337
|
+
const dispatches = [];
|
|
316
338
|
for (const yaml of [{ description: '', ...config.orchestrator }, ...(config.subagents ?? [])]) {
|
|
317
339
|
const entry = await compileEntrySpec(yaml, opts);
|
|
318
340
|
if (entry.kind === 'workflow')
|
|
319
341
|
workflows.push({ yaml, workflow: entry.workflow });
|
|
342
|
+
else if (entry.kind === 'dispatch')
|
|
343
|
+
dispatches.push({ yaml, dispatch: entry.dispatch });
|
|
320
344
|
else
|
|
321
345
|
agents.push({ yaml, spec: entry.spec });
|
|
322
346
|
}
|
|
323
|
-
// A map runs one agent per item; a nested graph is not an agent a map item can be (ADR 0106).
|
|
347
|
+
// A map runs one agent per item; a nested graph or a nested dispatch syndicate is not an agent a map item can be (ADR 0106, ADR 0120).
|
|
324
348
|
for (const [node, entry] of Object.entries(config.workflow.nodes ?? {})) {
|
|
325
|
-
|
|
349
|
+
if (nodeKind(entry) !== 'map')
|
|
350
|
+
continue;
|
|
351
|
+
const mapped = workflows.find((w) => w.yaml.name === entry.map) ?? dispatches.find((d) => d.yaml.name === entry.map);
|
|
326
352
|
if (mapped) {
|
|
327
|
-
|
|
353
|
+
const what = 'workflow' in mapped ? 'a workflow syndicate' : 'a dispatch syndicate';
|
|
354
|
+
throw new Error(`${ref ?? config.syndicate_name}: the map node '${node}' runs '${mapped.yaml.name}', ${what} (${mapped.yaml.yaml_reference}); a map runs one agent per item, so make it a node of its own.`);
|
|
328
355
|
}
|
|
329
356
|
}
|
|
330
357
|
return {
|
|
@@ -333,6 +360,7 @@ export async function compileWorkflowSpec(config, opts = {}, name, description,
|
|
|
333
360
|
config: { ...config, syndicate_name: workflowName },
|
|
334
361
|
agents,
|
|
335
362
|
workflows,
|
|
363
|
+
dispatches,
|
|
336
364
|
resolveTool: (tool) => resolveNamedTools([tool], opts.onUnknownTool)[0],
|
|
337
365
|
};
|
|
338
366
|
}
|
|
@@ -346,16 +374,20 @@ export function workflowAgentSpecs(spec) {
|
|
|
346
374
|
* as its whole graph under the entry's name and description. A delegated
|
|
347
375
|
* subagent, a dispatch route and a workflow node all compile through here,
|
|
348
376
|
* so the graph a file describes is the graph that runs (ADR 0106).
|
|
349
|
-
*
|
|
350
|
-
*
|
|
377
|
+
* A nested workflow pauses wherever it runs (ADR 0111, ADR 0119);
|
|
378
|
+
* `delegated` is accepted for the callers that pass it and no longer
|
|
379
|
+
* changes what compiles.
|
|
351
380
|
*/
|
|
352
381
|
export async function compileEntrySpec(subCfg, opts = {}, delegated = false) {
|
|
353
382
|
if (subCfg.yaml_reference && !subCfg.a2a_agent_url) {
|
|
354
383
|
const nestedOpts = nestedOptions(subCfg.yaml_reference, opts);
|
|
355
|
-
const nested = loadNestedSyndicate(subCfg.yaml_reference, nestedOpts
|
|
384
|
+
const nested = loadNestedSyndicate(subCfg.yaml_reference, nestedOpts);
|
|
356
385
|
if (isWorkflowSyndicate(nested)) {
|
|
357
386
|
return { kind: 'workflow', workflow: await compileWorkflowSpec(nested, nestedOpts, subCfg.name, subCfg.description, subCfg.yaml_reference, delegated) };
|
|
358
387
|
}
|
|
388
|
+
// A nested dispatch syndicate classifies and routes as at the top (ADR 0120), wherever it runs.
|
|
389
|
+
if (isDispatchSyndicate(nested))
|
|
390
|
+
return { kind: 'dispatch', dispatch: compileDispatchEntry(subCfg, nested, nestedOpts) };
|
|
359
391
|
return { kind: 'agent', spec: await compileSpec(nested, nestedOpts, subCfg.name, subCfg.description) };
|
|
360
392
|
}
|
|
361
393
|
return { kind: 'agent', spec: await compileSubagentSpec(subCfg, opts) };
|
|
@@ -389,11 +421,14 @@ export async function compileSubagentSpec(subCfg, opts = {}) {
|
|
|
389
421
|
if (isWorkflowSyndicate(nested)) {
|
|
390
422
|
throw new Error(`${subCfg.yaml_reference}: '${subCfg.name}' is a workflow syndicate, which runs as its whole graph, not as one agent; compile the entry with compileEntrySpec (ADR 0106).`);
|
|
391
423
|
}
|
|
424
|
+
if (isDispatchSyndicate(nested)) {
|
|
425
|
+
throw new Error(`${subCfg.yaml_reference}: '${subCfg.name}' is a dispatch syndicate, which runs as its own turn (its classifier, then a route), not as one agent; compile the entry with compileEntrySpec (ADR 0120).`);
|
|
426
|
+
}
|
|
392
427
|
return compileSpec(nested, nestedOpts, subCfg.name, subCfg.description);
|
|
393
428
|
}
|
|
394
429
|
// YAML v2 (ADR 0115): the engine form, for a config built in code (a loaded one already is).
|
|
395
430
|
const agent = toEngineAgent(subCfg);
|
|
396
|
-
const gated = gateTools(await resolveAgentTools(agent
|
|
431
|
+
const gated = gateTools(await resolveAgentTools(agent, opts), agent.require_approval, agent.name);
|
|
397
432
|
const model = resolveAgentModel(agent.model, opts);
|
|
398
433
|
// model_overrides for the provider the agent runs on, before the skills index is appended.
|
|
399
434
|
const { instruction, tools } = await withSkills(instructionFor(agent, model.modelId), gated, agent.skills, agent.name, opts);
|
|
@@ -427,14 +462,14 @@ export async function compileSpec(config, opts = {}, overrideName, overrideDescr
|
|
|
427
462
|
}
|
|
428
463
|
// A delegated subagent: a pause inside it, a nested workflow's included, reaches the caller (ADR 0110, ADR 0111).
|
|
429
464
|
const entry = await compileEntrySpec(subCfg, opts, true);
|
|
430
|
-
return entry.kind === '
|
|
465
|
+
return entry.kind === 'agent' ? { kind: 'agent', agent: entry.spec } : entry;
|
|
431
466
|
}));
|
|
432
467
|
const orchestrator = toEngineAgent(config.orchestrator); // YAML v2 (ADR 0115), for a config built in code
|
|
433
468
|
const name = overrideName || orchestrator.name;
|
|
434
469
|
// The orchestrator's own tools resolve as a subagent's do: registry
|
|
435
|
-
// names, OpenAPI operations, and the MCP
|
|
436
|
-
// mcp_tools, under its
|
|
437
|
-
const own = gateTools(await resolveAgentTools(orchestrator
|
|
470
|
+
// names, OpenAPI operations, and the MCP servers' tools (narrowed by
|
|
471
|
+
// mcp_tools or each mcp_servers entry's tools, under its grant), so a one-agent syndicate can reach an MCP server.
|
|
472
|
+
const own = gateTools(await resolveAgentTools(orchestrator, opts), orchestrator.require_approval, name);
|
|
438
473
|
const model = resolveAgentModel(orchestrator.model, opts);
|
|
439
474
|
// The skills toolset goes last, after the delegations and the agent's own tools; the
|
|
440
475
|
// skills index goes after the instruction with its model_overrides entry applied.
|
|
@@ -32,13 +32,14 @@
|
|
|
32
32
|
* caller's. ADK classes are told apart by ADK's own Symbol.for marks, so
|
|
33
33
|
* this file imports nothing from ADK.
|
|
34
34
|
*/
|
|
35
|
-
import type { AgentSpec, CompileOptions, WorkflowSpec } from './compile.ts';
|
|
35
|
+
import type { AgentSpec, CompileOptions, DispatchSpec, WorkflowSpec } from './compile.ts';
|
|
36
36
|
import type { SubagentYamlConfig, SyndicateYamlConfig } from './loadSyndicate.ts';
|
|
37
37
|
import type { ModelAdapter } from './models/contract.ts';
|
|
38
38
|
import type { WorkflowSubagent } from './runtime/native/delegate.ts';
|
|
39
39
|
import type { NativeAgent } from './runtime/native/request.ts';
|
|
40
40
|
export { UnsupportedOnRuntimeError, unsupportedOnNative } from './runtime/runtimeFlag.ts';
|
|
41
41
|
import type { WorkflowGraph } from './workflow/graph.ts';
|
|
42
|
+
import type { NestedRun } from './workflow/turn.ts';
|
|
42
43
|
/**
|
|
43
44
|
* The class name of an ADK model the runtime cannot run, else undefined:
|
|
44
45
|
* any ADK BaseLlm (ADR 0107; the ADK runtime that ran one left in 1.0.0).
|
|
@@ -67,8 +68,8 @@ export interface NativeWorkflow {
|
|
|
67
68
|
graph: WorkflowGraph;
|
|
68
69
|
agents: Map<string, NativeAgent>;
|
|
69
70
|
resolveTool: (name: string) => unknown;
|
|
70
|
-
/** The nodes that are a nested workflow syndicate, each compiled the same way,
|
|
71
|
-
workflows: Map<string, NativeWorkflow>;
|
|
71
|
+
/** The nodes that are a nested workflow syndicate, each compiled the same way (ADR 0106), or a nested dispatch syndicate, its own turn (ADR 0120), by YAML name. */
|
|
72
|
+
workflows: Map<string, NativeWorkflow | NestedRun>;
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
74
75
|
* A workflow spec for the walk: the graph, every agent compiled from its
|
|
@@ -82,6 +83,13 @@ export declare function compileNativeWorkflow(spec: WorkflowSpec): NativeWorkflo
|
|
|
82
83
|
* session lib/runtime/native/delegate.ts opened.
|
|
83
84
|
*/
|
|
84
85
|
export declare function workflowSubagentOf(workflow: NativeWorkflow): WorkflowSubagent;
|
|
86
|
+
/**
|
|
87
|
+
* A nested dispatch syndicate as the delegated subagent the native loop
|
|
88
|
+
* calls, or the node a walk runs (ADR 0120): one call runs it as its own
|
|
89
|
+
* turn (nestedDispatchWalk) on the child session lib/runtime/native/delegate.ts
|
|
90
|
+
* (or lib/workflow/turn.ts) opened.
|
|
91
|
+
*/
|
|
92
|
+
export declare function dispatchSubagentOf(dispatch: DispatchSpec): WorkflowSubagent & NestedRun;
|
|
85
93
|
/** One subagent entry (a dispatch route) for the native loop: compileSubagentSpec, then compileNative. */
|
|
86
94
|
export declare function compileNativeSubagent(subCfg: SubagentYamlConfig, opts?: CompileOptions): Promise<NativeAgent>;
|
|
87
95
|
/** What nativeAdapterFor reads from a spec: its model resolution, the other ids it calls, and its delegated subagents. */
|