deepspace 0.4.2 → 0.4.4
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/dist/cli.js +1442 -275
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +197 -425
- package/dist/index.js +621 -1665
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +207 -28
- package/dist/server.js +713 -26
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +207 -28
- package/dist/worker.js +713 -26
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +1 -1
- package/features/canvas/src/CanvasListPage.tsx +1 -1
- package/features/canvas/src/components/CanvasView.tsx +1 -1
- package/features/docs/src/DocsEditorPage.tsx +1 -1
- package/features/docs/src/InviteDialog.tsx +130 -139
- package/features/file-attachments/src/AttachmentsPage.tsx +1 -4
- package/features/file-manager/src/FileManagerPage.tsx +2 -1
- package/features/items/src/ItemsPage.tsx +1 -1
- package/features/landing/src/LandingPage.tsx +1 -1
- package/features/landing/src/primitives.tsx +1 -1
- package/features/leaderboard/src/LeaderboardPage.tsx +1 -1
- package/features/leaderboard/src/leaderboard-constants.ts +1 -1
- package/features/search-bar/src/SearchOverlay.tsx +1 -1
- package/features/tasks/src/TasksPage.tsx +1 -1
- package/features/tasks/src/tasks-constants.ts +1 -1
- package/package.json +1 -18
- package/dist/testing.d.ts +0 -115
package/dist/worker.js
CHANGED
|
@@ -1952,17 +1952,24 @@ async function handleSetRole(ctx, ws, attachment, payload) {
|
|
|
1952
1952
|
import * as Y3 from "yjs";
|
|
1953
1953
|
|
|
1954
1954
|
// src/server/utils/tools.ts
|
|
1955
|
+
var DEFAULT_QUERY_LIMIT = 50;
|
|
1956
|
+
function applyAiToolDefaults(toolName, params) {
|
|
1957
|
+
if (toolName === "records.query" && params.limit === void 0) {
|
|
1958
|
+
return { ...params, limit: DEFAULT_QUERY_LIMIT };
|
|
1959
|
+
}
|
|
1960
|
+
return params;
|
|
1961
|
+
}
|
|
1955
1962
|
var BUILT_IN_TOOLS = [
|
|
1956
1963
|
// Record tools (structured collections with RBAC)
|
|
1957
1964
|
{
|
|
1958
1965
|
name: "records.query",
|
|
1959
|
-
description:
|
|
1966
|
+
description: `Query records from a collection with optional filtering, ordering, and pagination. Returns at most \`limit\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: an oversized page is truncated to the leading records that fit and flagged with \`{ truncated, returned, total }\`, so narrow with a \`where\` filter (or page via \`orderBy\` + \`limit\`) to see the rest.`,
|
|
1960
1967
|
params: {
|
|
1961
1968
|
collection: { type: "string", description: "Collection name to query", required: true },
|
|
1962
1969
|
where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
|
|
1963
1970
|
orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
|
|
1964
1971
|
orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
|
|
1965
|
-
limit: { type: "number", description:
|
|
1972
|
+
limit: { type: "number", description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false }
|
|
1966
1973
|
}
|
|
1967
1974
|
},
|
|
1968
1975
|
{
|
|
@@ -2132,11 +2139,11 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
2132
2139
|
}
|
|
2133
2140
|
if (path.startsWith("tools/describe/")) {
|
|
2134
2141
|
const toolName = path.replace("tools/describe/", "");
|
|
2135
|
-
const
|
|
2136
|
-
if (!
|
|
2142
|
+
const tool2 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
|
|
2143
|
+
if (!tool2) {
|
|
2137
2144
|
return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS });
|
|
2138
2145
|
}
|
|
2139
|
-
return Response.json({ tool }, { headers: CORS_HEADERS });
|
|
2146
|
+
return Response.json({ tool: tool2 }, { headers: CORS_HEADERS });
|
|
2140
2147
|
}
|
|
2141
2148
|
if (path.startsWith("tools/execute") && request.method === "POST") {
|
|
2142
2149
|
return handleToolExecute(ctx, request);
|
|
@@ -2153,8 +2160,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2153
2160
|
{ status: 400, headers: CORS_HEADERS }
|
|
2154
2161
|
);
|
|
2155
2162
|
}
|
|
2156
|
-
const { tool, params = {} } = body;
|
|
2157
|
-
if (!
|
|
2163
|
+
const { tool: tool2, params = {} } = body;
|
|
2164
|
+
if (!tool2) {
|
|
2158
2165
|
return Response.json(
|
|
2159
2166
|
{ success: false, error: 'Missing "tool" in request body' },
|
|
2160
2167
|
{ status: 400, headers: CORS_HEADERS }
|
|
@@ -2171,8 +2178,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2171
2178
|
userRole = user.role;
|
|
2172
2179
|
}
|
|
2173
2180
|
}
|
|
2174
|
-
if (
|
|
2175
|
-
const result2 = await executeYjsTool(ctx,
|
|
2181
|
+
if (tool2.startsWith("yjs.")) {
|
|
2182
|
+
const result2 = await executeYjsTool(ctx, tool2, params, userId, userRole);
|
|
2176
2183
|
const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
|
|
2177
2184
|
return Response.json(result2, { status: status2, headers: CORS_HEADERS });
|
|
2178
2185
|
}
|
|
@@ -2183,7 +2190,7 @@ async function handleToolExecute(ctx, request) {
|
|
|
2183
2190
|
getPermissionContext: ctx.getPermissionContext,
|
|
2184
2191
|
send: ctx.send
|
|
2185
2192
|
};
|
|
2186
|
-
const result = await executeTool(recordCtx,
|
|
2193
|
+
const result = await executeTool(recordCtx, tool2, params, userId, userRole, isAppAction);
|
|
2187
2194
|
const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
|
|
2188
2195
|
return Response.json(result, { status, headers: CORS_HEADERS });
|
|
2189
2196
|
}
|
|
@@ -3168,13 +3175,13 @@ var YjsRoom = class extends BaseRoom {
|
|
|
3168
3175
|
onMessage(ws, user, message) {
|
|
3169
3176
|
}
|
|
3170
3177
|
onBinaryMessage(ws, user, data) {
|
|
3171
|
-
const
|
|
3172
|
-
const decoder = createDecoder(
|
|
3178
|
+
const bytes2 = new Uint8Array(data);
|
|
3179
|
+
const decoder = createDecoder(bytes2);
|
|
3173
3180
|
const messageType = readVarUint(decoder);
|
|
3174
3181
|
if (messageType === MSG_SYNC) {
|
|
3175
|
-
this.handleSync(ws, decoder,
|
|
3182
|
+
this.handleSync(ws, decoder, bytes2);
|
|
3176
3183
|
} else if (messageType === MSG_AWARENESS) {
|
|
3177
|
-
this.handleAwareness(ws, decoder,
|
|
3184
|
+
this.handleAwareness(ws, decoder, bytes2);
|
|
3178
3185
|
}
|
|
3179
3186
|
}
|
|
3180
3187
|
onDisconnect(ws, user) {
|
|
@@ -4379,6 +4386,319 @@ function cronMatches(parsed, timezone, date) {
|
|
|
4379
4386
|
return parsed.minute.has(minute) && parsed.hour.has(hour) && parsed.dayOfMonth.has(dayOfMonth) && parsed.month.has(month) && parsed.dayOfWeek.has(dayOfWeek);
|
|
4380
4387
|
}
|
|
4381
4388
|
|
|
4389
|
+
// src/server/rooms/secrets-room.ts
|
|
4390
|
+
var INTERNAL_HEADER = "x-deepspace-internal";
|
|
4391
|
+
var INTERNAL_VALUE = "app-secrets";
|
|
4392
|
+
var VERSION = "app-secret-v1";
|
|
4393
|
+
var NAME_INFO = "deepspace app secret name v1";
|
|
4394
|
+
var VALUE_INFO = "deepspace app secret value v1";
|
|
4395
|
+
var HMAC_INFO = "deepspace app secret lookup v1";
|
|
4396
|
+
var STORE_ID = "default";
|
|
4397
|
+
var SecretsRoom = class extends BaseRoom {
|
|
4398
|
+
initialized = false;
|
|
4399
|
+
onMessage(_ws, _user, _message) {
|
|
4400
|
+
}
|
|
4401
|
+
async fetch(request) {
|
|
4402
|
+
this.ensureInitialized();
|
|
4403
|
+
return super.fetch(request);
|
|
4404
|
+
}
|
|
4405
|
+
async onRequest(request) {
|
|
4406
|
+
if (request.headers.get(INTERNAL_HEADER) !== INTERNAL_VALUE) {
|
|
4407
|
+
return json({ error: "Not found" }, 404);
|
|
4408
|
+
}
|
|
4409
|
+
const url = new URL(request.url);
|
|
4410
|
+
try {
|
|
4411
|
+
if (request.method === "GET" && url.pathname.endsWith("/list")) {
|
|
4412
|
+
return json({ version: this.storeVersion(), secrets: await this.list() });
|
|
4413
|
+
}
|
|
4414
|
+
if (request.method === "GET" && url.pathname.endsWith("/pull")) {
|
|
4415
|
+
return json({ version: this.storeVersion(), secrets: await this.pull() });
|
|
4416
|
+
}
|
|
4417
|
+
if (request.method === "GET" && url.pathname.endsWith("/version")) {
|
|
4418
|
+
return json({ version: this.storeVersion() });
|
|
4419
|
+
}
|
|
4420
|
+
if (request.method === "PATCH" && url.pathname.endsWith("/patch")) {
|
|
4421
|
+
const body = await request.json();
|
|
4422
|
+
const result = await this.patch(body);
|
|
4423
|
+
return json(result);
|
|
4424
|
+
}
|
|
4425
|
+
if (request.method === "PUT" && url.pathname.endsWith("/replace")) {
|
|
4426
|
+
const body = await request.json();
|
|
4427
|
+
const result = await this.replace(body);
|
|
4428
|
+
return json(result);
|
|
4429
|
+
}
|
|
4430
|
+
} catch (err) {
|
|
4431
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4432
|
+
const status = message.includes("conflict") ? 409 : 500;
|
|
4433
|
+
return json({ error: message }, status);
|
|
4434
|
+
}
|
|
4435
|
+
return json({ error: "Not found" }, 404);
|
|
4436
|
+
}
|
|
4437
|
+
async list() {
|
|
4438
|
+
const rows = this.sql.exec(
|
|
4439
|
+
`SELECT name_encrypted, version, updated_by, created_at, updated_at
|
|
4440
|
+
FROM app_secrets_store ORDER BY updated_at DESC`
|
|
4441
|
+
).toArray();
|
|
4442
|
+
const out = [];
|
|
4443
|
+
for (const row of rows) {
|
|
4444
|
+
out.push({
|
|
4445
|
+
name: await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO),
|
|
4446
|
+
version: row.version,
|
|
4447
|
+
createdAt: row.created_at,
|
|
4448
|
+
updatedAt: row.updated_at,
|
|
4449
|
+
updatedBy: row.updated_by
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
4453
|
+
}
|
|
4454
|
+
async pull() {
|
|
4455
|
+
const rows = this.sql.exec(
|
|
4456
|
+
`SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
|
|
4457
|
+
FROM app_secrets_store`
|
|
4458
|
+
).toArray();
|
|
4459
|
+
const out = {};
|
|
4460
|
+
for (const row of rows) {
|
|
4461
|
+
const name = await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO);
|
|
4462
|
+
out[name] = await decryptText(this.requireKey(), row.value_encrypted, VALUE_INFO);
|
|
4463
|
+
}
|
|
4464
|
+
return out;
|
|
4465
|
+
}
|
|
4466
|
+
async patch(body) {
|
|
4467
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4468
|
+
const actor = body.updatedBy ?? null;
|
|
4469
|
+
const key = this.requireKey();
|
|
4470
|
+
const setEntries = Object.entries(body.set ?? {});
|
|
4471
|
+
const deleteNames = body.delete ?? [];
|
|
4472
|
+
for (const name of [...setEntries.map(([name2]) => name2), ...deleteNames]) {
|
|
4473
|
+
validateSecretName(name);
|
|
4474
|
+
}
|
|
4475
|
+
const writes = [];
|
|
4476
|
+
for (const [name, value] of setEntries) {
|
|
4477
|
+
const nameHash = await hashName(key, name);
|
|
4478
|
+
const existing = this.sql.exec(
|
|
4479
|
+
`SELECT version, created_at FROM app_secrets_store WHERE name_hash = ?`,
|
|
4480
|
+
nameHash
|
|
4481
|
+
).toArray()[0];
|
|
4482
|
+
writes.push({
|
|
4483
|
+
nameHash,
|
|
4484
|
+
nameEncrypted: await encryptText(key, name, NAME_INFO),
|
|
4485
|
+
valueEncrypted: await encryptText(key, value, VALUE_INFO),
|
|
4486
|
+
version: (existing?.version ?? 0) + 1,
|
|
4487
|
+
createdAt: existing?.created_at ?? now
|
|
4488
|
+
});
|
|
4489
|
+
}
|
|
4490
|
+
const deletes = [];
|
|
4491
|
+
for (const name of deleteNames) {
|
|
4492
|
+
deletes.push(await hashName(key, name));
|
|
4493
|
+
}
|
|
4494
|
+
this.transaction(() => {
|
|
4495
|
+
for (const write of writes) {
|
|
4496
|
+
this.sql.exec(
|
|
4497
|
+
`INSERT OR REPLACE INTO app_secrets_store
|
|
4498
|
+
(name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
|
|
4499
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
4500
|
+
write.nameHash,
|
|
4501
|
+
write.nameEncrypted,
|
|
4502
|
+
write.valueEncrypted,
|
|
4503
|
+
write.version,
|
|
4504
|
+
actor,
|
|
4505
|
+
write.createdAt,
|
|
4506
|
+
now
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
4509
|
+
for (const nameHash of deletes) {
|
|
4510
|
+
this.sql.exec(`DELETE FROM app_secrets_store WHERE name_hash = ?`, nameHash);
|
|
4511
|
+
}
|
|
4512
|
+
this.bumpStoreVersion(now);
|
|
4513
|
+
});
|
|
4514
|
+
return { version: this.storeVersion(), secrets: await this.list() };
|
|
4515
|
+
}
|
|
4516
|
+
async replace(body) {
|
|
4517
|
+
const next = body.secrets ?? {};
|
|
4518
|
+
for (const name of Object.keys(next)) validateSecretName(name);
|
|
4519
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4520
|
+
const actor = body.updatedBy ?? null;
|
|
4521
|
+
const key = this.requireKey();
|
|
4522
|
+
const writes = [];
|
|
4523
|
+
for (const [name, value] of Object.entries(next)) {
|
|
4524
|
+
writes.push({
|
|
4525
|
+
nameHash: await hashName(key, name),
|
|
4526
|
+
nameEncrypted: await encryptText(key, name, NAME_INFO),
|
|
4527
|
+
valueEncrypted: await encryptText(key, value, VALUE_INFO)
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
this.transaction(() => {
|
|
4531
|
+
this.sql.exec(`DELETE FROM app_secrets_store`);
|
|
4532
|
+
for (const write of writes) {
|
|
4533
|
+
this.sql.exec(
|
|
4534
|
+
`INSERT INTO app_secrets_store
|
|
4535
|
+
(name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
|
|
4536
|
+
VALUES (?, ?, ?, 1, ?, ?, ?)`,
|
|
4537
|
+
write.nameHash,
|
|
4538
|
+
write.nameEncrypted,
|
|
4539
|
+
write.valueEncrypted,
|
|
4540
|
+
actor,
|
|
4541
|
+
now,
|
|
4542
|
+
now
|
|
4543
|
+
);
|
|
4544
|
+
}
|
|
4545
|
+
this.bumpStoreVersion(now);
|
|
4546
|
+
});
|
|
4547
|
+
return { version: this.storeVersion(), secrets: await this.list() };
|
|
4548
|
+
}
|
|
4549
|
+
transaction(run) {
|
|
4550
|
+
this.state.storage.transactionSync(run);
|
|
4551
|
+
}
|
|
4552
|
+
migrateLegacyProductionSecrets() {
|
|
4553
|
+
const legacyExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secrets'`).toArray().length > 0;
|
|
4554
|
+
if (!legacyExists) return;
|
|
4555
|
+
const currentCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
|
|
4556
|
+
if ((currentCount?.count ?? 0) > 0) return;
|
|
4557
|
+
this.sql.exec(`
|
|
4558
|
+
INSERT OR IGNORE INTO app_secrets_store
|
|
4559
|
+
(name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
|
|
4560
|
+
SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
|
|
4561
|
+
FROM app_secrets
|
|
4562
|
+
WHERE env = 'production'
|
|
4563
|
+
`);
|
|
4564
|
+
const legacyVersionExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secret_env_versions'`).toArray().length > 0;
|
|
4565
|
+
const legacyVersion = legacyVersionExists ? this.sql.exec(`SELECT version, updated_at FROM app_secret_env_versions WHERE env = 'production'`).toArray()[0] : void 0;
|
|
4566
|
+
if (legacyVersion) {
|
|
4567
|
+
this.sql.exec(
|
|
4568
|
+
`INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
|
|
4569
|
+
STORE_ID,
|
|
4570
|
+
legacyVersion.version,
|
|
4571
|
+
legacyVersion.updated_at
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
4574
|
+
const migratedCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
|
|
4575
|
+
if (!legacyVersion && (migratedCount?.count ?? 0) > 0) {
|
|
4576
|
+
this.sql.exec(
|
|
4577
|
+
`INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
|
|
4578
|
+
STORE_ID,
|
|
4579
|
+
1,
|
|
4580
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
4581
|
+
);
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
ensureInitialized() {
|
|
4585
|
+
if (this.initialized) return;
|
|
4586
|
+
this.initialized = true;
|
|
4587
|
+
this.sql.exec(`
|
|
4588
|
+
CREATE TABLE IF NOT EXISTS app_secrets_store (
|
|
4589
|
+
name_hash TEXT NOT NULL,
|
|
4590
|
+
name_encrypted TEXT NOT NULL,
|
|
4591
|
+
value_encrypted TEXT NOT NULL,
|
|
4592
|
+
version INTEGER NOT NULL,
|
|
4593
|
+
updated_by TEXT,
|
|
4594
|
+
created_at TEXT NOT NULL,
|
|
4595
|
+
updated_at TEXT NOT NULL,
|
|
4596
|
+
PRIMARY KEY (name_hash)
|
|
4597
|
+
)
|
|
4598
|
+
`);
|
|
4599
|
+
this.sql.exec(`
|
|
4600
|
+
CREATE TABLE IF NOT EXISTS app_secret_store_versions (
|
|
4601
|
+
id TEXT PRIMARY KEY,
|
|
4602
|
+
version INTEGER NOT NULL,
|
|
4603
|
+
updated_at TEXT NOT NULL
|
|
4604
|
+
)
|
|
4605
|
+
`);
|
|
4606
|
+
this.migrateLegacyProductionSecrets();
|
|
4607
|
+
}
|
|
4608
|
+
requireKey() {
|
|
4609
|
+
if (!this.env.APP_SECRETS_KEY) {
|
|
4610
|
+
throw new Error("APP_SECRETS_KEY binding is missing");
|
|
4611
|
+
}
|
|
4612
|
+
return this.env.APP_SECRETS_KEY;
|
|
4613
|
+
}
|
|
4614
|
+
storeVersion() {
|
|
4615
|
+
const row = this.sql.exec(
|
|
4616
|
+
`SELECT version FROM app_secret_store_versions WHERE id = ?`,
|
|
4617
|
+
STORE_ID
|
|
4618
|
+
).toArray()[0];
|
|
4619
|
+
return row?.version ?? 0;
|
|
4620
|
+
}
|
|
4621
|
+
bumpStoreVersion(now) {
|
|
4622
|
+
const next = this.storeVersion() + 1;
|
|
4623
|
+
this.sql.exec(
|
|
4624
|
+
`INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
|
|
4625
|
+
STORE_ID,
|
|
4626
|
+
next,
|
|
4627
|
+
now
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
};
|
|
4631
|
+
function validateSecretName(name) {
|
|
4632
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
4633
|
+
throw new Error(`Invalid secret name "${name}"`);
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
function json(body, status = 200) {
|
|
4637
|
+
return new Response(JSON.stringify(body), {
|
|
4638
|
+
status,
|
|
4639
|
+
headers: { "Content-Type": "application/json" }
|
|
4640
|
+
});
|
|
4641
|
+
}
|
|
4642
|
+
async function encryptText(root, plaintext, info) {
|
|
4643
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
4644
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
4645
|
+
const key = await aesKey(root, salt, info);
|
|
4646
|
+
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
|
|
4647
|
+
return [VERSION, b64(salt), b64(iv), b64(new Uint8Array(ciphertext))].join(":");
|
|
4648
|
+
}
|
|
4649
|
+
async function decryptText(root, encoded, info) {
|
|
4650
|
+
const [version, saltB64, ivB64, bodyB64] = encoded.split(":");
|
|
4651
|
+
if (version !== VERSION || !saltB64 || !ivB64 || !bodyB64) {
|
|
4652
|
+
throw new Error("Corrupt app secret ciphertext");
|
|
4653
|
+
}
|
|
4654
|
+
const key = await aesKey(root, unb64(saltB64), info);
|
|
4655
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
4656
|
+
{ name: "AES-GCM", iv: bytes(unb64(ivB64)) },
|
|
4657
|
+
key,
|
|
4658
|
+
bytes(unb64(bodyB64))
|
|
4659
|
+
);
|
|
4660
|
+
return new TextDecoder().decode(plaintext);
|
|
4661
|
+
}
|
|
4662
|
+
async function hashName(root, name) {
|
|
4663
|
+
const key = await hmacKey(root, HMAC_INFO);
|
|
4664
|
+
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`production:${name}`));
|
|
4665
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
4666
|
+
}
|
|
4667
|
+
async function aesKey(root, salt, info) {
|
|
4668
|
+
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
|
|
4669
|
+
return crypto.subtle.deriveKey(
|
|
4670
|
+
{ name: "HKDF", hash: "SHA-256", salt: bytes(salt), info: new TextEncoder().encode(info) },
|
|
4671
|
+
base,
|
|
4672
|
+
{ name: "AES-GCM", length: 256 },
|
|
4673
|
+
false,
|
|
4674
|
+
["encrypt", "decrypt"]
|
|
4675
|
+
);
|
|
4676
|
+
}
|
|
4677
|
+
async function hmacKey(root, info) {
|
|
4678
|
+
const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
|
|
4679
|
+
return crypto.subtle.deriveKey(
|
|
4680
|
+
{ name: "HKDF", hash: "SHA-256", salt: bytes(new Uint8Array(16)), info: new TextEncoder().encode(info) },
|
|
4681
|
+
base,
|
|
4682
|
+
{ name: "HMAC", hash: "SHA-256", length: 256 },
|
|
4683
|
+
false,
|
|
4684
|
+
["sign"]
|
|
4685
|
+
);
|
|
4686
|
+
}
|
|
4687
|
+
function b64(bytes2) {
|
|
4688
|
+
let s = "";
|
|
4689
|
+
for (const b of bytes2) s += String.fromCharCode(b);
|
|
4690
|
+
return btoa(s);
|
|
4691
|
+
}
|
|
4692
|
+
function unb64(value) {
|
|
4693
|
+
const s = atob(value);
|
|
4694
|
+
const out = new Uint8Array(s.length);
|
|
4695
|
+
for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
|
|
4696
|
+
return out;
|
|
4697
|
+
}
|
|
4698
|
+
function bytes(input) {
|
|
4699
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4382
4702
|
// src/server/rooms/job-room.ts
|
|
4383
4703
|
var RUNNING_STALE_MS = 16 * 60 * 1e3;
|
|
4384
4704
|
var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4996,6 +5316,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
|
4996
5316
|
"AUTH_WORKER_URL",
|
|
4997
5317
|
"APP_IDENTITY_TOKEN",
|
|
4998
5318
|
"APP_OWNER_JWT",
|
|
5319
|
+
"APP_SECRETS_KEY",
|
|
4999
5320
|
"USAGE_EVENTS"
|
|
5000
5321
|
]);
|
|
5001
5322
|
function validateBindingManifest(manifest) {
|
|
@@ -6110,7 +6431,7 @@ function authWorkerFetch(env, path, init) {
|
|
|
6110
6431
|
function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
6111
6432
|
const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
|
|
6112
6433
|
const room = env.RECORD_ROOMS.get(roomIdObj);
|
|
6113
|
-
async function executeTool3(
|
|
6434
|
+
async function executeTool3(tool2, params) {
|
|
6114
6435
|
const response = await room.fetch(new Request("https://internal/api/tools/execute", {
|
|
6115
6436
|
method: "POST",
|
|
6116
6437
|
headers: {
|
|
@@ -6118,11 +6439,11 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
|
6118
6439
|
"X-User-Id": ownerUserId,
|
|
6119
6440
|
"X-App-Action": "true"
|
|
6120
6441
|
},
|
|
6121
|
-
body: JSON.stringify({ tool, params })
|
|
6442
|
+
body: JSON.stringify({ tool: tool2, params })
|
|
6122
6443
|
}));
|
|
6123
6444
|
const result = await response.json();
|
|
6124
6445
|
if (!result.success) {
|
|
6125
|
-
throw new Error(`RecordRoom tool ${
|
|
6446
|
+
throw new Error(`RecordRoom tool ${tool2} failed: ${result.error || "Unknown error"}`);
|
|
6126
6447
|
}
|
|
6127
6448
|
return result.data;
|
|
6128
6449
|
}
|
|
@@ -6243,6 +6564,87 @@ function createDeepSpaceAI(env, provider, options = {}) {
|
|
|
6243
6564
|
}
|
|
6244
6565
|
}
|
|
6245
6566
|
|
|
6567
|
+
// src/server/utils/composio-tools.ts
|
|
6568
|
+
import { tool, jsonSchema } from "ai";
|
|
6569
|
+
var EMPTY_SCHEMA = { type: "object", properties: {} };
|
|
6570
|
+
async function requestComposio(env, endpoint, params, authToken) {
|
|
6571
|
+
const res = await apiWorkerFetch(env, `/api/integrations/composio/${endpoint}`, {
|
|
6572
|
+
method: "POST",
|
|
6573
|
+
headers: {
|
|
6574
|
+
"Content-Type": "application/json",
|
|
6575
|
+
Authorization: `Bearer ${authToken}`
|
|
6576
|
+
},
|
|
6577
|
+
body: JSON.stringify(params)
|
|
6578
|
+
});
|
|
6579
|
+
const text = await res.text();
|
|
6580
|
+
let parsed = {};
|
|
6581
|
+
try {
|
|
6582
|
+
parsed = text ? JSON.parse(text) : {};
|
|
6583
|
+
} catch {
|
|
6584
|
+
}
|
|
6585
|
+
const detail = parsed.message || parsed.error || text || `HTTP ${res.status}`;
|
|
6586
|
+
return { ok: res.ok, success: parsed.success === true, data: parsed.data, detail };
|
|
6587
|
+
}
|
|
6588
|
+
function toolKey(slug) {
|
|
6589
|
+
const key = slug.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6590
|
+
return key.length <= 64 ? key : key.slice(0, 64);
|
|
6591
|
+
}
|
|
6592
|
+
async function composioTools(env, options) {
|
|
6593
|
+
const { authToken } = options;
|
|
6594
|
+
if (!authToken) {
|
|
6595
|
+
throw new Error(
|
|
6596
|
+
"composioTools: options.authToken is required. Pass the end user's JWT so the tools run as that user (Composio resolves their connected accounts by it) and the calls are billed to them."
|
|
6597
|
+
);
|
|
6598
|
+
}
|
|
6599
|
+
const list = await requestComposio(
|
|
6600
|
+
env,
|
|
6601
|
+
"list-tools",
|
|
6602
|
+
{
|
|
6603
|
+
toolkit: options.toolkit,
|
|
6604
|
+
toolSlugs: options.tools?.length ? options.tools.join(",") : void 0,
|
|
6605
|
+
search: options.search,
|
|
6606
|
+
limit: options.limit ?? 20
|
|
6607
|
+
},
|
|
6608
|
+
authToken
|
|
6609
|
+
);
|
|
6610
|
+
if (!list.ok || !list.success) {
|
|
6611
|
+
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
6612
|
+
}
|
|
6613
|
+
const items = Array.isArray(list.data?.items) ? list.data.items : [];
|
|
6614
|
+
const tools = {};
|
|
6615
|
+
for (const item of items) {
|
|
6616
|
+
if (!item?.slug) continue;
|
|
6617
|
+
const slug = item.slug;
|
|
6618
|
+
const schema = item.input_parameters ?? EMPTY_SCHEMA;
|
|
6619
|
+
tools[toolKey(slug)] = tool({
|
|
6620
|
+
description: item.description ?? item.name ?? slug,
|
|
6621
|
+
inputSchema: jsonSchema(schema),
|
|
6622
|
+
execute: async (args) => {
|
|
6623
|
+
const r = await requestComposio(
|
|
6624
|
+
env,
|
|
6625
|
+
"execute-tool",
|
|
6626
|
+
{ slug, arguments: args ?? {} },
|
|
6627
|
+
authToken
|
|
6628
|
+
);
|
|
6629
|
+
if (!r.ok || !r.success) {
|
|
6630
|
+
return { error: true, message: `Composio tool ${slug} failed: ${r.detail}` };
|
|
6631
|
+
}
|
|
6632
|
+
const data = r.data;
|
|
6633
|
+
if (data && typeof data === "object" && data.requiresConnection) {
|
|
6634
|
+
const toolkit = data.toolkit;
|
|
6635
|
+
return {
|
|
6636
|
+
requiresConnection: true,
|
|
6637
|
+
toolkit,
|
|
6638
|
+
message: `The user has not connected ${toolkit ?? "this app"}. Ask them to connect it before using this tool.`
|
|
6639
|
+
};
|
|
6640
|
+
}
|
|
6641
|
+
return data;
|
|
6642
|
+
}
|
|
6643
|
+
});
|
|
6644
|
+
}
|
|
6645
|
+
return tools;
|
|
6646
|
+
}
|
|
6647
|
+
|
|
6246
6648
|
// src/server/utils/screenshot.ts
|
|
6247
6649
|
async function captureScreenshot(env, opts) {
|
|
6248
6650
|
const headers = new Headers({
|
|
@@ -6330,6 +6732,45 @@ function applySlidingWindow(messages, charCap, minKept) {
|
|
|
6330
6732
|
}
|
|
6331
6733
|
return out;
|
|
6332
6734
|
}
|
|
6735
|
+
var TRIMMABLE_ARRAY_PATHS = [
|
|
6736
|
+
["records"],
|
|
6737
|
+
["data", "records"],
|
|
6738
|
+
["items"],
|
|
6739
|
+
["data", "items"],
|
|
6740
|
+
["data"]
|
|
6741
|
+
];
|
|
6742
|
+
function getPath(root, path) {
|
|
6743
|
+
let cur = root;
|
|
6744
|
+
for (const key of path) {
|
|
6745
|
+
if (!cur || typeof cur !== "object") return void 0;
|
|
6746
|
+
cur = cur[key];
|
|
6747
|
+
}
|
|
6748
|
+
return cur;
|
|
6749
|
+
}
|
|
6750
|
+
function setPath(root, path, value, flags) {
|
|
6751
|
+
const obj = root && typeof root === "object" ? root : {};
|
|
6752
|
+
const [head, ...tail] = path;
|
|
6753
|
+
if (tail.length === 0) {
|
|
6754
|
+
return { ...obj, [head]: value, ...flags };
|
|
6755
|
+
}
|
|
6756
|
+
return { ...obj, [head]: setPath(obj[head], tail, value, flags) };
|
|
6757
|
+
}
|
|
6758
|
+
function locateTrimmableArray(result) {
|
|
6759
|
+
for (const path of TRIMMABLE_ARRAY_PATHS) {
|
|
6760
|
+
const arr = getPath(result, path);
|
|
6761
|
+
if (Array.isArray(arr)) return { path, items: arr };
|
|
6762
|
+
}
|
|
6763
|
+
for (const [key, value] of Object.entries(result)) {
|
|
6764
|
+
if (Array.isArray(value)) return { path: [key], items: value };
|
|
6765
|
+
}
|
|
6766
|
+
const data = result.data;
|
|
6767
|
+
if (data && typeof data === "object") {
|
|
6768
|
+
for (const [key, value] of Object.entries(data)) {
|
|
6769
|
+
if (Array.isArray(value)) return { path: ["data", key], items: value };
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
return void 0;
|
|
6773
|
+
}
|
|
6333
6774
|
function capToolResultSize(result, byteCap) {
|
|
6334
6775
|
let serialized;
|
|
6335
6776
|
try {
|
|
@@ -6339,6 +6780,31 @@ function capToolResultSize(result, byteCap) {
|
|
|
6339
6780
|
}
|
|
6340
6781
|
if (typeof serialized !== "string") return result;
|
|
6341
6782
|
if (serialized.length <= byteCap) return result;
|
|
6783
|
+
if (result && typeof result === "object") {
|
|
6784
|
+
const located = locateTrimmableArray(result);
|
|
6785
|
+
if (located) {
|
|
6786
|
+
const { path, items } = located;
|
|
6787
|
+
const total = items.length;
|
|
6788
|
+
const build = (k) => setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total });
|
|
6789
|
+
const fits = (k) => {
|
|
6790
|
+
try {
|
|
6791
|
+
return JSON.stringify(build(k)).length <= byteCap;
|
|
6792
|
+
} catch {
|
|
6793
|
+
return false;
|
|
6794
|
+
}
|
|
6795
|
+
};
|
|
6796
|
+
if (fits(0)) {
|
|
6797
|
+
let lo = 0;
|
|
6798
|
+
let hi = total;
|
|
6799
|
+
while (lo < hi) {
|
|
6800
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
6801
|
+
if (fits(mid)) lo = mid;
|
|
6802
|
+
else hi = mid - 1;
|
|
6803
|
+
}
|
|
6804
|
+
if (lo > 0) return build(lo);
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6342
6808
|
return {
|
|
6343
6809
|
success: false,
|
|
6344
6810
|
truncated: true,
|
|
@@ -6547,7 +7013,7 @@ function makeDefaultSummarizer(env, options = {}) {
|
|
|
6547
7013
|
}
|
|
6548
7014
|
|
|
6549
7015
|
// src/server/utils/chat-history.ts
|
|
6550
|
-
async function executeTool2(stub, userId,
|
|
7016
|
+
async function executeTool2(stub, userId, tool2, params) {
|
|
6551
7017
|
const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
|
|
6552
7018
|
method: "POST",
|
|
6553
7019
|
headers: {
|
|
@@ -6555,11 +7021,11 @@ async function executeTool2(stub, userId, tool, params) {
|
|
|
6555
7021
|
"X-User-Id": userId,
|
|
6556
7022
|
"X-App-Action": "true"
|
|
6557
7023
|
},
|
|
6558
|
-
body: JSON.stringify({ tool, params })
|
|
7024
|
+
body: JSON.stringify({ tool: tool2, params })
|
|
6559
7025
|
}));
|
|
6560
7026
|
const body = await res.json();
|
|
6561
7027
|
if (!body.success) {
|
|
6562
|
-
throw new Error(`chat-history ${
|
|
7028
|
+
throw new Error(`chat-history ${tool2} failed: ${body.error ?? "unknown error"}`);
|
|
6563
7029
|
}
|
|
6564
7030
|
return body.data;
|
|
6565
7031
|
}
|
|
@@ -6778,8 +7244,8 @@ import { importSPKI, jwtVerify } from "jose";
|
|
|
6778
7244
|
|
|
6779
7245
|
// src/server/auth/utils.ts
|
|
6780
7246
|
var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
|
|
6781
|
-
function bufferToHex(
|
|
6782
|
-
const view =
|
|
7247
|
+
function bufferToHex(bytes2) {
|
|
7248
|
+
const view = bytes2 instanceof Uint8Array ? bytes2 : new Uint8Array(bytes2);
|
|
6783
7249
|
let hex = "";
|
|
6784
7250
|
for (const byte of view) {
|
|
6785
7251
|
hex += byte.toString(16).padStart(2, "0");
|
|
@@ -6802,9 +7268,9 @@ function decodeJwtPayload(token) {
|
|
|
6802
7268
|
if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
|
|
6803
7269
|
try {
|
|
6804
7270
|
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
|
|
6805
|
-
const
|
|
6806
|
-
const
|
|
6807
|
-
const payload = JSON.parse(
|
|
7271
|
+
const bytes2 = base64ToUint8Array(normalized);
|
|
7272
|
+
const json2 = new TextDecoder().decode(bytes2);
|
|
7273
|
+
const payload = JSON.parse(json2);
|
|
6808
7274
|
return {
|
|
6809
7275
|
iss: payload.iss ?? null,
|
|
6810
7276
|
aud: payload.aud ?? null,
|
|
@@ -6956,6 +7422,7 @@ async function timingSafeEqualHex(a, b) {
|
|
|
6956
7422
|
// src/server/auth/betterAuth.ts
|
|
6957
7423
|
import { betterAuth } from "better-auth";
|
|
6958
7424
|
import { organization, twoFactor } from "better-auth/plugins";
|
|
7425
|
+
import { SignJWT, importPKCS8 } from "jose";
|
|
6959
7426
|
function createDeepSpaceAuth(config) {
|
|
6960
7427
|
const socialProviders = {};
|
|
6961
7428
|
if (config.google) {
|
|
@@ -6964,6 +7431,20 @@ function createDeepSpaceAuth(config) {
|
|
|
6964
7431
|
if (config.github) {
|
|
6965
7432
|
socialProviders.github = config.github;
|
|
6966
7433
|
}
|
|
7434
|
+
if (config.microsoft) {
|
|
7435
|
+
socialProviders.microsoft = config.microsoft;
|
|
7436
|
+
}
|
|
7437
|
+
if (config.apple) {
|
|
7438
|
+
const apple = config.apple;
|
|
7439
|
+
socialProviders.apple = async () => {
|
|
7440
|
+
try {
|
|
7441
|
+
return { clientId: apple.clientId, clientSecret: await generateAppleClientSecret(apple) };
|
|
7442
|
+
} catch (err) {
|
|
7443
|
+
console.error("[deepspace] failed to mint Apple client secret; Apple sign-in disabled", err);
|
|
7444
|
+
return { clientId: apple.clientId, clientSecret: "" };
|
|
7445
|
+
}
|
|
7446
|
+
};
|
|
7447
|
+
}
|
|
6967
7448
|
return betterAuth({
|
|
6968
7449
|
database: config.database,
|
|
6969
7450
|
baseURL: config.baseURL,
|
|
@@ -6976,11 +7457,212 @@ function createDeepSpaceAuth(config) {
|
|
|
6976
7457
|
"https://deep.space",
|
|
6977
7458
|
"https://*.deep.space",
|
|
6978
7459
|
"https://*.app.space",
|
|
6979
|
-
"http://localhost:*"
|
|
7460
|
+
"http://localhost:*",
|
|
7461
|
+
// Apple posts its OAuth callback from this origin (form_post).
|
|
7462
|
+
"https://appleid.apple.com"
|
|
6980
7463
|
],
|
|
7464
|
+
// Apple returns its callback as a cross-site form_post, which a
|
|
7465
|
+
// SameSite=Lax cookie won't ride along with — so the OAuth handshake
|
|
7466
|
+
// cookie must be SameSite=None. Scoped to just that cookie; the session
|
|
7467
|
+
// cookie stays Lax. Gated on Apple being configured: deployments without
|
|
7468
|
+
// Apple keep Lax entirely. Note that when Apple IS configured, this also
|
|
7469
|
+
// relaxes the handshake cookie for Google/GitHub/Microsoft on the same
|
|
7470
|
+
// instance — which is safe, since the state is encrypted and verified
|
|
7471
|
+
// independently of SameSite (plus a separate per-flow CSRF cookie).
|
|
7472
|
+
...config.apple ? {
|
|
7473
|
+
advanced: {
|
|
7474
|
+
cookies: {
|
|
7475
|
+
state: { attributes: { sameSite: "none", secure: true } },
|
|
7476
|
+
oauth_state: { attributes: { sameSite: "none", secure: true } }
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
} : {},
|
|
6981
7480
|
plugins: [organization(), twoFactor()]
|
|
6982
7481
|
});
|
|
6983
7482
|
}
|
|
7483
|
+
var cachedAppleSecret = null;
|
|
7484
|
+
async function generateAppleClientSecret(apple) {
|
|
7485
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
7486
|
+
if (cachedAppleSecret && cachedAppleSecret.keyId === apple.keyId && cachedAppleSecret.expiresAt - now > 3600) {
|
|
7487
|
+
return cachedAppleSecret.token;
|
|
7488
|
+
}
|
|
7489
|
+
const pem = apple.privateKey.replace(/\\n/g, "\n");
|
|
7490
|
+
const key = await importPKCS8(pem, "ES256");
|
|
7491
|
+
const expiresAt = now + 180 * 24 * 60 * 60;
|
|
7492
|
+
const token = await new SignJWT({}).setProtectedHeader({ alg: "ES256", kid: apple.keyId }).setIssuer(apple.teamId).setSubject(apple.clientId).setAudience("https://appleid.apple.com").setIssuedAt(now).setExpirationTime(expiresAt).sign(key);
|
|
7493
|
+
cachedAppleSecret = { keyId: apple.keyId, token, expiresAt };
|
|
7494
|
+
return token;
|
|
7495
|
+
}
|
|
7496
|
+
|
|
7497
|
+
// src/server/secrets.ts
|
|
7498
|
+
var DEFAULT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7499
|
+
function createSecrets(env, options = {}) {
|
|
7500
|
+
const refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
|
|
7501
|
+
const now = options.now ?? (() => Date.now());
|
|
7502
|
+
const cache = /* @__PURE__ */ new Map();
|
|
7503
|
+
let loadedAll = null;
|
|
7504
|
+
let loadedVersion = 0;
|
|
7505
|
+
let lastCheckedAt = 0;
|
|
7506
|
+
function stub() {
|
|
7507
|
+
if (!env.SECRETS_ROOMS) throw new Error("SECRETS_ROOMS binding is missing");
|
|
7508
|
+
if (!env.APP_SECRETS_KEY) throw new Error("APP_SECRETS_KEY binding is missing");
|
|
7509
|
+
const id = env.SECRETS_ROOMS.idFromName("app-secrets");
|
|
7510
|
+
return env.SECRETS_ROOMS.get(id);
|
|
7511
|
+
}
|
|
7512
|
+
async function fetchJson(path) {
|
|
7513
|
+
const res = await stub().fetch(`https://app-secrets/${path}`, {
|
|
7514
|
+
headers: { "x-deepspace-internal": "app-secrets" }
|
|
7515
|
+
});
|
|
7516
|
+
const body = await res.json().catch(() => ({}));
|
|
7517
|
+
if (!res.ok) throw new Error(body.error ?? `App secrets unavailable (${res.status})`);
|
|
7518
|
+
return body;
|
|
7519
|
+
}
|
|
7520
|
+
async function pullAll() {
|
|
7521
|
+
const body = await fetchJson("pull");
|
|
7522
|
+
const version = typeof body.version === "number" ? body.version : 0;
|
|
7523
|
+
cache.clear();
|
|
7524
|
+
for (const [name, value] of Object.entries(body.secrets ?? {})) {
|
|
7525
|
+
cache.set(name, value);
|
|
7526
|
+
}
|
|
7527
|
+
loadedVersion = version;
|
|
7528
|
+
lastCheckedAt = now();
|
|
7529
|
+
return version;
|
|
7530
|
+
}
|
|
7531
|
+
async function loadAll() {
|
|
7532
|
+
await pullAll();
|
|
7533
|
+
}
|
|
7534
|
+
async function ensureLoaded() {
|
|
7535
|
+
if (!loadedAll) {
|
|
7536
|
+
loadedAll = loadAll().catch((err) => {
|
|
7537
|
+
loadedAll = null;
|
|
7538
|
+
throw err;
|
|
7539
|
+
});
|
|
7540
|
+
}
|
|
7541
|
+
await loadedAll;
|
|
7542
|
+
}
|
|
7543
|
+
async function maybeCheckForUpdates() {
|
|
7544
|
+
if (!loadedAll) return;
|
|
7545
|
+
if (refreshIntervalMs < 0) return;
|
|
7546
|
+
if (now() - lastCheckedAt < refreshIntervalMs) return;
|
|
7547
|
+
try {
|
|
7548
|
+
await client.checkForUpdates();
|
|
7549
|
+
} catch {
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
function localDevValue(name) {
|
|
7553
|
+
const value = env[name];
|
|
7554
|
+
return env.APP_SECRETS_LOCAL_FALLBACK === "true" && typeof value === "string" ? value : void 0;
|
|
7555
|
+
}
|
|
7556
|
+
const client = {
|
|
7557
|
+
async get(name) {
|
|
7558
|
+
const local = localDevValue(name);
|
|
7559
|
+
if (local !== void 0) return local;
|
|
7560
|
+
if (!cache.has(name)) await ensureLoaded();
|
|
7561
|
+
else await maybeCheckForUpdates();
|
|
7562
|
+
return cache.get(name);
|
|
7563
|
+
},
|
|
7564
|
+
async require(name) {
|
|
7565
|
+
const value = await this.get(name);
|
|
7566
|
+
if (value === void 0) throw new Error(`Missing app secret "${name}"`);
|
|
7567
|
+
return value;
|
|
7568
|
+
},
|
|
7569
|
+
async getMany(names) {
|
|
7570
|
+
const out = {};
|
|
7571
|
+
for (const name of names) out[name] = await this.get(name);
|
|
7572
|
+
return out;
|
|
7573
|
+
},
|
|
7574
|
+
async preload(names) {
|
|
7575
|
+
if (env.APP_SECRETS_LOCAL_FALLBACK === "true") {
|
|
7576
|
+
const missing = [];
|
|
7577
|
+
for (const name of names) {
|
|
7578
|
+
const local = localDevValue(name);
|
|
7579
|
+
if (local !== void 0) cache.set(name, local);
|
|
7580
|
+
else missing.push(name);
|
|
7581
|
+
}
|
|
7582
|
+
if (missing.length === 0) {
|
|
7583
|
+
await maybeCheckForUpdates();
|
|
7584
|
+
return;
|
|
7585
|
+
}
|
|
7586
|
+
}
|
|
7587
|
+
await ensureLoaded();
|
|
7588
|
+
await maybeCheckForUpdates();
|
|
7589
|
+
},
|
|
7590
|
+
async refresh(refreshOptions = {}) {
|
|
7591
|
+
if (refreshOptions.force || !loadedAll) {
|
|
7592
|
+
const previous = loadedVersion;
|
|
7593
|
+
const version = await pullAll();
|
|
7594
|
+
loadedAll = Promise.resolve();
|
|
7595
|
+
return { changed: version !== previous, version };
|
|
7596
|
+
}
|
|
7597
|
+
return this.checkForUpdates();
|
|
7598
|
+
},
|
|
7599
|
+
async checkForUpdates() {
|
|
7600
|
+
await ensureLoaded();
|
|
7601
|
+
const body = await fetchJson("version");
|
|
7602
|
+
const version = typeof body.version === "number" ? body.version : 0;
|
|
7603
|
+
lastCheckedAt = now();
|
|
7604
|
+
if (version === loadedVersion) return { changed: false, version };
|
|
7605
|
+
const pulledVersion = await pullAll();
|
|
7606
|
+
return { changed: true, version: pulledVersion };
|
|
7607
|
+
},
|
|
7608
|
+
invalidate() {
|
|
7609
|
+
cache.clear();
|
|
7610
|
+
loadedAll = null;
|
|
7611
|
+
loadedVersion = 0;
|
|
7612
|
+
lastCheckedAt = 0;
|
|
7613
|
+
},
|
|
7614
|
+
async withFreshSecrets(operation, retryOptions = {}) {
|
|
7615
|
+
let first;
|
|
7616
|
+
try {
|
|
7617
|
+
first = await operation({ secrets: client, attempt: 1, refreshed: false });
|
|
7618
|
+
} catch (err) {
|
|
7619
|
+
if (!shouldRefreshOnError(err)) throw err;
|
|
7620
|
+
const refresh = await refreshAfterFailure(err);
|
|
7621
|
+
if (!refresh.changed && !retryOptions.retryWhenUnchanged) throw err;
|
|
7622
|
+
return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
|
|
7623
|
+
}
|
|
7624
|
+
if (first instanceof Response && shouldRefreshOnStatus(first.status)) {
|
|
7625
|
+
const refresh = await refreshAfterFailure(first);
|
|
7626
|
+
if (refresh.changed || retryOptions.retryWhenUnchanged) {
|
|
7627
|
+
return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
return first;
|
|
7631
|
+
}
|
|
7632
|
+
};
|
|
7633
|
+
async function refreshAfterFailure(original) {
|
|
7634
|
+
try {
|
|
7635
|
+
return await client.refresh({ force: true });
|
|
7636
|
+
} catch (err) {
|
|
7637
|
+
const originalMessage = describeFailure(original);
|
|
7638
|
+
const refreshMessage = err instanceof Error ? err.message : String(err);
|
|
7639
|
+
throw new Error(`Secret refresh failed after API failure (${originalMessage}): ${refreshMessage}`);
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
return client;
|
|
7643
|
+
}
|
|
7644
|
+
function shouldRefreshOnError(error) {
|
|
7645
|
+
const status = statusFromError(error);
|
|
7646
|
+
return status !== void 0 && shouldRefreshOnStatus(status);
|
|
7647
|
+
}
|
|
7648
|
+
function shouldRefreshOnStatus(status) {
|
|
7649
|
+
return status === 401 || status === 403;
|
|
7650
|
+
}
|
|
7651
|
+
function statusFromError(error) {
|
|
7652
|
+
if (!error || typeof error !== "object") return void 0;
|
|
7653
|
+
const record = error;
|
|
7654
|
+
if (typeof record.status === "number") return record.status;
|
|
7655
|
+
if (typeof record.statusCode === "number") return record.statusCode;
|
|
7656
|
+
if (typeof record.response?.status === "number") return record.response.status;
|
|
7657
|
+
return void 0;
|
|
7658
|
+
}
|
|
7659
|
+
function describeFailure(value) {
|
|
7660
|
+
if (value instanceof Response) return `HTTP ${value.status}`;
|
|
7661
|
+
if (value instanceof Error) return value.message;
|
|
7662
|
+
const status = statusFromError(value);
|
|
7663
|
+
if (status) return `HTTP ${status}`;
|
|
7664
|
+
return String(value);
|
|
7665
|
+
}
|
|
6984
7666
|
export {
|
|
6985
7667
|
AI_CHATS_SCHEMA,
|
|
6986
7668
|
AI_MESSAGES_SCHEMA,
|
|
@@ -7001,6 +7683,7 @@ export {
|
|
|
7001
7683
|
CronRoom,
|
|
7002
7684
|
DEFAULT_CONTEXT_CONFIG,
|
|
7003
7685
|
DEFAULT_DO_MANIFEST,
|
|
7686
|
+
DEFAULT_QUERY_LIMIT,
|
|
7004
7687
|
DIRECTORY_SCHEMAS,
|
|
7005
7688
|
GLOBAL_DO_TYPES,
|
|
7006
7689
|
GLOBAL_DO_TYPE_NAMES,
|
|
@@ -7027,6 +7710,7 @@ export {
|
|
|
7027
7710
|
SYSTEM_COLLECTIONS,
|
|
7028
7711
|
SYSTEM_MANAGED_COLUMNS,
|
|
7029
7712
|
SchemaRegistry,
|
|
7713
|
+
SecretsRoom,
|
|
7030
7714
|
USERS_COLUMNS,
|
|
7031
7715
|
VOTING_SCHEMAS,
|
|
7032
7716
|
WORKSPACE_SCHEMAS,
|
|
@@ -7034,6 +7718,7 @@ export {
|
|
|
7034
7718
|
analyzePermissions,
|
|
7035
7719
|
apiWorkerFetch,
|
|
7036
7720
|
appendMessage,
|
|
7721
|
+
applyAiToolDefaults,
|
|
7037
7722
|
applySlidingWindow,
|
|
7038
7723
|
authWorkerFetch,
|
|
7039
7724
|
bindingManifestFromOutputConfig,
|
|
@@ -7051,6 +7736,7 @@ export {
|
|
|
7051
7736
|
coerceValue,
|
|
7052
7737
|
collectionTableName,
|
|
7053
7738
|
columnId,
|
|
7739
|
+
composioTools,
|
|
7054
7740
|
computeDoMigration,
|
|
7055
7741
|
computeHmacHex,
|
|
7056
7742
|
createChat,
|
|
@@ -7059,6 +7745,7 @@ export {
|
|
|
7059
7745
|
createDeepSpaceAuth,
|
|
7060
7746
|
createEncoder,
|
|
7061
7747
|
createScopedR2Handler,
|
|
7748
|
+
createSecrets,
|
|
7062
7749
|
dataToColumnValues,
|
|
7063
7750
|
decodeJwtPayload,
|
|
7064
7751
|
deleteChatCascade,
|