@retinue/agentkit 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +59 -277
  2. package/dist/adapters/audio/openai.d.ts +36 -0
  3. package/dist/adapters/audio/openai.js +136 -0
  4. package/dist/adapters/bullmq/lock.d.ts +26 -2
  5. package/dist/adapters/embeddings/openai.d.ts +45 -0
  6. package/dist/adapters/embeddings/openai.js +109 -0
  7. package/dist/adapters/memory/connections.d.ts +10 -0
  8. package/dist/adapters/memory/connections.js +87 -0
  9. package/dist/adapters/memory/graph.d.ts +17 -0
  10. package/dist/adapters/memory/graph.js +267 -0
  11. package/dist/adapters/memory/index.d.ts +3 -0
  12. package/dist/adapters/memory/index.js +3 -0
  13. package/dist/adapters/memory/rate-limit.d.ts +14 -0
  14. package/dist/adapters/memory/rate-limit.js +28 -0
  15. package/dist/adapters/memory/runtime.js +0 -0
  16. package/dist/adapters/postgres/connections.d.ts +15 -0
  17. package/dist/adapters/postgres/connections.js +134 -0
  18. package/dist/adapters/postgres/graph.d.ts +24 -0
  19. package/dist/adapters/postgres/graph.js +325 -0
  20. package/dist/adapters/postgres/index.d.ts +2 -0
  21. package/dist/adapters/postgres/index.js +2 -0
  22. package/dist/adapters/postgres/migrations.js +198 -0
  23. package/dist/adapters/postgres/run-store.js +26 -2
  24. package/dist/adapters/postgres/schema.d.ts +61 -2
  25. package/dist/adapters/postgres/schema.js +95 -10
  26. package/dist/adapters/redis/index.d.ts +1 -0
  27. package/dist/adapters/redis/index.js +1 -0
  28. package/dist/adapters/redis/rate-limit.d.ts +46 -0
  29. package/dist/adapters/redis/rate-limit.js +55 -0
  30. package/dist/adapters/supabase/index.d.ts +15 -0
  31. package/dist/adapters/supabase/index.js +15 -0
  32. package/dist/adapters/supabase/rls.js +33 -0
  33. package/dist/agents/agent.d.ts +99 -1
  34. package/dist/agents/agent.js +215 -16
  35. package/dist/agents/define.js +24 -4
  36. package/dist/agents/engine.d.ts +48 -0
  37. package/dist/agents/engine.js +370 -13
  38. package/dist/audio/index.d.ts +151 -0
  39. package/dist/audio/index.js +183 -0
  40. package/dist/capabilities/index.d.ts +5 -1
  41. package/dist/capabilities/index.js +23 -0
  42. package/dist/capabilities/runtime.d.ts +8 -0
  43. package/dist/connections/cipher.d.ts +103 -0
  44. package/dist/connections/cipher.js +141 -0
  45. package/dist/connections/index.d.ts +156 -0
  46. package/dist/connections/index.js +72 -0
  47. package/dist/connections/oauth/client.d.ts +84 -0
  48. package/dist/connections/oauth/client.js +141 -0
  49. package/dist/connections/oauth/index.d.ts +166 -0
  50. package/dist/connections/oauth/index.js +226 -0
  51. package/dist/connections/oauth/service.d.ts +90 -0
  52. package/dist/connections/oauth/service.js +132 -0
  53. package/dist/connections/pause.d.ts +68 -0
  54. package/dist/connections/pause.js +97 -0
  55. package/dist/connections/resolver.d.ts +59 -0
  56. package/dist/connections/resolver.js +138 -0
  57. package/dist/connections/resume.d.ts +64 -0
  58. package/dist/connections/resume.js +99 -0
  59. package/dist/connections/vault-cipher.d.ts +95 -0
  60. package/dist/connections/vault-cipher.js +206 -0
  61. package/dist/core/budget.d.ts +55 -0
  62. package/dist/core/budget.js +56 -0
  63. package/dist/core/content-parts.d.ts +26 -2
  64. package/dist/core/content-parts.js +1 -0
  65. package/dist/core/context.d.ts +25 -0
  66. package/dist/core/errors.d.ts +1 -1
  67. package/dist/core/errors.js +15 -0
  68. package/dist/core/events.d.ts +110 -2
  69. package/dist/core/events.js +33 -2
  70. package/dist/core/index.d.ts +1 -0
  71. package/dist/core/index.js +1 -0
  72. package/dist/core/validation.js +19 -0
  73. package/dist/documents/index.d.ts +14 -0
  74. package/dist/documents/parsers/text.d.ts +16 -0
  75. package/dist/documents/parsers/text.js +54 -2
  76. package/dist/entries/connections.d.ts +10 -0
  77. package/dist/entries/connections.js +10 -0
  78. package/dist/entries/guardrails.d.ts +14 -0
  79. package/dist/entries/guardrails.js +14 -0
  80. package/dist/entries/knowledge.d.ts +17 -0
  81. package/dist/entries/knowledge.js +16 -0
  82. package/dist/entries/mcp-server.d.ts +9 -0
  83. package/dist/entries/mcp-server.js +9 -0
  84. package/dist/entries/runtime.d.ts +10 -0
  85. package/dist/entries/runtime.js +8 -0
  86. package/dist/entries/testing.d.ts +28 -0
  87. package/dist/entries/testing.js +28 -0
  88. package/dist/graphql/resolvers.d.ts +7 -1
  89. package/dist/graphql/resolvers.js +20 -0
  90. package/dist/graphql/schema.d.ts +1 -1
  91. package/dist/graphql/schema.js +44 -0
  92. package/dist/guardrails/index.d.ts +115 -0
  93. package/dist/guardrails/index.js +108 -0
  94. package/dist/guardrails/moderation.d.ts +53 -0
  95. package/dist/guardrails/moderation.js +75 -0
  96. package/dist/guardrails/pii.d.ts +75 -0
  97. package/dist/guardrails/pii.js +193 -0
  98. package/dist/knowledge/communities.d.ts +166 -0
  99. package/dist/knowledge/communities.js +377 -0
  100. package/dist/knowledge/graph-global.d.ts +153 -0
  101. package/dist/knowledge/graph-global.js +246 -0
  102. package/dist/knowledge/graph-retrieval.d.ts +150 -0
  103. package/dist/knowledge/graph-retrieval.js +303 -0
  104. package/dist/knowledge/graph.d.ts +223 -0
  105. package/dist/knowledge/graph.js +369 -0
  106. package/dist/knowledge/index.d.ts +25 -0
  107. package/dist/knowledge/index.js +37 -1
  108. package/dist/knowledge/navigate.d.ts +89 -0
  109. package/dist/knowledge/navigate.js +107 -0
  110. package/dist/knowledge/retrieval.d.ts +98 -5
  111. package/dist/knowledge/retrieval.js +158 -28
  112. package/dist/mcp-server/index.d.ts +168 -0
  113. package/dist/mcp-server/index.js +175 -0
  114. package/dist/models/extraction.d.ts +73 -0
  115. package/dist/models/extraction.js +118 -0
  116. package/dist/models/index.d.ts +40 -1
  117. package/dist/models/index.js +22 -1
  118. package/dist/models/pricing.d.ts +2 -0
  119. package/dist/models/pricing.js +17 -1
  120. package/dist/models/provider-factory.js +9 -7
  121. package/dist/models/streaming.d.ts +111 -1
  122. package/dist/models/streaming.js +184 -3
  123. package/dist/persistence/index.d.ts +230 -0
  124. package/dist/persistence/index.js +2 -0
  125. package/dist/runtime/index.d.ts +1 -1
  126. package/dist/runtime/index.js +12 -0
  127. package/dist/runtime/worker.d.ts +36 -1
  128. package/dist/runtime/worker.js +37 -4
  129. package/dist/security/checklist.js +55 -0
  130. package/dist/security/findings.js +18 -9
  131. package/dist/server/bin.d.ts +19 -0
  132. package/dist/server/bin.js +226 -0
  133. package/dist/server/boot.d.ts +20 -2
  134. package/dist/server/boot.js +29 -3
  135. package/dist/server/cli.d.ts +13 -0
  136. package/dist/server/cli.js +7 -4
  137. package/dist/server/doctor.d.ts +74 -0
  138. package/dist/server/doctor.js +280 -0
  139. package/dist/skills/catalogue.d.ts +49 -0
  140. package/dist/skills/catalogue.js +61 -0
  141. package/dist/skills/context.d.ts +61 -0
  142. package/dist/skills/context.js +107 -0
  143. package/dist/skills/index.d.ts +2 -0
  144. package/dist/skills/index.js +2 -0
  145. package/dist/telemetry/spans.js +17 -0
  146. package/dist/testing/conformance/artifact-exports.d.ts +20 -0
  147. package/dist/testing/conformance/artifact-exports.js +172 -0
  148. package/dist/testing/conformance/artifacts.d.ts +11 -0
  149. package/dist/testing/conformance/artifacts.js +316 -0
  150. package/dist/testing/conformance/audio.d.ts +38 -0
  151. package/dist/testing/conformance/audio.js +145 -0
  152. package/dist/testing/conformance/capability.d.ts +23 -0
  153. package/dist/testing/conformance/capability.js +27 -0
  154. package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
  155. package/dist/testing/conformance/checkpoint-store.js +72 -0
  156. package/dist/testing/conformance/connections.d.ts +14 -0
  157. package/dist/testing/conformance/connections.js +171 -0
  158. package/dist/testing/conformance/conversation-store.d.ts +10 -0
  159. package/dist/testing/conformance/conversation-store.js +60 -0
  160. package/dist/testing/conformance/evaluation.d.ts +10 -0
  161. package/dist/testing/conformance/evaluation.js +295 -0
  162. package/dist/testing/conformance/files.d.ts +19 -0
  163. package/dist/testing/conformance/files.js +454 -0
  164. package/dist/testing/conformance/flows.d.ts +16 -0
  165. package/dist/testing/conformance/flows.js +193 -0
  166. package/dist/testing/conformance/graph.d.ts +22 -0
  167. package/dist/testing/conformance/graph.js +500 -0
  168. package/dist/testing/conformance/hitl.d.ts +25 -0
  169. package/dist/testing/conformance/hitl.js +523 -0
  170. package/dist/testing/conformance/index.d.ts +163 -0
  171. package/dist/testing/conformance/index.js +321 -0
  172. package/dist/testing/conformance/invariants.d.ts +23 -0
  173. package/dist/testing/conformance/invariants.js +80 -0
  174. package/dist/testing/conformance/knowledge.d.ts +41 -0
  175. package/dist/testing/conformance/knowledge.js +592 -0
  176. package/dist/testing/conformance/parents.d.ts +50 -0
  177. package/dist/testing/conformance/parents.js +39 -0
  178. package/dist/testing/conformance/rate-limit.d.ts +25 -0
  179. package/dist/testing/conformance/rate-limit.js +71 -0
  180. package/dist/testing/conformance/records.d.ts +36 -0
  181. package/dist/testing/conformance/records.js +400 -0
  182. package/dist/testing/conformance/rollups.d.ts +30 -0
  183. package/dist/testing/conformance/rollups.js +425 -0
  184. package/dist/testing/conformance/run-coordinator.d.ts +27 -0
  185. package/dist/testing/conformance/run-coordinator.js +150 -0
  186. package/dist/testing/conformance/run-event-log.d.ts +9 -0
  187. package/dist/testing/conformance/run-event-log.js +138 -0
  188. package/dist/testing/conformance/run-store.d.ts +12 -0
  189. package/dist/testing/conformance/run-store.js +294 -0
  190. package/dist/testing/conformance/session-state.d.ts +38 -0
  191. package/dist/testing/conformance/session-state.js +198 -0
  192. package/dist/testing/conformance/usage-limits.d.ts +10 -0
  193. package/dist/testing/conformance/usage-limits.js +309 -0
  194. package/dist/testing/conformance.d.ts +8 -0
  195. package/dist/testing/conformance.js +8 -0
  196. package/dist/testing/memory-backend.d.ts +39 -0
  197. package/dist/testing/memory-backend.js +41 -0
  198. package/dist/testing/pglite.d.ts +55 -0
  199. package/dist/testing/pglite.js +78 -0
  200. package/dist/testing/stub-model.d.ts +58 -0
  201. package/dist/testing/stub-model.js +71 -0
  202. package/dist/testing/supabase-storage-double.d.ts +35 -0
  203. package/dist/testing/supabase-storage-double.js +101 -0
  204. package/dist/toolkit/files.d.ts +125 -0
  205. package/dist/toolkit/files.js +320 -0
  206. package/dist/toolkit/http.d.ts +27 -0
  207. package/dist/toolkit/http.js +48 -0
  208. package/dist/toolkit/index.d.ts +31 -1
  209. package/dist/toolkit/index.js +26 -1
  210. package/dist/toolkit/mime.d.ts +97 -0
  211. package/dist/toolkit/mime.js +233 -0
  212. package/dist/toolkit/sandbox.d.ts +119 -0
  213. package/dist/toolkit/sandbox.js +239 -0
  214. package/dist/toolkit/ssrf.d.ts +139 -0
  215. package/dist/toolkit/ssrf.js +349 -0
  216. package/dist/toolkit/vendor.d.ts +91 -0
  217. package/dist/toolkit/vendor.js +153 -0
  218. package/dist/toolkit/web.d.ts +13 -0
  219. package/dist/toolkit/web.js +7 -1
  220. package/dist/tools/budget.d.ts +28 -0
  221. package/dist/tools/budget.js +35 -0
  222. package/dist/tools/credentials.d.ts +316 -0
  223. package/dist/tools/credentials.js +369 -0
  224. package/dist/tools/define.d.ts +45 -0
  225. package/dist/tools/define.js +26 -0
  226. package/dist/tools/find.d.ts +109 -0
  227. package/dist/tools/find.js +210 -0
  228. package/dist/tools/index.d.ts +35 -2
  229. package/dist/tools/index.js +11 -0
  230. package/dist/tools/library/audio.d.ts +63 -0
  231. package/dist/tools/library/audio.js +148 -0
  232. package/dist/tools/library/fs.d.ts +24 -0
  233. package/dist/tools/library/fs.js +102 -0
  234. package/dist/tools/library/index.d.ts +37 -2
  235. package/dist/tools/library/index.js +47 -0
  236. package/dist/tools/library/shell.d.ts +45 -0
  237. package/dist/tools/library/shell.js +70 -0
  238. package/dist/tools/meta-tools.js +8 -0
  239. package/dist/tools/registry.d.ts +146 -0
  240. package/dist/tools/registry.js +250 -4
  241. package/dist/usage/index.d.ts +21 -0
  242. package/dist/usage/index.js +1 -0
  243. package/dist/usage/rate-limit.d.ts +122 -0
  244. package/dist/usage/rate-limit.js +131 -0
  245. package/dist/usage/recorder.js +3 -0
  246. package/package.json +33 -2
