@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.
- package/build-artifact.ts +130 -0
- package/build-flutter-web.ts +508 -0
- package/deployment-config/README.md +414 -0
- package/deployment-config/cli.ts +228 -0
- package/deployment-config/generate.ts +689 -0
- package/deployment-config/jsonc.ts +18 -0
- package/deployment-config/profile-schema.generated.ts +391 -0
- package/deployment-config/profile.schema.json +358 -0
- package/deployment-config/profile.ts +124 -0
- package/migrations/0001_better_auth.sql +15 -0
- package/migrations/0002_drop_account_issuer.sql +8 -0
- package/package.json +61 -5
- package/release-version.ts +20 -0
- package/src/account-admission.ts +179 -0
- package/src/account-deletion.ts +274 -0
- package/src/admin-entrypoint.ts +194 -0
- package/src/admin-identities.ts +26 -0
- package/src/audit.ts +103 -0
- package/src/auth-package.access.ts +24 -0
- package/src/auth-package.ts +36 -0
- package/src/avatar-state-cleanup.ts +107 -0
- package/src/billing-computer.ts +267 -0
- package/src/billing-readiness.ts +21 -0
- package/src/billing.ts +461 -0
- package/src/bot-capabilities.ts +480 -0
- package/src/bot-recovery.ts +1 -0
- package/src/bot-state-channel.ts +702 -0
- package/src/bot-state.ts +3995 -0
- package/src/bot-template-cleanup.ts +98 -0
- package/src/bot-title-cleanup.ts +104 -0
- package/src/brand-icon.png +0 -0
- package/src/brand-logo.ts +2 -0
- package/src/brand.ts +36 -0
- package/src/client-compatibility.ts +51 -0
- package/src/compaction-announcement-cleanup.ts +113 -0
- package/src/computer-egress.ts +110 -0
- package/src/computer-host.ts +92 -0
- package/src/computer-screenshot-cleanup.ts +78 -0
- package/src/contracts.ts +832 -0
- package/src/debug.ts +272 -0
- package/src/default-packages-marker-cleanup.ts +39 -0
- package/src/deployment-policy-admin-host.ts +121 -0
- package/src/deployment-policy.ts +426 -0
- package/src/directory-profile-cleanup.ts +101 -0
- package/src/durable-rpc.ts +753 -0
- package/src/durable-session.ts +21 -0
- package/src/entry-boundary.ts +103 -0
- package/src/frock-ai.ts +338 -0
- package/src/gateway.ts +1392 -0
- package/src/group-chat.ts +736 -0
- package/src/hidden-bot-notifications-cleanup.ts +32 -0
- package/src/index.ts +2798 -0
- package/src/insights.ts +15 -0
- package/src/machine-messages-cleanup.ts +121 -0
- package/src/machine-socket.ts +164 -0
- package/src/memory-records.ts +181 -0
- package/src/memory.ts +145 -0
- package/src/model-rates.ts +252 -0
- package/src/native-auth.ts +1006 -0
- package/src/native-sessions.ts +121 -0
- package/src/notification-state-cleanup.ts +18 -0
- package/src/ollama-web-search-cleanup.ts +69 -0
- package/src/package-page-shapes-cleanup.ts +190 -0
- package/src/plugin-egress.ts +31 -0
- package/src/plugin-page-route.ts +72 -0
- package/src/plugin-panels-cleanup.ts +101 -0
- package/src/prepared-input-cleanup.ts +105 -0
- package/src/production-secrets.ts +484 -0
- package/src/project-cleanup.ts +123 -0
- package/src/project-events-cleanup.ts +152 -0
- package/src/publication-state-cleanup.ts +92 -0
- package/src/push.ts +498 -0
- package/src/request-body.ts +165 -0
- package/src/routine-state-cleanup.ts +57 -0
- package/src/search.ts +88 -0
- package/src/sidebar-label-cleanup.ts +165 -0
- package/src/skill-index-cleanup.ts +53 -0
- package/src/supersede-cleanup.ts +162 -0
- package/src/test-chat-cleanup.ts +93 -0
- package/src/uploads.ts +486 -0
- package/src/user-application.ts +1068 -0
- package/src/user-configuration.ts +4491 -0
- package/src/voice-assistant.ts +4999 -0
- package/src/voice-dictation.ts +743 -0
- package/src/working-context-cleanup.ts +96 -0
- package/src/workspace.ts +201 -0
- package/wrangler.jsonc +385 -0
- 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
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -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