@frockbot/cloudflare 0.0.0 → 0.7.291

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 (88) hide show
  1. package/build-artifact.ts +130 -0
  2. package/build-flutter-web.ts +508 -0
  3. package/deployment-config/README.md +414 -0
  4. package/deployment-config/cli.ts +228 -0
  5. package/deployment-config/generate.ts +689 -0
  6. package/deployment-config/jsonc.ts +18 -0
  7. package/deployment-config/profile-schema.generated.ts +391 -0
  8. package/deployment-config/profile.schema.json +358 -0
  9. package/deployment-config/profile.ts +124 -0
  10. package/migrations/0001_better_auth.sql +15 -0
  11. package/migrations/0002_drop_account_issuer.sql +8 -0
  12. package/package.json +61 -5
  13. package/release-version.ts +20 -0
  14. package/src/account-admission.ts +179 -0
  15. package/src/account-deletion.ts +274 -0
  16. package/src/admin-entrypoint.ts +194 -0
  17. package/src/admin-identities.ts +26 -0
  18. package/src/audit.ts +103 -0
  19. package/src/auth-package.access.ts +24 -0
  20. package/src/auth-package.ts +36 -0
  21. package/src/avatar-state-cleanup.ts +107 -0
  22. package/src/billing-computer.ts +267 -0
  23. package/src/billing-readiness.ts +21 -0
  24. package/src/billing.ts +461 -0
  25. package/src/bot-capabilities.ts +480 -0
  26. package/src/bot-recovery.ts +1 -0
  27. package/src/bot-state-channel.ts +702 -0
  28. package/src/bot-state.ts +3995 -0
  29. package/src/bot-template-cleanup.ts +98 -0
  30. package/src/bot-title-cleanup.ts +104 -0
  31. package/src/brand-icon.png +0 -0
  32. package/src/brand-logo.ts +2 -0
  33. package/src/brand.ts +36 -0
  34. package/src/client-compatibility.ts +51 -0
  35. package/src/compaction-announcement-cleanup.ts +113 -0
  36. package/src/computer-egress.ts +110 -0
  37. package/src/computer-host.ts +92 -0
  38. package/src/computer-screenshot-cleanup.ts +78 -0
  39. package/src/contracts.ts +832 -0
  40. package/src/debug.ts +272 -0
  41. package/src/default-packages-marker-cleanup.ts +39 -0
  42. package/src/deployment-policy-admin-host.ts +121 -0
  43. package/src/deployment-policy.ts +426 -0
  44. package/src/directory-profile-cleanup.ts +101 -0
  45. package/src/durable-rpc.ts +753 -0
  46. package/src/durable-session.ts +21 -0
  47. package/src/entry-boundary.ts +103 -0
  48. package/src/frock-ai.ts +338 -0
  49. package/src/gateway.ts +1392 -0
  50. package/src/group-chat.ts +736 -0
  51. package/src/hidden-bot-notifications-cleanup.ts +32 -0
  52. package/src/index.ts +2798 -0
  53. package/src/insights.ts +15 -0
  54. package/src/machine-messages-cleanup.ts +121 -0
  55. package/src/machine-socket.ts +164 -0
  56. package/src/memory-records.ts +181 -0
  57. package/src/memory.ts +145 -0
  58. package/src/model-rates.ts +252 -0
  59. package/src/native-auth.ts +1006 -0
  60. package/src/native-sessions.ts +121 -0
  61. package/src/notification-state-cleanup.ts +18 -0
  62. package/src/ollama-web-search-cleanup.ts +69 -0
  63. package/src/package-page-shapes-cleanup.ts +190 -0
  64. package/src/plugin-egress.ts +31 -0
  65. package/src/plugin-page-route.ts +72 -0
  66. package/src/plugin-panels-cleanup.ts +101 -0
  67. package/src/prepared-input-cleanup.ts +105 -0
  68. package/src/production-secrets.ts +484 -0
  69. package/src/project-cleanup.ts +123 -0
  70. package/src/project-events-cleanup.ts +152 -0
  71. package/src/publication-state-cleanup.ts +92 -0
  72. package/src/push.ts +498 -0
  73. package/src/request-body.ts +165 -0
  74. package/src/routine-state-cleanup.ts +57 -0
  75. package/src/search.ts +88 -0
  76. package/src/sidebar-label-cleanup.ts +165 -0
  77. package/src/skill-index-cleanup.ts +53 -0
  78. package/src/supersede-cleanup.ts +162 -0
  79. package/src/test-chat-cleanup.ts +93 -0
  80. package/src/uploads.ts +486 -0
  81. package/src/user-application.ts +1068 -0
  82. package/src/user-configuration.ts +4491 -0
  83. package/src/voice-assistant.ts +4999 -0
  84. package/src/voice-dictation.ts +743 -0
  85. package/src/working-context-cleanup.ts +96 -0
  86. package/src/workspace.ts +201 -0
  87. package/wrangler.jsonc +385 -0
  88. package/README.md +0 -3
