@zackbart/connecta 0.28.1 → 0.29.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 (319) hide show
  1. package/CHANGELOG.md +788 -5
  2. package/README.md +62 -42
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1584 -1580
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +93 -71
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +59 -7
  52. package/dist/connectors/api-connector.js +106 -19
  53. package/dist/connectors/api.js +4 -1
  54. package/dist/connectors/guarded-fetch.d.ts +144 -0
  55. package/dist/connectors/guarded-fetch.js +84 -35
  56. package/dist/connectors/negotiation-cache.js +46 -0
  57. package/dist/connectors/option-shapes.js +27 -0
  58. package/dist/connectors/remote-mcp.d.ts +30 -4
  59. package/dist/connectors/remote-mcp.js +1774 -320
  60. package/dist/connectors/resource-uri.js +335 -0
  61. package/dist/credential-contract.d.ts +10 -0
  62. package/dist/credential-rules.js +1 -3
  63. package/dist/credential-url.js +108 -0
  64. package/dist/credentials.d.ts +6 -1
  65. package/dist/credentials.js +27 -18
  66. package/dist/d1.d.ts +130 -0
  67. package/dist/d1.js +290 -0
  68. package/dist/describe-config.d.ts +165 -0
  69. package/dist/describe-config.js +315 -0
  70. package/dist/described.js +106 -0
  71. package/dist/downstream-input-context.js +119 -0
  72. package/dist/downstream-input.js +323 -0
  73. package/dist/errors.d.ts +97 -20
  74. package/dist/errors.js +158 -80
  75. package/dist/execute.js +453 -305
  76. package/dist/executor-admission.d.ts +1 -0
  77. package/dist/executor-admission.js +11 -15
  78. package/dist/executor-contract.d.ts +20 -0
  79. package/dist/executor-contract.js +48 -0
  80. package/dist/executor-result.js +24 -9
  81. package/dist/executors/quickjs-child.js +3 -4
  82. package/dist/executors/quickjs-runtime.js +147 -59
  83. package/dist/executors/quickjs.js +29 -35
  84. package/dist/favicon.js +1 -2
  85. package/dist/fix-prompt.js +1 -3
  86. package/dist/guest-runtime.js +188 -0
  87. package/dist/guest-types.d.ts +176 -0
  88. package/dist/guest-types.js +1 -0
  89. package/dist/html-security.js +24 -0
  90. package/dist/identity.js +1 -3
  91. package/dist/inbound-credential.js +52 -0
  92. package/dist/index.d.ts +24 -256
  93. package/dist/index.js +65 -478
  94. package/dist/invocation-auth.js +69 -0
  95. package/dist/invocation.js +230 -73
  96. package/dist/json-schema.d.ts +1 -1
  97. package/dist/mcp-client-context.js +35 -0
  98. package/dist/mcp-result.js +11 -7
  99. package/dist/meta-output.js +76 -0
  100. package/dist/meta-tool-names.d.ts +2 -0
  101. package/dist/meta-tool-names.js +3 -0
  102. package/dist/meta-tools.js +285 -209
  103. package/dist/module-contracts.d.ts +8 -23
  104. package/dist/node.d.ts +0 -2
  105. package/dist/node.js +2 -2
  106. package/dist/oauth-callback-outcome.js +1 -3
  107. package/dist/oauth-handoff.js +117 -0
  108. package/dist/oauth-sealing.js +20 -0
  109. package/dist/operator-record.d.ts +137 -0
  110. package/dist/operator-record.js +420 -0
  111. package/dist/operator-ui/assets.js +31 -0
  112. package/dist/operator-ui/contract.d.ts +72 -0
  113. package/dist/operator-ui/contract.js +1 -0
  114. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  115. package/dist/operator-ui/generated.js +5 -4
  116. package/dist/operator-ui/model.d.ts +12 -87
  117. package/dist/operator-ui/model.js +2 -9
  118. package/dist/operator-ui/setup-commands.js +1 -3
  119. package/dist/operator-ui/view.d.ts +323 -0
  120. package/dist/operator-ui/view.js +62 -189
  121. package/dist/page-styles.js +2 -3
  122. package/dist/program-source.js +14 -2
  123. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  124. package/dist/provider.d.ts +142 -0
  125. package/dist/provider.js +187 -0
  126. package/dist/providers/_shared/google/result-size.js +57 -0
  127. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  128. package/dist/providers/_shared/google/service-account.js +500 -0
  129. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  130. package/dist/providers/_shared/google/workspace.js +518 -0
  131. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  132. package/dist/providers/basecamp/index.js +997 -0
  133. package/dist/providers/basecamp/skill.generated.js +9 -0
  134. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  135. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  136. package/dist/providers/breeze/skill.generated.js +9 -0
  137. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  138. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  139. package/dist/providers/ccb/skill.generated.js +9 -0
  140. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  141. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  142. package/dist/providers/cloudflare/skill.generated.js +10 -0
  143. package/dist/providers/docs/index.d.ts +54 -0
  144. package/dist/providers/docs/index.js +1237 -0
  145. package/dist/providers/docs/skill.generated.js +9 -0
  146. package/dist/providers/drive/index.d.ts +53 -0
  147. package/dist/providers/drive/index.js +1523 -0
  148. package/dist/providers/drive/skill.generated.js +9 -0
  149. package/dist/providers/forms/index.d.ts +54 -0
  150. package/dist/providers/forms/index.js +1217 -0
  151. package/dist/providers/forms/skill.generated.js +11 -0
  152. package/dist/providers/github/auth.d.ts +32 -0
  153. package/dist/providers/github/auth.js +361 -0
  154. package/dist/providers/github/hosted.js +218 -0
  155. package/dist/providers/github/index.d.ts +13 -0
  156. package/dist/providers/github/index.js +140 -0
  157. package/dist/providers/github/key.js +100 -0
  158. package/dist/providers/github/rest.js +321 -0
  159. package/dist/providers/github/scope.d.ts +29 -0
  160. package/dist/providers/github/scope.js +124 -0
  161. package/dist/providers/github/skill.generated.js +8 -0
  162. package/dist/providers/gmail/index.d.ts +51 -0
  163. package/dist/providers/gmail/index.js +1436 -0
  164. package/dist/providers/gmail/skill.generated.js +9 -0
  165. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  166. package/dist/providers/linear/index.js +352 -0
  167. package/dist/providers/linear/skill.generated.js +8 -0
  168. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  169. package/dist/providers/mixpanel/index.js +416 -0
  170. package/dist/providers/mixpanel/skill.generated.js +9 -0
  171. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  172. package/dist/providers/{notion.js → notion/index.js} +226 -239
  173. package/dist/providers/notion/skill.generated.js +10 -0
  174. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  175. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  176. package/dist/providers/overflow/skill.generated.js +8 -0
  177. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  178. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  179. package/dist/providers/planning-center/skill.generated.js +10 -0
  180. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  181. package/dist/providers/revenuecat/index.js +557 -0
  182. package/dist/providers/revenuecat/skill.generated.js +11 -0
  183. package/dist/providers/sheets/index.d.ts +54 -0
  184. package/dist/providers/sheets/index.js +1237 -0
  185. package/dist/providers/sheets/skill.generated.js +10 -0
  186. package/dist/providers/slides/index.d.ts +53 -0
  187. package/dist/providers/slides/index.js +2645 -0
  188. package/dist/providers/slides/skill.generated.js +9 -0
  189. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  190. package/dist/providers/stripe/index.js +266 -0
  191. package/dist/providers/stripe/skill.generated.js +11 -0
  192. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  193. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  194. package/dist/providers/tithely/skill.generated.js +8 -0
  195. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  196. package/dist/providers/vercel/index.js +1038 -0
  197. package/dist/providers/vercel/mcp-ownership.js +212 -0
  198. package/dist/providers/vercel/skill.generated.js +11 -0
  199. package/dist/registry.d.ts +108 -171
  200. package/dist/registry.js +513 -925
  201. package/dist/request-state.js +21 -0
  202. package/dist/resource-read.js +100 -0
  203. package/dist/result-shapes.js +17 -49
  204. package/dist/retired-toolkits.js +1 -1
  205. package/dist/routes/access-tokens.js +3 -6
  206. package/dist/routes/activity-context.js +21 -0
  207. package/dist/routes/activity-disclosure.js +40 -0
  208. package/dist/routes/activity.js +59 -21
  209. package/dist/routes/actor-labels.js +3 -50
  210. package/dist/routes/connect.js +221 -0
  211. package/dist/routes/credentials.js +15 -27
  212. package/dist/routes/mcp.js +356 -130
  213. package/dist/routes/oauth-client-metadata.js +15 -0
  214. package/dist/routes/oauth-management.js +45 -107
  215. package/dist/routes/oauth.js +110 -57
  216. package/dist/routes/operator.js +3 -3
  217. package/dist/routes/shared.d.ts +25 -64
  218. package/dist/routes/shared.js +160 -80
  219. package/dist/routes/skills.js +30 -0
  220. package/dist/routes/ui-config.js +225 -0
  221. package/dist/routes/ui-data.js +40 -35
  222. package/dist/routes/ui.js +66 -56
  223. package/dist/runtime/admission.js +4 -2
  224. package/dist/runtime/call-admission.js +3 -1
  225. package/dist/runtime/connector-scope.js +1 -4
  226. package/dist/runtime/run.js +10 -39
  227. package/dist/runtime/services.js +12 -117
  228. package/dist/runtime/shared-read.js +1 -3
  229. package/dist/runtime/storage.js +8 -1
  230. package/dist/sent-secrets.d.ts +52 -0
  231. package/dist/sent-secrets.js +474 -0
  232. package/dist/server.js +28 -71
  233. package/dist/skills.js +406 -174
  234. package/dist/sqlite.d.ts +48 -0
  235. package/dist/sqlite.js +166 -0
  236. package/dist/storage/keys.js +428 -0
  237. package/dist/storage/memory.js +7 -0
  238. package/dist/storage/sql.d.ts +66 -0
  239. package/dist/storage/sql.js +534 -0
  240. package/dist/timeout.js +4 -2
  241. package/dist/tool-name.js +17 -0
  242. package/dist/tool-safety.d.ts +9 -35
  243. package/dist/tool-safety.js +20 -40
  244. package/dist/types.d.ts +320 -54
  245. package/dist/types.js +0 -1
  246. package/dist/ui.d.ts +7 -19
  247. package/dist/ui.js +76 -90
  248. package/dist/url-safety.js +3 -6
  249. package/dist/usage-guide.js +137 -0
  250. package/dist/validate.d.ts +7 -0
  251. package/dist/validate.js +309 -47
  252. package/dist/version.d.ts +1 -7
  253. package/dist/version.js +3 -7
  254. package/dist/worker.d.ts +11 -0
  255. package/dist/worker.js +434 -0
  256. package/documentation/architecture.md +664 -231
  257. package/documentation/auth.md +936 -546
  258. package/documentation/code-mode.md +501 -312
  259. package/documentation/meta-tools.md +449 -200
  260. package/documentation/operator-tests.md +93 -0
  261. package/documentation/provider-migration-0.29.md +60 -0
  262. package/examples/worker/AGENTS.md +34 -12
  263. package/examples/worker/README.md +286 -255
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  266. package/examples/worker/src/connecta.config.ts +151 -0
  267. package/examples/worker/src/index.ts +14 -197
  268. package/examples/worker/wrangler.jsonc +15 -35
  269. package/package.json +120 -54
  270. package/templates/node/.dockerignore +1 -2
  271. package/templates/node/.env.example +20 -21
  272. package/templates/node/AGENTS.md +21 -17
  273. package/templates/node/Dockerfile +3 -5
  274. package/templates/node/README.md +133 -58
  275. package/templates/node/docker-compose.yml +11 -12
  276. package/templates/node/package.json +4 -2
  277. package/templates/node/src/connecta.config.ts +119 -0
  278. package/templates/node/src/index.ts +9 -154
  279. package/templates/node/src/provision-token.ts +17 -0
  280. package/AGENTS.md +0 -170
  281. package/dist/artifacts/connector.d.ts +0 -58
  282. package/dist/artifacts/connector.js +0 -730
  283. package/dist/artifacts/document.js +0 -86
  284. package/dist/artifacts/guide.js +0 -140
  285. package/dist/artifacts/html-scan.js +0 -329
  286. package/dist/artifacts/json.js +0 -78
  287. package/dist/artifacts/kv-store.d.ts +0 -20
  288. package/dist/artifacts/kv-store.js +0 -140
  289. package/dist/artifacts/markdown.js +0 -478
  290. package/dist/artifacts/module.d.ts +0 -37
  291. package/dist/artifacts/module.js +0 -66
  292. package/dist/artifacts/operations.d.ts +0 -197
  293. package/dist/artifacts/operations.js +0 -1002
  294. package/dist/artifacts/refresh.d.ts +0 -55
  295. package/dist/artifacts/refresh.js +0 -236
  296. package/dist/artifacts/routes.js +0 -206
  297. package/dist/artifacts/types.d.ts +0 -214
  298. package/dist/artifacts/types.js +0 -22
  299. package/dist/artifacts/validate.d.ts +0 -62
  300. package/dist/artifacts/validate.js +0 -651
  301. package/dist/artifacts.d.ts +0 -6
  302. package/dist/artifacts.js +0 -9
  303. package/dist/auth/bearer.d.ts +0 -11
  304. package/dist/auth/bearer.js +0 -50
  305. package/dist/providers/basecamp.js +0 -445
  306. package/dist/providers/linear.js +0 -217
  307. package/dist/providers/mixpanel.js +0 -247
  308. package/dist/providers/revenuecat.js +0 -317
  309. package/dist/providers/stripe.js +0 -253
  310. package/dist/providers/vercel.js +0 -1253
  311. package/dist/storage/file.d.ts +0 -15
  312. package/dist/storage/file.js +0 -455
  313. package/ethos.md +0 -93
  314. package/examples/worker/src/cloudflare-kv.ts +0 -43
  315. package/examples/worker/src/d1-activity-row.ts +0 -155
  316. package/examples/worker/src/d1-activity.ts +0 -147
  317. package/examples/worker/src/d1-storage.ts +0 -138
  318. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  319. package/templates/node/src/file-activity.ts +0 -182
