melchizedek-agents 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.env.example +10 -0
  2. package/AGENT_SETUP.md +3 -1
  3. package/CHANGELOG.md +232 -0
  4. package/DOCUMENTATION.md +91 -16
  5. package/ONBOARDING.md +339 -0
  6. package/QUICKSTART.md +15 -2
  7. package/README.md +5 -2
  8. package/config/agents/examples/council.yaml +1 -1
  9. package/config/agents/examples/critic.yaml +8 -8
  10. package/config/agents/examples/image_production.yaml +4 -3
  11. package/config/agents/syndicate.schema.json +65 -7
  12. package/config/agents/syndicateSchema.yaml +18 -6
  13. package/config/agents/templates/intake_extractor.yaml +3 -2
  14. package/config/agents/templates/systems_operator.yaml +5 -2
  15. package/dist/lib/a2a/app.d.ts +12 -0
  16. package/dist/lib/a2a/app.js +22 -11
  17. package/dist/lib/a2a/executor.js +14 -6
  18. package/dist/lib/a2a/oauthSetup.d.ts +16 -0
  19. package/dist/lib/a2a/oauthSetup.js +57 -3
  20. package/dist/lib/agentDialect.d.ts +18 -8
  21. package/dist/lib/agentDialect.js +36 -19
  22. package/dist/lib/compile.d.ts +38 -7
  23. package/dist/lib/compile.js +57 -37
  24. package/dist/lib/compileNative.d.ts +11 -3
  25. package/dist/lib/compileNative.js +18 -1
  26. package/dist/lib/doctor.d.ts +76 -0
  27. package/dist/lib/doctor.js +159 -7
  28. package/dist/lib/loadSyndicate.d.ts +9 -0
  29. package/dist/lib/models/capabilities.d.ts +9 -0
  30. package/dist/lib/models/capabilities.js +24 -0
  31. package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
  32. package/dist/lib/models/chatCompletionsAdapter.js +19 -5
  33. package/dist/lib/models/claudeAdapter.js +4 -0
  34. package/dist/lib/models/contract.d.ts +5 -2
  35. package/dist/lib/models/gateway.d.ts +2 -1
  36. package/dist/lib/models/gateway.js +4 -2
  37. package/dist/lib/models/gatewayAdapter.d.ts +7 -1
  38. package/dist/lib/models/gatewayAdapter.js +8 -0
  39. package/dist/lib/models/geminiAdapter.js +8 -2
  40. package/dist/lib/models/genaiMapping.d.ts +8 -4
  41. package/dist/lib/models/genaiMapping.js +23 -9
  42. package/dist/lib/models/gptAdapter.d.ts +4 -2
  43. package/dist/lib/models/gptAdapter.js +11 -4
  44. package/dist/lib/models/grokAdapter.d.ts +3 -1
  45. package/dist/lib/models/grokAdapter.js +11 -3
  46. package/dist/lib/models/kimiAdapter.d.ts +2 -1
  47. package/dist/lib/models/kimiAdapter.js +3 -3
  48. package/dist/lib/models/reasoning.d.ts +30 -2
  49. package/dist/lib/models/reasoning.js +56 -5
  50. package/dist/lib/onboarding.d.ts +134 -0
  51. package/dist/lib/onboarding.js +539 -0
  52. package/dist/lib/runtime/approvals.d.ts +2 -0
  53. package/dist/lib/runtime/approvals.js +4 -3
  54. package/dist/lib/runtime/credentials.d.ts +6 -0
  55. package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
  56. package/dist/lib/runtime/native/agentLoop.js +2 -0
  57. package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
  58. package/dist/lib/runtime/native/checkpoint.js +13 -19
  59. package/dist/lib/runtime/native/delegate.d.ts +82 -1
  60. package/dist/lib/runtime/native/delegate.js +157 -2
  61. package/dist/lib/runtime/native/interrupts.d.ts +88 -5
  62. package/dist/lib/runtime/native/interrupts.js +220 -30
  63. package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
  64. package/dist/lib/runtime/syndicateTurn.js +295 -62
  65. package/dist/lib/runtime/turnControl.d.ts +41 -0
  66. package/dist/lib/session/transcript.d.ts +9 -0
  67. package/dist/lib/session/transcript.js +20 -1
  68. package/dist/lib/skills.d.ts +18 -0
  69. package/dist/lib/skills.js +73 -1
  70. package/dist/lib/storage/postgres/index.d.ts +6 -1
  71. package/dist/lib/storage/postgres/index.js +1 -1
  72. package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
  73. package/dist/lib/storage/postgres/taskQueue.js +10 -3
  74. package/dist/lib/syndicateSchema.d.ts +38 -1
  75. package/dist/lib/syndicateSchema.js +25 -18
  76. package/dist/lib/tools/auth.d.ts +13 -1
  77. package/dist/lib/tools/auth.js +10 -2
  78. package/dist/lib/tools/credentialHosts.d.ts +88 -0
  79. package/dist/lib/tools/credentialHosts.js +165 -0
  80. package/dist/lib/tools/credentialUses.d.ts +92 -0
  81. package/dist/lib/tools/credentialUses.js +112 -0
  82. package/dist/lib/tools/mcpToolFactory.js +1 -1
  83. package/dist/lib/tools/oauthConsent.js +8 -1
  84. package/dist/lib/tools/oauthHosts.d.ts +2 -0
  85. package/dist/lib/tools/oauthHosts.js +2 -2
  86. package/dist/lib/tools/oauthTools.d.ts +10 -1
  87. package/dist/lib/tools/oauthTools.js +29 -3
  88. package/dist/lib/tools/openapiTools.d.ts +7 -0
  89. package/dist/lib/tools/openapiTools.js +49 -3
  90. package/dist/lib/tools/taskTools.d.ts +32 -2
  91. package/dist/lib/tools/taskTools.js +93 -33
  92. package/dist/lib/workflow/turn.d.ts +22 -3
  93. package/dist/lib/workflow/turn.js +151 -31
  94. package/dist/lib/workflow.d.ts +10 -7
  95. package/dist/lib/workflow.js +10 -7
  96. package/dist/scripts/a2a_server.d.ts +2 -2
  97. package/dist/scripts/a2a_server.js +11 -2
  98. package/dist/scripts/assistant_worker.d.ts +2 -0
  99. package/dist/scripts/assistant_worker.js +13 -4
  100. package/dist/scripts/doctor.d.ts +4 -2
  101. package/dist/scripts/doctor.js +6 -4
  102. package/dist/scripts/setup.d.ts +23 -0
  103. package/dist/scripts/setup.js +154 -0
  104. package/dist/scripts/skills_install.d.ts +3 -1
  105. package/dist/scripts/skills_install.js +17 -2
  106. package/dist/scripts/yaml_codemod.d.ts +12 -2
  107. package/dist/scripts/yaml_codemod.js +115 -33
  108. package/package.json +4 -1
  109. package/skills/README.md +11 -2
  110. package/skills/briefs/_shared.md +1 -1
  111. package/skills/briefs/melchizedek-models.md +4 -2
  112. package/skills/briefs/skills-readme.md +3 -1
  113. package/skills/melchizedek-models/SKILL.md +6 -2
  114. package/skills/melchizedek-onboard/SKILL.md +49 -0
  115. package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
  116. package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
  117. package/skills/melchizedek-onboard-local/SKILL.md +41 -0
  118. package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
