@zackbart/connecta 0.28.1 → 0.30.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 (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -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/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
@@ -1,55 +0,0 @@
1
- import type { ArtifactOperations } from "./operations.js";
2
- import type { ArtifactHeadRecord, ArtifactRunRecord } from "./types.js";
3
- import type { RenderPage } from "./operations.js";
4
- /** Safe metadata only. Logs and downstream errors never enter the viewer. */
5
- export declare function freshnessOf(head: ArtifactHeadRecord, now?: number): {
6
- state: "unconfigured";
7
- } | {
8
- running?: boolean;
9
- last?: {
10
- runId: string;
11
- at: string;
12
- status: "succeeded" | "unchanged" | "failed" | "superseded";
13
- documentVersion?: number;
14
- };
15
- dueAt?: string;
16
- state: "stale" | "current";
17
- schedule: import("./types.js").ArtifactRefreshSchedule;
18
- document: string;
19
- programVersion: number;
20
- };
21
- export interface ArtifactRefreshRuntime {
22
- /** The core's bounded read-only program runner; no sandbox code enters this subpath. */
23
- execute(program: string, owner: NonNullable<ArtifactHeadRecord["refresh"]>["owner"], signal: AbortSignal): Promise<{
24
- content: {
25
- type: string;
26
- text?: string;
27
- }[];
28
- isError?: boolean;
29
- }>;
30
- claimMs: number;
31
- /** UI branding shared by the viewer and every render check. */
32
- branding?: import("../types.js").ConnectaBranding;
33
- }
34
- export type RefreshOutcome = {
35
- status: "skipped";
36
- } | {
37
- status: "succeeded" | "unchanged" | "failed" | "superseded";
38
- runId: string;
39
- documentVersion?: number;
40
- errorCode?: string;
41
- };
42
- export declare class ArtifactRefreshService {
43
- #private;
44
- constructor(ops: ArtifactOperations);
45
- bind(runtime: ArtifactRefreshRuntime): void;
46
- setRender(renderFor: (signal: AbortSignal) => RenderPage | undefined): void;
47
- run(id: string, trigger: ArtifactRunRecord["trigger"]): Promise<RefreshOutcome>;
48
- runDue(): Promise<{
49
- scanned: number;
50
- started: number;
51
- succeeded: number;
52
- failed: number;
53
- capped: boolean;
54
- }>;
55
- }
@@ -1,236 +0,0 @@
1
- // Deployment-triggered artifact refresh. No interval, cron, or background
2
- // fiber lives here: Node and Workers decide when to call runDue().
3
- const MAX_DUE_PER_TICK = 10;
4
- const MAX_HEADS_PER_TICK = 1_000;
5
- const MAX_MESSAGE_CHARS = 500;
6
- /** Safe metadata only. Logs and downstream errors never enter the viewer. */
7
- export function freshnessOf(head, now = Date.now()) {
8
- const refresh = head.refresh;
9
- if (!refresh)
10
- return { state: "unconfigured" };
11
- const period = refresh.schedule === "daily" ? 86_400_000
12
- : refresh.schedule === "weekly" ? 604_800_000 : undefined;
13
- const dueAt = period === undefined ? undefined
14
- : new Date(Date.parse(refresh.last?.at ?? refresh.configuredAt) + period).toISOString();
15
- const stale = refresh.last?.status === "failed" || refresh.last?.status === "superseded" ||
16
- (dueAt !== undefined && now >= Date.parse(dueAt));
17
- return {
18
- state: stale ? "stale" : "current",
19
- schedule: refresh.schedule,
20
- document: refresh.document,
21
- programVersion: refresh.program.version,
22
- ...(dueAt ? { dueAt } : {}),
23
- ...(refresh.last ? { last: refresh.last } : {}),
24
- ...(refresh.claim && Date.parse(refresh.claim.until) > now ? { running: true } : {}),
25
- };
26
- }
27
- function capture(text, bytes) {
28
- if (typeof text !== "string" || !text)
29
- return undefined;
30
- const encoder = new TextEncoder();
31
- let clipped = text.slice(0, bytes);
32
- while (encoder.encode(clipped).byteLength > bytes)
33
- clipped = clipped.slice(0, -1);
34
- return clipped;
35
- }
36
- function responsePayload(result) {
37
- const text = result.content.find((block) => block.type === "text")?.text;
38
- if (!text)
39
- return { error: { code: "executor_failed", message: "Refresh program returned no result." } };
40
- try {
41
- const parsed = JSON.parse(text);
42
- return parsed;
43
- }
44
- catch {
45
- return { error: { code: "executor_failed", message: result.isError ? text : "Refresh program returned an invalid result." } };
46
- }
47
- }
48
- async function beforeDeadline(work, signal) {
49
- if (signal.aborted)
50
- throw new Error("Refresh deadline expired.");
51
- let onAbort;
52
- const expired = new Promise((_resolve, reject) => {
53
- onAbort = () => reject(new Error("Refresh deadline expired."));
54
- signal.addEventListener("abort", onAbort, { once: true });
55
- });
56
- try {
57
- return await Promise.race([work, expired]);
58
- }
59
- finally {
60
- if (onAbort)
61
- signal.removeEventListener("abort", onAbort);
62
- }
63
- }
64
- export class ArtifactRefreshService {
65
- #ops;
66
- #runtime;
67
- #renderFor;
68
- constructor(ops) {
69
- this.#ops = ops;
70
- }
71
- bind(runtime) {
72
- if (this.#runtime)
73
- throw new Error("One artifacts() module cannot be bound to two deployments.");
74
- this.#runtime = runtime;
75
- }
76
- setRender(renderFor) {
77
- this.#renderFor = renderFor;
78
- }
79
- async run(id, trigger) {
80
- const runtime = this.#runtime;
81
- if (!runtime)
82
- throw new Error("Create a Connecta deployment with this artifacts() module before refreshing.");
83
- const runId = crypto.randomUUID();
84
- const claimed = await this.#ops.claimRefresh(id, runId, trigger, trigger === "schedule", runtime.claimMs);
85
- if (!claimed.ok) {
86
- if ("skipped" in claimed)
87
- return { status: "skipped" };
88
- throw new Error(claimed.message);
89
- }
90
- const run = claimed.run;
91
- const deadline = Date.parse(claimed.head.refresh.claim.until);
92
- const controller = new AbortController();
93
- const timeout = setTimeout(() => controller.abort(), Math.max(0, deadline - Date.now()));
94
- let outcome;
95
- try {
96
- await this.#ops.store.putRun(id, run);
97
- if (Date.now() >= deadline)
98
- throw new Error("Refresh claim expired before execution.");
99
- if (!claimed.program)
100
- throw new Error("Refresh program body is unavailable.");
101
- const program = await beforeDeadline(this.#ops.store.body(claimed.program), controller.signal);
102
- if (program === null)
103
- throw new Error("Refresh program body is unavailable.");
104
- const response = responsePayload(await beforeDeadline(runtime.execute(program, claimed.head.refresh.owner, controller.signal), controller.signal));
105
- const logs = capture(response.logs, this.#ops.context.limits.runLogBytes);
106
- if (logs)
107
- run.logs = logs;
108
- if (response.error) {
109
- run.status = "failed";
110
- run.errorCode = capture(response.error.code, 80) ?? "executor_failed";
111
- const message = capture(response.error.message, MAX_MESSAGE_CHARS);
112
- if (message)
113
- run.message = message;
114
- outcome = { status: "failed", runId, errorCode: run.errorCode };
115
- }
116
- else if (response.result === null || response.result === undefined) {
117
- run.status = "failed";
118
- run.errorCode = "invalid_result";
119
- run.message = "Refresh program must return a non-null JSON value.";
120
- outcome = { status: "failed", runId, errorCode: run.errorCode };
121
- }
122
- else {
123
- const document = claimed.head.refresh.document;
124
- const baseVersion = claimed.head.documents[document]?.version ?? 0;
125
- const render = this.#renderFor?.(controller.signal);
126
- const saved = await this.#ops.setDocuments({
127
- id,
128
- documents: { [document]: { baseVersion, value: response.result } },
129
- by: { kind: "refresh" },
130
- op: "refresh", runId,
131
- programVersion: run.programVersion,
132
- ...(render ? { render } : {}),
133
- });
134
- if (saved.ok) {
135
- run.status = "succeeded";
136
- const documentVersion = saved.head.documents[document]?.version;
137
- if (documentVersion !== undefined)
138
- run.documentVersion = documentVersion;
139
- outcome = { status: "succeeded", runId,
140
- ...(documentVersion !== undefined ? { documentVersion } : {}) };
141
- }
142
- else {
143
- run.status = saved.code === "conflict" ? "superseded" : "failed";
144
- run.errorCode = saved.code;
145
- const message = capture(saved.message, MAX_MESSAGE_CHARS);
146
- if (message)
147
- run.message = message;
148
- outcome = { status: run.status, runId, errorCode: run.errorCode };
149
- }
150
- }
151
- }
152
- catch (error) {
153
- run.status = "failed";
154
- run.errorCode = "refresh_failed";
155
- const message = capture(error instanceof Error ? error.message : String(error), MAX_MESSAGE_CHARS);
156
- if (message)
157
- run.message = message;
158
- outcome = { status: "failed", runId, errorCode: run.errorCode };
159
- }
160
- finally {
161
- clearTimeout(timeout);
162
- controller.abort();
163
- }
164
- run.finishedAt = new Date().toISOString();
165
- await this.#ops.finishRefresh(id, run);
166
- return outcome;
167
- }
168
- async runDue() {
169
- if (!this.#runtime)
170
- throw new Error("Create a Connecta deployment before running refreshes.");
171
- let after = await this.#ops.store.refreshScanCursor();
172
- let scanned = 0;
173
- let started = 0;
174
- let succeeded = 0;
175
- let failed = 0;
176
- while (scanned < MAX_HEADS_PER_TICK && started < MAX_DUE_PER_TICK) {
177
- const limit = Math.min(100, MAX_HEADS_PER_TICK - scanned);
178
- const page = await this.#ops.store.heads({
179
- ...(after ? { after } : {}),
180
- limit,
181
- });
182
- if (page.next !== undefined && after !== undefined && page.next <= after) {
183
- throw new Error("Artifact head scan did not advance.");
184
- }
185
- if (!page.heads.length) {
186
- if (page.next) {
187
- after = page.next;
188
- scanned += limit;
189
- continue;
190
- }
191
- after = undefined;
192
- break;
193
- }
194
- for (const { id, head } of page.heads) {
195
- scanned++;
196
- after = id;
197
- if (!head.refresh || head.archived)
198
- continue;
199
- let outcome;
200
- try {
201
- outcome = await this.run(id, "schedule");
202
- }
203
- catch {
204
- // One broken record or transient run write does not starve later heads.
205
- started++;
206
- failed++;
207
- if (started >= MAX_DUE_PER_TICK)
208
- break;
209
- continue;
210
- }
211
- if (outcome.status === "skipped")
212
- continue;
213
- started++;
214
- if (outcome.status === "succeeded" || outcome.status === "unchanged")
215
- succeeded++;
216
- else
217
- failed++;
218
- if (started >= MAX_DUE_PER_TICK)
219
- break;
220
- }
221
- if (page.next) {
222
- scanned += limit - page.heads.length;
223
- if (started < MAX_DUE_PER_TICK)
224
- after = page.next;
225
- }
226
- if (!page.next || started >= MAX_DUE_PER_TICK) {
227
- if (!page.next && started < MAX_DUE_PER_TICK && scanned < MAX_HEADS_PER_TICK)
228
- after = undefined;
229
- break;
230
- }
231
- }
232
- await this.#ops.store.setRefreshScanCursor(after);
233
- return { scanned, started, succeeded, failed,
234
- capped: scanned >= MAX_HEADS_PER_TICK || started >= MAX_DUE_PER_TICK };
235
- }
236
- }
@@ -1,206 +0,0 @@
1
- // The routes behind artifact pages: a JSON API the operator shell calls with
2
- // its session, and one constant, data-free frame the page renders in.
3
- //
4
- // The operator UI authenticates with a header, not a cookie — a bearer from
5
- // localStorage or a Clerk session token — so a bare navigation to a page
6
- // carries no credential. The trusted shell therefore fetches the page from
7
- // `_api` and hands it to the frame with `postMessage`; the frame itself is
8
- // served to anyone and holds nothing. What makes it safe is its response
9
- // header: `sandbox allow-scripts` without `allow-same-origin` gives the page
10
- // an opaque origin — no cookies, no storage, no route of this deployment it
11
- // could call — and the fetch directives leave it no network.
12
- import { resolveActorLabels, actorKey } from "../routes/actor-labels.js";
13
- import { authorize, mayViewArtifacts, privateJson, validateAuthPermissions, } from "../routes/shared.js";
14
- import { resolveBranding, schemeAttribute, themeCss } from "../branding.js";
15
- import { TOKENS_CSS } from "../page-styles.js";
16
- import { frameCsp } from "./document.js";
17
- import { scriptSafeJson } from "./json.js";
18
- import { freshnessOf } from "./refresh.js";
19
- import { ARTIFACT_ID } from "./validate.js";
20
- const LIBRARY_PAGE = 50;
21
- const MAX_QUERY_CHARS = 200;
22
- const MAX_PINS = 32;
23
- const PIN = /^([A-Za-z_][A-Za-z0-9_]{0,63}):(\d{1,9})$/;
24
- const notFound = () => privateJson({ error: "not found" }, { status: 404 });
25
- /** How long the frame waits for its document before saying it did not come. */
26
- const FRAME_STALL_MS = 15_000;
27
- /**
28
- * The frame's bootstrap: wait for exactly one document from the parent, then
29
- * become it.
30
- *
31
- * Until the document arrives the frame shows one quiet line on the viewer's
32
- * own surface, in the deployment's scheme and tokens, instead of a blank white
33
- * box; if the handshake never completes, the line says so. None of it is page
34
- * data — the scheme and tokens are deployment configuration — and all of it is
35
- * gone the moment `document.open` replaces this document with the page.
36
- */
37
- function frameBootstrap(expectedOrigin, theme) {
38
- return `<!doctype html>
39
- <html lang="en"${schemeAttribute(theme)}><head><meta charset="utf-8"><meta name="referrer" content="no-referrer">
40
- <style>${TOKENS_CSS}${themeCss(theme)}html{background:var(--surface)}body{color:var(--muted);font:.875rem/1.5 var(--sans);margin:0;padding:1.25rem 1.5rem}</style>
41
- </head><body>
42
- <p id="frame-status" role="status">Loading page…</p>
43
- <script>
44
- (() => {
45
- const expected = ${scriptSafeJson(expectedOrigin)};
46
- if (window.parent === window) return;
47
- const status = document.getElementById("frame-status");
48
- const stalled = setTimeout(() => {
49
- status.textContent = "This page did not load. Reload to try again.";
50
- }, ${FRAME_STALL_MS});
51
- const receive = (event) => {
52
- if (event.source !== window.parent || event.origin !== expected) return;
53
- const message = event.data;
54
- if (!message || message.type !== "document" || typeof message.html !== "string") return;
55
- window.removeEventListener("message", receive);
56
- clearTimeout(stalled);
57
- document.open();
58
- document.write(message.html);
59
- document.close();
60
- };
61
- window.addEventListener("message", receive);
62
- window.parent.postMessage({ type: "ready" }, expected);
63
- })();
64
- </script>
65
- </body></html>`;
66
- }
67
- export function artifactRoutes(options) {
68
- const ops = options.operations;
69
- const csp = frameCsp(options.allowlist);
70
- /** The viewer's identity, or the Response that refuses it. */
71
- const admit = async (context) => {
72
- const { request, baseUrl, opts, runtimeContext } = context;
73
- if (opts.auth.length === 0) {
74
- // An open deployment has nobody to show a team page to.
75
- return privateJson({ error: "artifact pages need inbound authentication" }, { status: 403 });
76
- }
77
- const authz = await authorize(request, baseUrl, opts.auth, runtimeContext, opts.identity, false);
78
- if (!authz.ok)
79
- return authz.response;
80
- try {
81
- validateAuthPermissions(authz, opts.registry);
82
- }
83
- catch {
84
- return privateJson({ error: "invalid identity permission" }, { status: 403 });
85
- }
86
- // Refused like an absent page, so a slug's existence is not an oracle.
87
- return mayViewArtifacts(authz, opts.registry) ? authz : notFound();
88
- };
89
- const labelsFor = async (context, actors) => {
90
- const labels = await resolveActorLabels(actors, context.opts.auth);
91
- return (actor) => ({
92
- label: labels.get(actorKey(actor)) ?? actor.id ?? actor.kind,
93
- });
94
- };
95
- const library = async (context) => {
96
- const admitted = await admit(context);
97
- if (admitted instanceof Response)
98
- return admitted;
99
- const params = context.url.searchParams;
100
- const query = (params.get("q") ?? "").slice(0, MAX_QUERY_CHARS);
101
- const cursor = params.get("cursor") ?? undefined;
102
- const listed = await ops.list({
103
- limit: LIBRARY_PAGE,
104
- ...(query ? { query } : {}),
105
- ...(params.get("archived") === "1" ? { includeArchived: true } : {}),
106
- ...(cursor !== undefined ? { cursor } : {}),
107
- });
108
- if (!listed.ok)
109
- return privateJson({ error: "invalid cursor" }, { status: 400 });
110
- const label = await labelsFor(context, listed.items.map(({ head }) => head.updatedBy));
111
- return privateJson({
112
- artifacts: listed.items.map(({ id, head }) => ({
113
- id,
114
- title: head.title,
115
- kind: head.kind,
116
- viewVersion: head.view.version,
117
- updatedAt: head.updatedAt,
118
- updatedBy: label(head.updatedBy),
119
- archived: head.archived,
120
- freshness: freshnessOf(head),
121
- })),
122
- ...(listed.nextCursor !== undefined ? { nextCursor: listed.nextCursor } : {}),
123
- });
124
- };
125
- const view = async (context, id) => {
126
- const admitted = await admit(context);
127
- if (admitted instanceof Response)
128
- return admitted;
129
- const params = context.url.searchParams;
130
- const pinned = params.get("v");
131
- let pin;
132
- if (pinned !== null) {
133
- const pins = params.getAll("d");
134
- if (!/^\d{1,9}$/.test(pinned) || pins.length > MAX_PINS)
135
- return notFound();
136
- const documents = {};
137
- for (const entry of pins) {
138
- const match = PIN.exec(entry);
139
- if (!match?.[1] || match[1] === "__proto__")
140
- return notFound();
141
- documents[match[1]] = Number(match[2]);
142
- }
143
- pin = { view: Number(pinned), documents };
144
- }
145
- const theme = resolveBranding(context.opts.branding).theme;
146
- const page = await ops.page(id, pin ?? {}, theme);
147
- if (!page.ok)
148
- return notFound();
149
- const origin = new URL(context.baseUrl).origin;
150
- const url = `${origin}/artifacts/${id}`;
151
- const documents = Object.entries(page.documents).map(([name, { record }]) => ({
152
- name,
153
- version: record.version,
154
- updatedAt: record.at,
155
- }));
156
- const pins = documents.map(({ name, version }) => `d=${name}:${version}`);
157
- const label = await labelsFor(context, [page.view.by]);
158
- return privateJson({
159
- id,
160
- title: page.head.title,
161
- kind: page.head.kind,
162
- archived: page.head.archived,
163
- snapshot: pin !== undefined,
164
- latestViewVersion: page.head.view.version,
165
- view: {
166
- version: page.view.version,
167
- at: page.view.at,
168
- by: label(page.view.by),
169
- },
170
- documents,
171
- freshness: freshnessOf(page.head),
172
- url,
173
- snapshotUrl: `${url}/v/${page.view.version}${pins.length ? `?${pins.join("&")}` : ""}`,
174
- document: page.document,
175
- });
176
- };
177
- return async (context) => {
178
- const { path, request } = context;
179
- const read = request.method === "GET" || request.method === "HEAD";
180
- if (path === "/artifacts/_frame") {
181
- if (!read)
182
- return privateJson({ error: "method not allowed" }, { status: 405 });
183
- const bootstrap = frameBootstrap(new URL(context.baseUrl).origin, resolveBranding(context.opts.branding).theme);
184
- return new Response(request.method === "HEAD" ? null : bootstrap, {
185
- headers: {
186
- "Content-Type": "text/html; charset=utf-8",
187
- "Content-Security-Policy": csp,
188
- "X-Content-Type-Options": "nosniff",
189
- "Cache-Control": "no-store",
190
- "Cross-Origin-Resource-Policy": "same-origin",
191
- "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=()",
192
- },
193
- });
194
- }
195
- if (!path.startsWith("/artifacts/_api/"))
196
- return null;
197
- if (request.method !== "GET")
198
- return privateJson({ error: "method not allowed" }, { status: 405 });
199
- if (path === "/artifacts/_api/list")
200
- return library(context);
201
- const match = /^\/artifacts\/_api\/view\/([^/]+)$/.exec(path);
202
- if (match?.[1] && ARTIFACT_ID.test(match[1]))
203
- return view(context, match[1]);
204
- return notFound();
205
- };
206
- }