@@ -1,155 +0,0 @@
1
- // Keep this pure mapping module dependency-free so the repository's tests can
2
- // exercise the example before the package's dist/ entrypoint has been built.
3
- // The public adapter in d1-activity.ts remains checked against ActivityStore.
4
- export interface ActivityEvent {
5
- schemaVersion: 1;
6
- id: string;
7
- occurredAt: string;
8
- requestId: string;
9
- actor: {
10
- kind: string;
11
- id?: string;
12
- namespace?: string;
13
- };
14
- connectorId: string;
15
- toolName: string;
16
- address: string;
17
- source:
18
- | "call_tool"
19
- | "call_destructive_tool"
20
- | "batch_call"
21
- | "execute_code"
22
- // History only: rows written before 0.28.0 removed program pauses.
23
- | "resume_execution";
24
- outcome:
25
- | "success"
26
- | "error"
27
- | "timeout"
28
- | "cancelled"
29
- // History only, like `resume_execution` above.
30
- | "paused"
31
- | "approved";
32
- durationMs: number;
33
- attempts: number;
34
- errorCode?: string;
35
- friction?:
36
- | "tool_not_found"
37
- | "schema_retry"
38
- | "destructive_reroute"
39
- | "auth_required"
40
- | "result_too_large";
41
- /** History only: the scope an `approved` row covered, before 0.28.0. */
42
- approval?: "call" | "tool";
43
- serverName: string;
44
- serverVersion: string;
45
- deploymentId?: string;
46
- }
47
-
48
- /**
49
- * Backfill for rows written before `friction` had a column of its own. Every
50
- * arm must agree with the package's own `agentFrictionForCode`; the repository
51
- * test suite pins that, because a dashboard that groups by friction should not
52
- * change its answer depending on which side of a migration a row landed on.
53
- * Friction that never had an error code — an oversized but successful result —
54
- * is only recoverable from the column, which is why the column exists.
55
- */
56
- function frictionForCode(code: string | null): ActivityEvent["friction"] {
57
- switch (code) {
58
- case "unknown_address":
59
- case "unknown_tool":
60
- case "ambiguous_tool_alias":
61
- return "tool_not_found";
62
- case "invalid_args":
63
- return "schema_retry";
64
- case "destructive_tool_requires_approval":
65
- return "destructive_reroute";
66
- case "auth_required":
67
- return "auth_required";
68
- case "result_too_large":
69
- return "result_too_large";
70
- default:
71
- return undefined;
72
- }
73
- }
74
-
75
- export interface ActivityRow {
76
- id: string;
77
- occurred_at_ms: number;
78
- request_id: string;
79
- actor_kind: string;
80
- actor_id: string | null;
81
- actor_namespace: string | null;
82
- connector_id: string;
83
- tool_name: string;
84
- source: ActivityEvent["source"];
85
- outcome: ActivityEvent["outcome"];
86
- duration_ms: number;
87
- attempts: number;
88
- error_code: string | null;
89
- friction: ActivityEvent["friction"] | null;
90
- /** Absent from tables created before 0.26.0; null on every row since 0.28.0. */
91
- approval?: ActivityEvent["approval"] | null;
92
- server_name: string;
93
- server_version: string;
94
- deployment_id: string | null;
95
- }
96
-
97
- export function activityEventToRow(
98
- event: ActivityEvent,
99
- ): ActivityRow {
100
- return {
101
- id: event.id,
102
- occurred_at_ms: Date.parse(event.occurredAt),
103
- request_id: event.requestId,
104
- actor_kind: event.actor.kind,
105
- actor_id: event.actor.id ?? null,
106
- actor_namespace: event.actor.namespace ?? null,
107
- connector_id: event.connectorId,
108
- tool_name: event.toolName,
109
- source: event.source,
110
- outcome: event.outcome,
111
- duration_ms: event.durationMs,
112
- attempts: event.attempts,
113
- error_code: event.errorCode ?? null,
114
- // Stored beside the code rather than derived from it: a truncated result is
115
- // friction on a call that succeeded, so it has no error code to derive from
116
- // — and `error_code IS NOT NULL` stays an honest count of failures.
117
- friction: event.friction ?? null,
118
- approval: event.approval ?? null,
119
- server_name: event.serverName,
120
- server_version: event.serverVersion,
121
- deployment_id: event.deploymentId ?? null,
122
- };
123
- }
124
-
125
- export function activityRowToEvent(
126
- row: ActivityRow,
127
- ): ActivityEvent {
128
- const friction = row.friction ?? frictionForCode(row.error_code);
129
- return {
130
- schemaVersion: 1,
131
- id: row.id,
132
- occurredAt: new Date(row.occurred_at_ms).toISOString(),
133
- requestId: row.request_id,
134
- actor: {
135
- kind: row.actor_kind,
136
- ...(row.actor_id ? { id: row.actor_id } : {}),
137
- ...(row.actor_namespace ? { namespace: row.actor_namespace } : {}),
138
- },
139
- connectorId: row.connector_id,
140
- toolName: row.tool_name,
141
- address: `${row.connector_id}.${row.tool_name}`,
142
- source: row.source,
143
- outcome: row.outcome,
144
- durationMs: row.duration_ms,
145
- attempts: row.attempts,
146
- ...(row.error_code ? { errorCode: row.error_code } : {}),
147
- ...(friction ? { friction } : {}),
148
- ...(row.approval ? { approval: row.approval } : {}),
149
- serverName: row.server_name,
150
- serverVersion: row.server_version,
151
- ...(row.deployment_id
152
- ? { deploymentId: row.deployment_id }
153
- : {}),
154
- };
155
- }
@@ -1,147 +0,0 @@
1
- import type {
2
- ActivityPage,
3
- ActivityStore,
4
- } from "@zackbart/connecta";
5
- import { InvalidActivityCursorError } from "@zackbart/connecta/activity";
6
- import {
7
- activityEventToRow,
8
- activityRowToEvent,
9
- type ActivityRow,
10
- } from "./d1-activity-row.js";
11
-
12
- interface Cursor {
13
- occurredAtMs: number;
14
- id: string;
15
- }
16
-
17
- function encodeCursor(row: ActivityRow): string {
18
- return btoa(`${row.occurred_at_ms}:${row.id}`);
19
- }
20
-
21
- function decodeCursor(value: string): Cursor {
22
- let decoded: string;
23
- try {
24
- decoded = atob(value);
25
- } catch {
26
- throw new InvalidActivityCursorError();
27
- }
28
- const separator = decoded.indexOf(":");
29
- const occurredAtMs = Number(decoded.slice(0, separator));
30
- const id = decoded.slice(separator + 1);
31
- if (
32
- separator < 1 ||
33
- !Number.isSafeInteger(occurredAtMs) ||
34
- occurredAtMs < 0 ||
35
- !/^[0-9a-f-]{36}$/i.test(id)
36
- ) {
37
- throw new InvalidActivityCursorError();
38
- }
39
- return { occurredAtMs, id };
40
- }
41
-
42
- /** One append-only row per completed downstream call; no arguments or results. */
43
- export function d1ActivityStore(db: D1Database): ActivityStore {
44
- return {
45
- async record(event) {
46
- const row = activityEventToRow(event);
47
- await db
48
- .prepare(
49
- `INSERT INTO tool_call_activity (
50
- id, occurred_at_ms, request_id, actor_kind, actor_id,
51
- actor_namespace,
52
- connector_id, tool_name, source, outcome, duration_ms, attempts,
53
- error_code, friction, approval, server_name, server_version,
54
- deployment_id
55
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
56
- )
57
- .bind(
58
- row.id,
59
- row.occurred_at_ms,
60
- row.request_id,
61
- row.actor_kind,
62
- row.actor_id,
63
- row.actor_namespace,
64
- row.connector_id,
65
- row.tool_name,
66
- row.source,
67
- row.outcome,
68
- row.duration_ms,
69
- row.attempts,
70
- row.error_code,
71
- row.friction ?? null,
72
- row.approval ?? null,
73
- row.server_name,
74
- row.server_version,
75
- row.deployment_id,
76
- )
77
- .run();
78
- },
79
-
80
- async list({ cursor, limit }): Promise<ActivityPage> {
81
- const boundedLimit = Math.min(100, Math.max(1, Math.trunc(limit)));
82
- const pageSize = boundedLimit + 1;
83
- const position = cursor ? decodeCursor(cursor) : undefined;
84
- const statement = position
85
- ? db
86
- .prepare(
87
- `SELECT * FROM tool_call_activity
88
- WHERE occurred_at_ms < ?
89
- OR (occurred_at_ms = ? AND id < ?)
90
- ORDER BY occurred_at_ms DESC, id DESC
91
- LIMIT ?`,
92
- )
93
- .bind(
94
- position.occurredAtMs,
95
- position.occurredAtMs,
96
- position.id,
97
- pageSize,
98
- )
99
- : db
100
- .prepare(
101
- `SELECT * FROM tool_call_activity
102
- ORDER BY occurred_at_ms DESC, id DESC
103
- LIMIT ?`,
104
- )
105
- .bind(pageSize);
106
- const result = await statement.all<ActivityRow>();
107
- const rows = result.results ?? [];
108
- const hasMore = rows.length > boundedLimit;
109
- const visible = hasMore ? rows.slice(0, boundedLimit) : rows;
110
- const last = visible.at(-1);
111
- return {
112
- events: visible.map(activityRowToEvent),
113
- ...(hasMore && last ? { nextCursor: encodeCursor(last) } : {}),
114
- };
115
- },
116
- };
117
- }
118
-
119
- /**
120
- * Daily retention pass. Each statement is capped at 5,000 rows, then repeats
121
- * up to a per-run ceiling so normal traffic catches up without allowing one
122
- * scheduled invocation to consume unbounded queries.
123
- */
124
- export async function pruneActivity(
125
- db: D1Database,
126
- retentionDays: number,
127
- maxBatches = 20,
128
- ): Promise<void> {
129
- const cutoff = Date.now() -
130
- Math.max(1, Math.trunc(retentionDays)) * 24 * 60 * 60 * 1_000;
131
- const batchLimit = Math.max(1, Math.trunc(maxBatches));
132
- for (let batch = 0; batch < batchLimit; batch++) {
133
- const result = await db
134
- .prepare(
135
- `DELETE FROM tool_call_activity
136
- WHERE id IN (
137
- SELECT id FROM tool_call_activity
138
- WHERE occurred_at_ms < ?
139
- ORDER BY occurred_at_ms ASC
140
- LIMIT 5000
141
- )`,
142
- )
143
- .bind(cutoff)
144
- .run();
145
- if ((result.meta.changes ?? 0) < 5_000) return;
146
- }
147
- }
@@ -1,138 +0,0 @@
1
- import type { KVStorage } from "@zackbart/connecta";
2
-
3
- /**
4
- * KVStorage over a D1 database, with the atomic `compareAndSet` that Workers
5
- * KV cannot offer. Apply the `connecta_kv` schema in README.md § "Strongly
6
- * consistent storage" before first use.
7
- *
8
- * Every compare-and-set is a single SQL statement. D1 runs one database's
9
- * statements one at a time against its primary, so the comparison and the
10
- * write cannot interleave with another request's: of fifty concurrent claims
11
- * on an absent key, exactly one changes a row.
12
- *
13
- * Expiry is judged by the Worker's clock at each call, the same `Date.now()`
14
- * the core uses. An expired row reads as absent immediately and is removed
15
- * physically by later writes, a bounded batch at a time, so no request starts
16
- * a background job and no cron is required.
17
- */
18
- export function d1Storage(db: D1Database): KVStorage {
19
- // Every statement that tests liveness binds the current time as ?2.
20
- const live = "(expires_at_ms IS NULL OR expires_at_ms > ?2)";
21
- const expiresAt = (now: number, ttlSeconds?: number): number | null => {
22
- if (ttlSeconds !== undefined && !Number.isFinite(ttlSeconds)) {
23
- throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
24
- }
25
- if (!ttlSeconds) return null;
26
- const expiry = now + ttlSeconds * 1000;
27
- if (!Number.isFinite(expiry)) {
28
- throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
29
- }
30
- return expiry;
31
- };
32
- const changed = (result: D1Result) => result.meta.changes > 0;
33
- return {
34
- async get(key) {
35
- const row = await db
36
- .prepare(`SELECT value FROM connecta_kv WHERE key = ?1 AND ${live}`)
37
- .bind(key, Date.now())
38
- .first<{ value: string }>();
39
- return row?.value ?? null;
40
- },
41
- async set(key, value, opts) {
42
- const now = Date.now();
43
- const expiry = expiresAt(now, opts?.ttlSeconds);
44
- await db.batch([
45
- db
46
- .prepare(
47
- `DELETE FROM connecta_kv WHERE key IN (
48
- SELECT key FROM connecta_kv WHERE expires_at_ms <= ?1 LIMIT 16
49
- )`,
50
- )
51
- .bind(now),
52
- db
53
- .prepare(
54
- `INSERT INTO connecta_kv (key, value, expires_at_ms)
55
- VALUES (?1, ?2, ?3)
56
- ON CONFLICT (key) DO UPDATE SET
57
- value = excluded.value, expires_at_ms = excluded.expires_at_ms`,
58
- )
59
- .bind(key, value, expiry),
60
- ]);
61
- },
62
- async delete(key) {
63
- await db.prepare("DELETE FROM connecta_kv WHERE key = ?1").bind(key).run();
64
- },
65
- async list(prefix) {
66
- const { results } = await db
67
- .prepare(
68
- `SELECT key FROM connecta_kv
69
- WHERE key >= ?1 AND substr(CAST(key AS BLOB), 1, length(CAST(?1 AS BLOB))) = CAST(?1 AS BLOB) AND ${live}`,
70
- )
71
- .bind(prefix, Date.now())
72
- .all<{ key: string }>();
73
- // Sort in JavaScript: SQLite orders UTF-8 bytes, the contract orders
74
- // the UTF-16 code units every other adapter sorts by.
75
- return results.map((row) => row.key).sort();
76
- },
77
- async compareAndSet(key, expected, next, opts) {
78
- const now = Date.now();
79
- if (next === null && expected === null) {
80
- // Nothing to write; the claim holds exactly when no live row exists.
81
- const row = await db
82
- .prepare(`SELECT 1 AS present FROM connecta_kv WHERE key = ?1 AND ${live}`)
83
- .bind(key, now)
84
- .first();
85
- return row === null;
86
- }
87
- if (next === null) {
88
- return changed(
89
- await db
90
- .prepare(
91
- `DELETE FROM connecta_kv WHERE key = ?1 AND ${live} AND value = ?3`,
92
- )
93
- .bind(key, now, expected)
94
- .run(),
95
- );
96
- }
97
- let expiry: number | null;
98
- try {
99
- expiry = expiresAt(now, opts?.ttlSeconds);
100
- } catch (error) {
101
- // Invalid write options do not change a failed comparison's result.
102
- // Ordinary claims still use one atomic statement without this read.
103
- const current = await db
104
- .prepare(`SELECT value FROM connecta_kv WHERE key = ?1 AND ${live}`)
105
- .bind(key, now)
106
- .first<{ value: string }>();
107
- if ((current?.value ?? null) !== expected) return false;
108
- throw error;
109
- }
110
- if (expected === null) {
111
- // Insert, or take over a row that has expired. A live row makes the
112
- // upsert's WHERE false, so nothing changes and the claim is refused.
113
- return changed(
114
- await db
115
- .prepare(
116
- `INSERT INTO connecta_kv (key, value, expires_at_ms)
117
- VALUES (?1, ?3, ?4)
118
- ON CONFLICT (key) DO UPDATE SET
119
- value = excluded.value, expires_at_ms = excluded.expires_at_ms
120
- WHERE connecta_kv.expires_at_ms IS NOT NULL
121
- AND connecta_kv.expires_at_ms <= ?2`,
122
- )
123
- .bind(key, now, next, expiry)
124
- .run(),
125
- );
126
- }
127
- return changed(
128
- await db
129
- .prepare(
130
- `UPDATE connecta_kv SET value = ?4, expires_at_ms = ?5
131
- WHERE key = ?1 AND ${live} AND value = ?3`,
132
- )
133
- .bind(key, now, expected, next, expiry)
134
- .run(),
135
- );
136
- },
137
- };
138
- }
@@ -1,29 +0,0 @@
1
- import type { ArtifactBlobStore } from "@zackbart/connecta/artifacts";
2
-
3
- /**
4
- * Artifact bodies — page sources, data documents, refresh programs — in an R2
5
- * bucket, beside a D1-backed `kvArtifactStore` that keeps the heads, versions,
6
- * and runs. Optional: without it, bodies live in D1 rows, which the default
7
- * limits keep well under D1's row size. Use it once pages or documents grow,
8
- * or to keep large bodies out of the database a backup copies.
9
- *
10
- * Keys are content addresses, so writing one twice writes the same bytes, and
11
- * nothing here ever deletes one: versions are immutable, and a rollback points
12
- * back at an old body rather than copying it.
13
- */
14
- export function r2ArtifactBlobs(
15
- bucket: R2Bucket,
16
- prefix = "artifacts/blobs/",
17
- ): ArtifactBlobStore {
18
- return {
19
- async put(key, body) {
20
- await bucket.put(`${prefix}${key}`, body, {
21
- httpMetadata: { contentType: "text/plain; charset=utf-8" },
22
- });
23
- },
24
- async get(key) {
25
- const object = await bucket.get(`${prefix}${key}`);
26
- return object ? object.text() : null;
27
- },
28
- };
29
- }
@@ -1,182 +0,0 @@
1
- /**
2
- * Payload-free tool activity, kept in a JSON-lines file beside the state file.
3
- *
4
- * Deployment-owned on purpose: Connecta ships the `ActivityStore` contract, not
5
- * a backend, because retention is a decision only the operator can make. This
6
- * is the Node counterpart of the Worker example's D1 store, sized for one
7
- * container rather than a fleet — a write appends one line, and the log is
8
- * bounded by a count of events instead of by age, so nothing has to be
9
- * scheduled.
10
- *
11
- * Nothing wires it by default. `src/index.ts` carries the commented lines that
12
- * turn it on; README.md § "Turn on the operator surface" explains why they are
13
- * commented.
14
- */
15
- import {
16
- appendFileSync,
17
- chmodSync,
18
- existsSync,
19
- mkdirSync,
20
- readFileSync,
21
- renameSync,
22
- writeFileSync,
23
- } from "node:fs";
24
- import { dirname } from "node:path";
25
- import type {
26
- ActivityPage,
27
- ActivityStore,
28
- ToolCallActivityEvent,
29
- } from "@zackbart/connecta";
30
- import { InvalidActivityCursorError } from "@zackbart/connecta/activity";
31
-
32
- /**
33
- * Events allowed past `maxEvents` before the log is rewritten. Trimming on the
34
- * write that first crosses the ceiling would rewrite the whole file on every
35
- * call from then on; this pays that cost once per slack window instead.
36
- */
37
- const COMPACTION_SLACK = 500;
38
-
39
- export interface FileActivityOptions {
40
- /** Newest events retained after a compaction. Default 5,000. */
41
- maxEvents?: number;
42
- }
43
-
44
- /** Newest first, with the id breaking ties inside a millisecond. */
45
- function newestFirst(
46
- a: ToolCallActivityEvent,
47
- b: ToolCallActivityEvent,
48
- ): number {
49
- const byTime = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
50
- return byTime !== 0 ? byTime : b.id.localeCompare(a.id);
51
- }
52
-
53
- function encodeCursor(event: ToolCallActivityEvent): string {
54
- return Buffer.from(`${Date.parse(event.occurredAt)}:${event.id}`).toString(
55
- "base64",
56
- );
57
- }
58
-
59
- function decodeCursor(value: string): { occurredAtMs: number; id: string } {
60
- const decoded = Buffer.from(value, "base64").toString("utf8");
61
- const separator = decoded.indexOf(":");
62
- const occurredAtMs = Number(decoded.slice(0, separator));
63
- const id = decoded.slice(separator + 1);
64
- if (separator < 1 || !Number.isSafeInteger(occurredAtMs) || !id) {
65
- throw new InvalidActivityCursorError();
66
- }
67
- return { occurredAtMs, id };
68
- }
69
-
70
- /** Strictly older than the cursor position, in the order above. */
71
- function isBefore(
72
- event: ToolCallActivityEvent,
73
- position: { occurredAtMs: number; id: string },
74
- ): boolean {
75
- const occurredAtMs = Date.parse(event.occurredAt);
76
- if (occurredAtMs !== position.occurredAtMs) {
77
- return occurredAtMs < position.occurredAtMs;
78
- }
79
- return event.id.localeCompare(position.id) < 0;
80
- }
81
-
82
- /**
83
- * One append-only record per completed downstream call. Events carry no
84
- * arguments, results, generated code, or raw error messages — the store never
85
- * has a payload to leak. Writes are best-effort by contract: a throw here is
86
- * logged by Connecta and never reaches the tool result.
87
- */
88
- export function fileActivityStore(
89
- path: string,
90
- opts: FileActivityOptions = {},
91
- ): ActivityStore {
92
- const maxEvents = Math.max(1, Math.trunc(opts.maxEvents ?? 5_000));
93
- // Activity records who called what, so it is owner-only like the state file.
94
- const tighten = () => {
95
- try {
96
- chmodSync(path, 0o600);
97
- } catch {
98
- // Non-POSIX filesystem or a race on the file — leave the mode as-is.
99
- }
100
- };
101
- let events: ToolCallActivityEvent[] = [];
102
- // Damage found at load that the file itself still carries: a dropped line, or
103
- // a last line with no newline after it. Both are repaired by rewriting the
104
- // file from what parsed, because the alternative is appending onto a
105
- // fragment and losing the *next* event too — one that never had anything
106
- // wrong with it.
107
- let repairNeeded = false;
108
- if (existsSync(path)) {
109
- tighten();
110
- // A half-written trailing line is the normal cost of an append log, and
111
- // one unreadable line is not a reason to lose the history above it. Unlike
112
- // the state file there is nothing irreplaceable here — this is a record of
113
- // calls that already happened — so damaged lines are dropped, loudly.
114
- const raw = readFileSync(path, "utf8");
115
- const lines = raw.split("\n").filter(Boolean);
116
- let dropped = 0;
117
- for (const line of lines) {
118
- try {
119
- events.push(JSON.parse(line) as ToolCallActivityEvent);
120
- } catch {
121
- dropped += 1;
122
- }
123
- }
124
- if (dropped > 0) {
125
- console.error(
126
- `[connecta] activity log ${path}: dropped ${dropped} unreadable ` +
127
- `line(s) of ${lines.length}.`,
128
- );
129
- }
130
- repairNeeded = dropped > 0 || (raw.length > 0 && !raw.endsWith("\n"));
131
- }
132
- const ensureDirectory = () => {
133
- const dir = dirname(path);
134
- if (dir) mkdirSync(dir, { recursive: true, mode: 0o700 });
135
- };
136
- const compact = () => {
137
- events = [...events].sort(newestFirst).slice(0, maxEvents);
138
- ensureDirectory();
139
- const tmp = `${path}.tmp`;
140
- // 0o600 on the temp file; the rename preserves it, so the live log is
141
- // never briefly world-readable.
142
- writeFileSync(
143
- tmp,
144
- events.map((event) => `${JSON.stringify(event)}\n`).join(""),
145
- { mode: 0o600 },
146
- );
147
- renameSync(tmp, path);
148
- tighten();
149
- };
150
- // Repair before the first append rather than at the next restart: the
151
- // fragment is on disk until something rewrites the file, and an append onto
152
- // it corrupts a good event into an unreadable line.
153
- if (repairNeeded) compact();
154
- return {
155
- record(event) {
156
- events.push(event);
157
- if (events.length > maxEvents + COMPACTION_SLACK) {
158
- compact();
159
- return;
160
- }
161
- ensureDirectory();
162
- appendFileSync(path, `${JSON.stringify(event)}\n`, { mode: 0o600 });
163
- tighten();
164
- },
165
-
166
- async list({ cursor, limit }): Promise<ActivityPage> {
167
- const boundedLimit = Math.min(100, Math.max(1, Math.trunc(limit)));
168
- const position = cursor ? decodeCursor(cursor) : undefined;
169
- const ordered = [...events]
170
- .sort(newestFirst)
171
- .filter((event) => !position || isBefore(event, position));
172
- const visible = ordered.slice(0, boundedLimit);
173
- const last = visible.at(-1);
174
- return {
175
- events: visible,
176
- ...(ordered.length > visible.length && last
177
- ? { nextCursor: encodeCursor(last) }
178
- : {}),
179
- };
180
- },
181
- };
182
- }