@@ -264,31 +264,44 @@ function nestedOptions(ref, opts) {
264
264
  return { ...opts, [NESTING]: [...chain, ref] };
265
265
  }
266
266
  /**
267
- * A nested `yaml_reference:` syndicate, loaded, with the approval gates that
268
- * cannot pause where it runs refused by name (ADR 0110, ADR 0111):
269
- * - a nested delegate syndicate's gates pause the turn through the open
270
- * call, wherever it runs;
271
- * - a nested dispatch syndicate runs its classifier alone (compileSpec
272
- * lists no route on it), so its orchestrator may gate, and a gate on a
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.
267
+ * A nested `yaml_reference:` syndicate, loaded. Every pause inside one
268
+ * reaches the turn wherever it runs (ADR 0110, ADR 0111, ADR 0119,
269
+ * ADR 0120): a nested delegate syndicate's through the open call, a nested
270
+ * workflow's through its walk, a nested dispatch syndicate's through its
271
+ * own turn, whose routes run as they run at the top. Nothing is refused
272
+ * here any more.
278
273
  */
279
- function loadNestedSyndicate(ref, opts, delegated = false) {
274
+ function loadNestedSyndicate(ref, opts) {
280
275
  opts.log?.(`Loading nested syndicate: ${ref}`);
281
- const nested = (opts.loadNested ?? loadSyndicate)(ref);
282
- if (isDispatchSyndicate(nested)) {
283
- const gated = (nested.subagents ?? []).find((s) => agentGates(s));
284
- if (gated) {
285
- throw new Error(`${ref}: approval gates (require_approval, or skill scripts) on the route '${gated.name}' never run: a nested dispatch syndicate runs its classifier alone. Gate the classifier, or run the syndicate as its own.`);
276
+ return (opts.loadNested ?? loadSyndicate)(ref);
277
+ }
278
+ /**
279
+ * A nested dispatch syndicate as its entry runs it (ADR 0120). Its routes
280
+ * compile when its turn routes to one, as at the top, so a route that
281
+ * cannot compile fails that turn; the references below it are followed
282
+ * here (loaded, not compiled: no tool is resolved and no MCP server is
283
+ * reached), so a reference cycle or a depth past the limit is refused at
284
+ * load, by name, before any model call. A route may not carry the entry's
285
+ * name: the pause walk tells the nested syndicate from its routes by name.
286
+ */
287
+ function compileDispatchEntry(subCfg, nested, nestedOpts) {
288
+ const ref = subCfg.yaml_reference;
289
+ for (const route of nested.subagents ?? []) {
290
+ if (route.name === subCfg.name) {
291
+ throw new Error(`${ref}: the route '${route.name}' has the name its caller gives the nested syndicate; rename the route or the entry.`);
286
292
  }
287
293
  }
288
- else if (isWorkflowSyndicate(nested) && !delegated && declaresApprovals(nested)) {
289
- throw new Error(`${ref}: approval gates (require_approval, or skill scripts) in a workflow run as a dispatch route or a workflow node cannot pause the turn yet; delegate to the workflow as a subagent, or run it as its own syndicate.`);
294
+ followReferences(nested, nestedOpts);
295
+ return { name: subCfg.name, description: subCfg.description ?? '', config: nested, ref, compile: nestedOpts };
296
+ }
297
+ /** Loads every `yaml_reference` below `config`, depth first, through nestedOptions: a cycle or a chain past the limit throws by name. */
298
+ function followReferences(config, opts) {
299
+ for (const entry of config.subagents ?? []) {
300
+ if (!entry.yaml_reference || entry.a2a_agent_url)
301
+ continue;
302
+ const below = nestedOptions(entry.yaml_reference, opts);
303
+ followReferences(loadNestedSyndicate(entry.yaml_reference, below), below);
290
304
  }
291
- return nested;
292
305
  }
293
306
  /**
294
307
  * The spec of a workflow syndicate: every agent through compileSubagentSpec
@@ -296,35 +309,34 @@ function loadNestedSyndicate(ref, opts, delegated = false) {
296
309
  * lookup for its tool nodes. `name` and `description` name a nested one
297
310
  * after its subagent entry (ADR 0098); `ref` is the file it came from.
298
311
  *
299
- * A nested workflow delegated to as a subagent (`delegated`) pauses through
300
- * its caller's open call (ADR 0111). Run as a dispatch route or a workflow
301
- * node, its pause cannot reach the turn yet: an `ask_user` node is refused
302
- * by name there.
312
+ * A nested workflow's pauses (an `ask_user` node, a gate) reach the turn
313
+ * wherever it runs (ADR 0111, ADR 0119). `delegated` is accepted for the
314
+ * callers that pass it and no longer changes what compiles.
303
315
  */
304
316
  export async function compileWorkflowSpec(config, opts = {}, name, description, ref, delegated = false) {
305
317
  if (!isWorkflowSyndicate(config))
306
318
  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
319
  const workflowName = name || config.syndicate_name;
314
320
  const agents = [];
315
321
  const workflows = [];
322
+ const dispatches = [];
316
323
  for (const yaml of [{ description: '', ...config.orchestrator }, ...(config.subagents ?? [])]) {
317
324
  const entry = await compileEntrySpec(yaml, opts);
318
325
  if (entry.kind === 'workflow')
319
326
  workflows.push({ yaml, workflow: entry.workflow });
327
+ else if (entry.kind === 'dispatch')
328
+ dispatches.push({ yaml, dispatch: entry.dispatch });
320
329
  else
321
330
  agents.push({ yaml, spec: entry.spec });
322
331
  }
323
- // A map runs one agent per item; a nested graph is not an agent a map item can be (ADR 0106).
332
+ // 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
333
  for (const [node, entry] of Object.entries(config.workflow.nodes ?? {})) {
325
- const mapped = nodeKind(entry) === 'map' ? workflows.find((w) => w.yaml.name === entry.map) : undefined;
334
+ if (nodeKind(entry) !== 'map')
335
+ continue;
336
+ const mapped = workflows.find((w) => w.yaml.name === entry.map) ?? dispatches.find((d) => d.yaml.name === entry.map);
326
337
  if (mapped) {
327
- throw new Error(`${ref ?? config.syndicate_name}: the map node '${node}' runs '${mapped.yaml.name}', a workflow syndicate (${mapped.yaml.yaml_reference}); a map runs one agent per item, so make the workflow a node of its own.`);
338
+ const what = 'workflow' in mapped ? 'a workflow syndicate' : 'a dispatch syndicate';
339
+ 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
340
  }
329
341
  }
330
342
  return {
@@ -333,6 +345,7 @@ export async function compileWorkflowSpec(config, opts = {}, name, description,
333
345
  config: { ...config, syndicate_name: workflowName },
334
346
  agents,
335
347
  workflows,
348
+ dispatches,
336
349
  resolveTool: (tool) => resolveNamedTools([tool], opts.onUnknownTool)[0],
337
350
  };
338
351
  }
@@ -346,16 +359,20 @@ export function workflowAgentSpecs(spec) {
346
359
  * as its whole graph under the entry's name and description. A delegated
347
360
  * subagent, a dispatch route and a workflow node all compile through here,
348
361
  * so the graph a file describes is the graph that runs (ADR 0106).
349
- * `delegated` marks a delegated subagent's entry, whose nested workflow may
350
- * pause (ADR 0111).
362
+ * A nested workflow pauses wherever it runs (ADR 0111, ADR 0119);
363
+ * `delegated` is accepted for the callers that pass it and no longer
364
+ * changes what compiles.
351
365
  */
352
366
  export async function compileEntrySpec(subCfg, opts = {}, delegated = false) {
353
367
  if (subCfg.yaml_reference && !subCfg.a2a_agent_url) {
354
368
  const nestedOpts = nestedOptions(subCfg.yaml_reference, opts);
355
- const nested = loadNestedSyndicate(subCfg.yaml_reference, nestedOpts, delegated);
369
+ const nested = loadNestedSyndicate(subCfg.yaml_reference, nestedOpts);
356
370
  if (isWorkflowSyndicate(nested)) {
357
371
  return { kind: 'workflow', workflow: await compileWorkflowSpec(nested, nestedOpts, subCfg.name, subCfg.description, subCfg.yaml_reference, delegated) };
358
372
  }
373
+ // A nested dispatch syndicate classifies and routes as at the top (ADR 0120), wherever it runs.
374
+ if (isDispatchSyndicate(nested))
375
+ return { kind: 'dispatch', dispatch: compileDispatchEntry(subCfg, nested, nestedOpts) };
359
376
  return { kind: 'agent', spec: await compileSpec(nested, nestedOpts, subCfg.name, subCfg.description) };
360
377
  }
361
378
  return { kind: 'agent', spec: await compileSubagentSpec(subCfg, opts) };
@@ -389,6 +406,9 @@ export async function compileSubagentSpec(subCfg, opts = {}) {
389
406
  if (isWorkflowSyndicate(nested)) {
390
407
  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
408
  }
409
+ if (isDispatchSyndicate(nested)) {
410
+ 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).`);
411
+ }
392
412
  return compileSpec(nested, nestedOpts, subCfg.name, subCfg.description);
393
413
  }
394
414
  // YAML v2 (ADR 0115): the engine form, for a config built in code (a loaded one already is).
@@ -427,7 +447,7 @@ export async function compileSpec(config, opts = {}, overrideName, overrideDescr
427
447
  }
428
448
  // A delegated subagent: a pause inside it, a nested workflow's included, reaches the caller (ADR 0110, ADR 0111).
429
449
  const entry = await compileEntrySpec(subCfg, opts, true);
430
- return entry.kind === 'workflow' ? entry : { kind: 'agent', agent: entry.spec };
450
+ return entry.kind === 'agent' ? { kind: 'agent', agent: entry.spec } : entry;
431
451
  }));
432
452
  const orchestrator = toEngineAgent(config.orchestrator); // YAML v2 (ADR 0115), for a config built in code
433
453
  const name = overrideName || orchestrator.name;
@@ -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, by YAML name (ADR 0106). */
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. */
@@ -35,7 +35,7 @@
35
35
  import { remoteAgentOwnTool } from './a2a/remoteAgent.js';
36
36
  import { compileSpec, compileSubagentSpec, workflowAgentSpecs } from './compile.js';
37
37
  import { resolveAdapter } from './models/registry.js';
38
- import { subagentTool, workflowSubagentTool } from './runtime/native/delegate.js';
38
+ import { nestedDispatchWalk, subagentTool, workflowSubagentTool } from './runtime/native/delegate.js';
39
39
  import { unsupportedOnNative } from './runtime/runtimeFlag.js';
40
40
  export { UnsupportedOnRuntimeError, unsupportedOnNative } from './runtime/runtimeFlag.js';
41
41
  import { instructionToolOf, toolOf, toolsetOf } from './tools/tool.js';
@@ -108,6 +108,8 @@ export function compileNative(spec) {
108
108
  tools.push(subagentTool(compileNative(entry.agent)));
109
109
  else if (entry.kind === 'workflow')
110
110
  tools.push(workflowSubagentTool(workflowSubagentOf(compileNativeWorkflow(entry.workflow))));
111
+ else if (entry.kind === 'dispatch')
112
+ tools.push(workflowSubagentTool(dispatchSubagentOf(entry.dispatch)));
111
113
  else if (entry.kind === 'remote')
112
114
  tools.push(remoteAgentOwnTool({ name: entry.name, description: entry.description, url: entry.url }));
113
115
  else
@@ -182,6 +184,8 @@ export function compileNativeWorkflow(spec) {
182
184
  const workflows = new Map();
183
185
  for (const { yaml, workflow } of spec.workflows)
184
186
  workflows.set(yaml.name, compileNativeWorkflow(workflow));
187
+ for (const { yaml, dispatch } of spec.dispatches ?? [])
188
+ workflows.set(yaml.name, dispatchSubagentOf(dispatch));
185
189
  refuseUnrunnableNodes(graph, spec.resolveTool);
186
190
  return { name: spec.name, description: spec.description, graph, agents, resolveTool: spec.resolveTool, workflows };
187
191
  }
@@ -214,6 +218,19 @@ export function workflowSubagentOf(workflow) {
214
218
  }),
215
219
  };
216
220
  }
221
+ /**
222
+ * A nested dispatch syndicate as the delegated subagent the native loop
223
+ * calls, or the node a walk runs (ADR 0120): one call runs it as its own
224
+ * turn (nestedDispatchWalk) on the child session lib/runtime/native/delegate.ts
225
+ * (or lib/workflow/turn.ts) opened.
226
+ */
227
+ export function dispatchSubagentOf(dispatch) {
228
+ return {
229
+ name: dispatch.name,
230
+ ...(dispatch.description ? { description: dispatch.description } : {}),
231
+ walk: (run) => nestedDispatchWalk({ name: dispatch.name, config: dispatch.config, compile: dispatch.compile }, run),
232
+ };
233
+ }
217
234
  /** One subagent entry (a dispatch route) for the native loop: compileSubagentSpec, then compileNative. */
218
235
  export async function compileNativeSubagent(subCfg, opts = {}) {
219
236
  return compileNative(await compileSubagentSpec(subCfg, opts));
@@ -75,6 +75,21 @@ export interface DoctorOAuth {
75
75
  set: Record<string, boolean>;
76
76
  problems: string[];
77
77
  }
78
+ /**
79
+ * The static credential variables the syndicates send, under the operator's
80
+ * credential host allowlist (ADR 0122). Present when any syndicate sends one
81
+ * or MELCHIZEDEK_CREDENTIAL_HOSTS is set. Names and hosts only.
82
+ */
83
+ export interface DoctorCredentials {
84
+ /** Whether MELCHIZEDEK_CREDENTIAL_HOSTS is set. */
85
+ set: boolean;
86
+ /** Every variable the syndicates send (bearer_env, api_key.env, client_secret_env), sorted. */
87
+ envs: string[];
88
+ /** With no allowlist: the variables sent wherever the YAML says, named. Not a --check failure. */
89
+ warning?: string;
90
+ /** A malformed allowlist, and each use it refuses (`file: agent · tools · role: why`). A --check failure. */
91
+ problems: string[];
92
+ }
78
93
  export interface DoctorSyndicate {
79
94
  /** Path relative to the agents dir, e.g. "examples/council.yaml". */
80
95
  file: string;
@@ -106,6 +121,11 @@ export interface DoctorSyndicate {
106
121
  };
107
122
  /** The tools that need an OAuth grant (ADR 0112), when any do. */
108
123
  grants?: DoctorGrant[];
124
+ /** The static credential variables its tools send (ADR 0122), and what the allowlist refuses; when it sends any. */
125
+ credentials?: {
126
+ envs: string[];
127
+ problems: string[];
128
+ };
109
129
  }
110
130
  export interface Unlock {
111
131
  env: string;
@@ -141,6 +161,45 @@ export interface DoctorResult {
141
161
  counts: Record<VerdictState, number>;
142
162
  /** The OAuth setup, when any syndicate declares a grant or its variables are set. */
143
163
  oauth?: DoctorOAuth;
164
+ /** The credential host allowlist (ADR 0122), when any syndicate sends a static credential or it is set. */
165
+ credentials?: DoctorCredentials;
166
+ /** How each provider would be reached here: the providers line, as data. */
167
+ providers: ProviderPath[];
168
+ /** How the A2A server would authenticate and bill, when any of its variables is set. */
169
+ serving?: DoctorServing;
170
+ }
171
+ /**
172
+ * How one provider would be reached under this environment (the doctor's
173
+ * providers line): funded or not, directly or through the gateway, and on
174
+ * which platform. `melchizedek-setup --auto` reads its levels from this.
175
+ */
176
+ export interface ProviderPath {
177
+ provider: ProviderId;
178
+ label: string;
179
+ funded: boolean;
180
+ transport: 'direct' | 'gateway';
181
+ /** The gateway id when transport is 'gateway'. */
182
+ gateway?: string;
183
+ /** `local` for Ollama; a cloud platform (vertex, bedrock, azure) or `direct` otherwise. */
184
+ platform: Platform | 'local';
185
+ keyEnv: string | null;
186
+ }
187
+ /**
188
+ * The A2A server's identity and billing settings (scripts/a2a_server.ts),
189
+ * names and presence only: which `A2A_AUTH` mode, whether the secret and the
190
+ * mode's own variables are set, and who pays (`A2A_KEY_MODE`).
191
+ */
192
+ export interface DoctorServing {
193
+ /** A2A_AUTH as set, or `secret` (the server's default). */
194
+ auth: string;
195
+ /** True when A2A_AUTH is set explicitly. */
196
+ authDeclared: boolean;
197
+ /** A2A_KEY_MODE as set, or `server`. */
198
+ keyMode: string;
199
+ /** Which of the serving variables are set (never their values). */
200
+ set: Record<string, boolean>;
201
+ /** What the server would refuse to start on, by variable name. */
202
+ problems: string[];
144
203
  }
145
204
  export interface DoctorEndpoint {
146
205
  provider: ProviderId;
@@ -166,6 +225,16 @@ export interface DoctorEndpoint {
166
225
  export declare function runtimeReport(env?: NodeJS.ProcessEnv): DoctorRuntime;
167
226
  /** One row per provider that is not on its vendor's public API with an env key. */
168
227
  export declare function endpointRows(): DoctorEndpoint[];
228
+ /** One status per provider, probed with an id of its own prefix (the providers line). */
229
+ export declare function providerPaths(): ProviderPath[];
230
+ /** The serving variables the doctor reports by presence. */
231
+ export declare const SERVING_ENV: readonly ['A2A_AUTH', 'A2A_SERVER_SECRET', 'A2A_CALLERS', 'A2A_JWT_ISSUER', 'A2A_JWT_AUDIENCE', 'A2A_JWT_JWKS_URL', 'A2A_JWT_SECRET', 'A2A_TRUSTED_USER_HEADER', 'A2A_KEY_MODE', 'PUBLIC_URL'];
232
+ /**
233
+ * How the A2A server would authenticate callers and who would pay, from the
234
+ * environment; undefined when none of its variables is set. The checks are
235
+ * the ones scripts/a2a_server.ts stops on, reported by name.
236
+ */
237
+ export declare function servingReport(env?: NodeJS.ProcessEnv): DoctorServing | undefined;
169
238
  export declare const CONSOLE_URLS: Record<ProviderId, string>;
170
239
  /**
171
240
  * Every syndicate file the loader can see: the root (this deployment's
@@ -194,6 +263,13 @@ export declare function runDoctor(options?: {
194
263
  agentsDir?: string;
195
264
  scripts?: Record<string, string>;
196
265
  }): DoctorResult;
266
+ /**
267
+ * Why `melchizedek-doctor --check` exits non-zero, one line each; empty when
268
+ * it passes. A blocked syndicate, a runtime problem, the OAuth setup's and
269
+ * the credential allowlist's problems, and each grant whose hosts the OAuth
270
+ * host allowlist refuses (ADR 0114). Names and hosts only, never a value.
271
+ */
272
+ export declare function checkProblems(result: DoctorResult): string[];
197
273
  export declare function renderDoctor(result: DoctorResult, opts?: {
198
274
  color?: boolean;
199
275
  }): string;
@@ -38,6 +38,9 @@ import { agentOAuthGrants, oauthEnvNames } from './tools/oauthTools.js';
38
38
  import { grantHostProblems, OAUTH_REDIRECT_URI_ENV, oauthEnvProblems } from './a2a/oauthSetup.js';
39
39
  import { CREDENTIAL_KEY_ENV } from './tools/credentialCipher.js';
40
40
  import { OAUTH_HOSTS_ENV } from './tools/oauthHosts.js';
41
+ import { CREDENTIAL_HOSTS_ENV, credentialHosts } from './tools/credentialHosts.js';
42
+ import { syndicateCredentialUses } from './tools/credentialUses.js';
43
+ import { credentialHostEnvProblems, unboundCredentialWarning } from './a2a/oauthSetup.js';
41
44
  function resolvable(module) {
42
45
  try {
43
46
  createRequire(import.meta.url).resolve(module);
@@ -100,6 +103,69 @@ export function endpointRows() {
100
103
  }
101
104
  return rows;
102
105
  }
106
+ /** One status per provider, probed with an id of its own prefix (the providers line). */
107
+ export function providerPaths() {
108
+ return Object.keys(PROVIDERS).map((p) => {
109
+ const r = describeCapabilities(PROBE_ID[p]);
110
+ return {
111
+ provider: p,
112
+ label: PROVIDERS[p].label,
113
+ funded: r.funded,
114
+ transport: r.transport,
115
+ ...(r.gateway ? { gateway: r.gateway } : {}),
116
+ platform: p === 'ollama' ? 'local' : (r.platform ?? 'direct'),
117
+ keyEnv: r.keyEnv,
118
+ };
119
+ });
120
+ }
121
+ /** The serving variables the doctor reports by presence. */
122
+ export const SERVING_ENV = [
123
+ 'A2A_AUTH',
124
+ 'A2A_SERVER_SECRET',
125
+ 'A2A_CALLERS',
126
+ 'A2A_JWT_ISSUER',
127
+ 'A2A_JWT_AUDIENCE',
128
+ 'A2A_JWT_JWKS_URL',
129
+ 'A2A_JWT_SECRET',
130
+ 'A2A_TRUSTED_USER_HEADER',
131
+ 'A2A_KEY_MODE',
132
+ 'PUBLIC_URL',
133
+ ];
134
+ /**
135
+ * How the A2A server would authenticate callers and who would pay, from the
136
+ * environment; undefined when none of its variables is set. The checks are
137
+ * the ones scripts/a2a_server.ts stops on, reported by name.
138
+ */
139
+ export function servingReport(env = process.env) {
140
+ const has = (name) => !!env[name]?.trim();
141
+ const set = Object.fromEntries(SERVING_ENV.map((name) => [name, has(name)]));
142
+ if (!Object.values(set).some(Boolean))
143
+ return undefined;
144
+ // A value outside the known modes is reported as `invalid`, never echoed:
145
+ // a secret pasted into the wrong variable must not reach the terminal.
146
+ const rawAuth = (env.A2A_AUTH ?? '').trim().toLowerCase() || 'secret';
147
+ const rawKeyMode = (env.A2A_KEY_MODE ?? '').trim().toLowerCase() || 'server';
148
+ const auth = ['secret', 'callers', 'jwt', 'header'].includes(rawAuth) ? rawAuth : 'invalid';
149
+ const keyMode = rawKeyMode === 'server' || rawKeyMode === 'byok' ? rawKeyMode : 'invalid';
150
+ const problems = [];
151
+ if (auth === 'invalid')
152
+ problems.push('A2A_AUTH must be secret, callers, jwt or header');
153
+ if (auth === 'callers' && !has('A2A_CALLERS'))
154
+ problems.push('A2A_AUTH=callers needs A2A_CALLERS');
155
+ if (auth === 'jwt' && (!has('A2A_JWT_ISSUER') || !has('A2A_JWT_AUDIENCE')))
156
+ problems.push('A2A_AUTH=jwt needs A2A_JWT_ISSUER and A2A_JWT_AUDIENCE');
157
+ if (auth === 'jwt' && !has('A2A_JWT_JWKS_URL') && !has('A2A_JWT_SECRET'))
158
+ problems.push('A2A_AUTH=jwt needs A2A_JWT_JWKS_URL or A2A_JWT_SECRET');
159
+ if (auth === 'header' && (!has('A2A_TRUSTED_USER_HEADER') || !has('A2A_SERVER_SECRET')))
160
+ problems.push('A2A_AUTH=header needs A2A_TRUSTED_USER_HEADER and A2A_SERVER_SECRET');
161
+ if (keyMode !== 'server' && keyMode !== 'byok')
162
+ problems.push("A2A_KEY_MODE must be 'server' or 'byok'");
163
+ if (has('PUBLIC_URL') && !has('A2A_AUTH'))
164
+ problems.push('PUBLIC_URL is set, so A2A_AUTH must be set explicitly (with A2A_SERVED_AGENTS and A2A_TRUST_PROXY)');
165
+ if (auth === 'secret' && !has('A2A_SERVER_SECRET') && keyMode === 'byok')
166
+ problems.push('A2A_KEY_MODE=byok without A2A_SERVER_SECRET (or caller tokens) serves unauthenticated, loopback only');
167
+ return { auth, authDeclared: has('A2A_AUTH'), keyMode, set, problems };
168
+ }
103
169
  // ── Where to get a key ───────────────────────────────────────────────────────
104
170
  export const CONSOLE_URLS = {
105
171
  gemini: 'https://aistudio.google.com',
@@ -305,6 +371,17 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
305
371
  const nestedErrors = [];
306
372
  const grants = [];
307
373
  walk(config, '', { load, seen: new Set([file, path.basename(file)]), nestedErrors, grants }, rows);
374
+ // Static credentials and the credential host allowlist (ADR 0122); a nested file that cannot load is reported above.
375
+ const loadQuiet = (ref) => {
376
+ try {
377
+ return load(ref);
378
+ }
379
+ catch {
380
+ return {};
381
+ }
382
+ };
383
+ const credentialEnvs = [...new Set(syndicateCredentialUses([config], loadQuiet).map((u) => u.env))].sort();
384
+ const credentials = credentialEnvs.length ? { envs: credentialEnvs, problems: credentialHostEnvProblems([config], process.env, loadQuiet) } : undefined;
308
385
  const memory = config.memory_system === 'long-term'
309
386
  ? {
310
387
  namespace: config.memory_namespace ?? LEGACY_MEMORY_APP_NAME,
@@ -324,6 +401,7 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
324
401
  ...(nestedErrors.length ? { error: nestedErrors.join('\n') } : {}),
325
402
  ...(memory ? { memory } : {}),
326
403
  ...(grants.length ? { grants } : {}),
404
+ ...(credentials ? { credentials } : {}),
327
405
  };
328
406
  }
329
407
  catch (err) {
@@ -405,8 +483,9 @@ export function runDoctor(options = {}) {
405
483
  const rawGateway = (process.env[GATEWAY_ENV] ?? '').trim();
406
484
  const gateway = rawGateway
407
485
  ? {
408
- id: cfg?.gateway.id ?? rawGateway,
409
- label: cfg?.gateway.label ?? rawGateway,
486
+ // An unrecognised value is never echoed: it may be a key pasted into the wrong variable.
487
+ id: cfg?.gateway.id ?? 'unrecognised',
488
+ label: cfg?.gateway.label ?? 'an unrecognised gateway',
410
489
  usable: gatewayUsable(),
411
490
  ...(gatewayProblem() ? { problem: gatewayProblem() } : {}),
412
491
  }
@@ -424,7 +503,39 @@ export function runDoctor(options = {}) {
424
503
  const grants = syndicates.flatMap((s) => s.grants ?? []).map((g) => ({ agent: g.agent, tools: g.tools, oauth2: { provider: g.provider, grant: g.grant, token_url: '' } }));
425
504
  const set = Object.fromEntries([CREDENTIAL_KEY_ENV, OAUTH_REDIRECT_URI_ENV, OAUTH_HOSTS_ENV].map((name) => [name, !!process.env[name]?.trim()]));
426
505
  const oauth = grants.length || Object.values(set).some(Boolean) ? { set, problems: oauthEnvProblems(grants) } : undefined;
427
- return { agentsDir, runtime: runtimeReport(), syndicates, unlocks, gateway, endpoints: endpointRows(), counts, ...(oauth ? { oauth } : {}) };
506
+ // The credential host allowlist (ADR 0122): unbound variables named, refusals listed, names and hosts only.
507
+ const credentialSet = !!process.env[CREDENTIAL_HOSTS_ENV]?.trim();
508
+ const envs = [...new Set(syndicates.flatMap((s) => s.credentials?.envs ?? []))].sort();
509
+ let credentials;
510
+ if (envs.length || credentialSet) {
511
+ const problems = [];
512
+ try {
513
+ credentialHosts();
514
+ }
515
+ catch (err) {
516
+ problems.push(err instanceof Error ? err.message : `${CREDENTIAL_HOSTS_ENV} is malformed`);
517
+ }
518
+ if (!problems.length)
519
+ for (const s of syndicates)
520
+ for (const p of s.credentials?.problems ?? [])
521
+ problems.push(`${s.file}: ${p}`);
522
+ const warning = unboundCredentialWarning(envs);
523
+ credentials = { set: credentialSet, envs, ...(warning ? { warning } : {}), problems };
524
+ }
525
+ const serving = servingReport();
526
+ return {
527
+ agentsDir,
528
+ runtime: runtimeReport(),
529
+ syndicates,
530
+ unlocks,
531
+ gateway,
532
+ endpoints: endpointRows(),
533
+ counts,
534
+ ...(oauth ? { oauth } : {}),
535
+ ...(credentials ? { credentials } : {}),
536
+ providers: providerPaths(),
537
+ ...(serving ? { serving } : {}),
538
+ };
428
539
  }
429
540
  // ── Rendering ────────────────────────────────────────────────────────────────
430
541
  const ANSI = {
@@ -445,6 +556,29 @@ const PROBE_ID = {
445
556
  moonshot: 'kimi-x',
446
557
  ollama: 'ollama/x',
447
558
  };
559
+ /**
560
+ * Why `melchizedek-doctor --check` exits non-zero, one line each; empty when
561
+ * it passes. A blocked syndicate, a runtime problem, the OAuth setup's and
562
+ * the credential allowlist's problems, and each grant whose hosts the OAuth
563
+ * host allowlist refuses (ADR 0114). Names and hosts only, never a value.
564
+ */
565
+ export function checkProblems(result) {
566
+ const problems = [];
567
+ if (result.counts.blocked > 0)
568
+ problems.push(`${result.counts.blocked} syndicate(s) blocked`);
569
+ if (result.runtime.problem)
570
+ problems.push(result.runtime.problem);
571
+ for (const p of result.oauth?.problems ?? [])
572
+ problems.push(`oauth: ${p}`);
573
+ for (const p of result.credentials?.problems ?? [])
574
+ problems.push(`credentials: ${p}`);
575
+ for (const s of result.syndicates) {
576
+ for (const g of s.grants ?? [])
577
+ for (const p of g.hostProblems ?? [])
578
+ problems.push(`${s.file}: ${g.agent} · ${g.provider}: ${p}`);
579
+ }
580
+ return problems;
581
+ }
448
582
  function pad(s, n) {
449
583
  return s.length >= n ? s.slice(0, n - 1) + '…' : s.padEnd(n);
450
584
  }
@@ -471,11 +605,10 @@ export function renderDoctor(result, opts = {}) {
471
605
  // Provider line: what is funded, and how. Each provider is probed with an
472
606
  // id of its own prefix, so the line names ITS key (a fallthrough once put
473
607
  // xAI's key beside Moonshot).
474
- const providerBits = Object.keys(PROVIDERS).map((p) => {
475
- const r = describeCapabilities(PROBE_ID[p]);
608
+ const providerBits = (result.providers ?? providerPaths()).map((r) => {
476
609
  const mark = !r.funded ? `${c.red}✗${c.reset}` : r.transport === 'gateway' ? `${c.yellow}◇${c.reset}` : `${c.green}✓${c.reset}`;
477
- 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}`;
478
- return `${mark} ${PROVIDERS[p].label} ${how}`;
610
+ const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` : r.platform === 'local' ? `${c.dim}local${c.reset}` : `${c.dim}${r.platform}${c.reset}`;
611
+ return `${mark} ${r.label} ${how}`;
479
612
  });
480
613
  lines.push('providers ' + providerBits.join(' '));
481
614
  for (const e of result.endpoints ?? []) {
@@ -490,6 +623,15 @@ export function renderDoctor(result, opts = {}) {
490
623
  ? `gateway ${c.red}✗${c.reset} ${result.gateway.problem}`
491
624
  : `gateway ${c.yellow}◇${c.reset} ${result.gateway.label} fills in for any provider whose direct key is absent ${c.dim}(native search is lost on that path)${c.reset}`);
492
625
  }
626
+ if (result.serving) {
627
+ // Names only: the A2A identity mode, who pays, and what the server would refuse.
628
+ const sv = result.serving;
629
+ const mark = sv.problems.length ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
630
+ const set = Object.entries(sv.set).filter(([, on]) => on).map(([name]) => name).join(', ');
631
+ lines.push(`serving ${mark} A2A_AUTH=${sv.auth}${sv.authDeclared ? '' : ' (default)'} · A2A_KEY_MODE=${sv.keyMode} ${c.dim}· set: ${set || 'none'}${c.reset}`);
632
+ for (const p of sv.problems)
633
+ lines.push(` ${c.red}✗ ${p}${c.reset}`);
634
+ }
493
635
  if (result.oauth) {
494
636
  // Names only: which variables are set, never their values (ADR 0114).
495
637
  const set = Object.entries(result.oauth.set).map(([name, on]) => `${name} ${on ? 'set' : 'unset'}`).join(' · ');
@@ -498,6 +640,16 @@ export function renderDoctor(result, opts = {}) {
498
640
  for (const p of result.oauth.problems)
499
641
  lines.push(` ${c.red}✗ ${p}${c.reset}`);
500
642
  }
643
+ if (result.credentials) {
644
+ // Names only: which variables are sent, never their values (ADR 0122).
645
+ const cr = result.credentials;
646
+ const mark = cr.problems.length ? `${c.red}✗${c.reset}` : cr.warning ? `${c.yellow}⚠${c.reset}` : `${c.green}✓${c.reset}`;
647
+ lines.push(`credentials ${mark} ${c.dim}${CREDENTIAL_HOSTS_ENV} ${cr.set ? 'set' : 'unset'}${cr.envs.length ? ` · sent: ${cr.envs.join(', ')}` : ''}${c.reset}`);
648
+ if (cr.warning)
649
+ lines.push(` ${c.yellow}⚠ ${cr.warning}${c.reset}`);
650
+ for (const p of cr.problems)
651
+ lines.push(` ${c.red}✗ ${p}${c.reset}`);
652
+ }
501
653
  lines.push('');
502
654
  // The table: one line per syndicate (name, verdict, run command), then
503
655
  // one indented line per agent.
@@ -129,6 +129,15 @@ export interface AgentYamlConfig {
129
129
  schema?: Record<string, unknown>;
130
130
  mime?: 'application/json' | 'text/plain';
131
131
  };
132
+ /**
133
+ * Which tools the model may call (v2, ADR 0117): `auto`, `none`,
134
+ * `required`, or `{ name }` for one tool. Replaces
135
+ * generateContentConfig.toolConfig.functionCallingConfig; folded into it
136
+ * by the loader (lib/agentDialect.ts toEngineAgent).
137
+ */
138
+ tool_choice?: 'auto' | 'none' | 'required' | {
139
+ name: string;
140
+ };
132
141
  /**
133
142
  * Per-provider instruction (v2), keyed by provider id (lib/models/providerMap.ts):
134
143
  * `instruction` replaces the agent's instruction, `instruction_append` is
@@ -74,6 +74,15 @@ export declare function capabilityOf(model: string, capability: Capability, opts
74
74
  }): CapabilityCell & {
75
75
  row: MatrixRow;
76
76
  };
77
+ /**
78
+ * The reasoning levels above `high` (ADR 0117), per row: what `reasoning:
79
+ * xhigh` and `reasoning: max` reach the provider as. A level a model lacks
80
+ * goes as its highest setting, and the request's `llm.request` span carries
81
+ * `llm.reasoning.weakened` with the level asked for. Asserted against the
82
+ * request bodies in tests/toolChoiceEffort.test.ts; the direct adapters read
83
+ * lib/models/reasoning.ts effortCeiling.
84
+ */
85
+ export declare const REASONING_ABOVE_HIGH: Readonly<Record<MatrixRow, string>>;
77
86
  /**
78
87
  * Whether an outputSchema goes in the same request as the agent's tools, as
79
88
  * the provider's own structured-output field, on the path `model` takes