melchizedek-agents 0.18.0 → 0.19.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 (281) hide show
  1. package/.env.example +12 -0
  2. package/CHANGELOG.md +825 -0
  3. package/DOCUMENTATION.md +217 -59
  4. package/README.md +42 -1
  5. package/config/agents/examples/harness.yaml +3 -1
  6. package/config/agents/examples/model_zoo.yaml +10 -9
  7. package/config/agents/syndicateSchema.yaml +50 -16
  8. package/config/agents/templates/account_memory.yaml +2 -6
  9. package/config/agents/templates/case_desk.yaml +3 -9
  10. package/config/agents/templates/draft_review.yaml +3 -9
  11. package/config/agents/templates/intake_extractor.yaml +1 -3
  12. package/config/agents/templates/research_brief.yaml +3 -9
  13. package/config/agents/templates/research_desk.yaml +3 -9
  14. package/config/agents/templates/review_panel.yaml +4 -12
  15. package/config/agents/templates/support_triage.yaml +4 -12
  16. package/config/agents/templates/systems_operator.yaml +2 -6
  17. package/db/hardening.sql +7 -1
  18. package/db/migrations/0013_tool_credentials.sql +260 -0
  19. package/dist/lib/a2a/app.d.ts +70 -6
  20. package/dist/lib/a2a/app.js +107 -2
  21. package/dist/lib/a2a/executor.d.ts +20 -3
  22. package/dist/lib/a2a/executor.js +66 -7
  23. package/dist/lib/a2a/remoteAgent.d.ts +24 -9
  24. package/dist/lib/a2a/remoteAgent.js +31 -14
  25. package/dist/lib/compile.d.ts +121 -30
  26. package/dist/lib/compile.js +137 -124
  27. package/dist/lib/compileAdk.d.ts +29 -0
  28. package/dist/lib/compileAdk.js +132 -0
  29. package/dist/lib/compileNative.d.ts +85 -0
  30. package/dist/lib/compileNative.js +222 -0
  31. package/dist/lib/config.d.ts +6 -5
  32. package/dist/lib/config.js +6 -5
  33. package/dist/lib/doctor.js +1 -0
  34. package/dist/lib/index.d.ts +2 -1
  35. package/dist/lib/index.js +2 -1
  36. package/dist/lib/loadSyndicate.d.ts +18 -0
  37. package/dist/lib/memory/erase.d.ts +5 -3
  38. package/dist/lib/memory/erase.js +5 -2
  39. package/dist/lib/memory/namespace.d.ts +8 -1
  40. package/dist/lib/memory/namespace.js +12 -6
  41. package/dist/lib/memory/providers.js +2 -1
  42. package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
  43. package/dist/lib/memory/supabaseMemoryService.js +42 -2
  44. package/dist/lib/model.d.ts +47 -0
  45. package/dist/lib/model.js +37 -0
  46. package/dist/lib/models/adapterResolver.d.ts +113 -0
  47. package/dist/lib/models/adapterResolver.js +136 -0
  48. package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
  49. package/dist/lib/models/adkGeminiAdapter.js +367 -0
  50. package/dist/lib/models/adkShim.d.ts +111 -0
  51. package/dist/lib/models/adkShim.js +136 -0
  52. package/dist/lib/models/capabilities.d.ts +3 -0
  53. package/dist/lib/models/capabilities.js +20 -13
  54. package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
  55. package/dist/lib/models/chatCompletionsAdapter.js +854 -0
  56. package/dist/lib/models/circuitBreaker.d.ts +42 -0
  57. package/dist/lib/models/circuitBreaker.js +70 -0
  58. package/dist/lib/models/claudeAdapter.d.ts +103 -0
  59. package/dist/lib/models/claudeAdapter.js +669 -0
  60. package/dist/lib/models/claudeLlm.d.ts +39 -62
  61. package/dist/lib/models/claudeLlm.js +44 -349
  62. package/dist/lib/models/claudeModels.d.ts +127 -0
  63. package/dist/lib/models/claudeModels.js +237 -0
  64. package/dist/lib/models/contract.d.ts +395 -0
  65. package/dist/lib/models/contract.js +39 -0
  66. package/dist/lib/models/endpoints.js +2 -1
  67. package/dist/lib/models/errorResponse.d.ts +50 -0
  68. package/dist/lib/models/errorResponse.js +48 -0
  69. package/dist/lib/models/fallback.d.ts +17 -10
  70. package/dist/lib/models/fallback.js +53 -42
  71. package/dist/lib/models/fallbackAdapter.d.ts +55 -0
  72. package/dist/lib/models/fallbackAdapter.js +62 -0
  73. package/dist/lib/models/gateway.js +2 -1
  74. package/dist/lib/models/gatewayAdapter.d.ts +51 -0
  75. package/dist/lib/models/gatewayAdapter.js +81 -0
  76. package/dist/lib/models/gatewayLlm.d.ts +12 -33
  77. package/dist/lib/models/gatewayLlm.js +14 -75
  78. package/dist/lib/models/geminiAdapter.d.ts +123 -0
  79. package/dist/lib/models/geminiAdapter.js +831 -0
  80. package/dist/lib/models/geminiState.d.ts +25 -0
  81. package/dist/lib/models/geminiState.js +25 -0
  82. package/dist/lib/models/genaiMapping.d.ts +263 -0
  83. package/dist/lib/models/genaiMapping.js +862 -0
  84. package/dist/lib/models/gptAdapter.d.ts +240 -0
  85. package/dist/lib/models/gptAdapter.js +804 -0
  86. package/dist/lib/models/gptLlm.d.ts +63 -123
  87. package/dist/lib/models/gptLlm.js +88 -602
  88. package/dist/lib/models/grokAdapter.d.ts +86 -0
  89. package/dist/lib/models/grokAdapter.js +142 -0
  90. package/dist/lib/models/grokLlm.d.ts +22 -45
  91. package/dist/lib/models/grokLlm.js +22 -67
  92. package/dist/lib/models/kimiAdapter.d.ts +157 -0
  93. package/dist/lib/models/kimiAdapter.js +203 -0
  94. package/dist/lib/models/kimiLlm.d.ts +15 -107
  95. package/dist/lib/models/kimiLlm.js +17 -153
  96. package/dist/lib/models/ollamaAdapter.d.ts +125 -0
  97. package/dist/lib/models/ollamaAdapter.js +155 -0
  98. package/dist/lib/models/ollamaLlm.d.ts +11 -107
  99. package/dist/lib/models/ollamaLlm.js +13 -151
  100. package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
  101. package/dist/lib/models/openAiCompatibleLlm.js +109 -671
  102. package/dist/lib/models/providerState.d.ts +63 -0
  103. package/dist/lib/models/providerState.js +61 -0
  104. package/dist/lib/models/reasoning.d.ts +26 -0
  105. package/dist/lib/models/reasoning.js +59 -0
  106. package/dist/lib/models/registry.d.ts +58 -35
  107. package/dist/lib/models/registry.js +89 -118
  108. package/dist/lib/models/retry.d.ts +2 -1
  109. package/dist/lib/models/retry.js +3 -2
  110. package/dist/lib/models/schemaNormalize.d.ts +98 -0
  111. package/dist/lib/models/schemaNormalize.js +321 -1
  112. package/dist/lib/models/tracedGemini.d.ts +41 -0
  113. package/dist/lib/models/tracedGemini.js +98 -0
  114. package/dist/lib/models/urls.d.ts +11 -0
  115. package/dist/lib/models/urls.js +16 -0
  116. package/dist/lib/net/redirects.d.ts +21 -2
  117. package/dist/lib/net/redirects.js +11 -34
  118. package/dist/lib/observability/audit.d.ts +5 -1
  119. package/dist/lib/observability/lineage.d.ts +14 -0
  120. package/dist/lib/observability/lineage.js +33 -0
  121. package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
  122. package/dist/lib/observability/supabaseSpanExporter.js +17 -9
  123. package/dist/lib/observability/tracer.d.ts +26 -8
  124. package/dist/lib/observability/tracer.js +43 -18
  125. package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
  126. package/dist/lib/runtime/adkMemoryBridge.js +68 -0
  127. package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
  128. package/dist/lib/runtime/adkSessionBridge.js +176 -0
  129. package/dist/lib/runtime/credentials.d.ts +59 -0
  130. package/dist/lib/runtime/credentials.js +62 -0
  131. package/dist/lib/runtime/events.d.ts +229 -0
  132. package/dist/lib/runtime/events.js +274 -0
  133. package/dist/lib/runtime/logging.d.ts +32 -0
  134. package/dist/lib/runtime/logging.js +55 -0
  135. package/dist/lib/runtime/memoryService.d.ts +72 -0
  136. package/dist/lib/runtime/memoryService.js +25 -0
  137. package/dist/lib/runtime/native/agentLoop.d.ts +203 -0
  138. package/dist/lib/runtime/native/agentLoop.js +687 -0
  139. package/dist/lib/runtime/native/compaction.d.ts +103 -0
  140. package/dist/lib/runtime/native/compaction.js +191 -0
  141. package/dist/lib/runtime/native/delegate.d.ts +98 -0
  142. package/dist/lib/runtime/native/delegate.js +173 -0
  143. package/dist/lib/runtime/native/history.d.ts +83 -0
  144. package/dist/lib/runtime/native/history.js +312 -0
  145. package/dist/lib/runtime/native/interrupts.d.ts +126 -0
  146. package/dist/lib/runtime/native/interrupts.js +291 -0
  147. package/dist/lib/runtime/native/request.d.ts +215 -0
  148. package/dist/lib/runtime/native/request.js +585 -0
  149. package/dist/lib/runtime/native/selfCorrection.d.ts +106 -0
  150. package/dist/lib/runtime/native/selfCorrection.js +253 -0
  151. package/dist/lib/runtime/native/step.d.ts +148 -0
  152. package/dist/lib/runtime/native/step.js +239 -0
  153. package/dist/lib/runtime/native/taskMode.d.ts +64 -0
  154. package/dist/lib/runtime/native/taskMode.js +126 -0
  155. package/dist/lib/runtime/native/telemetry.d.ts +75 -0
  156. package/dist/lib/runtime/native/telemetry.js +299 -0
  157. package/dist/lib/runtime/native/tempState.d.ts +31 -0
  158. package/dist/lib/runtime/native/tempState.js +56 -0
  159. package/dist/lib/runtime/nativeTurn.d.ts +86 -0
  160. package/dist/lib/runtime/nativeTurn.js +107 -0
  161. package/dist/lib/runtime/questions.d.ts +29 -7
  162. package/dist/lib/runtime/questions.js +47 -32
  163. package/dist/lib/runtime/runtimeFlag.d.ts +30 -0
  164. package/dist/lib/runtime/runtimeFlag.js +50 -0
  165. package/dist/lib/runtime/sessions.d.ts +166 -0
  166. package/dist/lib/runtime/sessions.js +201 -0
  167. package/dist/lib/runtime/syndicateTurn.d.ts +41 -5
  168. package/dist/lib/runtime/syndicateTurn.js +202 -32
  169. package/dist/lib/session/supabaseSessionService.d.ts +64 -17
  170. package/dist/lib/session/supabaseSessionService.js +148 -105
  171. package/dist/lib/session/transcript.d.ts +68 -22
  172. package/dist/lib/session/transcript.js +102 -28
  173. package/dist/lib/skills.js +33 -7
  174. package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
  175. package/dist/lib/storage/postgres/credentialStore.js +82 -0
  176. package/dist/lib/storage/postgres/index.d.ts +12 -0
  177. package/dist/lib/storage/postgres/index.js +6 -1
  178. package/dist/lib/storage/postgres/sessionService.d.ts +43 -15
  179. package/dist/lib/storage/postgres/sessionService.js +128 -66
  180. package/dist/lib/syndicateSchema.d.ts +47 -0
  181. package/dist/lib/syndicateSchema.js +72 -3
  182. package/dist/lib/toolRegistry.d.ts +15 -2
  183. package/dist/lib/toolRegistry.js +33 -17
  184. package/dist/lib/tools/adkTool.d.ts +81 -0
  185. package/dist/lib/tools/adkTool.js +268 -0
  186. package/dist/lib/tools/auth.d.ts +118 -0
  187. package/dist/lib/tools/auth.js +89 -0
  188. package/dist/lib/tools/collectionsSearchTool.d.ts +7 -6
  189. package/dist/lib/tools/collectionsSearchTool.js +11 -26
  190. package/dist/lib/tools/credentialCipher.d.ts +58 -0
  191. package/dist/lib/tools/credentialCipher.js +116 -0
  192. package/dist/lib/tools/credentialStore.d.ts +77 -0
  193. package/dist/lib/tools/credentialStore.js +246 -0
  194. package/dist/lib/tools/examples.d.ts +43 -0
  195. package/dist/lib/tools/examples.js +62 -0
  196. package/dist/lib/tools/generateImageTool.d.ts +15 -6
  197. package/dist/lib/tools/generateImageTool.js +19 -29
  198. package/dist/lib/tools/inspectImageTool.d.ts +9 -6
  199. package/dist/lib/tools/inspectImageTool.js +13 -17
  200. package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
  201. package/dist/lib/tools/mcpToolFactory.js +46 -47
  202. package/dist/lib/tools/memoryTools.d.ts +61 -0
  203. package/dist/lib/tools/memoryTools.js +119 -0
  204. package/dist/lib/tools/nativeTools.d.ts +38 -0
  205. package/dist/lib/tools/nativeTools.js +43 -0
  206. package/dist/lib/tools/oauthConsent.d.ts +181 -0
  207. package/dist/lib/tools/oauthConsent.js +330 -0
  208. package/dist/lib/tools/openapi/call.d.ts +108 -0
  209. package/dist/lib/tools/openapi/call.js +295 -0
  210. package/dist/lib/tools/openapi/parse.d.ts +147 -0
  211. package/dist/lib/tools/openapi/parse.js +570 -0
  212. package/dist/lib/tools/openapiTools.d.ts +25 -17
  213. package/dist/lib/tools/openapiTools.js +124 -121
  214. package/dist/lib/tools/scienceTools.d.ts +14 -14
  215. package/dist/lib/tools/skillToolset.d.ts +58 -87
  216. package/dist/lib/tools/skillToolset.js +61 -142
  217. package/dist/lib/tools/skills/env.d.ts +58 -0
  218. package/dist/lib/tools/skills/env.js +165 -0
  219. package/dist/lib/tools/skills/executor.d.ts +114 -0
  220. package/dist/lib/tools/skills/executor.js +331 -0
  221. package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
  222. package/dist/lib/tools/skills/frontmatter.js +215 -0
  223. package/dist/lib/tools/skills/loader.d.ts +91 -0
  224. package/dist/lib/tools/skills/loader.js +257 -0
  225. package/dist/lib/tools/skills/tools.d.ts +187 -0
  226. package/dist/lib/tools/skills/tools.js +386 -0
  227. package/dist/lib/tools/taskTools.d.ts +15 -15
  228. package/dist/lib/tools/tool.d.ts +354 -0
  229. package/dist/lib/tools/tool.js +304 -0
  230. package/dist/lib/tools/toolContract.d.ts +89 -33
  231. package/dist/lib/tools/toolContract.js +104 -82
  232. package/dist/lib/tools/urlContextTool.d.ts +3 -0
  233. package/dist/lib/tools/urlContextTool.js +5 -1
  234. package/dist/lib/tools/webExtractTool.d.ts +2 -2
  235. package/dist/lib/tools/webExtractTool.js +2 -1
  236. package/dist/lib/tools/webSearchTool.d.ts +6 -9
  237. package/dist/lib/tools/webSearchTool.js +14 -59
  238. package/dist/lib/tools/wikiTools.d.ts +19 -19
  239. package/dist/lib/tools/wikiTools.js +13 -9
  240. package/dist/lib/tools/xApiSearchTool.d.ts +2 -2
  241. package/dist/lib/tools/xApiSearchTool.js +2 -1
  242. package/dist/lib/tools/xSearchTool.d.ts +7 -9
  243. package/dist/lib/tools/xSearchTool.js +11 -62
  244. package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
  245. package/dist/lib/tools/xaiSearchParams.js +126 -0
  246. package/dist/lib/wiki/agentRun.d.ts +12 -0
  247. package/dist/lib/wiki/agentRun.js +72 -34
  248. package/dist/lib/wiki/entities.d.ts +12 -0
  249. package/dist/lib/wiki/entities.js +45 -3
  250. package/dist/lib/workflow/agentNode.d.ts +136 -0
  251. package/dist/lib/workflow/agentNode.js +274 -0
  252. package/dist/lib/workflow/graph.d.ts +177 -0
  253. package/dist/lib/workflow/graph.js +458 -0
  254. package/dist/lib/workflow/nodeEvents.d.ts +50 -0
  255. package/dist/lib/workflow/nodeEvents.js +101 -0
  256. package/dist/lib/workflow/pause.d.ts +122 -0
  257. package/dist/lib/workflow/pause.js +180 -0
  258. package/dist/lib/workflow/resume.d.ts +165 -0
  259. package/dist/lib/workflow/resume.js +440 -0
  260. package/dist/lib/workflow/route.d.ts +58 -0
  261. package/dist/lib/workflow/route.js +69 -0
  262. package/dist/lib/workflow/scheduler.d.ts +415 -0
  263. package/dist/lib/workflow/scheduler.js +743 -0
  264. package/dist/lib/workflow/toolNode.d.ts +127 -0
  265. package/dist/lib/workflow/toolNode.js +236 -0
  266. package/dist/lib/workflow/turn.d.ts +118 -0
  267. package/dist/lib/workflow/turn.js +216 -0
  268. package/dist/lib/workflowConfig.d.ts +3 -2
  269. package/dist/lib/workflowConfig.js +3 -12
  270. package/dist/scripts/a2a_server.js +4 -3
  271. package/dist/scripts/assistant_worker.d.ts +2 -1
  272. package/dist/scripts/assistant_worker.js +9 -4
  273. package/dist/scripts/syndicate_chat.js +19 -10
  274. package/package.json +4 -1
  275. package/skills/briefs/melchizedek-author.md +3 -2
  276. package/skills/briefs/melchizedek-models.md +13 -9
  277. package/skills/briefs/melchizedek.md +3 -3
  278. package/skills/melchizedek/SKILL.md +2 -1
  279. package/skills/melchizedek-author/SKILL.md +2 -1
  280. package/skills/melchizedek-author/assets/minimal.yaml +2 -0
  281. package/skills/melchizedek-models/SKILL.md +23 -6
