@frockbot/cloudflare 0.7.294 → 0.7.296
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/deployment-config/README.md +7 -3
- package/deployment-config/profile-schema.generated.ts +11 -1
- package/deployment-config/profile.schema.json +10 -1
- package/package.json +7 -7
- package/src/admin-identities.ts +29 -3
- package/src/bot-state.ts +17 -0
- package/src/contracts.ts +10 -1
- package/src/gateway.ts +4 -1
- package/src/index.ts +37 -4
- package/src/production-secrets.ts +6 -0
- package/src/user-application.ts +39 -1
|
@@ -121,9 +121,13 @@ the local Worker, and every generated config overrides it from the profile.
|
|
|
121
121
|
|
|
122
122
|
Some fields keep a placeholder rather than nothing: wrangler's validator refuses a `services` entry with no target and a `vectorize` entry with no index even in a config it never deploys, so the app Worker's two services and its Vectorize binding, and the admin portal's one service, all say `named-by-deployment-config`. Nothing reads those values — `wrangler dev --env development` and the `e2e` harness resolve their own environments, and the generator writes the deployment's own names. A bucket or database name is left out entirely, because wrangler does not ask for one.
|
|
123
123
|
|
|
124
|
-
`adminEmails`
|
|
125
|
-
`FROCKBOT_ADMIN_EMAILS`
|
|
126
|
-
|
|
124
|
+
`adminEmails` and `adminUserIds` are in the schema and in no generated config.
|
|
125
|
+
They are the `FROCKBOT_ADMIN_EMAILS` and `FROCKBOT_ADMIN_USER_IDS` secrets the
|
|
126
|
+
installer sets; the hosted deployment already carries the first as a repository
|
|
127
|
+
secret, which is why `hosted.json` omits it. An email makes an admin only once
|
|
128
|
+
the auth Package's provider verified it, so a deployment whose people have no
|
|
129
|
+
email names its admins by User id; `frockbot-deployment-config secrets` carries
|
|
130
|
+
`FROCKBOT_ADMIN_USER_IDS` like any other optional secret.
|
|
127
131
|
|
|
128
132
|
## Brand
|
|
129
133
|
|
|
@@ -181,13 +181,23 @@ export const DEPLOYMENT_PROFILE_SCHEMA_V1 = {
|
|
|
181
181
|
},
|
|
182
182
|
adminEmails: {
|
|
183
183
|
description:
|
|
184
|
-
"The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
|
|
184
|
+
"The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. Only a verified email counts. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
|
|
185
185
|
type: "array",
|
|
186
186
|
minItems: 1,
|
|
187
187
|
items: {
|
|
188
188
|
$ref: "#/$defs/email",
|
|
189
189
|
},
|
|
190
190
|
},
|
|
191
|
+
adminUserIds: {
|
|
192
|
+
description:
|
|
193
|
+
"Admins by User id, beside `adminEmails`, for a deployment whose people have no verified email: an id here is an admin exactly as a verified email on `adminEmails` is. The installer sets it as the Worker secret `FROCKBOT_ADMIN_USER_IDS`; no generated config carries it.",
|
|
194
|
+
type: "array",
|
|
195
|
+
minItems: 1,
|
|
196
|
+
items: {
|
|
197
|
+
type: "string",
|
|
198
|
+
pattern: "^[^,\\s]+$",
|
|
199
|
+
},
|
|
200
|
+
},
|
|
191
201
|
d1DatabaseId: {
|
|
192
202
|
description:
|
|
193
203
|
"The `AUTH_DB` database. Required by the `better-auth` Package unless the deploy supplies it with `--d1-database-id`, which is how a disposable stage names a database it creates in the same job.",
|
|
@@ -164,13 +164,22 @@
|
|
|
164
164
|
}
|
|
165
165
|
},
|
|
166
166
|
"adminEmails": {
|
|
167
|
-
"description": "The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
|
|
167
|
+
"description": "The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. Only a verified email counts. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
|
|
168
168
|
"type": "array",
|
|
169
169
|
"minItems": 1,
|
|
170
170
|
"items": {
|
|
171
171
|
"$ref": "#/$defs/email"
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
|
+
"adminUserIds": {
|
|
175
|
+
"description": "Admins by User id, beside `adminEmails`, for a deployment whose people have no verified email: an id here is an admin exactly as a verified email on `adminEmails` is. The installer sets it as the Worker secret `FROCKBOT_ADMIN_USER_IDS`; no generated config carries it.",
|
|
176
|
+
"type": "array",
|
|
177
|
+
"minItems": 1,
|
|
178
|
+
"items": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"pattern": "^[^,\\s]+$"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
174
183
|
"d1DatabaseId": {
|
|
175
184
|
"description": "The `AUTH_DB` database. Required by the `better-auth` Package unless the deploy supplies it with `--d1-database-id`, which is how a disposable stage names a database it creates in the same job.",
|
|
176
185
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/cloudflare",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.296",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.access.json && tsc --noEmit -p test/tsconfig.json && tsc --noEmit -p e2e/tsconfig.json"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@frockbot/app": "0.7.
|
|
27
|
-
"@frockbot/applets": "0.7.
|
|
28
|
-
"@frockbot/computer": "0.7.
|
|
29
|
-
"@frockbot/core": "0.7.
|
|
30
|
-
"@frockbot/frock-compose": "0.7.
|
|
31
|
-
"@frockbot/providers": "0.7.
|
|
26
|
+
"@frockbot/app": "0.7.296",
|
|
27
|
+
"@frockbot/applets": "0.7.296",
|
|
28
|
+
"@frockbot/computer": "0.7.296",
|
|
29
|
+
"@frockbot/core": "0.7.296",
|
|
30
|
+
"@frockbot/frock-compose": "0.7.296",
|
|
31
|
+
"@frockbot/providers": "0.7.296",
|
|
32
32
|
"@rive-app/flutter-native-wasm": "44.0.0",
|
|
33
33
|
"agents": "0.24.0",
|
|
34
34
|
"ai": "^7.0.107",
|
package/src/admin-identities.ts
CHANGED
|
@@ -13,14 +13,38 @@ export interface GatewayIdentityV1 {
|
|
|
13
13
|
mode: "better-auth" | "development";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The deployment's administrators by User id, as `FROCKBOT_ADMIN_USER_IDS`
|
|
18
|
+
* spells them: comma-separated and trimmed. A User id is opaque, so it is
|
|
19
|
+
* compared exactly. It is how a deployment whose people have no email names
|
|
20
|
+
* an admin.
|
|
21
|
+
*/
|
|
22
|
+
export function adminUserIdsV1(value: string | undefined): ReadonlySet<string> {
|
|
23
|
+
return new Set(
|
|
24
|
+
(value ?? "")
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((id) => id.trim())
|
|
27
|
+
.filter((id) => id.length > 0),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The two allowlists, as the deployment's secrets carry them. */
|
|
32
|
+
export interface DeploymentAdminsV1 {
|
|
33
|
+
emails?: string;
|
|
34
|
+
userIds?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
export function isDeploymentAdminV1(
|
|
17
38
|
identity: GatewayIdentityV1,
|
|
18
|
-
|
|
39
|
+
configured: DeploymentAdminsV1,
|
|
19
40
|
): boolean {
|
|
20
41
|
if (identity.mode === "development" && identity.id === DEVELOPMENT_USER_ID) {
|
|
21
42
|
return true;
|
|
22
43
|
}
|
|
23
|
-
const
|
|
44
|
+
const userIds = adminUserIdsV1(configured.userIds);
|
|
45
|
+
// A development id is whatever `?as_user=` said, so only a signed-in one counts.
|
|
46
|
+
if (identity.mode === "better-auth" && userIds.has(identity.id)) return true;
|
|
47
|
+
const emails = adminEmailsV1(configured.emails);
|
|
24
48
|
if (
|
|
25
49
|
identity.emailVerified === true &&
|
|
26
50
|
identity.email &&
|
|
@@ -28,5 +52,7 @@ export function isDeploymentAdminV1(
|
|
|
28
52
|
) {
|
|
29
53
|
return true;
|
|
30
54
|
}
|
|
31
|
-
return
|
|
55
|
+
return (
|
|
56
|
+
identity.mode === "development" && emails.size === 0 && userIds.size === 0
|
|
57
|
+
);
|
|
32
58
|
}
|
package/src/bot-state.ts
CHANGED
|
@@ -231,6 +231,7 @@ import {
|
|
|
231
231
|
type ClientRunLookupQueryV1,
|
|
232
232
|
type ClientRunStopCommandV1,
|
|
233
233
|
} from "@frockbot/app/shell/run-protocol";
|
|
234
|
+
import { decodeWorkLogQueryV1 } from "@frockbot/app/shell/work-log";
|
|
234
235
|
import {
|
|
235
236
|
decodeBotUnreadCommandV1,
|
|
236
237
|
type BotUnreadCommandV1,
|
|
@@ -3806,6 +3807,22 @@ export class BotState
|
|
|
3806
3807
|
return shell.listRuns(request.query as ClientRunListQueryV1);
|
|
3807
3808
|
}
|
|
3808
3809
|
|
|
3810
|
+
/** The Work log: a page of this Bot's Turns, every step of each. */
|
|
3811
|
+
async workLog(input: unknown) {
|
|
3812
|
+
const request = decodeRpcEnvelopeV1(input, {
|
|
3813
|
+
userId: rpcIdentifier,
|
|
3814
|
+
botId: rpcBotId,
|
|
3815
|
+
query: rpcDecoded(decodeWorkLogQueryV1),
|
|
3816
|
+
});
|
|
3817
|
+
const identity = {
|
|
3818
|
+
userId: request.userId as string,
|
|
3819
|
+
botId: request.botId as string,
|
|
3820
|
+
};
|
|
3821
|
+
const { shell } = await this.materialized(identity);
|
|
3822
|
+
await shell.validateIdentity(identity);
|
|
3823
|
+
return shell.workLog(request.query);
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3809
3826
|
/**
|
|
3810
3827
|
* The operator snapshot behind `/api/debug`. Bot-scoped like every other
|
|
3811
3828
|
* Bot RPC — the debug token authorizes the *caller*, it does not widen what
|
package/src/contracts.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
SettingsFrame,
|
|
21
21
|
ConnectionsFrame,
|
|
22
22
|
SettingsChangeCommand,
|
|
23
|
+
WorkLogPage,
|
|
23
24
|
} from "@frockbot/core/protocol-schemas";
|
|
24
25
|
import {
|
|
25
26
|
type AuthPackageV1,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
type SessionEvent,
|
|
29
30
|
} from "@frockbot/core/contracts";
|
|
30
31
|
import type { MachineResultDeliveryV1 } from "@frockbot/app/machine/delivery";
|
|
32
|
+
import type { WorkLogQueryV1 } from "@frockbot/app/shell/work-log";
|
|
31
33
|
import type { DebugGatewaySurface } from "./debug.js";
|
|
32
34
|
import type { BotTurnCommandRequestV1 } from "./durable-rpc.js";
|
|
33
35
|
import type {
|
|
@@ -275,6 +277,12 @@ export interface UserBotStateBinding {
|
|
|
275
277
|
botId: string;
|
|
276
278
|
query: ClientRunListQueryV1;
|
|
277
279
|
}): Promise<ClientRunListV1>;
|
|
280
|
+
/** Everything the Bot did, a page of Turns at a time. */
|
|
281
|
+
workLog(input: {
|
|
282
|
+
schemaVersion: 1;
|
|
283
|
+
botId: string;
|
|
284
|
+
query: WorkLogQueryV1;
|
|
285
|
+
}): Promise<WorkLogPage>;
|
|
278
286
|
lookupRun(input: {
|
|
279
287
|
schemaVersion: 1;
|
|
280
288
|
botId: string;
|
|
@@ -785,8 +793,9 @@ export interface GatewayDependencies {
|
|
|
785
793
|
admitAccount(
|
|
786
794
|
identity: AdmissionIdentityV1,
|
|
787
795
|
): Promise<AccountAdmissionDecisionV1>;
|
|
788
|
-
/** Raw deployment
|
|
796
|
+
/** Raw deployment secrets; only the derived `isAdmin` boolean reaches clients. */
|
|
789
797
|
adminEmails?: string;
|
|
798
|
+
adminUserIds?: string;
|
|
790
799
|
applicationHashFor(userId: string): Promise<string>;
|
|
791
800
|
botStateFor(userId: string): UserBotStateBinding;
|
|
792
801
|
userConfigurationFor(userId: string): UserConfigurationBinding;
|
package/src/gateway.ts
CHANGED
|
@@ -435,7 +435,10 @@ async function identifyRequest(
|
|
|
435
435
|
emailVerified: session?.user.emailVerified === true,
|
|
436
436
|
mode: development.userId ? "development" : "better-auth",
|
|
437
437
|
},
|
|
438
|
-
|
|
438
|
+
{
|
|
439
|
+
emails: dependencies.adminEmails,
|
|
440
|
+
userIds: dependencies.adminUserIds,
|
|
441
|
+
},
|
|
439
442
|
);
|
|
440
443
|
// Every authenticated request asks, so pausing an account takes effect on
|
|
441
444
|
// its next request rather than at its next sign-in. An admin is never
|
package/src/index.ts
CHANGED
|
@@ -59,6 +59,11 @@ import {
|
|
|
59
59
|
type ClientRunStopCommandV1,
|
|
60
60
|
type ClientRunStopReceiptV1,
|
|
61
61
|
} from "@frockbot/app/shell/run-protocol";
|
|
62
|
+
import {
|
|
63
|
+
decodeWorkLogQueryV1,
|
|
64
|
+
type WorkLogQueryV1,
|
|
65
|
+
} from "@frockbot/app/shell/work-log";
|
|
66
|
+
import type { WorkLogPage } from "@frockbot/core/protocol-schemas";
|
|
62
67
|
import {
|
|
63
68
|
decodeBotNotificationDirectoryViewV1,
|
|
64
69
|
decodeBotUnreadDirectoryViewV1,
|
|
@@ -163,6 +168,7 @@ import {
|
|
|
163
168
|
import {
|
|
164
169
|
DEVELOPMENT_USER_ID,
|
|
165
170
|
isDeploymentAdminV1,
|
|
171
|
+
type DeploymentAdminsV1,
|
|
166
172
|
} from "./admin-identities.js";
|
|
167
173
|
import type { DebugGatewaySurface } from "./debug.js";
|
|
168
174
|
import type { BotDebugQueryV1 } from "@frockbot/app/shell/debug-protocol";
|
|
@@ -369,6 +375,8 @@ interface Env {
|
|
|
369
375
|
MACHINE_TOKEN_SECRET?: string;
|
|
370
376
|
ALLOW_DEVELOPMENT_AUTH?: string;
|
|
371
377
|
FROCKBOT_ADMIN_EMAILS?: string;
|
|
378
|
+
/** Admins by User id, for a deployment whose people have no email. */
|
|
379
|
+
FROCKBOT_ADMIN_USER_IDS?: string;
|
|
372
380
|
ALLOWED_CLIENT_ORIGINS?: string;
|
|
373
381
|
/** Authorizes `/api/debug/*`. Absent disables the surface entirely. */
|
|
374
382
|
DEBUG_TOKEN?: string;
|
|
@@ -432,7 +440,7 @@ function debugSurface(env: Env): DebugGatewaySurface {
|
|
|
432
440
|
(await getAgentByName(env.VOICE_ASSISTANTS, userId)).debugSnapshot(),
|
|
433
441
|
isAdminUser: async (userId) => {
|
|
434
442
|
// The auth Package's store is the durable identity source. The path's
|
|
435
|
-
// User id is never trusted on its own: it must resolve to a stored
|
|
443
|
+
// User id is never trusted on its own: it must resolve to a stored identity,
|
|
436
444
|
// and that identity is evaluated by the same allowlist policy as the
|
|
437
445
|
// signed-in gateway. A Package that stores nothing resolves nothing, so
|
|
438
446
|
// the one write on this surface is refused rather than granted on the
|
|
@@ -447,7 +455,7 @@ function debugSurface(env: Env): DebugGatewaySurface {
|
|
|
447
455
|
emailVerified: identity.emailVerified,
|
|
448
456
|
mode: "better-auth",
|
|
449
457
|
},
|
|
450
|
-
env
|
|
458
|
+
deploymentAdmins(env),
|
|
451
459
|
)
|
|
452
460
|
);
|
|
453
461
|
},
|
|
@@ -501,6 +509,7 @@ interface BotStateRpc extends BotConfigurationBinding {
|
|
|
501
509
|
command: BotTurnCommandRequestV1<OwnedBotTurnCommand>,
|
|
502
510
|
): Promise<{ schemaVersion: 1; runId: string }>;
|
|
503
511
|
listRuns(query: ClientRunListQueryV1): Promise<ClientRunListV1>;
|
|
512
|
+
workLog(query: WorkLogQueryV1): Promise<WorkLogPage>;
|
|
504
513
|
debugSnapshot(query: BotDebugQueryV1): Promise<unknown>;
|
|
505
514
|
lookupRun(query: ClientRunLookupQueryV1): Promise<ClientRunLookupV1>;
|
|
506
515
|
runQuestions(query: ClientRunLookupQueryV1): Promise<ClientRunQuestionsV1>;
|
|
@@ -688,6 +697,7 @@ function botStateStub(env: Env, userId: string, botId: string): BotStateRpc {
|
|
|
688
697
|
admitRun: (command) => rpc.admitRun(botTurnRpcV1(command)),
|
|
689
698
|
listRuns: (query) =>
|
|
690
699
|
rpc.listRuns({ schemaVersion: 1, userId, botId, query }),
|
|
700
|
+
workLog: (query) => rpc.workLog({ schemaVersion: 1, userId, botId, query }),
|
|
691
701
|
debugSnapshot: (query) =>
|
|
692
702
|
rpc.debugSnapshot({ schemaVersion: 1, userId, botId, query }),
|
|
693
703
|
lookupRun: (query) =>
|
|
@@ -841,6 +851,13 @@ async function admitAccount(
|
|
|
841
851
|
);
|
|
842
852
|
}
|
|
843
853
|
|
|
854
|
+
function deploymentAdmins(env: Env): DeploymentAdminsV1 {
|
|
855
|
+
return {
|
|
856
|
+
emails: env.FROCKBOT_ADMIN_EMAILS,
|
|
857
|
+
userIds: env.FROCKBOT_ADMIN_USER_IDS,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
844
861
|
async function storedAdmissionIdentity(
|
|
845
862
|
env: Env,
|
|
846
863
|
userId: string,
|
|
@@ -860,7 +877,7 @@ async function storedAdmissionIdentity(
|
|
|
860
877
|
emailVerified: identity.emailVerified,
|
|
861
878
|
mode: "better-auth",
|
|
862
879
|
},
|
|
863
|
-
env
|
|
880
|
+
deploymentAdmins(env),
|
|
864
881
|
),
|
|
865
882
|
};
|
|
866
883
|
}
|
|
@@ -930,7 +947,8 @@ async function mayCreateIdentity(
|
|
|
930
947
|
emailVerified: candidate.emailVerified,
|
|
931
948
|
mode: "better-auth",
|
|
932
949
|
},
|
|
933
|
-
|
|
950
|
+
// No User exists yet, so only the email list can name one.
|
|
951
|
+
{ emails: env.FROCKBOT_ADMIN_EMAILS },
|
|
934
952
|
)
|
|
935
953
|
) {
|
|
936
954
|
return true;
|
|
@@ -1285,6 +1303,18 @@ export class UserBotState extends WorkerEntrypoint<Env, UserScopedProps> {
|
|
|
1285
1303
|
).listRuns(request.query as ClientRunListQueryV1);
|
|
1286
1304
|
}
|
|
1287
1305
|
|
|
1306
|
+
async workLog(input: unknown): Promise<WorkLogPage> {
|
|
1307
|
+
const request = decodeRpcEnvelopeV1(input, {
|
|
1308
|
+
botId: rpcBotId,
|
|
1309
|
+
query: rpcDecoded(decodeWorkLogQueryV1),
|
|
1310
|
+
});
|
|
1311
|
+
return botStateStub(
|
|
1312
|
+
this.env,
|
|
1313
|
+
this.ctx.props.userId,
|
|
1314
|
+
request.botId as string,
|
|
1315
|
+
).workLog(request.query as WorkLogQueryV1);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1288
1318
|
async lookupRun(input: unknown): Promise<ClientRunLookupV1> {
|
|
1289
1319
|
const request = decodeUserBotRunLookupRpcV1(input);
|
|
1290
1320
|
return botStateStub(
|
|
@@ -2754,6 +2784,9 @@ export default {
|
|
|
2754
2784
|
...(env.FROCKBOT_ADMIN_EMAILS
|
|
2755
2785
|
? { adminEmails: env.FROCKBOT_ADMIN_EMAILS }
|
|
2756
2786
|
: {}),
|
|
2787
|
+
...(env.FROCKBOT_ADMIN_USER_IDS
|
|
2788
|
+
? { adminUserIds: env.FROCKBOT_ADMIN_USER_IDS }
|
|
2789
|
+
: {}),
|
|
2757
2790
|
applicationHashFor: async () => env.DEFAULT_APPLICATION_HASH,
|
|
2758
2791
|
botStateFor: (userId) =>
|
|
2759
2792
|
runtimeExports.UserBotState({ props: { userId } }),
|
|
@@ -221,6 +221,12 @@ export const OPTIONAL_PRODUCTION_SECRETS_V1: readonly OptionalProductionSecretV1
|
|
|
221
221
|
degraded:
|
|
222
222
|
"nobody can administer beta access, so admission stays closed and only accounts already granted access can sign in",
|
|
223
223
|
},
|
|
224
|
+
{
|
|
225
|
+
name: "FROCKBOT_ADMIN_USER_IDS",
|
|
226
|
+
why: "Admins by User id, beside `FROCKBOT_ADMIN_EMAILS`, for a deployment whose people have no verified email.",
|
|
227
|
+
degraded:
|
|
228
|
+
"only a verified email on `FROCKBOT_ADMIN_EMAILS` makes an admin",
|
|
229
|
+
},
|
|
224
230
|
{
|
|
225
231
|
name: "DEBUG_TOKEN",
|
|
226
232
|
why: "Authorizes the read-only `/api/debug` operator surface.",
|
package/src/user-application.ts
CHANGED
|
@@ -29,6 +29,10 @@ import {
|
|
|
29
29
|
type ClientTurnRefusalV1,
|
|
30
30
|
} from "@frockbot/app/shell/run-protocol";
|
|
31
31
|
import { decodeApprovalDecisionCommandV1 } from "@frockbot/app/shell/approvals";
|
|
32
|
+
import {
|
|
33
|
+
decodeWorkLogQueryV1,
|
|
34
|
+
type WorkLogQueryV1,
|
|
35
|
+
} from "@frockbot/app/shell/work-log";
|
|
32
36
|
import {
|
|
33
37
|
decodeSecretSubmitCommandV1,
|
|
34
38
|
isSecretRequestIdV1,
|
|
@@ -726,6 +730,7 @@ function createUserApplicationRoute() {
|
|
|
726
730
|
}
|
|
727
731
|
|
|
728
732
|
const skillsMatch = url.pathname.match(/^\/api\/bots\/([^/]+)\/skills$/);
|
|
733
|
+
const workLogMatch = url.pathname.match(/^\/api\/bots\/([^/]+)\/work-log$/);
|
|
729
734
|
const workspaceFileMatch = url.pathname.match(
|
|
730
735
|
/^\/api\/bots\/([^/]+)\/workspace\/file$/,
|
|
731
736
|
);
|
|
@@ -744,6 +749,7 @@ function createUserApplicationRoute() {
|
|
|
744
749
|
);
|
|
745
750
|
if (
|
|
746
751
|
!skillsMatch &&
|
|
752
|
+
!workLogMatch &&
|
|
747
753
|
!workspaceFileMatch &&
|
|
748
754
|
!turnMatch &&
|
|
749
755
|
!lookupMatch &&
|
|
@@ -757,6 +763,7 @@ function createUserApplicationRoute() {
|
|
|
757
763
|
try {
|
|
758
764
|
const matched =
|
|
759
765
|
skillsMatch ??
|
|
766
|
+
workLogMatch ??
|
|
760
767
|
workspaceFileMatch ??
|
|
761
768
|
turnMatch ??
|
|
762
769
|
lookupMatch ??
|
|
@@ -776,7 +783,7 @@ function createUserApplicationRoute() {
|
|
|
776
783
|
env,
|
|
777
784
|
botId,
|
|
778
785
|
request.method === "GET" &&
|
|
779
|
-
Boolean(turnMatch || lookupMatch || questionsMatch),
|
|
786
|
+
Boolean(turnMatch || lookupMatch || questionsMatch || workLogMatch),
|
|
780
787
|
);
|
|
781
788
|
if (missingBot) return missingBot;
|
|
782
789
|
// An installed client older than protocol 3 refuses a Run it does not
|
|
@@ -800,6 +807,37 @@ function createUserApplicationRoute() {
|
|
|
800
807
|
}
|
|
801
808
|
}
|
|
802
809
|
|
|
810
|
+
if (workLogMatch) {
|
|
811
|
+
if (request.method !== "GET") return jsonError(405, "method not allowed");
|
|
812
|
+
let query: WorkLogQueryV1;
|
|
813
|
+
try {
|
|
814
|
+
const keys = [...url.searchParams.keys()];
|
|
815
|
+
if (
|
|
816
|
+
keys.some((key) => key !== "before") ||
|
|
817
|
+
url.searchParams.getAll("before").length > 1
|
|
818
|
+
) {
|
|
819
|
+
throw new Error("work log query is invalid");
|
|
820
|
+
}
|
|
821
|
+
const before = url.searchParams.get("before");
|
|
822
|
+
query = decodeWorkLogQueryV1({
|
|
823
|
+
schemaVersion: 1,
|
|
824
|
+
...(before === null ? {} : { before }),
|
|
825
|
+
});
|
|
826
|
+
} catch (error) {
|
|
827
|
+
return jsonError(
|
|
828
|
+
400,
|
|
829
|
+
error instanceof Error ? error.message : "invalid work log page",
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
return Response.json(
|
|
834
|
+
await env.BOT_STATE.workLog({ schemaVersion: 1, botId, query }),
|
|
835
|
+
);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
return botFailure(error, "work log failed");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
803
841
|
if (workspaceFileMatch) {
|
|
804
842
|
// Read-only, and the durable root and path arrive as one encoded
|
|
805
843
|
// `WorkspacePathV1` so the route cannot assemble a root the decoder
|