@@ -0,0 +1,96 @@
1
+ // Projects the working context for Sessions whose event log predates it.
2
+ //
3
+ // The working-context head arrived after these logs were written, and an
4
+ // append to a log with events but no head is refused as a gap, so every Turn
5
+ // on such a Session failed before its first event. Rebuilding is an archive
6
+ // read, which ordinary startup never does; the receipt makes it once per
7
+ // object. A log this process cannot read or project gets an empty context
8
+ // that starts after it, so the Bot answers without that history rather than
9
+ // not at all.
10
+
11
+ import { emptyConversationHeadV1 } from "@frockbot/core/contracts";
12
+ import {
13
+ SESSION_EVENT_LOG_INDEX_PREFIX,
14
+ SessionEventLog,
15
+ workingContextHeadKeyV1,
16
+ type WorkingContextStorageV1,
17
+ } from "@frockbot/core/durable";
18
+ import { replaceWorkingContextV1 } from "@frockbot/app/shell/working-context-store";
19
+
20
+ const RECEIPT = "maintenance:working-context-head:2026-09-23";
21
+ const PAGE = 50;
22
+
23
+ interface CleanupStorage extends WorkingContextStorageV1 {
24
+ transaction<T>(body: (tx: WorkingContextStorageV1) => Promise<T>): Promise<T>;
25
+ }
26
+
27
+ export async function projectUnprojectedSessionsV1(
28
+ storage: CleanupStorage,
29
+ ): Promise<void> {
30
+ if (await storage.get(RECEIPT)) return;
31
+ const sessions: { sessionId: string; eventCount: number }[] = [];
32
+ let start: string | undefined;
33
+ for (;;) {
34
+ const listed = await storage.list<{ eventCount?: number }>({
35
+ prefix: SESSION_EVENT_LOG_INDEX_PREFIX,
36
+ limit: PAGE,
37
+ ...(start ? { start } : {}),
38
+ });
39
+ for (const [key, index] of listed) {
40
+ const eventCount = index?.eventCount;
41
+ if (!Number.isSafeInteger(eventCount) || eventCount! <= 0) continue;
42
+ sessions.push({
43
+ sessionId: decodeURIComponent(
44
+ key.slice(SESSION_EVENT_LOG_INDEX_PREFIX.length),
45
+ ),
46
+ eventCount: eventCount!,
47
+ });
48
+ }
49
+ if (listed.size < PAGE) break;
50
+ start = `${[...listed.keys()].at(-1)}\0`;
51
+ }
52
+ const rebuilt: { sessionId: string; events: number; from: string }[] = [];
53
+ for (const { sessionId, eventCount } of sessions) {
54
+ if (await storage.get(workingContextHeadKeyV1(sessionId))) continue;
55
+ // Display fidelity leaves each model request as its bounded projection;
56
+ // the reducer never reads one back.
57
+ const events = await new SessionEventLog(storage)
58
+ .readDisplayRange(sessionId, 0, eventCount)
59
+ .catch(() => undefined);
60
+ let from: "log" | "empty" = "log";
61
+ if (events) {
62
+ try {
63
+ await storage.transaction(async (tx) => {
64
+ if (await tx.get(workingContextHeadKeyV1(sessionId))) return;
65
+ await replaceWorkingContextV1(tx, sessionId, events);
66
+ });
67
+ } catch {
68
+ from = "empty";
69
+ }
70
+ } else {
71
+ from = "empty";
72
+ }
73
+ if (from === "empty") {
74
+ // A throw here would reset the object on every wake, so the fallback is
75
+ // a plain write. Every Turn writes at least one event, so no earlier
76
+ // Turn number reaches past the event count.
77
+ if (!(await storage.get(workingContextHeadKeyV1(sessionId)))) {
78
+ await storage.put(workingContextHeadKeyV1(sessionId), {
79
+ ...emptyConversationHeadV1(sessionId),
80
+ nextSeq: eventCount,
81
+ projectedThroughSeq: eventCount,
82
+ nextTurn: eventCount + 1,
83
+ });
84
+ }
85
+ }
86
+ rebuilt.push({
87
+ sessionId,
88
+ events: eventCount,
89
+ from,
90
+ });
91
+ }
92
+ await storage.put(RECEIPT, { schemaVersion: 1, rebuilt });
93
+ if (rebuilt.length) {
94
+ console.info({ event: "working-context-cleanup", rebuilt });
95
+ }
96
+ }
@@ -0,0 +1,201 @@
1
+ // The `WORKSPACE_FILES` seam, bound in production.
2
+ //
3
+ // `app/shell/backend-skills.ts` reads one property off the Bot
4
+ // Durable Object's environment: a `WorkspaceFilesV1`. Until now nothing bound
5
+ // it, so the Skills Package was never mounted and no deployed Turn could load
6
+ // a Skill. This module is the binding: `WorkspaceFilesV1` over R2, with every
7
+ // generation recorded in the Bot Durable Object that owns the root.
8
+ //
9
+ // It wakes no Computer. "The Agent loop, Memory, Skills, Package composition,
10
+ // and Routines function correctly while the Computer is hibernated and do not
11
+ // wake it" — a read here is an R2 read and a Durable Object storage read, and
12
+ // nothing on this path can reach a Computer provider.
13
+ //
14
+ // The R2 types stop here: "Electron, Cloudflare, provider SDK, and Computer
15
+ // implementation types remain inside their adapters", so `@frockbot/workspace-
16
+ // store` sees only the structural `ObjectBucketV1` this file supplies.
17
+ import type {
18
+ WorkspaceFilesV1,
19
+ WorkspaceGenerationsV1,
20
+ } from "@frockbot/core/contracts";
21
+ import {
22
+ createObjectWorkspaceFilesV1,
23
+ workspaceObjectPrefixV1,
24
+ type ObjectBucketV1,
25
+ type ObjectHeadV1,
26
+ type WorkspaceGenerationPublicationV1,
27
+ type WorkspaceStoreSurfaceV1,
28
+ } from "@frockbot/core/workspace-store";
29
+
30
+ function head(object: R2Object): ObjectHeadV1 {
31
+ return {
32
+ key: object.key,
33
+ etag: object.etag,
34
+ size: object.size,
35
+ uploaded: object.uploaded,
36
+ ...(object.customMetadata ? { customMetadata: object.customMetadata } : {}),
37
+ };
38
+ }
39
+
40
+ /** The structural object store, over one R2 bucket. */
41
+ export function createR2ObjectBucketV1(bucket: R2Bucket): ObjectBucketV1 {
42
+ return {
43
+ get: async (key) => {
44
+ const object = await bucket.get(key);
45
+ if (!object) return null;
46
+ return {
47
+ ...head(object),
48
+ bytes: async () => new Uint8Array(await object.arrayBuffer()),
49
+ };
50
+ },
51
+ head: async (key) => {
52
+ const object = await bucket.head(key);
53
+ return object ? head(object) : null;
54
+ },
55
+ put: async (key, bytes, options) => {
56
+ const onlyIf = options?.onlyIf;
57
+ // `If-None-Match: *` is "create only if absent". `uploadedBefore` at the
58
+ // epoch says the same thing in a second way, and is sent alongside so
59
+ // the precondition holds even where a wildcard etag is compared
60
+ // literally. A create that silently overwrote would be
61
+ // last-writer-wins, which is forbidden outright.
62
+ const conditional: R2Conditional | undefined =
63
+ onlyIf === undefined
64
+ ? undefined
65
+ : onlyIf.etagDoesNotMatch === "*"
66
+ ? { etagDoesNotMatch: "*", uploadedBefore: new Date(0) }
67
+ : onlyIf;
68
+ const written = await bucket.put(key, bytes as ArrayBufferView, {
69
+ ...(conditional ? { onlyIf: conditional } : {}),
70
+ ...(options?.customMetadata
71
+ ? { customMetadata: options.customMetadata }
72
+ : {}),
73
+ ...(options?.contentType
74
+ ? { httpMetadata: { contentType: options.contentType } }
75
+ : {}),
76
+ });
77
+ return written ? head(written) : null;
78
+ },
79
+ delete: (key) => bucket.delete(key),
80
+ list: async (request) => {
81
+ const page = await bucket.list({
82
+ ...(request.prefix ? { prefix: request.prefix } : {}),
83
+ ...(request.cursor ? { cursor: request.cursor } : {}),
84
+ ...(request.limit ? { limit: request.limit } : {}),
85
+ include: ["customMetadata"],
86
+ });
87
+ return {
88
+ objects: page.objects.map(head),
89
+ truncated: page.truncated,
90
+ ...(page.truncated ? { cursor: page.cursor } : {}),
91
+ };
92
+ },
93
+ };
94
+ }
95
+
96
+ /** The bucket binding that backs durable roots. Optional: absence is a state. */
97
+ export interface WorkspaceStoreEnv {
98
+ MEMORY_FILES?: R2Bucket;
99
+ }
100
+
101
+ /**
102
+ * The Workspace file surface one Durable Object serves, or `undefined` when no
103
+ * bucket is bound — in which case the Skills Package is simply not mounted,
104
+ * visibly, rather than reading instructions from a store it invented.
105
+ */
106
+ export function createDurableWorkspaceFilesV1(
107
+ env: WorkspaceStoreEnv,
108
+ options: {
109
+ /**
110
+ * The generation ledger these roots record in. Required, and passed in
111
+ * rather than constructed here: a ledger caches the minting cursor while
112
+ * it is resident, so two instances on one Durable Object can read one
113
+ * cursor and mint one id twice. The object that owns the roots holds
114
+ * exactly one.
115
+ */
116
+ generations: WorkspaceGenerationsV1;
117
+ surface?: WorkspaceStoreSurfaceV1;
118
+ /**
119
+ * The User whose durable roots this store serves. Passed as soon as the
120
+ * Durable Object knows it — a Bot object learns its User on the RPC that
121
+ * addresses it — so a root belonging to another User is refused at the
122
+ * store rather than reaching object storage.
123
+ */
124
+ owner?: { userId: string };
125
+ onInstructionPublication?: (
126
+ event: WorkspaceGenerationPublicationV1,
127
+ ) => Promise<void>;
128
+ },
129
+ ): WorkspaceFilesV1 | undefined {
130
+ const bucket = env.MEMORY_FILES;
131
+ if (!bucket) return undefined;
132
+ return createObjectWorkspaceFilesV1({
133
+ bucket: createR2ObjectBucketV1(bucket),
134
+ generations: options.generations,
135
+ ...(options.owner ? { owner: options.owner } : {}),
136
+ ...(options.surface ? { surface: options.surface } : {}),
137
+ ...(options.onInstructionPublication
138
+ ? { onInstructionPublication: options.onInstructionPublication }
139
+ : {}),
140
+ });
141
+ }
142
+
143
+ /** Removes every object under one prefix, a page at a time. Idempotent. */
144
+ async function deleteObjectPrefixV1(
145
+ store: ObjectBucketV1,
146
+ prefix: string,
147
+ ): Promise<number> {
148
+ let removed = 0;
149
+ let cursor: string | undefined;
150
+ do {
151
+ const page = await store.list({
152
+ prefix,
153
+ ...(cursor === undefined ? {} : { cursor }),
154
+ limit: 1_000,
155
+ });
156
+ for (const object of page.objects) {
157
+ await store.delete(object.key);
158
+ removed += 1;
159
+ }
160
+ cursor = page.truncated ? page.cursor : undefined;
161
+ } while (cursor !== undefined);
162
+ return removed;
163
+ }
164
+
165
+ /**
166
+ * Every object of the two durable roots one Bot owns, removed from the bucket.
167
+ *
168
+ * A Bot owns exactly two roots — `bot-instructions` and `bot-memory` — and
169
+ * both are keyed by `<kind>:<userId>:<botId>`, so a prefix listing names this
170
+ * Bot's objects and nobody else's. The User's own Skills root, User Memory,
171
+ * and every `package-declared` root are User-scoped and are deliberately left
172
+ * alone: another Bot of the same User still reads them.
173
+ *
174
+ * This is a bulk removal rather than `WorkspaceStore.delete`, which is
175
+ * generation-fenced per file and leaves a tombstone marker. A deleted Bot has
176
+ * no generation ledger left to fence against and nothing to preserve a losing
177
+ * write for, so the bytes go. Conflict objects live under the same prefix and
178
+ * go with them.
179
+ *
180
+ * Idempotent: a second call over an empty prefix removes nothing.
181
+ */
182
+ export async function deleteBotWorkspaceRootsV1(
183
+ env: WorkspaceStoreEnv,
184
+ identity: { userId: string; botId: string },
185
+ ): Promise<number> {
186
+ const bucket = env.MEMORY_FILES;
187
+ if (!bucket) return 0;
188
+ const store = createR2ObjectBucketV1(bucket);
189
+ let removed = 0;
190
+ for (const kind of ["bot-instructions", "bot-memory"] as const) {
191
+ removed += await deleteObjectPrefixV1(
192
+ store,
193
+ workspaceObjectPrefixV1({
194
+ kind,
195
+ userId: identity.userId,
196
+ botId: identity.botId,
197
+ }),
198
+ );
199
+ }
200
+ return removed;
201
+ }
package/wrangler.jsonc ADDED
@@ -0,0 +1,385 @@
1
+ // Bindings, migrations, and the environments `wrangler dev` and the test
2
+ // harnesses run. No deployment identity: the account, the Worker names, the
3
+ // routes, the resource names and the identity vars belong to
4
+ // `deployments/<profile>.json`, and `bun run deployment:config` joins the
5
+ // two into the config `wrangler deploy -c` takes (ADR 0028).
6
+ {
7
+ "$schema": "./node_modules/wrangler/config-schema.json",
8
+ "name": "frockbot-cloudflare",
9
+ "main": "src/index.ts",
10
+ "compatibility_date": "2026-08-27",
11
+ "compatibility_flags": ["nodejs_compat"],
12
+ // The Flutter client, staged by `build-flutter-web.ts` under
13
+ // `_flutter/<buildHash>/`. It is uploaded with the deploy and answered by
14
+ // the asset router before this Worker runs, which is why the payload
15
+ // needs no route, no bucket and no seeding step: the same `wrangler dev`
16
+ // that runs the Worker locally serves it from the same directory.
17
+ //
18
+ // `html_handling` and `not_found_handling` are both off. The document is
19
+ // rendered per account by the application artifact, so an asset
20
+ // directory that answered `/` or invented an index would answer for it.
21
+ "assets": {
22
+ "directory": "./dist/web",
23
+ "html_handling": "none",
24
+ "not_found_handling": "none",
25
+ },
26
+ "worker_loaders": [
27
+ {
28
+ "binding": "USER_APPLICATIONS",
29
+ },
30
+ // Bot-authored Package isolates, loaded from the Bot Durable Object with
31
+ // `globalOutbound` disabled (plan Step 4 / decision D2). A separate binding
32
+ // so Bot Packages and user applications never share a loader namespace.
33
+ {
34
+ "binding": "BOT_PACKAGES",
35
+ },
36
+ ],
37
+ // Wrangler's validator refuses a service with no target and a Vectorize
38
+ // binding with no index, even in a config it never deploys, so both carry
39
+ // `named-by-deployment-config` here. The generator writes the deployment's
40
+ // own names; nothing reads these.
41
+ "services": [
42
+ // The shared Computer host (ADR 0004). It holds SPRITES_TOKEN and the
43
+ // Sprites SDK; this Worker sends only an opaque credential reference.
44
+ {
45
+ "binding": "COMPUTER_HOST",
46
+ "service": "named-by-deployment-config",
47
+ },
48
+ // The plugin build service. It type-checks, lints, bundles
49
+ // and boots a plugin's source and returns the artifacts; it holds no
50
+ // storage, so this Worker stays the only thing that writes R2 and the only
51
+ // thing that hash-verifies what came back.
52
+ {
53
+ "binding": "APPLET_BUILD",
54
+ "service": "named-by-deployment-config",
55
+ },
56
+ ],
57
+ "r2_buckets": [
58
+ {
59
+ "binding": "APPLICATION_ARTIFACTS",
60
+ },
61
+ {
62
+ "binding": "MEMORY_FILES",
63
+ },
64
+ ],
65
+ "d1_databases": [
66
+ {
67
+ "binding": "AUTH_DB",
68
+ "migrations_dir": "migrations",
69
+ },
70
+ ],
71
+ "vectorize": [
72
+ {
73
+ "binding": "MEMORY_INDEX",
74
+ "index_name": "named-by-deployment-config",
75
+ },
76
+ ],
77
+ "ai": {
78
+ "binding": "AI",
79
+ },
80
+ "durable_objects": {
81
+ "bindings": [
82
+ {
83
+ "name": "BOT_STATES",
84
+ "class_name": "BotState",
85
+ },
86
+ {
87
+ "name": "USER_CONFIGURATIONS",
88
+ "class_name": "UserConfiguration",
89
+ },
90
+ {
91
+ "name": "DEPLOYMENT_POLICY",
92
+ "class_name": "DeploymentPolicy",
93
+ },
94
+ // The account-wide voice session, one object per User (docs/voice.md).
95
+ // A Cloudflare Agents SDK class; the Bot runtime stays hand-rolled.
96
+ {
97
+ "name": "VOICE_ASSISTANTS",
98
+ "class_name": "VoiceAssistant",
99
+ },
100
+ // One object per Group Chat: its thread, read cursor and member Turns.
101
+ {
102
+ "name": "GROUP_CHATS",
103
+ "class_name": "GroupChat",
104
+ },
105
+ ],
106
+ },
107
+ "migrations": [
108
+ {
109
+ "tag": "v1",
110
+ "new_sqlite_classes": ["BotState"],
111
+ },
112
+ {
113
+ "tag": "v2",
114
+ "new_sqlite_classes": ["UserConfiguration"],
115
+ },
116
+ {
117
+ "tag": "v3",
118
+ "new_sqlite_classes": ["DeploymentPolicy"],
119
+ },
120
+ {
121
+ "tag": "v4",
122
+ "new_sqlite_classes": ["AppletState"],
123
+ },
124
+ {
125
+ "tag": "v5",
126
+ "new_sqlite_classes": ["VoiceSession"],
127
+ },
128
+ // `v5` is immutable once applied, so the class it created is retired by a
129
+ // later tag rather than by editing it away.
130
+ {
131
+ "tag": "v6",
132
+ "deleted_classes": ["VoiceSession"],
133
+ },
134
+ // A new class under a new name: the retired `VoiceSession` above cannot be
135
+ // revived, and this one is an Agents SDK object with SQLite storage.
136
+ {
137
+ "tag": "v7",
138
+ "new_sqlite_classes": ["VoiceAssistant"],
139
+ },
140
+ {
141
+ "tag": "v8",
142
+ "deleted_classes": ["AppletState"],
143
+ },
144
+ {
145
+ "tag": "v9",
146
+ "new_sqlite_classes": ["GroupChat"],
147
+ },
148
+ ],
149
+ "vars": {
150
+ "DEFAULT_APPLICATION_HASH": "foundation-v1",
151
+ },
152
+ "env": {
153
+ "development": {
154
+ // Never deployed, and without an empty list `wrangler dev` inherits the
155
+ // top-level custom domain and rewrites every request URL to
156
+ // bot.frockbot.com — which the native auth origin check then refuses.
157
+ "routes": [],
158
+ // The Flutter client, staged by `build-flutter-web.ts` under
159
+ // `_flutter/<buildHash>/`. It is uploaded with the deploy and answered by
160
+ // the asset router before this Worker runs, which is why the payload
161
+ // needs no route, no bucket and no seeding step: the same `wrangler dev`
162
+ // that runs the Worker locally serves it from the same directory.
163
+ //
164
+ // `html_handling` and `not_found_handling` are both off. The document is
165
+ // rendered per account by the application artifact, so an asset
166
+ // directory that answered `/` or invented an index would answer for it.
167
+ "assets": {
168
+ "directory": "./dist/web",
169
+ "html_handling": "none",
170
+ "not_found_handling": "none",
171
+ },
172
+ "worker_loaders": [
173
+ {
174
+ "binding": "USER_APPLICATIONS",
175
+ },
176
+ {
177
+ "binding": "BOT_PACKAGES",
178
+ },
179
+ ],
180
+ "services": [
181
+ {
182
+ "binding": "COMPUTER_HOST",
183
+ "service": "frockbot-computer-host",
184
+ },
185
+ {
186
+ "binding": "APPLET_BUILD",
187
+ "service": "frockbot-applet-build",
188
+ },
189
+ ],
190
+ "r2_buckets": [
191
+ {
192
+ "binding": "APPLICATION_ARTIFACTS",
193
+ "bucket_name": "frockbot-application-artifacts",
194
+ "remote": false,
195
+ },
196
+ {
197
+ "binding": "MEMORY_FILES",
198
+ "bucket_name": "frockbot-memory-files-development",
199
+ "remote": true,
200
+ },
201
+ ],
202
+ "d1_databases": [
203
+ {
204
+ "binding": "AUTH_DB",
205
+ "database_name": "frockbot-auth-development",
206
+ "database_id": "00000000-0000-0000-0000-000000000000",
207
+ "migrations_dir": "migrations",
208
+ "remote": false,
209
+ },
210
+ ],
211
+ "vectorize": [
212
+ {
213
+ "binding": "MEMORY_INDEX",
214
+ "index_name": "frockbot-memory-development",
215
+ "remote": true,
216
+ },
217
+ ],
218
+ "ai": {
219
+ "binding": "AI",
220
+ "remote": true,
221
+ },
222
+ "durable_objects": {
223
+ "bindings": [
224
+ {
225
+ "name": "BOT_STATES",
226
+ "class_name": "BotState",
227
+ },
228
+ {
229
+ "name": "USER_CONFIGURATIONS",
230
+ "class_name": "UserConfiguration",
231
+ },
232
+ {
233
+ "name": "DEPLOYMENT_POLICY",
234
+ "class_name": "DeploymentPolicy",
235
+ },
236
+ {
237
+ "name": "VOICE_ASSISTANTS",
238
+ "class_name": "VoiceAssistant",
239
+ },
240
+ {
241
+ "name": "GROUP_CHATS",
242
+ "class_name": "GroupChat",
243
+ },
244
+ ],
245
+ },
246
+ "vars": {
247
+ "DEFAULT_APPLICATION_HASH": "foundation-v1",
248
+ "FROCK_AI_GATEWAY_ID": "flock",
249
+ // Enables the compat HTTP transport, which is the only one that
250
+ // accepts a `dynamic/<route>` model (cloudflare/ai#617). Pairs
251
+ // with the FROCK_AI_GATEWAY_TOKEN secret.
252
+ "FROCK_AI_ACCOUNT_ID": "374784ce2915a597adcecf9bcb2c5ee6",
253
+ "FROCK_AI_AUTO_ROUTE": "flock-auto",
254
+ // Placeholder only. Production supplies this as a secret.
255
+ "FROCKBOT_ADMIN_EMAILS": "owner@example.com",
256
+ },
257
+ },
258
+ // The browser end-to-end harness (`apps/cloudflare/e2e/harness.ts`) runs
259
+ // `src/index.ts` — the deployed Worker, unmodified — under `wrangler dev`.
260
+ // It cannot use `development`: that environment marks `MEMORY_FILES`,
261
+ // `MEMORY_INDEX` and `AI` remote, and a remote binding makes `wrangler dev`
262
+ // open a proxy session against the Cloudflare API, which fails without a
263
+ // `CLOUDFLARE_API_TOKEN` and so cannot run on a pull request.
264
+ //
265
+ // Only binding locality differs. `main`, `compatibility_date`,
266
+ // `compatibility_flags`, `migrations` and the Durable Object classes are
267
+ // the top-level production ones, and every var is the production value.
268
+ // `MEMORY_INDEX` is omitted because workerd has no local Vectorize
269
+ // simulator. `AI` is a local RPC service under this environment: the e2e
270
+ // harness starts its auxiliary Worker in a second local Wrangler process,
271
+ // which the dev service registry connects at the same `run(model, input)`
272
+ // seam without spending account credits or requiring a Cloudflare token.
273
+ "e2e": {
274
+ // This environment is never deployed. Without an empty list it would
275
+ // inherit the top-level custom domain and a stray `wrangler deploy --env
276
+ // e2e` would reassign bot.frockbot.com to it.
277
+ "routes": [],
278
+ // The Flutter client, staged by `build-flutter-web.ts` under
279
+ // `_flutter/<buildHash>/`. It is uploaded with the deploy and answered by
280
+ // the asset router before this Worker runs, which is why the payload
281
+ // needs no route, no bucket and no seeding step: the same `wrangler dev`
282
+ // that runs the Worker locally serves it from the same directory.
283
+ //
284
+ // `html_handling` and `not_found_handling` are both off. The document is
285
+ // rendered per account by the application artifact, so an asset
286
+ // directory that answered `/` or invented an index would answer for it.
287
+ "assets": {
288
+ "directory": "./dist/web",
289
+ "html_handling": "none",
290
+ "not_found_handling": "none",
291
+ },
292
+ "worker_loaders": [
293
+ {
294
+ "binding": "USER_APPLICATIONS",
295
+ },
296
+ {
297
+ "binding": "BOT_PACKAGES",
298
+ },
299
+ ],
300
+ // Declared as production declares them. The Computer host does not run
301
+ // beside the harness, so `COMPUTER_HOST` reports "not connected" and no
302
+ // spec uses a Computer; a binding that is declared but unreachable is
303
+ // what production looks like when a dependency is down. The plugin build
304
+ // service runs beside it — `e2e/harness.ts` starts `apps/applet-build`
305
+ // under its own `wrangler dev` in the dev service registry.
306
+ "services": [
307
+ {
308
+ "binding": "COMPUTER_HOST",
309
+ "service": "frockbot-computer-host",
310
+ },
311
+ {
312
+ "binding": "APPLET_BUILD",
313
+ "service": "frockbot-applet-build",
314
+ },
315
+ {
316
+ "binding": "AI",
317
+ // The deployed e2e stand-in Worker; its Cloudflare name still reads
318
+ // `flock`, and renaming the service is a separate manual step.
319
+ "service": "frockbot-flock-ai-e2e",
320
+ "entrypoint": "FrockAiFake",
321
+ },
322
+ ],
323
+ "r2_buckets": [
324
+ {
325
+ "binding": "APPLICATION_ARTIFACTS",
326
+ "bucket_name": "frockbot-application-artifacts",
327
+ "remote": false,
328
+ },
329
+ {
330
+ "binding": "MEMORY_FILES",
331
+ "bucket_name": "frockbot-memory-files-e2e",
332
+ "remote": false,
333
+ },
334
+ ],
335
+ "d1_databases": [
336
+ {
337
+ "binding": "AUTH_DB",
338
+ "database_name": "frockbot-auth-e2e",
339
+ "database_id": "00000000-0000-0000-0000-000000000000",
340
+ "migrations_dir": "migrations",
341
+ "remote": false,
342
+ },
343
+ ],
344
+ "durable_objects": {
345
+ "bindings": [
346
+ {
347
+ "name": "BOT_STATES",
348
+ "class_name": "BotState",
349
+ },
350
+ {
351
+ "name": "USER_CONFIGURATIONS",
352
+ "class_name": "UserConfiguration",
353
+ },
354
+ {
355
+ "name": "DEPLOYMENT_POLICY",
356
+ "class_name": "DeploymentPolicy",
357
+ },
358
+ {
359
+ "name": "VOICE_ASSISTANTS",
360
+ "class_name": "VoiceAssistant",
361
+ },
362
+ {
363
+ "name": "GROUP_CHATS",
364
+ "class_name": "GroupChat",
365
+ },
366
+ ],
367
+ },
368
+ "vars": {
369
+ "DEFAULT_APPLICATION_HASH": "foundation-v1",
370
+ "FROCK_AI_GATEWAY_ID": "flock",
371
+ // Enables the compat HTTP transport, which is the only one that
372
+ // accepts a `dynamic/<route>` model (cloudflare/ai#617). Pairs
373
+ // with the FROCK_AI_GATEWAY_TOKEN secret.
374
+ "FROCK_AI_ACCOUNT_ID": "374784ce2915a597adcecf9bcb2c5ee6",
375
+ "FROCK_AI_AUTO_ROUTE": "flock-auto",
376
+ // The canonical `development` identity is admin in this environment;
377
+ // this placeholder also proves the configured-allowlist rule.
378
+ "FROCKBOT_ADMIN_EMAILS": "owner@example.com",
379
+ },
380
+ },
381
+ },
382
+ "observability": {
383
+ "enabled": true,
384
+ },
385
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # @frockbot/cloudflare
2
-
3
- Placeholder reserving this name. See https://github.com/timoconnellaus/frockbot.