@@ -0,0 +1,260 @@
1
+ -- ============================================================================
2
+ -- 0013_tool_credentials.sql — third-party OAuth tokens held for tools (ADR 0072)
3
+ -- ============================================================================
4
+ -- A tool that acts for an end user against a third-party API (a calendar, a
5
+ -- repository host, a CRM) needs that user's delegated access, not the
6
+ -- server's own key. melchizedek_tool_credentials holds one token set per
7
+ -- app, end user and provider:
8
+ --
9
+ -- app_name the app the run pins (the root syndicate's memory namespace)
10
+ -- user_id the A2A server's scope key, as sessions and tasks store it
11
+ -- provider the provider's name as the tool asks for it ('github')
12
+ -- scopes what the user granted
13
+ -- access_token_enc, refresh_token_enc
14
+ -- ciphertext only, in the engine's envelope ('mzc1.<key id>.…',
15
+ -- AES-256-GCM by default, lib/tools/credentialCipher.ts). The
16
+ -- CHECK refuses anything else, so a plaintext token cannot be
17
+ -- written by mistake. The key never reaches the database.
18
+ -- key_id which key sealed the row, so a wrong key fails closed by name
19
+ -- expires_at when the access token stops working (NULL: not stated)
20
+ -- version bumped by every write; a refresh writes only over the version
21
+ -- it read, so two instances refreshing at once keep one result
22
+ --
23
+ -- The server's role reads and writes it; no API role can (RLS on, grants
24
+ -- revoked, as db/hardening.sql repeats). melchizedek_erase_scope is replaced
25
+ -- with the 0011 function plus a `credentials` store, so one erase of a user
26
+ -- also removes their tokens (ADR 0020 item 7). Idempotent.
27
+ -- ============================================================================
28
+
29
+ CREATE TABLE IF NOT EXISTS melchizedek_tool_credentials (
30
+ app_name TEXT NOT NULL,
31
+ user_id TEXT NOT NULL,
32
+ provider TEXT NOT NULL,
33
+ scopes TEXT[] NOT NULL DEFAULT '{}',
34
+ access_token_enc TEXT NOT NULL CHECK (access_token_enc ~ '^mzc1[.]'),
35
+ refresh_token_enc TEXT CHECK (refresh_token_enc IS NULL OR refresh_token_enc ~ '^mzc1[.]'),
36
+ key_id TEXT NOT NULL,
37
+ expires_at TIMESTAMPTZ,
38
+ version INTEGER NOT NULL DEFAULT 1,
39
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
40
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
41
+ refreshed_at TIMESTAMPTZ,
42
+ PRIMARY KEY (app_name, user_id, provider)
43
+ );
44
+ -- Erase finds a user's rows across apps.
45
+ CREATE INDEX IF NOT EXISTS idx_melchizedek_tool_credentials_user
46
+ ON melchizedek_tool_credentials (user_id);
47
+
48
+ ALTER TABLE melchizedek_tool_credentials ENABLE ROW LEVEL SECURITY;
49
+ DO $$
50
+ BEGIN
51
+ REVOKE ALL ON melchizedek_tool_credentials FROM PUBLIC;
52
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
53
+ REVOKE ALL ON melchizedek_tool_credentials FROM anon;
54
+ END IF;
55
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
56
+ REVOKE ALL ON melchizedek_tool_credentials FROM authenticated;
57
+ END IF;
58
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
59
+ GRANT SELECT, INSERT, UPDATE, DELETE ON melchizedek_tool_credentials TO service_role;
60
+ END IF;
61
+ END $$;
62
+
63
+ -- One erase covers the credentials too: the 0011 function, plus the
64
+ -- `credentials` store at the end.
65
+ CREATE OR REPLACE FUNCTION melchizedek_erase_scope(
66
+ p_scope_key text,
67
+ p_namespace text DEFAULT NULL,
68
+ p_include_nested boolean DEFAULT false
69
+ ) RETURNS TABLE(store text, deleted bigint)
70
+ LANGUAGE plpgsql
71
+ SECURITY INVOKER
72
+ SET search_path = public
73
+ AS $$
74
+ DECLARE
75
+ -- LIKE pattern for scopes nested beneath this one, with the scope's own
76
+ -- wildcard characters escaped.
77
+ nested_like text := replace(replace(replace(p_scope_key, '\', '\\'), '%', '\%'), '_', '\_') || '/%';
78
+ contexts text[];
79
+ kept text[];
80
+ traces text[];
81
+ n bigint;
82
+ BEGIN
83
+ IF p_scope_key IS NULL OR btrim(p_scope_key) = '' THEN
84
+ RAISE EXCEPTION 'melchizedek_erase_scope: scope_key is required';
85
+ END IF;
86
+
87
+ -- The conversations (context ids) being erased. With a namespace, they are
88
+ -- the namespace's own session rows; without one, every row of the scope.
89
+ -- adk_sessions.id is '<app_name>:<user_id>:<context id>'.
90
+ SELECT coalesce(array_agg(DISTINCT substr(s.id, length(s.app_name) + length(s.user_id) + 3)), '{}')
91
+ INTO contexts
92
+ FROM adk_sessions s
93
+ WHERE (s.user_id = p_scope_key OR (p_include_nested AND s.user_id LIKE nested_like))
94
+ AND (p_namespace IS NULL OR s.app_name = p_namespace);
95
+
96
+ -- With a namespace: the conversations still live in another namespace,
97
+ -- which are the only ones a namespace erase keeps. Sub-agent rows share
98
+ -- their conversation's id, so excluding `contexts` keeps them with it.
99
+ SELECT coalesce(array_agg(DISTINCT substr(s.id, length(s.app_name) + length(s.user_id) + 3)), '{}')
100
+ INTO kept
101
+ FROM adk_sessions s
102
+ WHERE p_namespace IS NOT NULL
103
+ AND (s.user_id = p_scope_key OR (p_include_nested AND s.user_id LIKE nested_like))
104
+ AND s.app_name <> p_namespace
105
+ AND NOT (substr(s.id, length(s.app_name) + length(s.user_id) + 3) = ANY(contexts));
106
+
107
+ -- Ledger traces of the turns being erased, collected before anything is deleted.
108
+ IF to_regclass('public.adk_turns') IS NOT NULL THEN
109
+ EXECUTE $q$
110
+ SELECT coalesce(array_agg(DISTINCT trace_id), '{}') FROM adk_turns
111
+ WHERE (user_id = $1 OR ($2 AND user_id LIKE $3))
112
+ AND ($4 IS NULL OR session_id IS NULL OR NOT (session_id = ANY($5)))
113
+ $q$ INTO traces USING p_scope_key, p_include_nested, nested_like, p_namespace, kept;
114
+ ELSE
115
+ traces := '{}';
116
+ END IF;
117
+
118
+ -- Memory facts. user_key is '<namespace>/<scope>' and a namespace never
119
+ -- contains '/', so the scope is exactly what follows the first slash.
120
+ DELETE FROM adk_memory_facts f
121
+ WHERE (p_namespace IS NULL OR split_part(f.user_key, '/', 1) = p_namespace)
122
+ AND (substr(f.user_key, strpos(f.user_key, '/') + 1) = p_scope_key
123
+ OR (p_include_nested AND substr(f.user_key, strpos(f.user_key, '/') + 1) LIKE nested_like));
124
+ GET DIAGNOSTICS n = ROW_COUNT;
125
+ store := 'memory_facts'; deleted := n; RETURN NEXT;
126
+
127
+ -- Sessions: every row of the scope in those conversations, whatever app
128
+ -- name ADK gave it (subagents run under their own agent name).
129
+ DELETE FROM adk_sessions s
130
+ WHERE (s.user_id = p_scope_key OR (p_include_nested AND s.user_id LIKE nested_like))
131
+ AND substr(s.id, length(s.app_name) + length(s.user_id) + 3) = ANY(contexts);
132
+ GET DIAGNOSTICS n = ROW_COUNT;
133
+ store := 'sessions'; deleted := n; RETURN NEXT;
134
+
135
+ -- The ledger, when installed.
136
+ IF to_regclass('public.adk_turns') IS NOT NULL THEN
137
+ EXECUTE $q$
138
+ DELETE FROM adk_turns
139
+ WHERE (user_id = $1 OR ($2 AND user_id LIKE $3))
140
+ AND ($4 IS NULL OR session_id IS NULL OR NOT (session_id = ANY($5)))
141
+ $q$ USING p_scope_key, p_include_nested, nested_like, p_namespace, kept;
142
+ GET DIAGNOSTICS n = ROW_COUNT;
143
+ ELSE
144
+ n := 0;
145
+ END IF;
146
+ store := 'turns'; deleted := n; RETURN NEXT;
147
+
148
+ IF to_regclass('public.adk_telemetry') IS NOT NULL THEN
149
+ EXECUTE 'DELETE FROM adk_telemetry WHERE trace_id = ANY($1)' USING traces;
150
+ GET DIAGNOSTICS n = ROW_COUNT;
151
+ ELSE
152
+ n := 0;
153
+ END IF;
154
+ store := 'spans'; deleted := n; RETURN NEXT;
155
+
156
+ IF to_regclass('public.adk_payloads') IS NOT NULL THEN
157
+ EXECUTE 'DELETE FROM adk_payloads WHERE trace_id = ANY($1)' USING traces;
158
+ GET DIAGNOSTICS n = ROW_COUNT;
159
+ ELSE
160
+ n := 0;
161
+ END IF;
162
+ store := 'payloads'; deleted := n; RETURN NEXT;
163
+
164
+ IF to_regclass('public.adk_verdicts') IS NOT NULL THEN
165
+ EXECUTE 'DELETE FROM adk_verdicts WHERE trace_id = ANY($1)' USING traces;
166
+ GET DIAGNOSTICS n = ROW_COUNT;
167
+ ELSE
168
+ n := 0;
169
+ END IF;
170
+ store := 'verdicts'; deleted := n; RETURN NEXT;
171
+
172
+ IF EXISTS (
173
+ SELECT 1 FROM information_schema.columns
174
+ WHERE table_schema = 'public' AND table_name = 'adk_labels' AND column_name = 'trace_id'
175
+ ) THEN
176
+ EXECUTE 'DELETE FROM adk_labels WHERE trace_id = ANY($1)' USING traces;
177
+ GET DIAGNOSTICS n = ROW_COUNT;
178
+ ELSE
179
+ n := 0;
180
+ END IF;
181
+ store := 'labels'; deleted := n; RETURN NEXT;
182
+
183
+ IF to_regclass('public.adk_a2a_tasks') IS NOT NULL THEN
184
+ EXECUTE $q$
185
+ DELETE FROM adk_a2a_tasks
186
+ WHERE (owner = $1 OR ($2 AND owner LIKE $3))
187
+ AND ($4 IS NULL OR context_id IS NULL OR NOT (context_id = ANY($5)))
188
+ $q$ USING p_scope_key, p_include_nested, nested_like, p_namespace, kept;
189
+ GET DIAGNOSTICS n = ROW_COUNT;
190
+ ELSE
191
+ n := 0;
192
+ END IF;
193
+ store := 'tasks'; deleted := n; RETURN NEXT;
194
+
195
+ -- Memory ingestion markers (migration 0007): how far each conversation was
196
+ -- distilled. They carry the scope's key and its conversation ids.
197
+ IF to_regclass('public.melchizedek_memory_ingest') IS NOT NULL THEN
198
+ EXECUTE $q$
199
+ DELETE FROM melchizedek_memory_ingest m
200
+ WHERE ($1 IS NULL OR split_part(m.user_key, '/', 1) = $1)
201
+ AND (substr(m.user_key, strpos(m.user_key, '/') + 1) = $2
202
+ OR ($3 AND substr(m.user_key, strpos(m.user_key, '/') + 1) LIKE $4))
203
+ $q$ USING p_namespace, p_scope_key, p_include_nested, nested_like;
204
+ GET DIAGNOSTICS n = ROW_COUNT;
205
+ ELSE
206
+ n := 0;
207
+ END IF;
208
+ store := 'memory_markers'; deleted := n; RETURN NEXT;
209
+
210
+ -- The task tools' list and job queue (migration 0009), per caller across
211
+ -- every syndicate: erased with the whole scope, kept by a namespace erase.
212
+ n := 0;
213
+ IF p_namespace IS NULL AND to_regclass('public.melchizedek_tasks') IS NOT NULL THEN
214
+ EXECUTE $q$
215
+ DELETE FROM melchizedek_tasks WHERE owner = $1 OR ($2 AND owner LIKE $3)
216
+ $q$ USING p_scope_key, p_include_nested, nested_like;
217
+ GET DIAGNOSTICS n = ROW_COUNT;
218
+ EXECUTE $q$
219
+ DELETE FROM melchizedek_task_owners WHERE owner = $1 OR ($2 AND owner LIKE $3)
220
+ $q$ USING p_scope_key, p_include_nested, nested_like;
221
+ END IF;
222
+ store := 'task_tools'; deleted := n; RETURN NEXT;
223
+
224
+ -- Third-party credentials held for the scope's tools (this migration).
225
+ -- They are filed per app (the memory namespace the run pins), so a
226
+ -- namespace erase removes that app's and keeps the others; an erase
227
+ -- without one removes every app's.
228
+ IF to_regclass('public.melchizedek_tool_credentials') IS NOT NULL THEN
229
+ EXECUTE $q$
230
+ DELETE FROM melchizedek_tool_credentials c
231
+ WHERE ($1 IS NULL OR c.app_name = $1)
232
+ AND (c.user_id = $2 OR ($3 AND c.user_id LIKE $4))
233
+ $q$ USING p_namespace, p_scope_key, p_include_nested, nested_like;
234
+ GET DIAGNOSTICS n = ROW_COUNT;
235
+ ELSE
236
+ n := 0;
237
+ END IF;
238
+ store := 'credentials'; deleted := n; RETURN NEXT;
239
+ END;
240
+ $$;
241
+
242
+ -- No API role may execute it (db/hardening.sql repeats this for every
243
+ -- melchizedek function); the server's role gets it back.
244
+ DO $$
245
+ BEGIN
246
+ REVOKE ALL ON FUNCTION melchizedek_erase_scope(text, text, boolean) FROM PUBLIC;
247
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
248
+ REVOKE ALL ON FUNCTION melchizedek_erase_scope(text, text, boolean) FROM anon;
249
+ END IF;
250
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
251
+ REVOKE ALL ON FUNCTION melchizedek_erase_scope(text, text, boolean) FROM authenticated;
252
+ END IF;
253
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
254
+ GRANT EXECUTE ON FUNCTION melchizedek_erase_scope(text, text, boolean) TO service_role;
255
+ END IF;
256
+ END $$;
257
+
258
+ INSERT INTO melchizedek_schema_version (version, name)
259
+ VALUES (13, '0013_tool_credentials')
260
+ ON CONFLICT (version) DO NOTHING;
@@ -8,6 +8,10 @@
8
8
  * Middleware order, and why:
9
9
  * 1. /healthz, /readyz — unauthenticated, so a load balancer or
10
10
  * Kubernetes probe needs no secret. They reveal nothing but liveness.
11
+ * 1b. The OAuth consent callback, when `toolCredentials.consent` is set
12
+ * (ADR 0085): a provider redirects the person's browser to it, so it
13
+ * cannot carry the bearer. Its own rate limit; the single-use state
14
+ * nonce admits it.
11
15
  * 2. Failed-auth limiter — counts only 401s per IP, so the shared secret
12
16
  * cannot be guessed online at whatever rate the host allows.
13
17
  * 3. Bearer check (when a secret is configured), constant-time.
@@ -22,12 +26,19 @@
22
26
  * the handler cache, the rate-limiter counters. Sessions and memory are
23
27
  * durable when Supabase is configured. Run one replica, or put sticky
24
28
  * routing in front, until the task store is durable.
29
+ *
30
+ * Stores (ADR 0080): `storage` takes a session store and a memory service
31
+ * with either face, the engine's or ADK's. In-process sessions are the
32
+ * engine's InProcessSessionService. A `resolveModel` may still return an ADK
33
+ * model instance: its type is CompileOptions'.
25
34
  */
26
- import type { Express, Request } from 'express';
35
+ import type { Express, Request, RequestHandler } from 'express';
27
36
  import type { Store } from 'express-rate-limit';
28
37
  import type { AgentCard } from '@a2a-js/sdk';
29
38
  import type { TaskStore } from '@a2a-js/sdk/server';
30
- import type { BaseLlm, BaseMemoryService, BaseSessionService } from '@google/adk';
39
+ import type { CompileOptions } from '../compile.ts';
40
+ import type { EitherSessionService } from '../runtime/adkSessionBridge.ts';
41
+ import type { EitherMemoryService } from '../runtime/adkMemoryBridge.ts';
31
42
  import type { SyndicateYamlConfig } from '../loadSyndicate.ts';
32
43
  import type { ProviderEndpoint, ProviderId } from '../models/registry.ts';
33
44
  import type { TurnLock } from './turnLock.ts';
@@ -41,8 +52,27 @@ import type { A2AContext } from './executor.ts';
41
52
  import type { IdentityScheme } from './identity.ts';
42
53
  import type { Policy } from './policy.ts';
43
54
  import type { TaskRecord } from '../observability/metrics.ts';
55
+ import type { OAuthConsent, ToolCredentials } from '../tools/oauthConsent.ts';
44
56
  /** Agent ids address a config (file or "registry:<id>"): a safe charset only. */
45
57
  export declare function isValidAgentId(agentId: string): boolean;
58
+ /**
59
+ * The consent callback (ADR 0085): the provider's redirect, carrying `code`
60
+ * and `state` (or `error`). It never reads a redirect URI from the request,
61
+ * never logs the query, and answers a page that carries no value. The
62
+ * response is never cached and sends no referrer, since its URL holds the
63
+ * code.
64
+ */
65
+ export declare function consentCallback(consent: Pick<OAuthConsent, 'complete'>, opts?: {
66
+ resolveRequest?: A2AAppOptions['resolveRequest'];
67
+ /** The server's bearer secret, when it has one: the authenticator is consulted only behind it. */
68
+ serverSecret?: string;
69
+ /** Refuse a callback whose request the authenticator does not accept. Default true (ADR 0085). */
70
+ requireCallerIdentity?: boolean;
71
+ log?: (m: string) => void;
72
+ warn?: (m: string) => void;
73
+ }): RequestHandler;
74
+ /** What a model resolver returns, as the compiler takes it. */
75
+ type ResolvedModel = ReturnType<NonNullable<CompileOptions['resolveModel']>>;
46
76
  export interface A2AAppOptions {
47
77
  /** The syndicate served at /a2a/*: a YAML name or "registry:<id>". */
48
78
  defaultSyndicate: string;
@@ -58,8 +88,10 @@ export interface A2AAppOptions {
58
88
  * agent id (default: in-process).
59
89
  */
60
90
  storage?: {
61
- sessionService: BaseSessionService;
62
- memoryService?: BaseMemoryService;
91
+ /** The engine's SessionService or ADK's BaseSessionService (ADR 0080). */
92
+ sessionService: EitherSessionService;
93
+ /** The engine's MemoryService or ADK's BaseMemoryService. */
94
+ memoryService?: EitherMemoryService;
63
95
  taskStore?: (agentId: string) => TaskStore;
64
96
  /** Erase everything stored for a scope (DELETE /memory). Without it the route answers 501. */
65
97
  erase?: (scopeKey: string, options: {
@@ -155,6 +187,35 @@ export interface A2AAppOptions {
155
187
  };
156
188
  /** Adopter routes, mounted after authentication and before the A2A routes. */
157
189
  routes?: (app: Express) => void;
190
+ /**
191
+ * Tool credentials for every turn (ADR 0072): the sealed per-user store,
192
+ * and the OAuth consent step (ADR 0085, lib/tools/oauthConsent.ts). With
193
+ * `consent`, a call whose provider the user has not granted ends its task
194
+ * input-required with a `consent_request` data part, and the server mounts
195
+ * the consent callback (GET at the path of the consent's configured
196
+ * redirect URI): it completes the authorization-code flow with PKCE and
197
+ * stores the grant. The callback is reached by the person's browser, so it
198
+ * sits before the bearer check; it has its own rate limit, and when the
199
+ * request carries a credential the authenticator accepts, the caller must
200
+ * be the user the flow is for.
201
+ */
202
+ toolCredentials?: ToolCredentials & {
203
+ /** Callback requests per window per client IP. Default 30 per 15 minutes. */
204
+ callbackLimit?: {
205
+ windowMs: number;
206
+ max: number;
207
+ };
208
+ /**
209
+ * Refuse a callback whose request the authenticator does not accept.
210
+ * Default true: the browser that completes the grant must carry the
211
+ * flow's user's identity (a session cookie or a gateway header that
212
+ * resolveRequest reads), so a forwarded authorization link cannot link
213
+ * someone else's account (ADR 0085). false lets the state nonce alone
214
+ * bind the flow to its user, for a deployment whose browsers carry no
215
+ * identity and that accepts that risk.
216
+ */
217
+ requireCallerIdentity?: boolean;
218
+ };
158
219
  /** Refuse to start when Supabase hardening is missing (public deployments). */
159
220
  requireHardenedDb?: boolean;
160
221
  /** Wall-clock budget per task, ms. 0 = none. */
@@ -206,9 +267,11 @@ export interface A2AAppOptions {
206
267
  /**
207
268
  * Model resolution per request. Default: lib/models/registry.ts with the
208
269
  * caller's X-API-Key scoped to its X-Provider. Override to route through
209
- * your own gateway or credential store.
270
+ * your own gateway or credential store. Returns what
271
+ * CompileOptions.resolveModel returns: a model id, or a model instance
272
+ * (an ADK BaseLlm while the ADK runtime ships).
210
273
  */
211
- resolveModel?: (modelName: string | undefined, ctx: A2AContext) => string | BaseLlm | undefined;
274
+ resolveModel?: (modelName: string | undefined, ctx: A2AContext) => ResolvedModel;
212
275
  /** Bindings applied at every config load (e.g. current_date). */
213
276
  bindings?: () => Record<string, string>;
214
277
  log?: (message: string) => void;
@@ -278,3 +341,4 @@ export declare function compileAgentCard(config: SyndicateYamlConfig, opts: {
278
341
  */
279
342
  export declare function memoryAppName(cfg: SyndicateYamlConfig): string;
280
343
  export declare function createA2AApp(options: A2AAppOptions): Promise<A2AApp>;
344
+ export {};
@@ -8,6 +8,10 @@
8
8
  * Middleware order, and why:
9
9
  * 1. /healthz, /readyz — unauthenticated, so a load balancer or
10
10
  * Kubernetes probe needs no secret. They reveal nothing but liveness.
11
+ * 1b. The OAuth consent callback, when `toolCredentials.consent` is set
12
+ * (ADR 0085): a provider redirects the person's browser to it, so it
13
+ * cannot carry the bearer. Its own rate limit; the single-use state
14
+ * nonce admits it.
11
15
  * 2. Failed-auth limiter — counts only 401s per IP, so the shared secret
12
16
  * cannot be guessed online at whatever rate the host allows.
13
17
  * 3. Bearer check (when a secret is configured), constant-time.
@@ -22,6 +26,11 @@
22
26
  * the handler cache, the rate-limiter counters. Sessions and memory are
23
27
  * durable when Supabase is configured. Run one replica, or put sticky
24
28
  * routing in front, until the task store is durable.
29
+ *
30
+ * Stores (ADR 0080): `storage` takes a session store and a memory service
31
+ * with either face, the engine's or ADK's. In-process sessions are the
32
+ * engine's InProcessSessionService. A `resolveModel` may still return an ADK
33
+ * model instance: its type is CompileOptions'.
25
34
  */
26
35
  import express from 'express';
27
36
  import rateLimit, { ipKeyGenerator } from 'express-rate-limit';
@@ -30,7 +39,7 @@ import { AGENT_CARD_PATH } from '@a2a-js/sdk';
30
39
  import { DefaultRequestHandler, InMemoryTaskStore } from '@a2a-js/sdk/server';
31
40
  import { duplicateInterfacesForLegacy } from '@a2a-js/sdk/compat/v0_3';
32
41
  import { agentCardHandler, jsonRpcHandler, restHandler } from '@a2a-js/sdk/server/express';
33
- import { InMemorySessionService } from '@google/adk';
42
+ import { InProcessSessionService } from '../runtime/sessions.js';
34
43
  import { loadSyndicate, loadSyndicateFromRegistry } from '../loadSyndicate.js';
35
44
  import { providerForModel, resolveModel } from '../models/registry.js';
36
45
  import { createSupabaseServices, hasSupabaseCredentials } from '../persistence/supabaseProvider.js';
@@ -46,6 +55,7 @@ import { memoryCrossesProviders, memoryDestinations } from '../memory/providers.
46
55
  import { A2A_APP_NAME, SyndicateExecutor, TaskLimiter, deriveUserId, requestContextStorage, } from './executor.js';
47
56
  import { HEADER_VALUE_PATTERN, SCOPE_KEY_PATTERN } from './identity.js';
48
57
  import { createMetrics } from '../observability/metrics.js';
58
+ import { ConsentError } from '../tools/oauthConsent.js';
49
59
  const SURFACE_HEADERS = [
50
60
  ['x-surface', 'name'],
51
61
  ['x-surface-guild', 'guild'],
@@ -56,6 +66,78 @@ const SURFACE_HEADERS = [
56
66
  export function isValidAgentId(agentId) {
57
67
  return /^[A-Za-z0-9_.:-]+$/.test(agentId) && !agentId.includes('..');
58
68
  }
69
+ const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
70
+ /** True when the request's bearer is `secret` (constant-time). */
71
+ function bearerMatches(req, secret) {
72
+ const header = req.headers.authorization;
73
+ if (!header || !header.startsWith('Bearer '))
74
+ return false;
75
+ const given = createHash('sha256').update(header.substring(7)).digest();
76
+ return timingSafeEqual(given, createHash('sha256').update(secret).digest());
77
+ }
78
+ /** The page the person's browser lands on: what happened, and nothing else (no code, state or token). */
79
+ function consentPage(title, body) {
80
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(title)}</title></head>`
81
+ + `<body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem;line-height:1.5"><h1 style="font-size:1.25rem">${escapeHtml(title)}</h1><p>${escapeHtml(body)}</p></body></html>`;
82
+ }
83
+ /**
84
+ * The consent callback (ADR 0085): the provider's redirect, carrying `code`
85
+ * and `state` (or `error`). It never reads a redirect URI from the request,
86
+ * never logs the query, and answers a page that carries no value. The
87
+ * response is never cached and sends no referrer, since its URL holds the
88
+ * code.
89
+ */
90
+ export function consentCallback(consent, opts = {}) {
91
+ return async (req, res) => {
92
+ res.set({
93
+ 'Cache-Control': 'no-store',
94
+ Pragma: 'no-cache',
95
+ 'Referrer-Policy': 'no-referrer',
96
+ 'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'",
97
+ 'X-Content-Type-Options': 'nosniff',
98
+ 'X-Frame-Options': 'DENY',
99
+ });
100
+ // When the browser carries a credential the authenticator accepts, the caller must be the flow's user.
101
+ // Behind a server secret the authenticator is asked only once the secret matched, as on every other
102
+ // route: a trusted-header authenticator believes whoever reached it.
103
+ let callerUserId;
104
+ const gated = opts.serverSecret === undefined || bearerMatches(req, opts.serverSecret);
105
+ if (opts.resolveRequest && gated) {
106
+ try {
107
+ callerUserId = (await opts.resolveRequest(req))?.scopeKey;
108
+ }
109
+ catch {
110
+ callerUserId = undefined;
111
+ }
112
+ }
113
+ if (opts.requireCallerIdentity !== false && callerUserId === undefined) {
114
+ opts.warn?.('Consent callback refused: no authenticated caller.');
115
+ res.status(401).type('html').send(consentPage('Authorization not completed', 'Sign in, then open the authorization link again.'));
116
+ return;
117
+ }
118
+ try {
119
+ const done = await consent.complete({
120
+ state: req.query.state,
121
+ code: req.query.code,
122
+ error: req.query.error,
123
+ ...(callerUserId !== undefined ? { callerUserId } : {}),
124
+ });
125
+ opts.log?.(`✓ Consent callback: ${done.provider} granted (scope ${scopeHashOf(done.userId)})`);
126
+ res.status(200).type('html').send(consentPage('Authorization complete', 'You can close this window and send a message in your conversation to continue.'));
127
+ }
128
+ catch (err) {
129
+ if (err instanceof ConsentError) {
130
+ opts.warn?.(`Consent callback refused: ${err.code}${err.provider ? ` (${err.provider})` : ''}`);
131
+ const status = err.code === 'exchange_failed' ? 502 : err.code === 'store_failed' ? 500 : err.code === 'wrong_user' ? 403 : 400;
132
+ res.status(status).type('html').send(consentPage('Authorization not completed', err.message));
133
+ return;
134
+ }
135
+ // Nothing from an unexpected error reaches the page or the log: it may carry a value.
136
+ opts.warn?.('Consent callback failed unexpectedly.');
137
+ res.status(500).type('html').send(consentPage('Authorization not completed', 'The authorization could not be completed. Ask the agent again for a new link.'));
138
+ }
139
+ };
140
+ }
59
141
  const legacyCompat = { enabled: true };
60
142
  /**
61
143
  * The authenticated caller of the request being handled, for adopter routes
@@ -290,7 +372,7 @@ export async function createA2AApp(options) {
290
372
  * even when Supabase is configured (the docs promise "nothing persists"),
291
373
  * and only `long-term` syndicates get the memory service.
292
374
  */
293
- const internalSessions = new InMemorySessionService();
375
+ const internalSessions = new InProcessSessionService();
294
376
  // Long-term memory sends transcripts to its extraction and embedding
295
377
  // providers; say so once per syndicate when they are not the agents' own.
296
378
  const memoryFlowWarned = new Set();
@@ -386,6 +468,7 @@ export async function createA2AApp(options) {
386
468
  streamText: options.streamText,
387
469
  turnLock,
388
470
  turnLockWaitMs: options.turnLockWaitMs,
471
+ ...(options.toolCredentials ? { toolCredentials: { store: options.toolCredentials.store, ...(options.toolCredentials.consent ? { consent: options.toolCredentials.consent } : {}) } } : {}),
389
472
  limiter,
390
473
  agentId,
391
474
  policy: options.policy,
@@ -499,6 +582,28 @@ export async function createA2AApp(options) {
499
582
  }
500
583
  res.json({ status: 'ready', sessions: sessionBackend, inFlight: limiter.inFlight });
501
584
  });
585
+ // ── 1b. The OAuth consent callback (ADR 0085) ─────────────────────────────
586
+ // The provider redirects the person's browser here, so it cannot carry the
587
+ // A2A bearer: it sits before the bearer check, behind its own rate limit.
588
+ // The state nonce (single-use, expiring, bound to the user, the session
589
+ // and the paused call) is what admits it.
590
+ const consent = options.toolCredentials?.consent;
591
+ if (consent) {
592
+ const callbackLimit = options.toolCredentials?.callbackLimit ?? { windowMs: 15 * 60 * 1000, max: 30 };
593
+ app.get(new URL(consent.redirectUri).pathname, rateLimit({
594
+ windowMs: callbackLimit.windowMs,
595
+ max: callbackLimit.max,
596
+ standardHeaders: true,
597
+ legacyHeaders: false,
598
+ message: { error: 'Too many authorization attempts; try again later.' },
599
+ }), consentCallback(consent, {
600
+ resolveRequest: options.resolveRequest,
601
+ ...(options.serverSecret ? { serverSecret: options.serverSecret } : {}),
602
+ requireCallerIdentity: options.toolCredentials?.requireCallerIdentity !== false,
603
+ log,
604
+ warn,
605
+ }));
606
+ }
502
607
  // ── 2–3. Authentication ───────────────────────────────────────────────────
503
608
  const failWindow = options.authFailureLimit ?? { windowMs: 15 * 60 * 1000, max: 30 };
504
609
  app.use(rateLimit({
@@ -8,15 +8,23 @@
8
8
  * cancellation, the per-server concurrency cap, and memory ingestion after
9
9
  * the reply. Every turn semantic — dispatch, delegation, relay fallback,
10
10
  * guards, the step cap — is the runtime's.
11
+ *
12
+ * Its stores are the engine's (ADR 0080): it takes a session store and a
13
+ * memory service with either face, the engine's or ADK's, reads a session
14
+ * through the engine's SessionService, and hands the turn runner the faces
15
+ * its fixed signature names through the bridges
16
+ * (lib/runtime/adkSessionBridge.ts, lib/runtime/adkMemoryBridge.ts).
11
17
  */
12
18
  import { AsyncLocalStorage } from 'node:async_hooks';
13
19
  import type { AgentExecutor, ExecutionEventBus, RequestContext } from '@a2a-js/sdk/server';
14
- import type { BaseMemoryService, BaseSessionService } from '@google/adk';
15
20
  import type { CompileOptions } from '../compile.ts';
16
21
  import type { SyndicateYamlConfig } from '../loadSyndicate.ts';
17
22
  import type { TurnLock } from './turnLock.ts';
23
+ import type { EitherSessionService } from '../runtime/adkSessionBridge.ts';
24
+ import type { EitherMemoryService } from '../runtime/adkMemoryBridge.ts';
18
25
  import type { PendingApproval } from '../runtime/approvals.ts';
19
26
  import type { MessagePart, SyndicateTurnResult } from '../runtime/syndicateTurn.ts';
27
+ import type { ToolCredentials } from '../tools/oauthConsent.ts';
20
28
  import type { TaskRecord } from '../observability/metrics.ts';
21
29
  import type { AuditSink } from '../observability/audit.ts';
22
30
  import type { Policy } from './policy.ts';
@@ -94,8 +102,10 @@ export declare function a2aPartsToMessage(rawParts: unknown[]): {
94
102
  };
95
103
  export interface ExecutorOptions {
96
104
  config: SyndicateYamlConfig;
97
- sessionService: BaseSessionService;
98
- memoryService?: BaseMemoryService;
105
+ /** The conversation store: the engine's SessionService or ADK's BaseSessionService. */
106
+ sessionService: EitherSessionService;
107
+ /** Long-term memory: the engine's MemoryService or ADK's BaseMemoryService. */
108
+ memoryService?: EitherMemoryService;
99
109
  /** Builds the per-request model resolver from the caller's context (BYOK). */
100
110
  compileFor: (ctx: A2AContext) => CompileOptions;
101
111
  /** Wall-clock budget per task in ms; 0 or undefined = none. */
@@ -119,6 +129,8 @@ export interface ExecutorOptions {
119
129
  turnLock?: TurnLock;
120
130
  /** How long a second turn on a busy conversation waits, ms. Default 30 s. */
121
131
  turnLockWaitMs?: number;
132
+ /** Tool credentials and the consent step for every turn (ADR 0072, ADR 0085). */
133
+ toolCredentials?: ToolCredentials;
122
134
  log: (message: string) => void;
123
135
  warn: (message: string) => void;
124
136
  }
@@ -188,6 +200,11 @@ export declare function answerStream(eventBus: ExecutionEventBus, taskId: string
188
200
  export declare class SyndicateExecutor implements AgentExecutor {
189
201
  private readonly opts;
190
202
  private configHash;
203
+ /** The store as the engine reads it. */
204
+ private readonly sessions;
205
+ /** The faces the turn runner's fixed signature names (ADR 0045 item 4). */
206
+ private readonly turnSessions;
207
+ private readonly turnMemory;
191
208
  constructor(opts: ExecutorOptions);
192
209
  /** Provenance stamp for every turn this executor serves (lineage.ts). */
193
210
  configHashFor(): string;