@@ -0,0 +1,68 @@
1
+ /**
2
+ * A run that needs a connection pauses and asks — REQ-063 (#259), task #264.
3
+ *
4
+ * A tool call fails because the tenant has no connection, or the one they have expired or lacks a scope. Before
5
+ * this, that was `capability_unavailable`: the run died and a person read an error. Now it **pauses**, hands
6
+ * back a login URL, and resumes when consent completes.
7
+ *
8
+ * ## The third pause, not a new mechanism
9
+ *
10
+ * The runtime already does exactly this shape twice — `waiting-for-question` and `waiting-for-approval`, each
11
+ * with an event the engine emits, a status the worker parks in, and a resume to `queued`. Building a bespoke
12
+ * polling loop, or leaving a failed run for somebody to restart by hand, would be a second mechanism for the
13
+ * thing the durable runtime exists to do.
14
+ *
15
+ * ## Where it does **not** apply
16
+ *
17
+ * Only where a login URL exists. A token-based integration has nothing to redirect to, so a missing token must
18
+ * still fail with the message naming the ref. Getting this backwards produces the worst outcome available: a run
19
+ * that hangs for ever waiting for a consent screen nobody can reach.
20
+ *
21
+ * That decision comes from `ToolkitAuth.modes` (#260) — the reason `AuthMode` and `CredentialScheme` are
22
+ * separate axes. An OAuth access token is presented as a bearer, so a design that only knew the wire format
23
+ * could not answer this question at all.
24
+ */
25
+ import { AgentPlatformError } from "../core/errors.js";
26
+ import type { AuthMode } from "../tools/credentials.js";
27
+ /**
28
+ * Why a connection is not usable. Three triggers, all of which should pause.
29
+ *
30
+ * Separate values because a UI says different things: "connect your GitHub" is not "your GitHub connection
31
+ * expired" is not "grant one more permission", and a person who is told the wrong one goes looking in the wrong
32
+ * place.
33
+ */
34
+ export declare const CONNECTION_GAPS: readonly ["absent", "expired", "insufficient-scope"];
35
+ export type ConnectionGap = (typeof CONNECTION_GAPS)[number];
36
+ export type ConnectionNeed = {
37
+ readonly provider: string;
38
+ readonly gap: ConnectionGap;
39
+ /** Scopes the consent must ask for. For `insufficient-scope`, the missing ones plus what was already granted. */
40
+ readonly scopes: readonly string[];
41
+ readonly toolName?: string;
42
+ };
43
+ /**
44
+ * Recognises a failure as *a missing connection* rather than a broken tool.
45
+ *
46
+ * Structural rather than a string match on the message: a `details.connectionGap` set by the resolver, so
47
+ * rewording an error cannot silently turn a pausable failure into a fatal one. The marker is added by the
48
+ * connection resolver, which is the only code that knows the difference.
49
+ */
50
+ export declare const connectionNeedOf: (thrown: unknown) => ConnectionNeed | null;
51
+ /** Attaches the marker above to an error, so the engine can recognise it without parsing prose. */
52
+ export declare const withConnectionGap: (error: AgentPlatformError, need: ConnectionNeed) => AgentPlatformError;
53
+ /**
54
+ * Whether this provider can be connected by sending somebody somewhere — the branch that decides pause or fail.
55
+ *
56
+ * `undefined` modes means the host has not declared any, and the honest answer there is **no**: pausing a run
57
+ * for a provider that turns out to have no flow would hang it for ever, and failing is recoverable.
58
+ */
59
+ export declare const canPauseForConsent: (modes: readonly AuthMode[] | undefined) => boolean;
60
+ /**
61
+ * The message a person sees, per gap.
62
+ *
63
+ * Written here rather than in a UI so every client says the same thing, and so the distinction between the
64
+ * three gaps survives — a person told "connect your GitHub" when the real problem is a missing scope will
65
+ * disconnect and reconnect and land in exactly the same place.
66
+ */
67
+ export declare const consentPrompt: (need: ConnectionNeed) => string;
68
+ //# sourceMappingURL=pause.d.ts.map
@@ -0,0 +1,97 @@
1
+ /**
2
+ * A run that needs a connection pauses and asks — REQ-063 (#259), task #264.
3
+ *
4
+ * A tool call fails because the tenant has no connection, or the one they have expired or lacks a scope. Before
5
+ * this, that was `capability_unavailable`: the run died and a person read an error. Now it **pauses**, hands
6
+ * back a login URL, and resumes when consent completes.
7
+ *
8
+ * ## The third pause, not a new mechanism
9
+ *
10
+ * The runtime already does exactly this shape twice — `waiting-for-question` and `waiting-for-approval`, each
11
+ * with an event the engine emits, a status the worker parks in, and a resume to `queued`. Building a bespoke
12
+ * polling loop, or leaving a failed run for somebody to restart by hand, would be a second mechanism for the
13
+ * thing the durable runtime exists to do.
14
+ *
15
+ * ## Where it does **not** apply
16
+ *
17
+ * Only where a login URL exists. A token-based integration has nothing to redirect to, so a missing token must
18
+ * still fail with the message naming the ref. Getting this backwards produces the worst outcome available: a run
19
+ * that hangs for ever waiting for a consent screen nobody can reach.
20
+ *
21
+ * That decision comes from `ToolkitAuth.modes` (#260) — the reason `AuthMode` and `CredentialScheme` are
22
+ * separate axes. An OAuth access token is presented as a bearer, so a design that only knew the wire format
23
+ * could not answer this question at all.
24
+ */
25
+ import { AgentPlatformError, isAgentPlatformError } from "../core/errors.js";
26
+ /**
27
+ * Why a connection is not usable. Three triggers, all of which should pause.
28
+ *
29
+ * Separate values because a UI says different things: "connect your GitHub" is not "your GitHub connection
30
+ * expired" is not "grant one more permission", and a person who is told the wrong one goes looking in the wrong
31
+ * place.
32
+ */
33
+ export const CONNECTION_GAPS = ["absent", "expired", "insufficient-scope"];
34
+ /**
35
+ * Recognises a failure as *a missing connection* rather than a broken tool.
36
+ *
37
+ * Structural rather than a string match on the message: a `details.connectionGap` set by the resolver, so
38
+ * rewording an error cannot silently turn a pausable failure into a fatal one. The marker is added by the
39
+ * connection resolver, which is the only code that knows the difference.
40
+ */
41
+ export const connectionNeedOf = (thrown) => {
42
+ if (!isAgentPlatformError(thrown))
43
+ return null;
44
+ const details = thrown.details;
45
+ const gap = details?.["connectionGap"];
46
+ const provider = details?.["connectionProvider"];
47
+ if (typeof gap !== "string" || !CONNECTION_GAPS.includes(gap))
48
+ return null;
49
+ if (typeof provider !== "string" || provider === "")
50
+ return null;
51
+ const scopes = details?.["connectionScopes"];
52
+ return {
53
+ provider,
54
+ gap: gap,
55
+ scopes: Array.isArray(scopes) ? scopes.filter((s) => typeof s === "string") : [],
56
+ ...(typeof details?.["toolName"] === "string" ? { toolName: details["toolName"] } : {}),
57
+ };
58
+ };
59
+ /** Attaches the marker above to an error, so the engine can recognise it without parsing prose. */
60
+ export const withConnectionGap = (error, need) => new AgentPlatformError({
61
+ code: error.code,
62
+ message: error.message,
63
+ retryable: false,
64
+ details: {
65
+ ...(error.details ?? {}),
66
+ connectionGap: need.gap,
67
+ connectionProvider: need.provider,
68
+ connectionScopes: [...need.scopes],
69
+ ...(need.toolName === undefined ? {} : { toolName: need.toolName }),
70
+ },
71
+ });
72
+ /**
73
+ * Whether this provider can be connected by sending somebody somewhere — the branch that decides pause or fail.
74
+ *
75
+ * `undefined` modes means the host has not declared any, and the honest answer there is **no**: pausing a run
76
+ * for a provider that turns out to have no flow would hang it for ever, and failing is recoverable.
77
+ */
78
+ export const canPauseForConsent = (modes) => modes !== undefined && modes.includes("oauth2");
79
+ /**
80
+ * The message a person sees, per gap.
81
+ *
82
+ * Written here rather than in a UI so every client says the same thing, and so the distinction between the
83
+ * three gaps survives — a person told "connect your GitHub" when the real problem is a missing scope will
84
+ * disconnect and reconnect and land in exactly the same place.
85
+ */
86
+ export const consentPrompt = (need) => {
87
+ switch (need.gap) {
88
+ case "absent":
89
+ return `Connect ${need.provider} to continue.`;
90
+ case "expired":
91
+ return `Your ${need.provider} connection expired. Reconnect to continue.`;
92
+ case "insufficient-scope":
93
+ return (`Your ${need.provider} connection needs ${need.scopes.join(", ") || "additional permissions"}. ` +
94
+ "Reconnect and grant them to continue.");
95
+ }
96
+ };
97
+ //# sourceMappingURL=pause.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * A `CredentialResolver` over stored connections — the half that makes #260 useful, task #261.
3
+ *
4
+ * #260 made a credential a typed value and shipped one resolver: a static map, which serves exactly one tenant.
5
+ * This is the multi-tenant one, and it is the piece the whole integrations milestone was waiting for.
6
+ *
7
+ * ## Resolution is per call, and that is not an implementation detail
8
+ *
9
+ * `credentials.ts` explains why: a credential read once at startup survives its own rotation, and the failure
10
+ * looks like the vendor rejecting a token that "has not changed". This resolver therefore reads the store on
11
+ * every call and caches nothing. A deployment that wants caching adds it knowing its own rotation window —
12
+ * which is a decision only the deployment can make.
13
+ */
14
+ import { type CredentialResolver, type CredentialScheme } from "../tools/credentials.js";
15
+ import type { SecretCipher } from "./cipher.js";
16
+ import { type Connection, type ConnectionStore } from "./index.js";
17
+ export type ConnectionResolverDeps = {
18
+ readonly store: ConnectionStore;
19
+ readonly cipher: SecretCipher;
20
+ /**
21
+ * Which connection a bare `<provider>` ref means when a tenant has several.
22
+ *
23
+ * Defaults to the oldest, which is stable and explainable — "the first one you connected" — rather than
24
+ * newest, which would silently re-point every agent the moment somebody adds a second account.
25
+ */
26
+ readonly chooseDefault?: (candidates: readonly Connection[]) => Connection | undefined;
27
+ };
28
+ export declare const createConnectionCredentialResolver: (deps: ConnectionResolverDeps) => CredentialResolver;
29
+ /** Which scheme a connection presents, for `assertToolkitAuth` at wiring time. */
30
+ export declare const schemeOf: (connection: Connection) => CredentialScheme;
31
+ /**
32
+ * Re-seals every connection that is not already under the current key — task #261 AC-7.
33
+ *
34
+ * Rotation without this is theory: `SecretCipher` can *open* an old key's secrets, but nothing moves them
35
+ * forward, so the old key can never be retired and the first key is permanent in practice.
36
+ *
37
+ * The order is what makes it safe to interrupt. Each connection is opened, re-sealed and written **one at a
38
+ * time**, and a failure leaves everything before it done and everything after it untouched — both readable,
39
+ * because the old key is still configured. Running it twice is a no-op: a connection already on the current key
40
+ * is skipped.
41
+ *
42
+ * Deliberately not a transaction over the whole tenant. A rotation across thousands of connections held in one
43
+ * transaction is a long-running write that blocks and, if it fails at the end, achieves nothing.
44
+ */
45
+ export declare const resealConnections: (input: {
46
+ readonly store: ConnectionStore;
47
+ readonly cipher: SecretCipher;
48
+ readonly tenantId: string;
49
+ /** Reported per connection, so a long rotation is observable rather than silent. */
50
+ readonly onProgress?: (progress: {
51
+ readonly id: string;
52
+ readonly from: string;
53
+ readonly to: string;
54
+ }) => void;
55
+ }) => Promise<{
56
+ readonly resealed: number;
57
+ readonly skipped: number;
58
+ }>;
59
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1,138 @@
1
+ /**
2
+ * A `CredentialResolver` over stored connections — the half that makes #260 useful, task #261.
3
+ *
4
+ * #260 made a credential a typed value and shipped one resolver: a static map, which serves exactly one tenant.
5
+ * This is the multi-tenant one, and it is the piece the whole integrations milestone was waiting for.
6
+ *
7
+ * ## Resolution is per call, and that is not an implementation detail
8
+ *
9
+ * `credentials.ts` explains why: a credential read once at startup survives its own rotation, and the failure
10
+ * looks like the vendor rejecting a token that "has not changed". This resolver therefore reads the store on
11
+ * every call and caches nothing. A deployment that wants caching adds it knowing its own rotation window —
12
+ * which is a decision only the deployment can make.
13
+ */
14
+ import { AgentPlatformError } from "../core/errors.js";
15
+ import { createCredential, } from "../tools/credentials.js";
16
+ import { parseCredentialRef } from "./index.js";
17
+ import { withConnectionGap } from "./pause.js";
18
+ /**
19
+ * How the plaintext behind a connection maps back to a `Credential`.
20
+ *
21
+ * The sealed blob holds **only** the secret parts — the token, or the password of a basic pair. Everything
22
+ * non-secret (the username, the header name, the metadata) is stored in readable columns, so listing
23
+ * connections needs no key and a person can see *which account* a connection is for without decrypting
24
+ * anything.
25
+ */
26
+ const toCredential = (connection, plaintext) => {
27
+ const shared = {
28
+ mode: connection.mode,
29
+ ...(connection.metadata === undefined ? {} : { metadata: connection.metadata }),
30
+ };
31
+ switch (connection.scheme) {
32
+ case "bearer":
33
+ return createCredential({ scheme: "bearer", token: plaintext, ...shared });
34
+ case "basic": {
35
+ // The username lives in metadata because it is not a secret and a person needs to see it.
36
+ const username = connection.metadata?.username;
37
+ if (username === undefined)
38
+ throw new AgentPlatformError({
39
+ code: "invalid_input",
40
+ message: `connection "${connection.id}" is a basic credential with no \`metadata.username\`. The username is ` +
41
+ "stored readable on purpose — a connection list should show which account it is for without a key.",
42
+ retryable: false,
43
+ });
44
+ return createCredential({ scheme: "basic", username, password: plaintext, ...shared });
45
+ }
46
+ case "custom-header": {
47
+ const header = connection.metadata?.header;
48
+ if (header === undefined)
49
+ throw new AgentPlatformError({
50
+ code: "invalid_input",
51
+ message: `connection "${connection.id}" is a custom-header credential with no \`metadata.header\``,
52
+ retryable: false,
53
+ });
54
+ return createCredential({ scheme: "custom-header", header, value: plaintext, ...shared });
55
+ }
56
+ }
57
+ };
58
+ export const createConnectionCredentialResolver = (deps) => {
59
+ const chooseDefault = deps.chooseDefault ?? ((candidates) => candidates[0]);
60
+ return {
61
+ async resolve({ ref, context }) {
62
+ const { provider, id } = parseCredentialRef(ref);
63
+ const connection = id === undefined
64
+ ? chooseDefault(await deps.store.list({ tenantId: context.tenantId, provider }))
65
+ : ((await deps.store.get({ tenantId: context.tenantId, id })) ?? undefined);
66
+ if (connection === undefined || connection === null)
67
+ // Marked, so the engine can recognise it as *a missing connection* rather than a broken tool and pause
68
+ // the run instead of failing it — #264. Structural, not a string match: rewording this message must not
69
+ // silently turn a pausable failure into a fatal one.
70
+ throw withConnectionGap(new AgentPlatformError({
71
+ code: "capability_unavailable",
72
+ message: `no ${provider} connection for this workspace` +
73
+ (id === undefined ? "" : ` with id "${id}"`) +
74
+ ". Connect one, or check the credential reference.",
75
+ retryable: false,
76
+ }), { provider, gap: "absent", scopes: [] });
77
+ // A ref naming one provider must not resolve to another's credential, which is what a wrong id would do.
78
+ if (id !== undefined && connection.provider !== provider)
79
+ throw new AgentPlatformError({
80
+ code: "invalid_input",
81
+ message: `connection "${id}" is a ${connection.provider} connection, not ${provider}`,
82
+ retryable: false,
83
+ });
84
+ /**
85
+ * Expiry is reported, not refreshed — refresh is #233's, deliberately.
86
+ *
87
+ * Saying "expired" rather than sending it is the difference between a message an operator can act on and
88
+ * a vendor 401 that says the token is invalid.
89
+ */
90
+ if (connection.expiresAt !== undefined && Date.parse(connection.expiresAt) <= Date.now())
91
+ throw withConnectionGap(new AgentPlatformError({
92
+ code: "capability_unavailable",
93
+ message: `the ${provider} connection expired at ${connection.expiresAt}. Reconnect, or wire a refresh.`,
94
+ retryable: false,
95
+ }), { provider, gap: "expired", scopes: connection.grantedScopes ?? [] });
96
+ const plaintext = await deps.cipher.open(connection.sealed);
97
+ return toCredential(connection, plaintext);
98
+ },
99
+ };
100
+ };
101
+ /** Which scheme a connection presents, for `assertToolkitAuth` at wiring time. */
102
+ export const schemeOf = (connection) => connection.scheme;
103
+ /**
104
+ * Re-seals every connection that is not already under the current key — task #261 AC-7.
105
+ *
106
+ * Rotation without this is theory: `SecretCipher` can *open* an old key's secrets, but nothing moves them
107
+ * forward, so the old key can never be retired and the first key is permanent in practice.
108
+ *
109
+ * The order is what makes it safe to interrupt. Each connection is opened, re-sealed and written **one at a
110
+ * time**, and a failure leaves everything before it done and everything after it untouched — both readable,
111
+ * because the old key is still configured. Running it twice is a no-op: a connection already on the current key
112
+ * is skipped.
113
+ *
114
+ * Deliberately not a transaction over the whole tenant. A rotation across thousands of connections held in one
115
+ * transaction is a long-running write that blocks and, if it fails at the end, achieves nothing.
116
+ */
117
+ export const resealConnections = async (input) => {
118
+ const current = input.cipher.currentKeyId();
119
+ const connections = await input.store.list({ tenantId: input.tenantId });
120
+ let resealed = 0;
121
+ let skipped = 0;
122
+ for (const connection of connections) {
123
+ if (connection.sealed.keyId === current) {
124
+ skipped += 1;
125
+ continue;
126
+ }
127
+ const plaintext = await input.cipher.open(connection.sealed);
128
+ await input.store.update({
129
+ tenantId: input.tenantId,
130
+ id: connection.id,
131
+ patch: { sealed: await input.cipher.seal(plaintext) },
132
+ });
133
+ input.onProgress?.({ id: connection.id, from: connection.sealed.keyId, to: current });
134
+ resealed += 1;
135
+ }
136
+ return { resealed, skipped };
137
+ };
138
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Resuming a run once consent completes — REQ-063 (#259), task #264, AC-3, AC-5, AC-6 and AC-7.
3
+ *
4
+ * The mirror of `hitl`'s `resumeRun`: transition back to `queued` and enqueue. The run picks up from its
5
+ * checkpoint and re-attempts the tool call that paused it, so nothing before the pause is repeated — the same
6
+ * idempotency the approval path already relies on.
7
+ */
8
+ import type { ExecutionContext } from "../core/context.js";
9
+ import type { RunId } from "../core/ids.js";
10
+ import type { RunEventLog } from "../core/events.js";
11
+ import type { JobDispatcher } from "../runtime/index.js";
12
+ import type { RunStore } from "../persistence/index.js";
13
+ export type ConsentResumeDeps = {
14
+ readonly runs: RunStore;
15
+ readonly dispatcher: JobDispatcher;
16
+ readonly events?: RunEventLog;
17
+ readonly now?: () => string;
18
+ };
19
+ /**
20
+ * Resumes a run that was waiting for a connection.
21
+ *
22
+ * ## Only the right principal — AC-5
23
+ *
24
+ * This is the security property of the whole feature, and it is not about the run: it is about the **login
25
+ * URL**. That URL is rendered in a UI, appears in screenshots and may be pasted into a chat. If completing the
26
+ * flow it points at could resume *anyone's* run, a leaked URL would let a stranger attach their own account to
27
+ * somebody else's tenant — and every subsequent action the agent took against that provider would be theirs.
28
+ *
29
+ * So the run must belong to the caller's tenant. The OAuth flow has already bound its `state` to the tenant and
30
+ * the principal who began it (#262), which is the other half: the state cannot be redeemed by another person,
31
+ * and the resume cannot be driven from another tenant.
32
+ *
33
+ * ## Idempotent — AC-6
34
+ *
35
+ * A double-click, a retried redirect, a browser prefetch: the callback firing twice must queue the run **once**.
36
+ * `transition` is guarded by the current status, so a run already moved out of `waiting-for-connection` is
37
+ * reported as `resumed: false` rather than enqueued again — and a second job would mean the tool call that
38
+ * paused the run runs twice.
39
+ */
40
+ export declare const resumeAfterConsent: (deps: ConsentResumeDeps, input: {
41
+ readonly context: ExecutionContext;
42
+ readonly runId: RunId;
43
+ readonly provider: string;
44
+ readonly connectionId: string;
45
+ }) => Promise<{
46
+ readonly resumed: boolean;
47
+ readonly reason?: string;
48
+ }>;
49
+ /**
50
+ * Fails a run that has been waiting for a connection longer than the deployment allows — AC-7.
51
+ *
52
+ * A pause with no ceiling is a run that holds a row for ever, and the reaper does not sweep it: the reaper
53
+ * exists for *abandoned* runs, and this one is not abandoned — it is waiting for a person who is never coming
54
+ * back. So it needs its own ceiling, and failing with a reason beats a row nobody can explain.
55
+ */
56
+ export declare const expireStaleConsents: (deps: ConsentResumeDeps & {
57
+ readonly listWaiting: (before: string) => Promise<readonly {
58
+ readonly tenantId: string;
59
+ readonly id: RunId;
60
+ }[]>;
61
+ }, input: {
62
+ readonly olderThanMs: number;
63
+ }) => Promise<number>;
64
+ //# sourceMappingURL=resume.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Resuming a run once consent completes — REQ-063 (#259), task #264, AC-3, AC-5, AC-6 and AC-7.
3
+ *
4
+ * The mirror of `hitl`'s `resumeRun`: transition back to `queued` and enqueue. The run picks up from its
5
+ * checkpoint and re-attempts the tool call that paused it, so nothing before the pause is repeated — the same
6
+ * idempotency the approval path already relies on.
7
+ */
8
+ import { AgentPlatformError } from "../core/errors.js";
9
+ /**
10
+ * Resumes a run that was waiting for a connection.
11
+ *
12
+ * ## Only the right principal — AC-5
13
+ *
14
+ * This is the security property of the whole feature, and it is not about the run: it is about the **login
15
+ * URL**. That URL is rendered in a UI, appears in screenshots and may be pasted into a chat. If completing the
16
+ * flow it points at could resume *anyone's* run, a leaked URL would let a stranger attach their own account to
17
+ * somebody else's tenant — and every subsequent action the agent took against that provider would be theirs.
18
+ *
19
+ * So the run must belong to the caller's tenant. The OAuth flow has already bound its `state` to the tenant and
20
+ * the principal who began it (#262), which is the other half: the state cannot be redeemed by another person,
21
+ * and the resume cannot be driven from another tenant.
22
+ *
23
+ * ## Idempotent — AC-6
24
+ *
25
+ * A double-click, a retried redirect, a browser prefetch: the callback firing twice must queue the run **once**.
26
+ * `transition` is guarded by the current status, so a run already moved out of `waiting-for-connection` is
27
+ * reported as `resumed: false` rather than enqueued again — and a second job would mean the tool call that
28
+ * paused the run runs twice.
29
+ */
30
+ export const resumeAfterConsent = async (deps, input) => {
31
+ const now = deps.now ?? (() => new Date().toISOString());
32
+ const run = await deps.runs.findById({ tenantId: input.context.tenantId, id: input.runId });
33
+ if (run === null) {
34
+ /**
35
+ * Absent and not-yours are the same answer, deliberately.
36
+ *
37
+ * Distinguishing them tells a caller whether a run id exists in another tenant, which is exactly the probe
38
+ * a leaked login URL would be used for. `findById` is already tenant-scoped, so this branch covers both.
39
+ */
40
+ throw new AgentPlatformError({
41
+ code: "not_found",
42
+ message: `no run "${input.runId}" for this workspace`,
43
+ retryable: false,
44
+ });
45
+ }
46
+ if (run.status !== "waiting-for-connection") {
47
+ // Not an error: a retried callback on an already-resumed run is a normal thing a browser does.
48
+ return { resumed: false, reason: `the run is ${run.status}, not waiting for a connection` };
49
+ }
50
+ if (deps.events !== undefined) {
51
+ // Recorded before the transition, so a crash between the two leaves a log that says what happened rather
52
+ // than a run that moved for no visible reason.
53
+ await deps.events.append({
54
+ tenantId: input.context.tenantId,
55
+ event: {
56
+ type: "connection.completed",
57
+ runId: input.runId,
58
+ sequence: 0,
59
+ occurredAt: now(),
60
+ provider: input.provider,
61
+ connectionId: input.connectionId,
62
+ },
63
+ });
64
+ }
65
+ await deps.runs.transition({
66
+ tenantId: input.context.tenantId,
67
+ id: input.runId,
68
+ workerId: "connections",
69
+ to: "queued",
70
+ now: now(),
71
+ });
72
+ await deps.dispatcher.enqueueRun({ tenantId: input.context.tenantId, runId: input.runId });
73
+ return { resumed: true };
74
+ };
75
+ /**
76
+ * Fails a run that has been waiting for a connection longer than the deployment allows — AC-7.
77
+ *
78
+ * A pause with no ceiling is a run that holds a row for ever, and the reaper does not sweep it: the reaper
79
+ * exists for *abandoned* runs, and this one is not abandoned — it is waiting for a person who is never coming
80
+ * back. So it needs its own ceiling, and failing with a reason beats a row nobody can explain.
81
+ */
82
+ export const expireStaleConsents = async (deps, input) => {
83
+ const now = deps.now ?? (() => new Date().toISOString());
84
+ const cutoff = new Date(Date.parse(now()) - input.olderThanMs).toISOString();
85
+ const stale = await deps.listWaiting(cutoff);
86
+ let failed = 0;
87
+ for (const run of stale) {
88
+ await deps.runs.transition({
89
+ tenantId: run.tenantId,
90
+ id: run.id,
91
+ workerId: "connections",
92
+ to: "failed",
93
+ now: now(),
94
+ });
95
+ failed += 1;
96
+ }
97
+ return failed;
98
+ };
99
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * A `SecretCipher` over Supabase Vault — REQ-063 (#259), task #268.
3
+ *
4
+ * #261 shipped the seam with an app-side AES-256-GCM implementation and deliberately left this one out: there
5
+ * was no Vault to run it against, and an untested crypto implementation behind a seam that *looks* tested is
6
+ * worse than none. There is one now, and everything below has been run against it.
7
+ *
8
+ * ## The mapping, and the property it changes — AC-3
9
+ *
10
+ * Vault owns the ciphertext and hands back an **id**. The seam expects the caller to hold
11
+ * `{ keyId, algorithm, nonce, ciphertext }`. So:
12
+ *
13
+ * | Field | Holds |
14
+ * |---|---|
15
+ * | `algorithm` | `supabase-vault` — a sentinel, not a cipher name. The algorithm is Vault's business. |
16
+ * | `keyId` | `supabase-vault` — see the rotation note. Vault's own `key_id` column comes back **null**: the root key is held outside the database and Supabase does not expose an identity for it. |
17
+ * | `nonce` | A random **binding token**, not a nonce. See the tamper note. |
18
+ * | `ciphertext` | The Vault secret's **uuid**. Not ciphertext. |
19
+ *
20
+ * **A Vault-sealed row contains no secret material at all.** That is the property worth stating rather than
21
+ * discovering, and it cuts both ways:
22
+ *
23
+ * - A backup of the application's own tables carries no credentials — strictly better than app-side sealing,
24
+ * where the backup carries ciphertext that a leaked key would open.
25
+ * - The secret lives in `vault.secrets`, so **restoring the application tables without the `vault` schema
26
+ * leaves every connection pointing at nothing.** With app-side sealing the ciphertext travels with the row;
27
+ * here it does not, and a backup strategy that covers `public` and not `vault` silently loses every
28
+ * credential. That is a deployment fact, not a code one, and it belongs in a runbook.
29
+ *
30
+ * ## Deletion — AC-3, and a gap this found
31
+ *
32
+ * Deleting a connection row does **not** delete the Vault secret: the row held a pointer, and dropping a
33
+ * pointer leaves what it pointed at. `docs/18`'s deletion promises are therefore *not* satisfied by deleting
34
+ * the row alone, which is a difference from app-side sealing that nobody would notice until an audit.
35
+ *
36
+ * So `SecretCipher` gained an optional `forget`. App-side sealing has nothing to implement — the ciphertext
37
+ * dies with the row — and this implementation removes the Vault secret. A deployment on Vault that never calls
38
+ * it accumulates orphaned secrets that outlive the connections they belonged to.
39
+ *
40
+ * ## Tampering — AC-2, and where the guarantee genuinely differs
41
+ *
42
+ * AES-GCM authenticates: a flipped byte fails rather than decrypting to something else. A pointer cannot be
43
+ * authenticated by the thing it points at, so an attacker able to *write* the connections table could repoint
44
+ * one connection at another secret in the same Vault and read a credential they were not entitled to. That is
45
+ * a real weakening and it is not hypothetical — it is exactly the shape of a tenant-isolation bypass.
46
+ *
47
+ * Closed here with a **binding token**: `seal` stores a fresh 32-byte random value in the Vault secret's
48
+ * `description` and keeps it in the sealed value's `nonce` field; `open` compares them in constant time and
49
+ * refuses on mismatch. Repointing at another secret now fails, because that secret carries a different token.
50
+ * No application-side key is involved, so the "database cannot decrypt on its own" property is untouched.
51
+ *
52
+ * It is not equivalent to AEAD. An attacker who can write *both* the connections table and `vault.secrets`
53
+ * defeats it, where GCM would still fail. Stated rather than implied.
54
+ */
55
+ import type { SecretCipher } from "./cipher.js";
56
+ import type { SqlExecutor } from "../adapters/postgres/sql.js";
57
+ /**
58
+ * The sentinel in `algorithm`, and the reason it is not a cipher name.
59
+ *
60
+ * A reader of a stored row needs to know *which implementation* sealed it, not which primitive. `aes-256-gcm`
61
+ * tells the app-side cipher how to open the value; `supabase-vault` tells this one that the value is a pointer.
62
+ * Putting libsodium's actual construction here would be a claim about Vault's internals that could stop being
63
+ * true without this package noticing.
64
+ */
65
+ export declare const SUPABASE_VAULT: "supabase-vault";
66
+ export type VaultCipherOptions = {
67
+ /**
68
+ * Runs the `vault.*` calls. A **service-role** executor: `vault.decrypted_secrets` is not readable by
69
+ * `anon` or `authenticated`, which is most of what makes Vault worth using.
70
+ */
71
+ readonly sql: SqlExecutor;
72
+ /**
73
+ * Prefix for the Vault secret's `name`.
74
+ *
75
+ * Named at all because `vault.secrets.name` is unique and a deployment sharing a database with anything else
76
+ * that writes to Vault would otherwise collide. The name carries no secret and is not used for integrity —
77
+ * that is the token's job.
78
+ */
79
+ readonly namePrefix?: string;
80
+ /** Injectable so a test can assert the token is fresh per seal rather than trusting that it is. */
81
+ readonly randomToken?: () => string;
82
+ };
83
+ export declare const createSupabaseVaultCipher: (options: VaultCipherOptions) => SecretCipher;
84
+ /**
85
+ * Rotation is Vault's, not ours — AC-4, stated as a value so a test can assert it.
86
+ *
87
+ * `resealConnections` walks `sealed.keyId`; this cipher's is constant, so that walk finds nothing forever.
88
+ * Correct, and indistinguishable from a misconfigured rotation job unless somebody wrote it down.
89
+ */
90
+ export declare const vaultRotationIsExternal: {
91
+ readonly cipher: "supabase-vault";
92
+ readonly resealFinds: "nothing, by design";
93
+ readonly why: string;
94
+ };
95
+ //# sourceMappingURL=vault-cipher.d.ts.map