melchizedek-agents 0.16.2 → 0.17.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 +17 -0
- package/AGENT_SETUP.md +2 -1
- package/CHANGELOG.md +142 -0
- package/DOCUMENTATION.md +237 -7
- package/QUICKSTART.md +2 -2
- package/README.md +125 -149
- package/config/agents/examples/harness.yaml +135 -0
- package/config/agents/examples/model_zoo.yaml +23 -9
- package/config/agents/examples/pipeline.yaml +120 -0
- package/config/agents/examples/specs/open-meteo-forecast.json +77 -0
- package/config/agents/examples/specs/open-meteo-geocoding.json +53 -0
- package/config/agents/examples/weather.yaml +59 -0
- package/config/agents/syndicateSchema.yaml +112 -1
- package/db/migrations/0011_erase_expired.sql +198 -0
- package/dist/lib/a2a/app.d.ts +7 -0
- package/dist/lib/a2a/app.js +7 -1
- package/dist/lib/a2a/executor.js +41 -0
- package/dist/lib/compile.d.ts +40 -1
- package/dist/lib/compile.js +132 -14
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +17 -0
- package/dist/lib/doctor.js +17 -4
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/loadSyndicate.d.ts +46 -0
- package/dist/lib/loadSyndicate.js +19 -3
- package/dist/lib/memory/erase.d.ts +5 -0
- package/dist/lib/memory/erase.js +5 -0
- package/dist/lib/models/capabilities.js +10 -0
- package/dist/lib/models/endpoints.d.ts +1 -1
- package/dist/lib/models/endpoints.js +4 -1
- package/dist/lib/models/gateway.d.ts +2 -2
- package/dist/lib/models/gateway.js +4 -4
- package/dist/lib/models/kimiLlm.d.ts +123 -0
- package/dist/lib/models/kimiLlm.js +171 -0
- package/dist/lib/models/providerMap.d.ts +2 -2
- package/dist/lib/models/providerMap.js +4 -1
- package/dist/lib/models/registry.d.ts +1 -1
- package/dist/lib/models/registry.js +11 -3
- package/dist/lib/net/redirects.d.ts +14 -0
- package/dist/lib/net/redirects.js +94 -0
- package/dist/lib/observability/tracer.d.ts +1 -1
- package/dist/lib/persistence/supabaseProvider.d.ts +2 -6
- package/dist/lib/persistence/supabaseProvider.js +2 -31
- package/dist/lib/runtime/questions.d.ts +55 -0
- package/dist/lib/runtime/questions.js +107 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +42 -4
- package/dist/lib/runtime/syndicateTurn.js +162 -16
- package/dist/lib/storage/postgres/index.d.ts +6 -0
- package/dist/lib/storage/postgres/index.js +11 -0
- package/dist/lib/storage/rlsStatus.d.ts +45 -0
- package/dist/lib/storage/rlsStatus.js +69 -0
- package/dist/lib/syndicateSchema.d.ts +169 -0
- package/dist/lib/syndicateSchema.js +327 -2
- package/dist/lib/toolRegistry.js +9 -0
- package/dist/lib/tools/openapiTools.d.ts +46 -0
- package/dist/lib/tools/openapiTools.js +195 -0
- package/dist/lib/tools/skillToolset.d.ts +135 -0
- package/dist/lib/tools/skillToolset.js +247 -0
- package/dist/lib/tools/urlContextTool.d.ts +29 -0
- package/dist/lib/tools/urlContextTool.js +39 -0
- package/dist/lib/workflow.d.ts +72 -0
- package/dist/lib/workflow.js +180 -0
- package/dist/lib/workflowConfig.d.ts +95 -0
- package/dist/lib/workflowConfig.js +93 -0
- package/dist/scripts/syndicate_chat.js +39 -5
- package/package.json +4 -1
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +8 -4
- package/skills/briefs/melchizedek-memory.md +1 -1
- package/skills/briefs/melchizedek-models.md +1 -1
- package/skills/briefs/melchizedek-scribe.md +4 -4
- package/skills/briefs/melchizedek-serve.md +1 -1
- package/skills/briefs/melchizedek.md +4 -1
- package/skills/melchizedek/SKILL.md +4 -1
- package/skills/melchizedek-author/SKILL.md +7 -3
- package/skills/melchizedek-memory/SKILL.md +1 -1
- package/skills/melchizedek-models/SKILL.md +1 -1
- package/skills/melchizedek-scribe/SKILL.md +3 -3
- package/skills/melchizedek-serve/SKILL.md +1 -1
- /package/skills/melchizedek-author/{templates → assets}/minimal.yaml +0 -0
- /package/skills/melchizedek-scribe/{templates → assets}/brief.md +0 -0
package/dist/lib/a2a/app.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ import type { BaseLlm, BaseMemoryService, BaseSessionService } from '@google/adk
|
|
|
31
31
|
import type { SyndicateYamlConfig } from '../loadSyndicate.ts';
|
|
32
32
|
import type { ProviderEndpoint, ProviderId } from '../models/registry.ts';
|
|
33
33
|
import type { TurnLock } from './turnLock.ts';
|
|
34
|
+
import type { RlsHardeningStatus } from '../storage/rlsStatus.ts';
|
|
34
35
|
import type { EraseCounts } from '../memory/erase.ts';
|
|
35
36
|
import type { Embedder, MemoryExtractor } from '../memory/providers.ts';
|
|
36
37
|
import type { A2AContext } from './executor.ts';
|
|
@@ -68,6 +69,12 @@ export interface A2AAppOptions {
|
|
|
68
69
|
* migrations this package ships (ADR 0021).
|
|
69
70
|
*/
|
|
70
71
|
schemaVersion?: () => Promise<number | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Whether db/hardening.sql is in force (postgresStorage supplies it).
|
|
74
|
+
* When given, the server checks it at boot like the supabase-js path:
|
|
75
|
+
* fatal with `requireHardenedDb`, a warning otherwise.
|
|
76
|
+
*/
|
|
77
|
+
rlsHardening?: () => Promise<RlsHardeningStatus>;
|
|
71
78
|
/**
|
|
72
79
|
* One turn at a time per conversation across instances (postgresStorage
|
|
73
80
|
* supplies an advisory lock). Default: a lock in this process.
|
package/dist/lib/a2a/app.js
CHANGED
|
@@ -187,11 +187,13 @@ export async function createA2AApp(options) {
|
|
|
187
187
|
let memoryService;
|
|
188
188
|
let erase;
|
|
189
189
|
let readSchemaVersion;
|
|
190
|
+
let checkHardening;
|
|
190
191
|
if (options.storage) {
|
|
191
192
|
durableSessions = options.storage.sessionService;
|
|
192
193
|
memoryService = options.storage.memoryService;
|
|
193
194
|
erase = options.storage.erase;
|
|
194
195
|
readSchemaVersion = options.storage.schemaVersion;
|
|
196
|
+
checkHardening = options.storage.rlsHardening;
|
|
195
197
|
}
|
|
196
198
|
else if (hasSupabaseCredentials()) {
|
|
197
199
|
// ADR 0021 item 6: the supabase-js path stays for a transition period.
|
|
@@ -209,7 +211,11 @@ export async function createA2AApp(options) {
|
|
|
209
211
|
memoryService = services.memoryService;
|
|
210
212
|
erase = (scopeKey, eraseOpts) => eraseScope(services.rpcClient, scopeKey, eraseOpts);
|
|
211
213
|
readSchemaVersion = services.schemaVersion;
|
|
212
|
-
|
|
214
|
+
checkHardening = services.checkRlsHardening;
|
|
215
|
+
}
|
|
216
|
+
// Both storage paths: refuse a public deployment whose tables an API key can read.
|
|
217
|
+
if (checkHardening) {
|
|
218
|
+
const rls = await checkHardening();
|
|
213
219
|
if (rls.applied) {
|
|
214
220
|
log(`✓ DB hardening verified — ${rls.detail}.`);
|
|
215
221
|
}
|
package/dist/lib/a2a/executor.js
CHANGED
|
@@ -19,6 +19,7 @@ import { turnLockKey } from './turnLock.js';
|
|
|
19
19
|
import { ingestTurnMemory, runSyndicateTurn } from '../runtime/syndicateTurn.js';
|
|
20
20
|
import { approvalResponsePart, describeApproval, pendingApproval } from '../runtime/approvals.js';
|
|
21
21
|
import { declaresApprovals } from '../compile.js';
|
|
22
|
+
import { describeInput } from '../runtime/syndicateTurn.js';
|
|
22
23
|
export const requestContextStorage = new AsyncLocalStorage();
|
|
23
24
|
/** The app name every A2A session and memory silo is stored under. */
|
|
24
25
|
export const A2A_APP_NAME = 'melchizedek-a2a';
|
|
@@ -72,6 +73,9 @@ export function describeFailedTurn(result) {
|
|
|
72
73
|
if (result.failedStage === 'dispatch' && result.route) {
|
|
73
74
|
return `Error: [${error.code}] ${result.route.route} failed to answer — ${describeTurnError(error)}`;
|
|
74
75
|
}
|
|
76
|
+
if (result.failedStage === 'workflow') {
|
|
77
|
+
return `Error: [${error.code}] The workflow failed — ${describeTurnError(error)}`;
|
|
78
|
+
}
|
|
75
79
|
return `Error: [${error.code}] The agent run failed — ${describeTurnError(error)}`;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
@@ -209,6 +213,37 @@ function publishWorking(eventBus, taskId, contextId, text) {
|
|
|
209
213
|
metadata: undefined,
|
|
210
214
|
}));
|
|
211
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* A workflow paused on an `ask_user` node (lib/workflow.ts): the task ends
|
|
218
|
+
* input-required with the question, and a data part carrying it. The
|
|
219
|
+
* answer is simply the next message on the conversation; its text becomes
|
|
220
|
+
* the node's output and the graph resumes where it waited.
|
|
221
|
+
*/
|
|
222
|
+
function publishInputRequest(eventBus, taskId, contextId, input) {
|
|
223
|
+
const message = statusMessage(taskId, contextId, `Input needed: ${describeInput(input)}`);
|
|
224
|
+
message.parts.push({
|
|
225
|
+
content: {
|
|
226
|
+
$case: 'data',
|
|
227
|
+
value: {
|
|
228
|
+
type: 'input_request',
|
|
229
|
+
interrupt_id: input.id,
|
|
230
|
+
node: input.node,
|
|
231
|
+
message: input.message,
|
|
232
|
+
...(input.payload !== undefined ? { payload: input.payload } : {}),
|
|
233
|
+
...(input.schema !== undefined ? { schema: input.schema } : {}),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
metadata: undefined,
|
|
237
|
+
filename: '',
|
|
238
|
+
mediaType: 'application/json',
|
|
239
|
+
});
|
|
240
|
+
eventBus.publish(AgentEvent.statusUpdate({
|
|
241
|
+
taskId,
|
|
242
|
+
contextId,
|
|
243
|
+
status: { state: TaskState.TASK_STATE_INPUT_REQUIRED, message, timestamp: new Date().toISOString() },
|
|
244
|
+
metadata: undefined,
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
212
247
|
/**
|
|
213
248
|
* The turn paused on a gated tool call (ADR 0028): the task ends
|
|
214
249
|
* input-required, saying what is to be approved, with a data part a client
|
|
@@ -497,6 +532,12 @@ export class SyndicateExecutor {
|
|
|
497
532
|
await report(ctx, 'input-required', 'approval', u);
|
|
498
533
|
return;
|
|
499
534
|
}
|
|
535
|
+
if (result.status === 'input-required' && result.input) {
|
|
536
|
+
log(`⏸ Task ${short} waiting for input from ${result.input.node} after ${spent}`);
|
|
537
|
+
publishInputRequest(eventBus, taskId, contextId, result.input);
|
|
538
|
+
await report(ctx, 'input-required', 'input', u);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
500
541
|
if (result.status === 'failed') {
|
|
501
542
|
warn(`✗ Task ${short} failed [${result.error?.code}] after ${spent}`);
|
|
502
543
|
publishFinal(eventBus, taskId, contextId, 'failed', describeFailedTurn(result));
|
package/dist/lib/compile.d.ts
CHANGED
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
* Contract and rationale: lib/dispatch.ts.
|
|
29
29
|
*/
|
|
30
30
|
import { FunctionTool, LlmAgent } from '@google/adk';
|
|
31
|
+
import type { BaseTool } from '@google/adk';
|
|
31
32
|
import type { BaseLlm } from '@google/adk';
|
|
32
33
|
import type { SubagentYamlConfig, SyndicateYamlConfig } from './loadSyndicate.ts';
|
|
34
|
+
import type { SkillsConfig } from './tools/skillToolset.ts';
|
|
33
35
|
export interface CompileOptions {
|
|
34
36
|
/**
|
|
35
37
|
* Turns the YAML `model` string into what LlmAgent receives. Default:
|
|
@@ -48,7 +50,23 @@ export interface CompileOptions {
|
|
|
48
50
|
onUnknownTool?: (name: string) => void;
|
|
49
51
|
/** Progress/diagnostic line sink (nested loads, MCP discovery). */
|
|
50
52
|
log?: (message: string) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Node settings for an agent compiled as a workflow node (lib/workflow.ts):
|
|
55
|
+
* ADK's `retryConfig` and `timeout`, which an LlmAgent takes only at
|
|
56
|
+
* construction. Called with the agent's name; undefined means none.
|
|
57
|
+
*/
|
|
58
|
+
nodeConfig?: (agentName: string) => Record<string, unknown> | undefined;
|
|
51
59
|
}
|
|
60
|
+
/** An agent's `context:` block: compact a long conversation into a summary (ADR 0033). */
|
|
61
|
+
export interface ContextConfig {
|
|
62
|
+
/** Compact when the last request's prompt passed this many tokens. */
|
|
63
|
+
compact_after_tokens: number;
|
|
64
|
+
/** Events kept verbatim after the summary. Default 6. */
|
|
65
|
+
keep_recent_events?: number;
|
|
66
|
+
/** The model that writes the summary. Default: the agent's own. */
|
|
67
|
+
summary_model?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare const DEFAULT_KEEP_RECENT_EVENTS = 6;
|
|
52
70
|
/**
|
|
53
71
|
* A copy of a FunctionTool that runs only after a person approves the call
|
|
54
72
|
* (ADR 0028). ADK's own gate does the work: the call raises an
|
|
@@ -57,8 +75,29 @@ export interface CompileOptions {
|
|
|
57
75
|
* ungated for every other agent that lists the same tool.
|
|
58
76
|
*/
|
|
59
77
|
export declare function requireApprovalOn(tool: FunctionTool): FunctionTool;
|
|
60
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* A copy of an OpenAPI tool (lib/tools/openapiTools.ts) that runs only after
|
|
80
|
+
* a person approves the call: the same confirmation interrupt FunctionTool's
|
|
81
|
+
* own gate raises, so the turn pauses and resumes exactly as ADR 0028 says.
|
|
82
|
+
*/
|
|
83
|
+
export declare function requireApprovalOnBaseTool<T extends BaseTool>(tool: T): T;
|
|
84
|
+
/** True when any agent of a syndicate may pause for approval: a gated tool, or skill scripts. */
|
|
61
85
|
export declare function declaresApprovals(config: SyndicateYamlConfig): boolean;
|
|
86
|
+
/** An agent's `examples:` entry: one exchange the model should imitate. */
|
|
87
|
+
export interface ExampleConfig {
|
|
88
|
+
input: string;
|
|
89
|
+
output: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* ADK's ExampleTool from YAML pairs: never called by the model, it adds the
|
|
93
|
+
* exchanges to every request's instruction as few-shot examples.
|
|
94
|
+
*/
|
|
95
|
+
export declare function examplesTool(examples: ExampleConfig[] | undefined): unknown[];
|
|
96
|
+
/** True when a turn running this agent directly may pause for a person (ADR 0028). */
|
|
97
|
+
export declare function agentGates(agent: {
|
|
98
|
+
require_approval?: string[];
|
|
99
|
+
skills?: SkillsConfig;
|
|
100
|
+
} | undefined): boolean;
|
|
62
101
|
/**
|
|
63
102
|
* Builds ONE runnable agent from a subagent entry. A `yaml_reference` entry
|
|
64
103
|
* compiles the nested syndicate's whole graph under this entry's name and
|
package/dist/lib/compile.js
CHANGED
|
@@ -27,13 +27,16 @@
|
|
|
27
27
|
* dispatches to is identical to the one DELEGATE would have wrapped.
|
|
28
28
|
* Contract and rationale: lib/dispatch.ts.
|
|
29
29
|
*/
|
|
30
|
-
import { AgentTool, FunctionTool, LlmAgent } from '@google/adk';
|
|
30
|
+
import { AgentTool, BuiltInCodeExecutor, ExampleTool, FunctionTool, LLMRegistry, LlmAgent, LlmSummarizer, TokenBasedContextCompactor } from '@google/adk';
|
|
31
|
+
import { relative } from 'node:path';
|
|
31
32
|
import { isDispatchSyndicate } from './dispatch.js';
|
|
32
33
|
import { loadSyndicate, nestedLoader } from './loadSyndicate.js';
|
|
33
34
|
import { resolveTools as resolveNamedTools } from './toolRegistry.js';
|
|
34
35
|
import { createMcpTools } from './tools/mcpToolFactory.js';
|
|
35
36
|
import { capabilitySummary, describeCapabilities } from './models/capabilities.js';
|
|
36
37
|
import { remoteAgentTool } from './a2a/remoteAgent.js';
|
|
38
|
+
import { buildSkillHarness } from './tools/skillToolset.js';
|
|
39
|
+
import { buildOpenApiTools, isOpenApiTool, namesTool } from './tools/openapiTools.js';
|
|
37
40
|
/** generateContentConfig as every entrypoint has always sent it to ADK. */
|
|
38
41
|
function withServerSideToolInvocations(generateContentConfig) {
|
|
39
42
|
const cfg = (generateContentConfig ?? {});
|
|
@@ -68,6 +71,36 @@ function passthroughFields(cfg) {
|
|
|
68
71
|
out.disallowTransferToPeers = cfg.disallowTransferToPeers;
|
|
69
72
|
return out;
|
|
70
73
|
}
|
|
74
|
+
export const DEFAULT_KEEP_RECENT_EVENTS = 6;
|
|
75
|
+
/**
|
|
76
|
+
* The LlmAgent fields the engine builds from YAML rather than passing
|
|
77
|
+
* through (ADR 0033): `code_execution: gemini` (Gemini's server-side
|
|
78
|
+
* sandbox runs the model's Python; nothing runs on this host), `context:`
|
|
79
|
+
* (ADK's token-based compactor with an LLM summarizer, so a long
|
|
80
|
+
* conversation is summarized instead of overflowing the window), and
|
|
81
|
+
* `mode: task` (the agent works until it calls finish_task; on a workflow
|
|
82
|
+
* node, its arguments become the node's output).
|
|
83
|
+
*/
|
|
84
|
+
function executionFields(cfg, opts) {
|
|
85
|
+
const out = {};
|
|
86
|
+
if (cfg.code_execution === 'gemini')
|
|
87
|
+
out.codeExecutor = new BuiltInCodeExecutor();
|
|
88
|
+
if (cfg.mode)
|
|
89
|
+
out.mode = cfg.mode;
|
|
90
|
+
if (cfg.context) {
|
|
91
|
+
const resolve = opts.resolveModel ?? ((m) => m);
|
|
92
|
+
const summaryModel = resolve(cfg.context.summary_model ?? cfg.model);
|
|
93
|
+
const llm = typeof summaryModel === 'string' || !summaryModel ? LLMRegistry.newLlm(String(summaryModel ?? cfg.model)) : summaryModel;
|
|
94
|
+
out.contextCompactors = [
|
|
95
|
+
new TokenBasedContextCompactor({
|
|
96
|
+
tokenThreshold: cfg.context.compact_after_tokens,
|
|
97
|
+
eventRetentionSize: cfg.context.keep_recent_events ?? DEFAULT_KEEP_RECENT_EVENTS,
|
|
98
|
+
summarizer: new LlmSummarizer({ llm }),
|
|
99
|
+
}),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
71
104
|
/**
|
|
72
105
|
* Says, once per compiled agent, what its resolved path cannot honour — a
|
|
73
106
|
* dropped server-side tool, a gateway stand-in, or no route at all. Quiet
|
|
@@ -95,17 +128,46 @@ export function requireApprovalOn(tool) {
|
|
|
95
128
|
Object.defineProperty(gated, 'requireConfirmation', { value: true, enumerable: false });
|
|
96
129
|
return gated;
|
|
97
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* A copy of an OpenAPI tool (lib/tools/openapiTools.ts) that runs only after
|
|
133
|
+
* a person approves the call: the same confirmation interrupt FunctionTool's
|
|
134
|
+
* own gate raises, so the turn pauses and resumes exactly as ADR 0028 says.
|
|
135
|
+
*/
|
|
136
|
+
export function requireApprovalOnBaseTool(tool) {
|
|
137
|
+
const gated = Object.create(tool);
|
|
138
|
+
Object.defineProperty(gated, 'runAsync', {
|
|
139
|
+
value: async (request) => {
|
|
140
|
+
const ctx = request.toolContext;
|
|
141
|
+
if (!ctx.toolConfirmation) {
|
|
142
|
+
ctx.requestConfirmation({ hint: `Approval is required before ${tool.name} runs.`, payload: request.args });
|
|
143
|
+
ctx.actions.skipSummarization = true;
|
|
144
|
+
return { error: 'This call requires approval, please approve or reject.' };
|
|
145
|
+
}
|
|
146
|
+
if (!ctx.toolConfirmation.confirmed)
|
|
147
|
+
return { error: 'This call was rejected.' };
|
|
148
|
+
return tool.runAsync(request);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return gated;
|
|
152
|
+
}
|
|
98
153
|
function gateTools(tools, names, agentName) {
|
|
99
154
|
if (!names?.length)
|
|
100
155
|
return tools;
|
|
101
156
|
const wanted = new Set(names);
|
|
102
157
|
const out = tools.map((t) => {
|
|
103
|
-
const
|
|
104
|
-
|
|
158
|
+
const tool = t;
|
|
159
|
+
const name = tool?.name;
|
|
160
|
+
if (!name)
|
|
161
|
+
return t;
|
|
162
|
+
// An OpenAPI operation may be named as the YAML named it (its operationId).
|
|
163
|
+
const match = [...wanted].find((w) => w === name || (isOpenApiTool(t) && namesTool(w, { name, operation: tool.operation })));
|
|
164
|
+
if (!match)
|
|
105
165
|
return t;
|
|
106
|
-
wanted.delete(
|
|
166
|
+
wanted.delete(match);
|
|
167
|
+
if (isOpenApiTool(t))
|
|
168
|
+
return requireApprovalOnBaseTool(t);
|
|
107
169
|
if (!(t instanceof FunctionTool)) {
|
|
108
|
-
throw new Error(`${agentName}: '${name}' cannot require approval — only function tools from the registry can be gated (ADR 0028).`);
|
|
170
|
+
throw new Error(`${agentName}: '${name}' cannot require approval — only function tools from the registry and OpenAPI operations can be gated (ADR 0028).`);
|
|
109
171
|
}
|
|
110
172
|
return requireApprovalOn(t);
|
|
111
173
|
});
|
|
@@ -116,12 +178,33 @@ function gateTools(tools, names, agentName) {
|
|
|
116
178
|
}
|
|
117
179
|
return out;
|
|
118
180
|
}
|
|
119
|
-
/** True when any agent of a syndicate
|
|
181
|
+
/** True when any agent of a syndicate may pause for approval: a gated tool, or skill scripts. */
|
|
120
182
|
export function declaresApprovals(config) {
|
|
121
|
-
return
|
|
183
|
+
return agentGates(config.orchestrator) || (config.subagents ?? []).some((s) => agentGates(s));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* ADK's ExampleTool from YAML pairs: never called by the model, it adds the
|
|
187
|
+
* exchanges to every request's instruction as few-shot examples.
|
|
188
|
+
*/
|
|
189
|
+
export function examplesTool(examples) {
|
|
190
|
+
if (!examples?.length)
|
|
191
|
+
return [];
|
|
192
|
+
return [
|
|
193
|
+
new ExampleTool(examples.map((e) => ({ input: { role: 'user', parts: [{ text: e.input }] }, output: [{ role: 'model', parts: [{ text: e.output }] }] }))),
|
|
194
|
+
];
|
|
122
195
|
}
|
|
123
|
-
async function resolveAgentTools(toolNames, mcpServerUrl, opts) {
|
|
124
|
-
const tools = resolveNamedTools(toolNames, opts.onUnknownTool);
|
|
196
|
+
async function resolveAgentTools(toolNames, mcpServerUrl, opts, openapi, examples) {
|
|
197
|
+
const tools = [...resolveNamedTools(toolNames, opts.onUnknownTool), ...examplesTool(examples)];
|
|
198
|
+
// OpenAPI operations become tools here, so require_approval can name them.
|
|
199
|
+
for (const entry of openapi ?? []) {
|
|
200
|
+
const built = await buildOpenApiTools(entry);
|
|
201
|
+
opts.log?.(`openapi · ${relative(process.cwd(), entry.spec) || entry.spec}: ${built.map((t) => t.name).join(', ') || '(no operations)'}`);
|
|
202
|
+
for (const t of built) {
|
|
203
|
+
if (tools.some((existing) => existing?.name === t.name))
|
|
204
|
+
throw new Error(`openapi ${entry.spec}: tool '${t.name}' collides with another tool; set a prefix`);
|
|
205
|
+
tools.push(t);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
125
208
|
if (mcpServerUrl) {
|
|
126
209
|
opts.log?.(`Loading MCP tools: ${mcpServerUrl}`);
|
|
127
210
|
const mcpTools = await createMcpTools(mcpServerUrl);
|
|
@@ -132,6 +215,28 @@ async function resolveAgentTools(toolNames, mcpServerUrl, opts) {
|
|
|
132
215
|
}
|
|
133
216
|
return tools;
|
|
134
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* An agent's `skills:` block becomes one toolset among its tools and one
|
|
220
|
+
* block appended to its instruction (the frontmatter index, always in view).
|
|
221
|
+
* The registry tools a skill may unlock (`skills.tools`) are resolved here,
|
|
222
|
+
* by name, and handed to the toolset; they reach the model only after a
|
|
223
|
+
* skill naming them is loaded (lib/tools/skillToolset.ts).
|
|
224
|
+
*/
|
|
225
|
+
async function withSkills(instruction, tools, skills, agentName, opts) {
|
|
226
|
+
if (!skills)
|
|
227
|
+
return { instruction, tools };
|
|
228
|
+
const unlockable = resolveNamedTools(skills.tools, opts.onUnknownTool);
|
|
229
|
+
const harness = await buildSkillHarness(skills, unlockable);
|
|
230
|
+
const count = Object.keys(harness.skills).length;
|
|
231
|
+
opts.log?.(`skills · ${agentName}: ${count} skill${count === 1 ? '' : 's'} from ${skills.dir}${skills.scripts === 'local' ? ' · scripts run after approval' : ''}`);
|
|
232
|
+
for (const problem of harness.problems)
|
|
233
|
+
opts.log?.(`⚠ skills · ${skills.dir}: not loaded — ${problem}`);
|
|
234
|
+
return { instruction: `${instruction.trimEnd()}\n\n${harness.instruction}`, tools: [...tools, harness.toolset] };
|
|
235
|
+
}
|
|
236
|
+
/** True when a turn running this agent directly may pause for a person (ADR 0028). */
|
|
237
|
+
export function agentGates(agent) {
|
|
238
|
+
return !!agent?.require_approval?.length || agent?.skills?.scripts === 'local';
|
|
239
|
+
}
|
|
135
240
|
/**
|
|
136
241
|
* Builds ONE runnable agent from a subagent entry. A `yaml_reference` entry
|
|
137
242
|
* compiles the nested syndicate's whole graph under this entry's name and
|
|
@@ -154,18 +259,21 @@ export async function compileSubagent(subCfg, opts = {}) {
|
|
|
154
259
|
}
|
|
155
260
|
return compileGraph(nested, opts, subCfg.name, subCfg.description);
|
|
156
261
|
}
|
|
157
|
-
const
|
|
262
|
+
const gated = gateTools(await resolveAgentTools(subCfg.tools, subCfg.mcp_server_url, opts, subCfg.openapi, subCfg.examples), subCfg.require_approval, subCfg.name);
|
|
263
|
+
const { instruction, tools } = await withSkills(subCfg.instruction ?? '', gated, subCfg.skills, subCfg.name, opts);
|
|
158
264
|
const resolveModel = opts.resolveModel ?? ((m) => m);
|
|
159
265
|
logCapabilities(opts, subCfg.name, subCfg.model, subCfg.tools);
|
|
160
266
|
return new LlmAgent({
|
|
161
267
|
name: subCfg.name,
|
|
162
268
|
description: subCfg.description,
|
|
163
269
|
model: resolveModel(subCfg.model),
|
|
164
|
-
instruction
|
|
270
|
+
instruction,
|
|
165
271
|
tools: tools.length > 0 ? tools : undefined,
|
|
166
272
|
outputSchema: subCfg.outputSchema,
|
|
167
273
|
generateContentConfig: withServerSideToolInvocations(subCfg.generateContentConfig),
|
|
168
274
|
...passthroughFields(subCfg),
|
|
275
|
+
...executionFields(subCfg, opts),
|
|
276
|
+
...(opts.nodeConfig?.(subCfg.name) ?? {}),
|
|
169
277
|
});
|
|
170
278
|
}
|
|
171
279
|
/**
|
|
@@ -178,6 +286,14 @@ export async function compileGraph(config, opts = {}, overrideName, overrideDesc
|
|
|
178
286
|
// A registry definition carries its nested syndicates (ADR 0018 item 6).
|
|
179
287
|
if (config.bundled_references && !opts.loadNested)
|
|
180
288
|
opts = { ...opts, loadNested: nestedLoader(config) };
|
|
289
|
+
// A graph has no orchestrator-with-tools shape to build: its agents are
|
|
290
|
+
// nodes (lib/workflow.ts). Nested as a yaml_reference, only its
|
|
291
|
+
// orchestrator runs, since ADK cannot yet make a Workflow a subagent.
|
|
292
|
+
if (config.workflow && !overrideName) {
|
|
293
|
+
throw new Error(`${config.syndicate_name}: a workflow syndicate is compiled with compileWorkflow (lib/workflow.ts), not compileGraph`);
|
|
294
|
+
}
|
|
295
|
+
if (config.workflow)
|
|
296
|
+
opts.log?.(`${config.syndicate_name}: nested as a subagent, so only its orchestrator runs (a Workflow cannot be a subagent yet)`);
|
|
181
297
|
const compiledTools = isDispatchSyndicate(config)
|
|
182
298
|
? []
|
|
183
299
|
: await Promise.all((config.subagents ?? []).map(async (subCfg) => {
|
|
@@ -190,17 +306,19 @@ export async function compileGraph(config, opts = {}, overrideName, overrideDesc
|
|
|
190
306
|
// Orchestrator tools are registry names only — no entrypoint has ever
|
|
191
307
|
// attached an MCP server to an orchestrator, and this compiler preserves
|
|
192
308
|
// that exactly rather than widening the contract in passing.
|
|
193
|
-
compiledTools.push(...gateTools(await resolveAgentTools(config.orchestrator.tools, undefined, opts), config.orchestrator.require_approval, overrideName || config.orchestrator.name));
|
|
309
|
+
compiledTools.push(...gateTools(await resolveAgentTools(config.orchestrator.tools, undefined, opts, config.orchestrator.openapi, config.orchestrator.examples), config.orchestrator.require_approval, overrideName || config.orchestrator.name));
|
|
310
|
+
const { instruction, tools: orchestratorTools } = await withSkills(config.orchestrator.instruction, compiledTools, config.orchestrator.skills, overrideName || config.orchestrator.name, opts);
|
|
194
311
|
const resolveModel = opts.resolveModel ?? ((m) => m);
|
|
195
312
|
logCapabilities(opts, overrideName || config.orchestrator.name, config.orchestrator.model, config.orchestrator.tools);
|
|
196
313
|
return new LlmAgent({
|
|
197
314
|
name: overrideName || config.orchestrator.name,
|
|
198
315
|
description: overrideDescription || config.orchestrator.description,
|
|
199
316
|
model: resolveModel(config.orchestrator.model),
|
|
200
|
-
instruction
|
|
201
|
-
tools:
|
|
317
|
+
instruction,
|
|
318
|
+
tools: orchestratorTools.length > 0 ? orchestratorTools : undefined,
|
|
202
319
|
outputSchema: config.orchestrator.outputSchema,
|
|
203
320
|
generateContentConfig: withServerSideToolInvocations(config.orchestrator.generateContentConfig),
|
|
204
321
|
...passthroughFields(config.orchestrator),
|
|
322
|
+
...executionFields(config.orchestrator, opts),
|
|
205
323
|
});
|
|
206
324
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -59,6 +59,23 @@ export declare const DEFAULT_GROK_MODEL = "grok-4.7";
|
|
|
59
59
|
* accept the param and never receive it (lib/models/grokLlm.ts).
|
|
60
60
|
*/
|
|
61
61
|
export declare const DEFAULT_GROK_REASONING_EFFORT = "medium";
|
|
62
|
+
/**
|
|
63
|
+
* Default Moonshot model used when a kimi-* model is requested but no
|
|
64
|
+
* specific identifier is provided. Requires MOONSHOT_API_KEY
|
|
65
|
+
* (lib/models/kimiLlm.ts). kimi-k3 is the flagship, priced like a mid-tier
|
|
66
|
+
* closed model ($3 / $15, Claude Sonnet 4.6's price; Sonnet 5.5 is cheaper);
|
|
67
|
+
* kimi-k2.6 is the general tier at a quarter of that (the family table and
|
|
68
|
+
* the cost note are in the adapter's header).
|
|
69
|
+
*/
|
|
70
|
+
export declare const DEFAULT_KIMI_MODEL = "kimi-k3";
|
|
71
|
+
/**
|
|
72
|
+
* Reasoning effort sent with kimi-k3 requests: 'low' | 'high' | 'max'
|
|
73
|
+
* (Moonshot's own default is 'max'; K3 always thinks and has no off switch).
|
|
74
|
+
* Pinned to HIGH — one step below max, which is the benchmark setting and
|
|
75
|
+
* the slowest; an agent sets generateContentConfig.reasoningEffort to
|
|
76
|
+
* override. Source: platform.moonshot.ai › Guides › Reasoning effort.
|
|
77
|
+
*/
|
|
78
|
+
export declare const DEFAULT_KIMI_REASONING_EFFORT = "high";
|
|
62
79
|
/**
|
|
63
80
|
* Default open-weight model, served locally by Ollama (lib/models/ollamaLlm.ts).
|
|
64
81
|
* qwen3:8b is the smallest pulled model that supports tool calling — the
|
package/dist/lib/config.js
CHANGED
|
@@ -60,6 +60,23 @@ export const DEFAULT_GROK_MODEL = 'grok-4.7';
|
|
|
60
60
|
* accept the param and never receive it (lib/models/grokLlm.ts).
|
|
61
61
|
*/
|
|
62
62
|
export const DEFAULT_GROK_REASONING_EFFORT = 'medium';
|
|
63
|
+
/**
|
|
64
|
+
* Default Moonshot model used when a kimi-* model is requested but no
|
|
65
|
+
* specific identifier is provided. Requires MOONSHOT_API_KEY
|
|
66
|
+
* (lib/models/kimiLlm.ts). kimi-k3 is the flagship, priced like a mid-tier
|
|
67
|
+
* closed model ($3 / $15, Claude Sonnet 4.6's price; Sonnet 5.5 is cheaper);
|
|
68
|
+
* kimi-k2.6 is the general tier at a quarter of that (the family table and
|
|
69
|
+
* the cost note are in the adapter's header).
|
|
70
|
+
*/
|
|
71
|
+
export const DEFAULT_KIMI_MODEL = 'kimi-k3';
|
|
72
|
+
/**
|
|
73
|
+
* Reasoning effort sent with kimi-k3 requests: 'low' | 'high' | 'max'
|
|
74
|
+
* (Moonshot's own default is 'max'; K3 always thinks and has no off switch).
|
|
75
|
+
* Pinned to HIGH — one step below max, which is the benchmark setting and
|
|
76
|
+
* the slowest; an agent sets generateContentConfig.reasoningEffort to
|
|
77
|
+
* override. Source: platform.moonshot.ai › Guides › Reasoning effort.
|
|
78
|
+
*/
|
|
79
|
+
export const DEFAULT_KIMI_REASONING_EFFORT = 'high';
|
|
63
80
|
/**
|
|
64
81
|
* Default open-weight model, served locally by Ollama (lib/models/ollamaLlm.ts).
|
|
65
82
|
* qwen3:8b is the smallest pulled model that supports tool calling — the
|
package/dist/lib/doctor.js
CHANGED
|
@@ -84,6 +84,7 @@ export const CONSOLE_URLS = {
|
|
|
84
84
|
anthropic: 'https://console.anthropic.com',
|
|
85
85
|
openai: 'https://platform.openai.com/api-keys',
|
|
86
86
|
xai: 'https://console.x.ai',
|
|
87
|
+
moonshot: 'https://platform.moonshot.ai',
|
|
87
88
|
ollama: 'https://ollama.com',
|
|
88
89
|
};
|
|
89
90
|
// ── Discovery ────────────────────────────────────────────────────────────────
|
|
@@ -162,8 +163,9 @@ function walk(config, prefix, opts, rows) {
|
|
|
162
163
|
const orchModel = orch?.model;
|
|
163
164
|
if (orch && orchModel) {
|
|
164
165
|
// In DELEGATE mode the orchestrator calls its subagents as tools; under
|
|
165
|
-
// plan-dispatch it is a tool-less classifier and code runs the route
|
|
166
|
-
|
|
166
|
+
// plan-dispatch it is a tool-less classifier and code runs the route; in
|
|
167
|
+
// a workflow it is one node of the graph.
|
|
168
|
+
const delegates = !config.dispatch && !config.workflow && (config.subagents ?? []).length > 0;
|
|
167
169
|
rows.push({
|
|
168
170
|
agent: prefix + orch.name,
|
|
169
171
|
role: 'orchestrator',
|
|
@@ -390,6 +392,15 @@ const ANSI = {
|
|
|
390
392
|
red: '\x1b[31m',
|
|
391
393
|
cyan: '\x1b[36m',
|
|
392
394
|
};
|
|
395
|
+
/** One id per provider, in that provider's own prefix, for the providers line. */
|
|
396
|
+
const PROBE_ID = {
|
|
397
|
+
gemini: 'gemini-x',
|
|
398
|
+
anthropic: 'claude-x',
|
|
399
|
+
openai: 'gpt-x',
|
|
400
|
+
xai: 'grok-x',
|
|
401
|
+
moonshot: 'kimi-x',
|
|
402
|
+
ollama: 'ollama/x',
|
|
403
|
+
};
|
|
393
404
|
function pad(s, n) {
|
|
394
405
|
return s.length >= n ? s.slice(0, n - 1) + '…' : s.padEnd(n);
|
|
395
406
|
}
|
|
@@ -405,9 +416,11 @@ export function renderDoctor(result, opts = {}) {
|
|
|
405
416
|
: `${c.green}${text}${c.reset}`;
|
|
406
417
|
lines.push(`${c.bold}melchizedek doctor${c.reset} ${c.dim}· ${result.agentsDir}${c.reset}`);
|
|
407
418
|
lines.push('');
|
|
408
|
-
// Provider line: what is funded, and how.
|
|
419
|
+
// Provider line: what is funded, and how. Each provider is probed with an
|
|
420
|
+
// id of its own prefix, so the line names ITS key (a fallthrough once put
|
|
421
|
+
// xAI's key beside Moonshot).
|
|
409
422
|
const providerBits = Object.keys(PROVIDERS).map((p) => {
|
|
410
|
-
const r = describeCapabilities(p
|
|
423
|
+
const r = describeCapabilities(PROBE_ID[p]);
|
|
411
424
|
const mark = !r.funded ? `${c.red}✗${c.reset}` : r.transport === 'gateway' ? `${c.yellow}◇${c.reset}` : `${c.green}✓${c.reset}`;
|
|
412
425
|
const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` : p === 'ollama' ? `${c.dim}local${c.reset}` : `${c.dim}${r.platform ?? 'direct'}${c.reset}`;
|
|
413
426
|
return `${mark} ${PROVIDERS[p].label} ${how}`;
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type { PendingApproval } from './runtime/approvals.ts';
|
|
|
19
19
|
export type { DrainedRun, MessagePart, TurnUsage, RouteDecision, SyndicateTurnOptions, SyndicateTurnResult, TraceOptions, TurnEvents, TurnStage, } from './runtime/syndicateTurn.ts';
|
|
20
20
|
export { compileGraph, compileSubagent } from './compile.ts';
|
|
21
21
|
export type { CompileOptions } from './compile.ts';
|
|
22
|
+
export { compileWorkflow, isWorkflowSyndicate, describeInput } from './workflow.ts';
|
|
23
|
+
export type { WorkflowConfig, WorkflowNodeYaml, EdgeElement, PendingInput, CompiledWorkflow } from './workflow.ts';
|
|
22
24
|
export { createA2AApp, compileAgentCard, currentRequestContext } from './a2a/app.ts';
|
|
23
25
|
export type { A2AApp, A2AAppOptions, RequestIdentity } from './a2a/app.ts';
|
|
24
26
|
export type { A2AContext } from './a2a/executor.ts';
|
package/dist/lib/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { loadSyndicate, loadSyndicateFromRegistry, parseCliBindings, validateReg
|
|
|
17
17
|
export { runSyndicateTurn, ingestTurnMemory } from './runtime/syndicateTurn.js';
|
|
18
18
|
export { approvalResponsePart, pendingApproval, APPROVAL_REQUEST } from './runtime/approvals.js';
|
|
19
19
|
export { compileGraph, compileSubagent } from './compile.js';
|
|
20
|
+
export { compileWorkflow, isWorkflowSyndicate, describeInput } from './workflow.js';
|
|
20
21
|
// ── Serve it over A2A, or call a remote A2A agent ───────────────────────────
|
|
21
22
|
export { createA2AApp, compileAgentCard, currentRequestContext } from './a2a/app.js';
|
|
22
23
|
export { callerTokens, firstOf, hashCallerToken, jwtIdentity, parseCallers, scopeSegment, sharedSecret, trustedHeader, } from './a2a/identity.js';
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { DispatchConfig } from './dispatch.ts';
|
|
2
2
|
export type { DispatchConfig } from './dispatch.ts';
|
|
3
|
+
import type { SkillsConfig } from './tools/skillToolset.ts';
|
|
4
|
+
import type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
5
|
+
import type { ContextConfig, ExampleConfig } from './compile.ts';
|
|
6
|
+
export type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
7
|
+
export type { SkillsConfig } from './tools/skillToolset.ts';
|
|
8
|
+
import type { WorkflowConfig } from './workflowConfig.ts';
|
|
9
|
+
export type { WorkflowConfig } from './workflowConfig.ts';
|
|
3
10
|
export interface ThinkingConfig {
|
|
4
11
|
/** Token budget for internal reasoning. 0 = off, -1 = dynamic. */
|
|
5
12
|
thinkingBudget?: number;
|
|
@@ -81,6 +88,26 @@ export interface AgentYamlConfig {
|
|
|
81
88
|
*/
|
|
82
89
|
outputSchema?: Record<string, unknown>;
|
|
83
90
|
mcp_server_url?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Agent Skills (SKILL.md directories) this agent holds the way a coding
|
|
93
|
+
* harness does: the index in its instruction, a skill read on demand, its
|
|
94
|
+
* scripts run only after approval. lib/tools/skillToolset.ts.
|
|
95
|
+
*/
|
|
96
|
+
skills?: SkillsConfig;
|
|
97
|
+
/**
|
|
98
|
+
* HTTP APIs this agent may call, each from an OpenAPI 3 spec file: one tool
|
|
99
|
+
* per operation, the GET operations unless `operations` names others, auth
|
|
100
|
+
* from environment variables. lib/tools/openapiTools.ts.
|
|
101
|
+
*/
|
|
102
|
+
openapi?: OpenApiConfig[];
|
|
103
|
+
/** `gemini`: the model's Python runs in Gemini's server-side sandbox (ADR 0033). Gemini models only. */
|
|
104
|
+
code_execution?: 'gemini';
|
|
105
|
+
/** Compact a long conversation into a summary past a token threshold (ADR 0033). Delegate orchestrator only. */
|
|
106
|
+
context?: ContextConfig;
|
|
107
|
+
/** `task`: the agent works until it calls finish_task; its arguments are the node's output. Workflow nodes only. */
|
|
108
|
+
mode?: 'task';
|
|
109
|
+
/** Few-shot exchanges added to every request's instruction (ADK's ExampleTool). */
|
|
110
|
+
examples?: ExampleConfig[];
|
|
84
111
|
orchestration?: {
|
|
85
112
|
role?: 'primary' | 'sub-agent';
|
|
86
113
|
delegates?: string[];
|
|
@@ -121,6 +148,24 @@ export interface SyndicateYamlConfig {
|
|
|
121
148
|
* See lib/dispatch.ts for the full contract.
|
|
122
149
|
*/
|
|
123
150
|
dispatch?: DispatchConfig;
|
|
151
|
+
/**
|
|
152
|
+
* Self-correction (ADK's reflect-and-retry plugins), on by default:
|
|
153
|
+
* `model_errors` retries a model reply ADK marks malformed (default 2),
|
|
154
|
+
* `tool_errors` caps a tool's retries after it throws, with structured
|
|
155
|
+
* guidance to the model (default 3). 0 turns either off. ADR 0034.
|
|
156
|
+
*/
|
|
157
|
+
retries?: {
|
|
158
|
+
model_errors?: number;
|
|
159
|
+
tool_errors?: number;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Opts this syndicate into a WORKFLOW: a graph whose nodes are its agents
|
|
163
|
+
* (plus join, map, tool and ask_user nodes) and whose edges say what runs
|
|
164
|
+
* after what and on which route. The orchestrator is a node like any
|
|
165
|
+
* other; nothing delegates. Cannot be combined with `dispatch`.
|
|
166
|
+
* Contract: lib/workflow.ts.
|
|
167
|
+
*/
|
|
168
|
+
workflow?: WorkflowConfig;
|
|
124
169
|
/** Optional default variable definitions for {{token}} interpolation. */
|
|
125
170
|
variables?: VariableMap;
|
|
126
171
|
/** Defines the persistence and semantic memory layer for the syndicate. */
|
|
@@ -237,6 +282,7 @@ export declare function loadSyndicate(filename: string, options?: LoadSyndicateO
|
|
|
237
282
|
export declare function readSyndicateFile(filename: string, options?: Pick<LoadSyndicateOptions, 'agentsDir' | 'shippedFallback'>): {
|
|
238
283
|
raw: unknown;
|
|
239
284
|
label: string;
|
|
285
|
+
filePath: string;
|
|
240
286
|
};
|
|
241
287
|
/**
|
|
242
288
|
* Every nested `yaml_reference:` a syndicate reaches, read raw from the agents
|
|
@@ -228,8 +228,24 @@ export function collectGuards(config, loadNested = nestedLoader(config), seen =
|
|
|
228
228
|
}
|
|
229
229
|
export function loadSyndicate(filename, options = {}) {
|
|
230
230
|
const { bindings = {}, overrides } = options;
|
|
231
|
-
const { raw, label } = readSyndicateFile(filename, options);
|
|
232
|
-
return resolveAndValidate(raw, label, { bindings, overrides });
|
|
231
|
+
const { raw, label, filePath } = readSyndicateFile(filename, options);
|
|
232
|
+
return anchorSpecPaths(resolveAndValidate(raw, label, { bindings, overrides }), path.dirname(filePath));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* An `openapi:` spec path is written relative to the syndicate file, so a
|
|
236
|
+
* file copied with its specs keeps working from any working directory. A
|
|
237
|
+
* definition from the registry has no file and resolves against the cwd.
|
|
238
|
+
*/
|
|
239
|
+
function anchorSpecPaths(config, dir) {
|
|
240
|
+
const anchor = (agent) => {
|
|
241
|
+
for (const entry of agent?.openapi ?? [])
|
|
242
|
+
if (!path.isAbsolute(entry.spec))
|
|
243
|
+
entry.spec = path.resolve(dir, entry.spec);
|
|
244
|
+
};
|
|
245
|
+
anchor(config.orchestrator);
|
|
246
|
+
for (const sub of config.subagents ?? [])
|
|
247
|
+
anchor(sub);
|
|
248
|
+
return config;
|
|
233
249
|
}
|
|
234
250
|
/**
|
|
235
251
|
* Read one syndicate file, unparsed values and all, from the agents root (the
|
|
@@ -266,7 +282,7 @@ export function readSyndicateFile(filename, options = {}) {
|
|
|
266
282
|
// is under it), so a multi-file nested load says WHICH file is wrong.
|
|
267
283
|
const rel = path.relative(process.cwd(), filePath);
|
|
268
284
|
const label = rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? rel : filePath;
|
|
269
|
-
return { raw: parse(fileContent), label };
|
|
285
|
+
return { raw: parse(fileContent), label, filePath };
|
|
270
286
|
}
|
|
271
287
|
/**
|
|
272
288
|
* Every nested `yaml_reference:` a syndicate reaches, read raw from the agents
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* markers, and (without a namespace) its task tools' list and jobs
|
|
9
9
|
* (migration 0010), in one transaction. Returns what each store lost.
|
|
10
10
|
*
|
|
11
|
+
* With a namespace, ledger turns and A2A tasks are kept only when their
|
|
12
|
+
* conversation is still live in another namespace (migration 0011): they do
|
|
13
|
+
* not record the namespace, and a conversation whose session expired must
|
|
14
|
+
* not survive an erase.
|
|
15
|
+
*
|
|
11
16
|
* Throws on any failure: a deletion request that silently did nothing is
|
|
12
17
|
* worse than an error the caller can surface and retry.
|
|
13
18
|
*/
|