@neta-art/cohub 8.17.0 → 8.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -9
- package/dist/chunks/environment.d.ts +525 -5
- package/dist/chunks/environment.js +1 -0
- package/dist/chunks/http.d.ts +51 -2
- package/dist/chunks/http.js +975 -943
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +35 -4
- package/dist/index.js +834 -30
- package/dist/protocol/dist/app-authorization.d.ts +1 -0
- package/dist/protocol/dist/index.d.ts +4 -0
- package/dist/protocol/dist/model/session.d.ts +1 -0
- package/dist/protocol/dist/model/turn.d.ts +1 -0
- package/dist/protocol/dist/realtime/types.d.ts +2 -0
- package/dist/protocol/dist/runtime/archive.d.ts +1 -0
- package/dist/protocol/dist/runtime/context.d.ts +1 -0
- package/dist/protocol/dist/runtime/index.d.ts +3 -0
- package/dist/types.d.ts +1 -0
- package/docs/app-runtime-guide.md +145 -92
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,55 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as BoardEffectInputSchema, A as SkillsApi, B as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, C as SessionPatchReducer, D as ReferencesApi, E as SessionAccessApi, F as ChannelsApi, G as parseDesktopCommand, H as DESKTOP_COMMAND_VERSION, I as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, J as NAVIGATION_ERROR_MESSAGE_MAX_LENGTH, K as parseDesktopCommandId, L as DESKTOP_COMMAND_MAX_TIMEOUT_MS, M as ModelsApi, N as GenerationsApi, O as SearchApi, P as CronJobsApi, Q as BoardSemanticMutationSchema, R as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, S as parseAssistantMessageCommit, T as ensureRealtimeConnected, U as isDesktopCallMethod, V as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, W as isTerminalDesktopCommandStatus, X as BoardItemPatchSchema, Y as BoardAuthoringItemSchema, Z as BoardSemanticCommandSchema, _ as PublicInviteApi, a as DesktopCommandsApi, at as BoardCompositionSchema, b as SessionGenerationStreamClient, c as ReferralsApi, ct as parseBoardCompositionInput, d as TasksApi, dt as BOARD_BUILTIN_CAPABILITIES, et as BoardEffectSchema, f as BoardClient, ft as BOARD_BUILTIN_CLIP_KINDS, g as SpacesApi, h as SpaceWebhooksApi, i as WorkCommerceApi, it as BoardCompositionInputSchema, j as PromptsApi, k as PublicAssetsApi, l as UsersApi, lt as BoardConnectionSchema, m as SpacePublicFilesApi, mt as DEFAULT_BOARD_RENDER_LIMITS, n as createHttpClient, nt as BOARD_ANIMATION_CHANNELS, o as UiCommandsApi, ot as BoardProceduralClipSchema, p as SpaceClient, pt as BOARD_BUILTIN_EFFECT_KINDS, q as resolveOpenSurface, r as AppCommerceApi, rt as BOARD_ANIMATION_CHANNEL_CAPABILITIES, s as AppsApi, st as BoardTrackSchema, t as CohubHttpClient, tt as parseBoardEffectInput, u as UserApi, ut as BOARD_ARROW_STROKE_SIZE, v as buildSpaceInvitePath, w as createSessionPatchReducer, x as createSessionGenerationStreamClient, y as buildSpacePath, z as DESKTOP_COMMAND_PENDING_TTL_SECONDS } from "./chunks/http.js";
|
|
2
2
|
import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as REALTIME_ROOM_EVENT_NAME_PATTERN, c as REQUEST_SOURCE_VIA_MAX_LENGTH, d as isRequestSourceEmpty, f as isRequestSourceUuid, g as readRequestSourceFromEnv, h as parseRequestSourceFromHeaders, i as matchesUnauthorizedErrorToken, l as hasRequestSourceIdentity, m as normalizeRequestSource, n as HttpTransport, o as COHUB_SOURCE_HEADER, p as mergeRequestSourceIntoMeta, r as joinApiUrl, s as COHUB_SOURCE_HEADER_NAMES, t as HttpError, u as isRequestSourceClientId, v as resolveRequestSourceChannel, y as isUuid } from "./chunks/transport.js";
|
|
3
3
|
import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebsocketUrl, f as PERMISSIONS, h as scopeListHasPermission, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, m as isUserLevelPermission, n as getCohubContext, o as resolveApiBaseUrl, p as USER_LEVEL_PERMISSIONS, r as normalizeBaseUrl, s as resolveCohubEnvironment, t as COHUB_ENVIRONMENTS, u as resolveVoiceInputWebsocketUrl } from "./chunks/environment.js";
|
|
4
4
|
import { a as extractBillingPayload, c as isFeatureNotEntitledError, i as FEATURE_NOT_ENTITLED_ERROR_CODE, l as isHttpErrorCode, n as createWebsocketClient, o as isBillingAccessBlockedCode, r as BILLING_ACCESS_BLOCKED_ERROR_CODE, s as isBillingAccessBlockedError, t as WebsocketClient } from "./chunks/websocket.js";
|
|
5
5
|
import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
|
|
6
6
|
import { filterSpacePickerItems, normalizeSpacePickerQuery, orderSpacePickerItems, selectSpacePickerItems } from "./space-picker.js";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
//#region ../protocol/dist/app-authorization.js
|
|
9
|
+
const appAuthorizationTargetSchema = z.discriminatedUnion("kind", [
|
|
10
|
+
z.object({ kind: z.literal("account") }),
|
|
11
|
+
z.object({
|
|
12
|
+
kind: z.literal("space"),
|
|
13
|
+
spaceId: z.string().min(1)
|
|
14
|
+
}),
|
|
15
|
+
z.object({ kind: z.literal("pick-space") })
|
|
16
|
+
]);
|
|
17
|
+
const appAuthorizationRequestSchema = z.object({
|
|
18
|
+
target: appAuthorizationTargetSchema,
|
|
19
|
+
scopes: z.array(z.string().min(1)).min(1).max(128),
|
|
20
|
+
reason: z.string().max(280).optional(),
|
|
21
|
+
alwaysAsk: z.boolean().optional(),
|
|
22
|
+
fallback: z.enum(["allow", "none"]).default("allow")
|
|
23
|
+
});
|
|
24
|
+
const appAuthorizationGrantSchema = z.object({
|
|
25
|
+
id: z.string().min(1),
|
|
26
|
+
spaceId: z.string().min(1),
|
|
27
|
+
scopes: z.array(z.string()),
|
|
28
|
+
expiresAt: z.string().nullable()
|
|
29
|
+
});
|
|
30
|
+
const appAuthorizationResultSchema = z.discriminatedUnion("status", [
|
|
31
|
+
z.object({
|
|
32
|
+
status: z.literal("granted"),
|
|
33
|
+
requestedTarget: appAuthorizationTargetSchema,
|
|
34
|
+
target: z.discriminatedUnion("kind", [z.object({ kind: z.literal("account") }), z.object({
|
|
35
|
+
kind: z.literal("space"),
|
|
36
|
+
spaceId: z.string().min(1),
|
|
37
|
+
name: z.string().nullable()
|
|
38
|
+
})]),
|
|
39
|
+
resolution: z.enum([
|
|
40
|
+
"requested",
|
|
41
|
+
"selected",
|
|
42
|
+
"fallback"
|
|
43
|
+
]),
|
|
44
|
+
grant: appAuthorizationGrantSchema
|
|
45
|
+
}),
|
|
46
|
+
z.object({ status: z.literal("cancelled") }),
|
|
47
|
+
z.object({
|
|
48
|
+
status: z.literal("denied"),
|
|
49
|
+
code: z.string()
|
|
50
|
+
})
|
|
51
|
+
]);
|
|
52
|
+
//#endregion
|
|
8
53
|
//#region ../protocol/dist/board.js
|
|
9
54
|
const BOARD_DOCUMENT_KIND = "cohub.board";
|
|
10
55
|
const BOARD_MANIFEST_KIND = "cohub.board.manifest";
|
|
@@ -1205,6 +1250,451 @@ function filterGenerationDeclarationsByPolicy(declarations, policy) {
|
|
|
1205
1250
|
});
|
|
1206
1251
|
}
|
|
1207
1252
|
//#endregion
|
|
1253
|
+
//#region ../protocol/dist/core/content-schema.js
|
|
1254
|
+
const meta = z.record(z.string(), z.unknown());
|
|
1255
|
+
const contentBlockSchema = z.lazy(() => z.discriminatedUnion("type", [
|
|
1256
|
+
z.object({
|
|
1257
|
+
type: z.literal("text"),
|
|
1258
|
+
text: z.string(),
|
|
1259
|
+
_meta: meta.optional()
|
|
1260
|
+
}),
|
|
1261
|
+
z.object({
|
|
1262
|
+
type: z.literal("thinking"),
|
|
1263
|
+
thinking: z.string(),
|
|
1264
|
+
signature: z.string().optional(),
|
|
1265
|
+
_meta: meta.optional()
|
|
1266
|
+
}),
|
|
1267
|
+
z.object({
|
|
1268
|
+
type: z.literal("image"),
|
|
1269
|
+
source: z.union([z.object({
|
|
1270
|
+
type: z.literal("url"),
|
|
1271
|
+
url: z.string().url()
|
|
1272
|
+
}), z.object({
|
|
1273
|
+
type: z.literal("base64"),
|
|
1274
|
+
media_type: z.string(),
|
|
1275
|
+
data: z.string()
|
|
1276
|
+
})]),
|
|
1277
|
+
_meta: meta.optional()
|
|
1278
|
+
}),
|
|
1279
|
+
z.object({
|
|
1280
|
+
type: z.literal("shell_command"),
|
|
1281
|
+
command: z.string(),
|
|
1282
|
+
rawText: z.string(),
|
|
1283
|
+
_meta: meta.optional()
|
|
1284
|
+
}),
|
|
1285
|
+
z.object({
|
|
1286
|
+
type: z.literal("tool_use"),
|
|
1287
|
+
id: z.string().min(1),
|
|
1288
|
+
name: z.string().min(1),
|
|
1289
|
+
input: z.record(z.string(), z.unknown()),
|
|
1290
|
+
_meta: meta.optional()
|
|
1291
|
+
}),
|
|
1292
|
+
z.object({
|
|
1293
|
+
type: z.literal("tool_result"),
|
|
1294
|
+
tool_use_id: z.string().min(1),
|
|
1295
|
+
content: z.union([z.string(), z.array(contentBlockSchema)]),
|
|
1296
|
+
is_error: z.boolean().optional(),
|
|
1297
|
+
_meta: meta.optional()
|
|
1298
|
+
}),
|
|
1299
|
+
z.object({
|
|
1300
|
+
type: z.literal("system_note"),
|
|
1301
|
+
note_type: z.enum([
|
|
1302
|
+
"session_created",
|
|
1303
|
+
"forked",
|
|
1304
|
+
"compacted",
|
|
1305
|
+
"info"
|
|
1306
|
+
]),
|
|
1307
|
+
text: z.string(),
|
|
1308
|
+
_meta: meta.optional()
|
|
1309
|
+
})
|
|
1310
|
+
]));
|
|
1311
|
+
//#endregion
|
|
1312
|
+
//#region ../protocol/dist/runtime/archive.js
|
|
1313
|
+
const RUNTIME_ARCHIVE_SEGMENT_BYTES = 4194304;
|
|
1314
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
1315
|
+
const runtimeArchiveSegmentSchema = z.object({
|
|
1316
|
+
offset: z.number().int().safe().nonnegative(),
|
|
1317
|
+
sizeBytes: z.number().int().min(1).max(RUNTIME_ARCHIVE_SEGMENT_BYTES),
|
|
1318
|
+
sha256: digest,
|
|
1319
|
+
md5: z.string().regex(/^[a-f0-9]{32}$/)
|
|
1320
|
+
}).strict();
|
|
1321
|
+
const harnessArchiveSchema = z.object({
|
|
1322
|
+
sessionId: z.string().uuid(),
|
|
1323
|
+
turnId: z.string().uuid(),
|
|
1324
|
+
harness: z.enum(["pi", "codex"])
|
|
1325
|
+
}).strict();
|
|
1326
|
+
const harnessArchiveIndexSchema = harnessArchiveSchema.extend({
|
|
1327
|
+
version: z.literal(1),
|
|
1328
|
+
nativeFormat: z.enum(["pi.jsonl", "codex.rollout"]),
|
|
1329
|
+
nativeSessionId: z.string().min(1).max(255),
|
|
1330
|
+
parentTurnId: z.string().uuid().nullable(),
|
|
1331
|
+
sizeBytes: z.number().int().safe().positive(),
|
|
1332
|
+
sha256: digest,
|
|
1333
|
+
segments: z.array(runtimeArchiveSegmentSchema).max(256)
|
|
1334
|
+
}).strict().refine((value) => value.nativeFormat === (value.harness === "pi" ? "pi.jsonl" : "codex.rollout"), "Native format mismatch");
|
|
1335
|
+
function validateArchiveBoundary(index, parent) {
|
|
1336
|
+
if (index.parentTurnId !== (parent?.turnId ?? null)) throw new Error("Archive parent mismatch");
|
|
1337
|
+
if (parent && (parent.sessionId !== index.sessionId || parent.harness !== index.harness || parent.nativeSessionId !== index.nativeSessionId)) throw new Error("Archive identity mismatch");
|
|
1338
|
+
let offset = parent?.sizeBytes ?? 0;
|
|
1339
|
+
for (const segment of index.segments) {
|
|
1340
|
+
if (segment.offset !== offset) throw new Error("Archive segments are not contiguous");
|
|
1341
|
+
offset += segment.sizeBytes;
|
|
1342
|
+
}
|
|
1343
|
+
if (offset !== index.sizeBytes || !parent && !index.segments.length) throw new Error("Archive is incomplete");
|
|
1344
|
+
if (!index.segments.length && parent?.sha256 !== index.sha256) throw new Error("Unchanged archive checksum mismatch");
|
|
1345
|
+
}
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region ../protocol/dist/runtime/context.js
|
|
1348
|
+
const createEmptyUsage = () => ({
|
|
1349
|
+
input: 0,
|
|
1350
|
+
output: 0,
|
|
1351
|
+
cacheRead: 0,
|
|
1352
|
+
cacheWrite: 0,
|
|
1353
|
+
totalTokens: 0,
|
|
1354
|
+
cost: {
|
|
1355
|
+
input: 0,
|
|
1356
|
+
output: 0,
|
|
1357
|
+
cacheRead: 0,
|
|
1358
|
+
cacheWrite: 0,
|
|
1359
|
+
total: 0
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
/**
|
|
1363
|
+
* Project one block as itself, or drop it. Nothing is ever rewritten into prompt text.
|
|
1364
|
+
* URL images, system notes and unknown blocks have no native representation here; the
|
|
1365
|
+
* durable copy stays in the database and is not faked into the model's input.
|
|
1366
|
+
*/
|
|
1367
|
+
function projectBlock(block) {
|
|
1368
|
+
if (block.type === "text") return {
|
|
1369
|
+
type: "text",
|
|
1370
|
+
text: block.text
|
|
1371
|
+
};
|
|
1372
|
+
if (block.type === "image") return block.source.type === "base64" ? {
|
|
1373
|
+
type: "image",
|
|
1374
|
+
data: block.source.data,
|
|
1375
|
+
mimeType: block.source.media_type
|
|
1376
|
+
} : null;
|
|
1377
|
+
if (block.type === "thinking") return {
|
|
1378
|
+
type: "thinking",
|
|
1379
|
+
thinking: block.thinking,
|
|
1380
|
+
...block.signature ? { thinkingSignature: block.signature } : {}
|
|
1381
|
+
};
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
const projectContent = (content) => content.map(projectBlock).filter((block) => block !== null);
|
|
1385
|
+
const isCompaction = (message) => message.role === "system" && message.content.some((block) => block.type === "system_note" && block.note_type === "compacted");
|
|
1386
|
+
/** A generation placeholder must never be projected as if it were a finished result. */
|
|
1387
|
+
const isPendingGenerationResult = (message) => message.role === "assistant" && message.meta?.messageKind === "generation_result" && message.meta?.generationStatus !== "completed" && message.meta?.generationStatus !== "failed";
|
|
1388
|
+
function selectRuntimeContextMessages(messages) {
|
|
1389
|
+
let boundary = -1;
|
|
1390
|
+
let latest = -1;
|
|
1391
|
+
messages.forEach((message, index) => {
|
|
1392
|
+
if (!isCompaction(message)) return;
|
|
1393
|
+
const compaction = message.meta?.compaction;
|
|
1394
|
+
const timestamp = Date.parse(compaction?.compactedAt ?? String(message.meta?.createdAt ?? ""));
|
|
1395
|
+
const order = Number.isFinite(timestamp) ? timestamp : index;
|
|
1396
|
+
if (order >= latest) {
|
|
1397
|
+
latest = order;
|
|
1398
|
+
boundary = index;
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
const compaction = messages[boundary];
|
|
1402
|
+
if (!compaction) return messages;
|
|
1403
|
+
return [compaction, ...messages.slice(boundary + 1).filter((message) => !isCompaction(message))];
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Compile durable, normalized messages into Pi-compatible context without replaying tools.
|
|
1407
|
+
* Representable blocks and tool pairing are preserved verbatim; anything without a native
|
|
1408
|
+
* representation is dropped rather than described in prompt text.
|
|
1409
|
+
*/
|
|
1410
|
+
function contextToPiMessages(messages, options = {}) {
|
|
1411
|
+
const result = [];
|
|
1412
|
+
for (const message of selectRuntimeContextMessages(messages)) {
|
|
1413
|
+
if (isPendingGenerationResult(message)) continue;
|
|
1414
|
+
const meta = {
|
|
1415
|
+
...message.meta,
|
|
1416
|
+
messageId: message.id,
|
|
1417
|
+
turnId: message.turnId
|
|
1418
|
+
};
|
|
1419
|
+
const recordedAt = Date.parse(String(message.meta?.createdAt ?? ""));
|
|
1420
|
+
const timestamp = Number.isFinite(recordedAt) ? recordedAt : 0;
|
|
1421
|
+
if (message.role === "system") continue;
|
|
1422
|
+
if (message.role !== "assistant") {
|
|
1423
|
+
const projected = projectContent(message.content);
|
|
1424
|
+
if (projected.length) result.push({
|
|
1425
|
+
role: "user",
|
|
1426
|
+
content: projected,
|
|
1427
|
+
timestamp,
|
|
1428
|
+
meta
|
|
1429
|
+
});
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const calls = new Map(message.content.flatMap((block) => block.type === "tool_use" ? [[block.id, block.name]] : []));
|
|
1433
|
+
const content = message.content.flatMap((block) => {
|
|
1434
|
+
if (block.type === "tool_result") return [];
|
|
1435
|
+
if (block.type === "tool_use") return [{
|
|
1436
|
+
type: "toolCall",
|
|
1437
|
+
id: block.id,
|
|
1438
|
+
name: block.name,
|
|
1439
|
+
arguments: block.input
|
|
1440
|
+
}];
|
|
1441
|
+
const projected = projectBlock(block);
|
|
1442
|
+
return projected ? [projected] : [];
|
|
1443
|
+
});
|
|
1444
|
+
if (content.length) result.push({
|
|
1445
|
+
role: "assistant",
|
|
1446
|
+
content,
|
|
1447
|
+
api: typeof message.meta?.nativeApi === "string" ? message.meta.nativeApi : options.resolveApi?.({
|
|
1448
|
+
provider: message.provider ?? null,
|
|
1449
|
+
model: message.model ?? null
|
|
1450
|
+
}) ?? "history",
|
|
1451
|
+
provider: message.provider ?? options.fallbackProvider ?? "history",
|
|
1452
|
+
model: message.model ?? "history",
|
|
1453
|
+
usage: {
|
|
1454
|
+
...createEmptyUsage(),
|
|
1455
|
+
...message.usage
|
|
1456
|
+
},
|
|
1457
|
+
stopReason: message.stopReason === "tool_use" ? "toolUse" : message.stopReason ?? (calls.size ? "toolUse" : "stop"),
|
|
1458
|
+
...message.errorMessage ? { errorMessage: message.errorMessage } : {},
|
|
1459
|
+
timestamp,
|
|
1460
|
+
meta
|
|
1461
|
+
});
|
|
1462
|
+
for (const block of message.content) {
|
|
1463
|
+
if (block.type !== "tool_result") continue;
|
|
1464
|
+
if (!calls.has(block.tool_use_id)) continue;
|
|
1465
|
+
result.push({
|
|
1466
|
+
role: "toolResult",
|
|
1467
|
+
toolCallId: block.tool_use_id,
|
|
1468
|
+
toolName: calls.get(block.tool_use_id) ?? "tool",
|
|
1469
|
+
content: typeof block.content === "string" ? [{
|
|
1470
|
+
type: "text",
|
|
1471
|
+
text: block.content
|
|
1472
|
+
}] : projectContent(block.content),
|
|
1473
|
+
isError: block.is_error ?? false,
|
|
1474
|
+
timestamp,
|
|
1475
|
+
meta
|
|
1476
|
+
});
|
|
1477
|
+
calls.delete(block.tool_use_id);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return result;
|
|
1481
|
+
}
|
|
1482
|
+
//#endregion
|
|
1483
|
+
//#region ../protocol/dist/runtime/index.js
|
|
1484
|
+
const RUNTIME_PROTOCOL_VERSION = 1;
|
|
1485
|
+
const RUNTIME_MAX_FRAME_BYTES = 33554432;
|
|
1486
|
+
const RUNTIME_RECOVERY_BATCH_SIZE = 64;
|
|
1487
|
+
z.enum([
|
|
1488
|
+
"cohub",
|
|
1489
|
+
"pi",
|
|
1490
|
+
"codex"
|
|
1491
|
+
]);
|
|
1492
|
+
const isLocalHarness = (value) => value === "pi" || value === "codex";
|
|
1493
|
+
const resolveHarness = (meta) => {
|
|
1494
|
+
const value = meta && typeof meta === "object" ? meta.harness : null;
|
|
1495
|
+
return isLocalHarness(value) ? value : "cohub";
|
|
1496
|
+
};
|
|
1497
|
+
const runtimeCapabilitiesSchema = z.object({
|
|
1498
|
+
harnesses: z.array(z.enum(["pi", "codex"])).min(1).max(2),
|
|
1499
|
+
models: z.array(z.object({
|
|
1500
|
+
harness: z.enum(["pi", "codex"]),
|
|
1501
|
+
provider: z.string().max(100),
|
|
1502
|
+
id: z.string().min(1).max(255),
|
|
1503
|
+
name: z.string().max(255)
|
|
1504
|
+
})).max(2e3)
|
|
1505
|
+
});
|
|
1506
|
+
const runtimePendingExecutionSchema = z.object({
|
|
1507
|
+
sessionId: z.string().uuid(),
|
|
1508
|
+
turnId: z.string().uuid(),
|
|
1509
|
+
harness: z.enum(["pi", "codex"])
|
|
1510
|
+
}).strict();
|
|
1511
|
+
z.object({
|
|
1512
|
+
connectionId: z.string().uuid(),
|
|
1513
|
+
endpoint: z.url({ protocol: /^wss?$/ }),
|
|
1514
|
+
ownerUserId: z.string().min(1),
|
|
1515
|
+
capabilities: runtimeCapabilitiesSchema
|
|
1516
|
+
});
|
|
1517
|
+
const runtimeReadySchema = z.object({
|
|
1518
|
+
type: z.literal("runtime.ready"),
|
|
1519
|
+
connectionId: z.string().uuid()
|
|
1520
|
+
});
|
|
1521
|
+
z.object({
|
|
1522
|
+
expectedTurnId: z.string().uuid(),
|
|
1523
|
+
revision: z.string().min(1),
|
|
1524
|
+
confirmed: z.literal(true)
|
|
1525
|
+
}).strict();
|
|
1526
|
+
const id = z.string().uuid();
|
|
1527
|
+
const ordinal = z.number().int().min(0).max(1e5);
|
|
1528
|
+
const content = z.array(contentBlockSchema).max(1e5);
|
|
1529
|
+
const count = z.number().finite().nonnegative().optional();
|
|
1530
|
+
const usageSchema = z.object({
|
|
1531
|
+
input: count,
|
|
1532
|
+
output: count,
|
|
1533
|
+
cacheRead: count,
|
|
1534
|
+
cacheWrite: count,
|
|
1535
|
+
totalTokens: count,
|
|
1536
|
+
cost: z.object({
|
|
1537
|
+
input: count,
|
|
1538
|
+
output: count,
|
|
1539
|
+
cacheRead: count,
|
|
1540
|
+
cacheWrite: count,
|
|
1541
|
+
total: count
|
|
1542
|
+
}).nullable().optional()
|
|
1543
|
+
});
|
|
1544
|
+
const runtimeMessageSchema = z.object({
|
|
1545
|
+
ordinal,
|
|
1546
|
+
content,
|
|
1547
|
+
provider: z.string().nullable().optional(),
|
|
1548
|
+
model: z.string().nullable().optional(),
|
|
1549
|
+
usage: usageSchema.nullable().optional(),
|
|
1550
|
+
stopReason: z.string().max(64).nullable().optional(),
|
|
1551
|
+
errorMessage: z.string().max(16384).nullable().optional()
|
|
1552
|
+
});
|
|
1553
|
+
const runtimeEventSchema = z.discriminatedUnion("type", [
|
|
1554
|
+
z.object({
|
|
1555
|
+
type: z.literal("message.start"),
|
|
1556
|
+
ordinal
|
|
1557
|
+
}),
|
|
1558
|
+
z.object({
|
|
1559
|
+
type: z.literal("text.delta"),
|
|
1560
|
+
ordinal,
|
|
1561
|
+
index: ordinal,
|
|
1562
|
+
kind: z.enum(["text", "thinking"]),
|
|
1563
|
+
delta: z.string()
|
|
1564
|
+
}),
|
|
1565
|
+
z.object({
|
|
1566
|
+
type: z.literal("content.replace"),
|
|
1567
|
+
ordinal,
|
|
1568
|
+
content
|
|
1569
|
+
}),
|
|
1570
|
+
z.object({
|
|
1571
|
+
type: z.literal("message.commit"),
|
|
1572
|
+
message: runtimeMessageSchema
|
|
1573
|
+
}),
|
|
1574
|
+
z.object({
|
|
1575
|
+
type: z.literal("turn.end"),
|
|
1576
|
+
message: runtimeMessageSchema,
|
|
1577
|
+
archive: harnessArchiveSchema.nullable().optional(),
|
|
1578
|
+
resume: z.enum([
|
|
1579
|
+
"native",
|
|
1580
|
+
"restored",
|
|
1581
|
+
"handoff",
|
|
1582
|
+
"new"
|
|
1583
|
+
])
|
|
1584
|
+
}),
|
|
1585
|
+
z.object({
|
|
1586
|
+
type: z.literal("context.required"),
|
|
1587
|
+
pendingTurnIds: z.array(id).max(2).optional(),
|
|
1588
|
+
historyOnly: z.boolean().optional()
|
|
1589
|
+
}),
|
|
1590
|
+
z.object({ type: z.literal("turn.acknowledged") }),
|
|
1591
|
+
z.object({
|
|
1592
|
+
type: z.literal("turn.error"),
|
|
1593
|
+
message: z.string().max(16384),
|
|
1594
|
+
uncertain: z.boolean().optional()
|
|
1595
|
+
})
|
|
1596
|
+
]);
|
|
1597
|
+
z.discriminatedUnion("type", [
|
|
1598
|
+
z.object({
|
|
1599
|
+
type: z.literal("runtime.hello"),
|
|
1600
|
+
version: z.literal(1),
|
|
1601
|
+
spaceId: id,
|
|
1602
|
+
token: z.string().min(1).max(16384),
|
|
1603
|
+
capabilities: runtimeCapabilitiesSchema
|
|
1604
|
+
}).strict(),
|
|
1605
|
+
z.object({
|
|
1606
|
+
type: z.literal("runtime.recovery"),
|
|
1607
|
+
executions: z.array(runtimePendingExecutionSchema).min(1).max(64)
|
|
1608
|
+
}).strict(),
|
|
1609
|
+
z.object({ type: z.literal("runtime.heartbeat") }),
|
|
1610
|
+
z.object({
|
|
1611
|
+
type: z.literal("runtime.auth"),
|
|
1612
|
+
token: z.string().min(1).max(16384)
|
|
1613
|
+
}),
|
|
1614
|
+
z.object({
|
|
1615
|
+
type: z.literal("runtime.event"),
|
|
1616
|
+
requestId: id,
|
|
1617
|
+
event: runtimeEventSchema
|
|
1618
|
+
})
|
|
1619
|
+
]);
|
|
1620
|
+
const runtimeContextSchema = z.object({
|
|
1621
|
+
complete: z.boolean().optional(),
|
|
1622
|
+
revision: z.string(),
|
|
1623
|
+
throughTurnId: id.nullable(),
|
|
1624
|
+
messages: z.array(z.object({
|
|
1625
|
+
id: z.string(),
|
|
1626
|
+
turnId: id,
|
|
1627
|
+
role: z.enum([
|
|
1628
|
+
"user",
|
|
1629
|
+
"assistant",
|
|
1630
|
+
"system"
|
|
1631
|
+
]),
|
|
1632
|
+
content,
|
|
1633
|
+
provider: z.string().nullable().optional(),
|
|
1634
|
+
model: z.string().nullable().optional(),
|
|
1635
|
+
usage: usageSchema.nullable().optional(),
|
|
1636
|
+
stopReason: z.string().nullable().optional(),
|
|
1637
|
+
errorMessage: z.string().nullable().optional(),
|
|
1638
|
+
meta: z.record(z.string(), z.unknown()).nullable().optional()
|
|
1639
|
+
})),
|
|
1640
|
+
archive: harnessArchiveSchema.nullable().optional(),
|
|
1641
|
+
resolvedTurnIds: z.array(id).max(2).optional(),
|
|
1642
|
+
settledTurnIds: z.array(id).max(2).optional()
|
|
1643
|
+
});
|
|
1644
|
+
const runtimeCommandSchema = z.discriminatedUnion("type", [
|
|
1645
|
+
z.object({
|
|
1646
|
+
type: z.literal("turn.recover"),
|
|
1647
|
+
requestId: id,
|
|
1648
|
+
execution: z.object({
|
|
1649
|
+
spaceId: id,
|
|
1650
|
+
sessionId: id,
|
|
1651
|
+
turnId: id,
|
|
1652
|
+
harness: z.enum(["pi", "codex"])
|
|
1653
|
+
})
|
|
1654
|
+
}),
|
|
1655
|
+
z.object({
|
|
1656
|
+
type: z.literal("turn.start"),
|
|
1657
|
+
requestId: id,
|
|
1658
|
+
resumeOnly: z.boolean().optional(),
|
|
1659
|
+
input: z.object({
|
|
1660
|
+
spaceId: id,
|
|
1661
|
+
sessionId: id,
|
|
1662
|
+
turnId: id,
|
|
1663
|
+
userMessageId: id,
|
|
1664
|
+
harness: z.enum(["pi", "codex"]),
|
|
1665
|
+
messages: z.array(z.object({
|
|
1666
|
+
turnId: id,
|
|
1667
|
+
userMessageId: id,
|
|
1668
|
+
userId: z.string().nullable(),
|
|
1669
|
+
content
|
|
1670
|
+
})).min(1).max(64),
|
|
1671
|
+
context: runtimeContextSchema,
|
|
1672
|
+
provider: z.string().nullable().optional(),
|
|
1673
|
+
model: z.string().nullable().optional(),
|
|
1674
|
+
thinkingLevel: z.string().nullable().optional(),
|
|
1675
|
+
accessMode: z.enum(["read_only", "full_access"])
|
|
1676
|
+
}).refine((input) => {
|
|
1677
|
+
const owner = input.messages.at(-1);
|
|
1678
|
+
return owner?.turnId === input.turnId && owner.userMessageId === input.userMessageId && new Set(input.messages.map((message) => message.turnId)).size === input.messages.length && new Set(input.messages.map((message) => message.userMessageId)).size === input.messages.length;
|
|
1679
|
+
}, "Runtime batch owner or message identity mismatch")
|
|
1680
|
+
}),
|
|
1681
|
+
z.object({
|
|
1682
|
+
type: z.literal("session.context"),
|
|
1683
|
+
requestId: id,
|
|
1684
|
+
context: runtimeContextSchema
|
|
1685
|
+
}),
|
|
1686
|
+
z.object({
|
|
1687
|
+
type: z.literal("turn.abort"),
|
|
1688
|
+
requestId: id
|
|
1689
|
+
}),
|
|
1690
|
+
z.object({
|
|
1691
|
+
type: z.literal("turn.ack"),
|
|
1692
|
+
requestId: id,
|
|
1693
|
+
revision: z.string(),
|
|
1694
|
+
turnId: id
|
|
1695
|
+
})
|
|
1696
|
+
]);
|
|
1697
|
+
//#endregion
|
|
1208
1698
|
//#region ../protocol/dist/public-identifiers.js
|
|
1209
1699
|
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
1210
1700
|
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
@@ -2187,6 +2677,18 @@ function attachAppEmbed(frame, options) {
|
|
|
2187
2677
|
}
|
|
2188
2678
|
//#endregion
|
|
2189
2679
|
//#region src/app-runtime.ts
|
|
2680
|
+
var AppRuntimeError = class extends Error {
|
|
2681
|
+
code;
|
|
2682
|
+
requestId;
|
|
2683
|
+
retryable;
|
|
2684
|
+
constructor(code, message, requestId) {
|
|
2685
|
+
super(message);
|
|
2686
|
+
this.code = code;
|
|
2687
|
+
this.requestId = requestId;
|
|
2688
|
+
this.name = "AppRuntimeError";
|
|
2689
|
+
this.retryable = code === "host_timeout" || code === "login_timeout" || code === "request_failed";
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2190
2692
|
const isBrowser$1 = () => typeof window !== "undefined" && typeof window.parent !== "undefined";
|
|
2191
2693
|
const hasParent = () => isBrowser$1() && window.parent !== window;
|
|
2192
2694
|
const getParentOrigin = () => {
|
|
@@ -2258,7 +2760,7 @@ var ParentBridgeTransport = class {
|
|
|
2258
2760
|
};
|
|
2259
2761
|
}
|
|
2260
2762
|
subscribeContextChanged(listener) {
|
|
2261
|
-
if (!hasParent()) return () => {};
|
|
2763
|
+
if (!hasParent() || typeof window.addEventListener !== "function") return () => {};
|
|
2262
2764
|
this.contextListeners.add(listener);
|
|
2263
2765
|
if (!this.contextListener) {
|
|
2264
2766
|
this.contextListener = (event) => {
|
|
@@ -2321,7 +2823,7 @@ var ParentBridgeTransport = class {
|
|
|
2321
2823
|
cleanup();
|
|
2322
2824
|
this.trustedParentOrigin = event.origin;
|
|
2323
2825
|
if (data.type === "cohub.app.error") {
|
|
2324
|
-
reject(new
|
|
2826
|
+
reject(new AppRuntimeError(data.code ?? "request_failed", data.message, requestId));
|
|
2325
2827
|
return;
|
|
2326
2828
|
}
|
|
2327
2829
|
resolve(data);
|
|
@@ -2362,6 +2864,7 @@ var PopupBrokerTransport = class {
|
|
|
2362
2864
|
if (message.type === "cohub.app.context") return {
|
|
2363
2865
|
type: "cohub.app.context.result",
|
|
2364
2866
|
context: {
|
|
2867
|
+
mode: "broker",
|
|
2365
2868
|
app: {
|
|
2366
2869
|
id: await this.resolveAppId() ?? "",
|
|
2367
2870
|
slug: "",
|
|
@@ -2397,6 +2900,7 @@ var PopupBrokerTransport = class {
|
|
|
2397
2900
|
return;
|
|
2398
2901
|
}
|
|
2399
2902
|
let ready = false;
|
|
2903
|
+
let loginPending = false;
|
|
2400
2904
|
let settled = false;
|
|
2401
2905
|
let timer = null;
|
|
2402
2906
|
let closeChecker = null;
|
|
@@ -2422,8 +2926,20 @@ var PopupBrokerTransport = class {
|
|
|
2422
2926
|
if (event.origin !== this.brokerOrigin) return;
|
|
2423
2927
|
const data = event.data;
|
|
2424
2928
|
if (!data) return;
|
|
2929
|
+
if (data.type === "cohub.app.broker.progress" && !loginPending && !ready) {
|
|
2930
|
+
loginPending = true;
|
|
2931
|
+
if (timer) clearTimeout(timer);
|
|
2932
|
+
timer = setTimeout(() => finish(() => reject(new AppRuntimeError("login_timeout", "Login did not complete in time."))), 6e5);
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2425
2935
|
if (data.type === "cohub.app.broker.ready" && !ready) {
|
|
2936
|
+
if (message.type === "cohub.app.authorize.v2" && data.authorizationVersion !== 2) {
|
|
2937
|
+
finish(() => reject(new AppRuntimeError("unsupported", "This host does not support structured authorization.")));
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2426
2940
|
ready = true;
|
|
2941
|
+
if (timer) clearTimeout(timer);
|
|
2942
|
+
timer = setTimeout(() => finish(() => reject(new AppRuntimeError("host_timeout", "Authorization timed out."))), timeoutMs);
|
|
2427
2943
|
try {
|
|
2428
2944
|
popup.postMessage({
|
|
2429
2945
|
...message,
|
|
@@ -2437,7 +2953,7 @@ var PopupBrokerTransport = class {
|
|
|
2437
2953
|
if (data.requestId !== requestId) return;
|
|
2438
2954
|
finish(() => {
|
|
2439
2955
|
if (data.type === "cohub.app.error") {
|
|
2440
|
-
reject(new
|
|
2956
|
+
reject(new AppRuntimeError(data.code ?? "request_failed", data.message, requestId));
|
|
2441
2957
|
return;
|
|
2442
2958
|
}
|
|
2443
2959
|
resolve(data);
|
|
@@ -2446,7 +2962,8 @@ var PopupBrokerTransport = class {
|
|
|
2446
2962
|
window.addEventListener("message", onMessage);
|
|
2447
2963
|
closeChecker = setInterval(() => {
|
|
2448
2964
|
if (popup.closed) finish(() => {
|
|
2449
|
-
if (
|
|
2965
|
+
if (message.type === "cohub.app.authorize.v2") resolve({ result: { status: "cancelled" } });
|
|
2966
|
+
else if (!ready) reject(/* @__PURE__ */ new Error("Authorization window was closed."));
|
|
2450
2967
|
else resolve(null);
|
|
2451
2968
|
});
|
|
2452
2969
|
}, 500);
|
|
@@ -2564,6 +3081,7 @@ var AppRuntimeApi = class {
|
|
|
2564
3081
|
}
|
|
2565
3082
|
constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
|
|
2566
3083
|
this.transport = transport;
|
|
3084
|
+
this.contextUnsubscribe = transport.subscribeContextChanged?.((context) => this.observeContext(context)) ?? null;
|
|
2567
3085
|
this.appIdResolver = appIdResolver;
|
|
2568
3086
|
this.diagnosticUnsubscribe = transport.subscribeDiagnostics?.((diagnostic) => {
|
|
2569
3087
|
console.warn(`[cohub] ${diagnostic.message} (${diagnostic.code})`, diagnostic.detail ?? {});
|
|
@@ -2605,6 +3123,10 @@ var AppRuntimeApi = class {
|
|
|
2605
3123
|
return this.storageKeysReady;
|
|
2606
3124
|
}
|
|
2607
3125
|
readStoredToken() {
|
|
3126
|
+
if (this.transport instanceof PopupBrokerTransport) {
|
|
3127
|
+
this.writeStoredToken(null);
|
|
3128
|
+
return null;
|
|
3129
|
+
}
|
|
2608
3130
|
if (!this.tokenStorageKey || typeof localStorage === "undefined") return null;
|
|
2609
3131
|
try {
|
|
2610
3132
|
return localStorage.getItem(this.tokenStorageKey);
|
|
@@ -2613,6 +3135,7 @@ var AppRuntimeApi = class {
|
|
|
2613
3135
|
}
|
|
2614
3136
|
}
|
|
2615
3137
|
writeStoredToken(token) {
|
|
3138
|
+
if (this.transport instanceof PopupBrokerTransport) token = null;
|
|
2616
3139
|
if (!this.tokenStorageKey || typeof localStorage === "undefined") return;
|
|
2617
3140
|
try {
|
|
2618
3141
|
if (token) localStorage.setItem(this.tokenStorageKey, token);
|
|
@@ -2638,11 +3161,30 @@ var AppRuntimeApi = class {
|
|
|
2638
3161
|
else localStorage.removeItem(this.grantsStorageKey);
|
|
2639
3162
|
} catch {}
|
|
2640
3163
|
}
|
|
3164
|
+
serverGrants = false;
|
|
3165
|
+
viewerIdentity;
|
|
3166
|
+
identityVersion = 0;
|
|
3167
|
+
contextUnsubscribe = null;
|
|
3168
|
+
observeContext(context) {
|
|
3169
|
+
if (context.capabilities?.serverGrants) this.serverGrants = true;
|
|
3170
|
+
if (context.viewer === void 0) return;
|
|
3171
|
+
const viewer = context.viewer?.userUuid ?? null;
|
|
3172
|
+
if (this.viewerIdentity !== void 0 && viewer !== this.viewerIdentity) {
|
|
3173
|
+
this.identityVersion++;
|
|
3174
|
+
this.token = null;
|
|
3175
|
+
this.authorizedGrants = null;
|
|
3176
|
+
this.writeStoredToken(null);
|
|
3177
|
+
this.writeStoredGrants(null);
|
|
3178
|
+
}
|
|
3179
|
+
this.viewerIdentity = viewer;
|
|
3180
|
+
}
|
|
2641
3181
|
async context() {
|
|
2642
|
-
|
|
3182
|
+
const response = await this.transport.request({ type: "cohub.app.context" }, {
|
|
2643
3183
|
timeoutMs: 8e3,
|
|
2644
3184
|
retryIntervalMs: 250
|
|
2645
|
-
})
|
|
3185
|
+
});
|
|
3186
|
+
if (response?.context) this.observeContext(response.context);
|
|
3187
|
+
return response?.context ?? null;
|
|
2646
3188
|
}
|
|
2647
3189
|
onContextChanged(listener) {
|
|
2648
3190
|
return this.transport.subscribeContextChanged?.(listener) ?? (() => {});
|
|
@@ -2659,6 +3201,8 @@ var AppRuntimeApi = class {
|
|
|
2659
3201
|
const unsubscribe = this.diagnosticUnsubscribe;
|
|
2660
3202
|
this.diagnosticUnsubscribe = null;
|
|
2661
3203
|
unsubscribe?.();
|
|
3204
|
+
this.contextUnsubscribe?.();
|
|
3205
|
+
this.contextUnsubscribe = null;
|
|
2662
3206
|
this.pointerForwarding = false;
|
|
2663
3207
|
this.pointerPending = null;
|
|
2664
3208
|
if (this.pointerFrame !== null) {
|
|
@@ -2720,12 +3264,13 @@ var AppRuntimeApi = class {
|
|
|
2720
3264
|
}
|
|
2721
3265
|
async getAccessToken(options) {
|
|
2722
3266
|
await this.ensureStorageKeys();
|
|
3267
|
+
const identityVersion = this.identityVersion;
|
|
2723
3268
|
if (this.token && !options?.forceRefresh) return this.token;
|
|
2724
3269
|
if (options?.forceRefresh) {
|
|
2725
3270
|
this.token = null;
|
|
2726
3271
|
this.writeStoredToken(null);
|
|
2727
3272
|
}
|
|
2728
|
-
if (options?.forceRefresh && this.authorizedGrants && this.authorizedGrants.length > 0) {
|
|
3273
|
+
if (!this.serverGrants && options?.forceRefresh && this.authorizedGrants && this.authorizedGrants.length > 0) {
|
|
2729
3274
|
let refreshed = false;
|
|
2730
3275
|
let next = [];
|
|
2731
3276
|
for (const grant of this.authorizedGrants) try {
|
|
@@ -2734,12 +3279,14 @@ var AppRuntimeApi = class {
|
|
|
2734
3279
|
scopes: grant.scopes,
|
|
2735
3280
|
spaceId: grant.spaceId
|
|
2736
3281
|
}, { timeoutMs: 12e4 });
|
|
3282
|
+
if (identityVersion !== this.identityVersion) return null;
|
|
2737
3283
|
const token = response?.token ?? null;
|
|
2738
3284
|
if (!token) continue;
|
|
2739
3285
|
this.token = token;
|
|
2740
3286
|
next = recordConsent(next, responseSpaceId(response?.space?.id) ?? grant.spaceId, grant.scopes);
|
|
2741
3287
|
refreshed = true;
|
|
2742
3288
|
} catch {
|
|
3289
|
+
if (identityVersion !== this.identityVersion) return null;
|
|
2743
3290
|
next = recordConsent(next, grant.spaceId, grant.scopes);
|
|
2744
3291
|
}
|
|
2745
3292
|
this.authorizedGrants = next;
|
|
@@ -2753,11 +3300,37 @@ var AppRuntimeApi = class {
|
|
|
2753
3300
|
type: "cohub.app.token",
|
|
2754
3301
|
forceRefresh: Boolean(options?.forceRefresh)
|
|
2755
3302
|
}, { timeoutMs: 2e4 });
|
|
3303
|
+
if (identityVersion !== this.identityVersion) return null;
|
|
2756
3304
|
this.token = response?.token ?? null;
|
|
2757
3305
|
this.writeStoredToken(this.token);
|
|
2758
3306
|
return this.token;
|
|
2759
3307
|
}
|
|
3308
|
+
/** Structured authorization. Legacy hosts cannot execute this message. */
|
|
3309
|
+
async authorize(input) {
|
|
3310
|
+
const parsed = appAuthorizationRequestSchema.safeParse(input);
|
|
3311
|
+
if (!parsed.success) throw new AppRuntimeError("invalid_request", "Invalid authorization request.");
|
|
3312
|
+
await this.ensureStorageKeys();
|
|
3313
|
+
const context = await this.context();
|
|
3314
|
+
if (context?.mode !== "broker" && context?.capabilities?.authorization !== 2) throw new AppRuntimeError("unsupported", "This host does not support structured authorization.");
|
|
3315
|
+
const identityVersion = this.identityVersion;
|
|
3316
|
+
const response = await this.transport.request({
|
|
3317
|
+
type: "cohub.app.authorize.v2",
|
|
3318
|
+
...parsed.data
|
|
3319
|
+
}, { timeoutMs: 12e4 });
|
|
3320
|
+
if (identityVersion !== this.identityVersion) throw new AppRuntimeError("session_changed", "The signed-in account changed. Please try again.");
|
|
3321
|
+
if (!response) throw new AppRuntimeError("host_timeout", "Authorization host did not respond. It may not support structured authorization.");
|
|
3322
|
+
const result = appAuthorizationResultSchema.safeParse(response.result);
|
|
3323
|
+
if (!result.success) throw new AppRuntimeError("invalid_response", "Invalid authorization result.");
|
|
3324
|
+
this.serverGrants = true;
|
|
3325
|
+
if (result.data.status === "granted") {
|
|
3326
|
+
if (typeof response.token !== "string" || !response.token) throw new AppRuntimeError("invalid_response", "Authorization token is missing.");
|
|
3327
|
+
this.token = response.token;
|
|
3328
|
+
this.writeStoredToken(this.token);
|
|
3329
|
+
}
|
|
3330
|
+
return result.data;
|
|
3331
|
+
}
|
|
2760
3332
|
/**
|
|
3333
|
+
* @deprecated Use authorize() to receive the actual target and grant.
|
|
2761
3334
|
* Requests viewer consent. With an accessible `spaceId` the grant targets
|
|
2762
3335
|
* that Space; otherwise the host resolves a viewer-controlled Space (the
|
|
2763
3336
|
* invocation or embedding Space, then the viewer's last picked Space, then
|
|
@@ -2788,6 +3361,7 @@ var AppRuntimeApi = class {
|
|
|
2788
3361
|
return Boolean(token);
|
|
2789
3362
|
}
|
|
2790
3363
|
/**
|
|
3364
|
+
* @deprecated Use authorize() with a pick-space target.
|
|
2791
3365
|
* Asks the viewer to pick a Space and grant the scopes on it — one consent
|
|
2792
3366
|
* dialog covers both. Resolves with the picked space so the app knows where
|
|
2793
3367
|
* it may act; `space` is null when the viewer denied.
|
|
@@ -2887,7 +3461,8 @@ function createSlugAppIdResolver(deps) {
|
|
|
2887
3461
|
if (typeof doFetch !== "function") return null;
|
|
2888
3462
|
const response = await doFetch(`${deps.apiBaseUrl}/api/apps/by-slug/${encodeURIComponent(deps.ownerUsername)}/${encodeURIComponent(deps.spaceSlug)}/${encodeURIComponent(deps.appSlug)}`);
|
|
2889
3463
|
if (!response.ok) throw new Error(`getBySlug failed: ${response.status}`);
|
|
2890
|
-
|
|
3464
|
+
const data = await response.json();
|
|
3465
|
+
return data?.app?.id ?? data?.work?.id ?? null;
|
|
2891
3466
|
})().catch(() => {
|
|
2892
3467
|
cached = null;
|
|
2893
3468
|
return null;
|
|
@@ -3027,14 +3602,16 @@ var CohubClient = class {
|
|
|
3027
3602
|
this.appRuntime.dispose();
|
|
3028
3603
|
}
|
|
3029
3604
|
auth = {
|
|
3605
|
+
authorize: (input) => this.appRuntime.authorize(input),
|
|
3030
3606
|
/**
|
|
3607
|
+
* @deprecated Use authorize() for the actual target and structured result.
|
|
3031
3608
|
* Ensure the app holds these scopes. Without an accessible `spaceId` the
|
|
3032
3609
|
* host targets a viewer-controlled Space (never the app author's home
|
|
3033
3610
|
* Space) — use `requestSpace` when the app needs to know which Space.
|
|
3034
3611
|
* Silent when a grant already covers them; `alwaysAsk` forces the dialog.
|
|
3035
3612
|
*/
|
|
3036
3613
|
request: (input) => this.appRuntime.requestAuthorization(input),
|
|
3037
|
-
/**
|
|
3614
|
+
/** @deprecated Use authorize({ target: { kind: "pick-space" }, scopes }). */
|
|
3038
3615
|
requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input),
|
|
3039
3616
|
/** One consent: create a viewer-owned Space and grant the scopes on it. `space` is `CreateSpaceInput`. */
|
|
3040
3617
|
requestCreateSpace: (input) => this.appRuntime.requestCreateSpaceAuthorization(input)
|
|
@@ -3393,6 +3970,7 @@ function clearGrantedAppScopes(userUuid, appId, spaceId) {
|
|
|
3393
3970
|
}
|
|
3394
3971
|
//#endregion
|
|
3395
3972
|
//#region src/app-bridge-core.ts
|
|
3973
|
+
var AppLoginRedirect = class extends Error {};
|
|
3396
3974
|
function readTokenResponse(value) {
|
|
3397
3975
|
if (!value || typeof value !== "object") return null;
|
|
3398
3976
|
const token = value.token;
|
|
@@ -3572,18 +4150,95 @@ function createAppBridgeCore(config) {
|
|
|
3572
4150
|
const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
|
|
3573
4151
|
const onStateChange = config.onStateChange;
|
|
3574
4152
|
let appToken = null;
|
|
4153
|
+
let tokenViewer;
|
|
4154
|
+
function synchronizeViewer(viewer) {
|
|
4155
|
+
if (tokenViewer !== void 0 && tokenViewer !== viewer) {
|
|
4156
|
+
appToken = null;
|
|
4157
|
+
sessionViewerGrants.clear();
|
|
4158
|
+
authoritativeGrants.clear();
|
|
4159
|
+
}
|
|
4160
|
+
tokenViewer = viewer;
|
|
4161
|
+
}
|
|
3575
4162
|
/** Consents made through this host, keyed by target space. */
|
|
3576
4163
|
const sessionViewerGrants = /* @__PURE__ */ new Map();
|
|
3577
4164
|
let activeInvocation;
|
|
3578
4165
|
let contextChangeVersion = 0;
|
|
3579
4166
|
const legacyRequestIds = /* @__PURE__ */ new Set();
|
|
4167
|
+
const authorizationRequests = /* @__PURE__ */ new Map();
|
|
4168
|
+
const authoritativeGrants = /* @__PURE__ */ new Map();
|
|
4169
|
+
function canJoin(pending, next, requestId) {
|
|
4170
|
+
const previous = authorizationRequests.get(pending.requestId);
|
|
4171
|
+
const current = authorizationRequests.get(requestId);
|
|
4172
|
+
return Boolean(previous) === Boolean(current) && previous?.fallback === current?.fallback && isSameConsent(pending, next);
|
|
4173
|
+
}
|
|
4174
|
+
let signInFlight = null;
|
|
4175
|
+
const pendingLoginKey = `cohub:app-login:${app.id}`;
|
|
4176
|
+
function rememberAuthorization(data) {
|
|
4177
|
+
if (data.createSpace) return;
|
|
4178
|
+
try {
|
|
4179
|
+
const structured = typeof data.requestId === "string" ? authorizationRequests.get(data.requestId) : void 0;
|
|
4180
|
+
sessionStorage.setItem(pendingLoginKey, JSON.stringify({
|
|
4181
|
+
at: Date.now(),
|
|
4182
|
+
data: {
|
|
4183
|
+
...structured ? {
|
|
4184
|
+
...structured,
|
|
4185
|
+
type: "cohub.app.authorize.v2"
|
|
4186
|
+
} : { type: data.type },
|
|
4187
|
+
requestId: data.requestId,
|
|
4188
|
+
scopes: data.scopes,
|
|
4189
|
+
reason: sanitizeReason(data.reason),
|
|
4190
|
+
spaceId: data.spaceId,
|
|
4191
|
+
selectSpace: data.selectSpace,
|
|
4192
|
+
alwaysAsk: data.alwaysAsk
|
|
4193
|
+
}
|
|
4194
|
+
}));
|
|
4195
|
+
} catch {}
|
|
4196
|
+
}
|
|
4197
|
+
function clearAuthorizationIntent(requestId) {
|
|
4198
|
+
try {
|
|
4199
|
+
const raw = sessionStorage.getItem(pendingLoginKey);
|
|
4200
|
+
if (!raw) return;
|
|
4201
|
+
const saved = JSON.parse(raw);
|
|
4202
|
+
if (isRecord(saved) && isRecord(saved.data) && saved.data.requestId === requestId) sessionStorage.removeItem(pendingLoginKey);
|
|
4203
|
+
} catch {}
|
|
4204
|
+
}
|
|
4205
|
+
async function resumeAuthorization() {
|
|
4206
|
+
if (state.pendingAuth || !await getViewerUuid()) return;
|
|
4207
|
+
try {
|
|
4208
|
+
const raw = sessionStorage.getItem(pendingLoginKey);
|
|
4209
|
+
if (!raw) return;
|
|
4210
|
+
const saved = JSON.parse(raw);
|
|
4211
|
+
if (!isRecord(saved) || typeof saved.at !== "number" || Date.now() - saved.at > 6e5 || !isRecord(saved.data)) {
|
|
4212
|
+
sessionStorage.removeItem(pendingLoginKey);
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
await handleMessage({ data: saved.data });
|
|
4216
|
+
} catch {}
|
|
4217
|
+
}
|
|
4218
|
+
async function startSignIn() {
|
|
4219
|
+
if (state.pendingAuth) rememberAuthorization({
|
|
4220
|
+
...state.pendingAuth,
|
|
4221
|
+
type: "cohub.app.authorize"
|
|
4222
|
+
});
|
|
4223
|
+
signInFlight ??= config.requestSignIn(typeof location !== "undefined" ? location.pathname + location.search + location.hash : "/").catch((error) => {
|
|
4224
|
+
signInFlight = null;
|
|
4225
|
+
throw error;
|
|
4226
|
+
});
|
|
4227
|
+
await signInFlight;
|
|
4228
|
+
}
|
|
3580
4229
|
async function getContext() {
|
|
3581
4230
|
const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
|
|
3582
4231
|
const shell = config.getShell?.() ?? config.shell;
|
|
3583
4232
|
const appScopes = clonePermissionScopes(app.appScopes);
|
|
3584
4233
|
const viewerUuid = await getViewerUuid();
|
|
4234
|
+
synchronizeViewer(viewerUuid);
|
|
3585
4235
|
const viewerGrants = mergeViewerGrants(viewerUuid, Array.from(sessionViewerGrants.values()));
|
|
3586
4236
|
return {
|
|
4237
|
+
capabilities: {
|
|
4238
|
+
authorization: 2,
|
|
4239
|
+
serverGrants: true
|
|
4240
|
+
},
|
|
4241
|
+
mode: authorizationContext.surface === "broker" ? "broker" : "bridge",
|
|
3587
4242
|
app: {
|
|
3588
4243
|
id: app.id,
|
|
3589
4244
|
slug: app.slug,
|
|
@@ -3625,13 +4280,63 @@ function createAppBridgeCore(config) {
|
|
|
3625
4280
|
};
|
|
3626
4281
|
}
|
|
3627
4282
|
function replyForRequest(requestId, payload, complete = false) {
|
|
4283
|
+
const request = authorizationRequests.get(requestId);
|
|
4284
|
+
if (request && payload.type === "cohub.app.error" && [
|
|
4285
|
+
"space_inaccessible",
|
|
4286
|
+
"scope_not_held",
|
|
4287
|
+
"app_not_accessible",
|
|
4288
|
+
"consent_required"
|
|
4289
|
+
].includes(String(payload.code))) payload = {
|
|
4290
|
+
...payload,
|
|
4291
|
+
type: "cohub.app.authorize.result",
|
|
4292
|
+
token: null,
|
|
4293
|
+
result: {
|
|
4294
|
+
status: "denied",
|
|
4295
|
+
code: payload.code
|
|
4296
|
+
}
|
|
4297
|
+
};
|
|
4298
|
+
if (request && payload.type === "cohub.app.authorize.result" && !payload.result) {
|
|
4299
|
+
const space = payload.space;
|
|
4300
|
+
const grant = space ? authoritativeGrants.get(space.id) : void 0;
|
|
4301
|
+
let result = { status: "cancelled" };
|
|
4302
|
+
if (payload.token && space && grant) result = {
|
|
4303
|
+
status: "granted",
|
|
4304
|
+
requestedTarget: request.target,
|
|
4305
|
+
target: request.target.kind === "account" ? { kind: "account" } : {
|
|
4306
|
+
kind: "space",
|
|
4307
|
+
spaceId: space.id,
|
|
4308
|
+
name: space.name
|
|
4309
|
+
},
|
|
4310
|
+
resolution: request.target.kind === "pick-space" ? "selected" : request.target.kind === "space" && request.target.spaceId !== space.id ? "fallback" : "requested",
|
|
4311
|
+
grant
|
|
4312
|
+
};
|
|
4313
|
+
else if (payload.token) payload = {
|
|
4314
|
+
type: "cohub.app.error",
|
|
4315
|
+
code: "invalid_response",
|
|
4316
|
+
message: "Missing authoritative grant."
|
|
4317
|
+
};
|
|
4318
|
+
if (payload.type !== "cohub.app.error") payload = {
|
|
4319
|
+
...payload,
|
|
4320
|
+
result
|
|
4321
|
+
};
|
|
4322
|
+
}
|
|
4323
|
+
if (complete && (payload.type === "cohub.app.authorize.result" || payload.type === "cohub.app.error" && [
|
|
4324
|
+
"invalid_request",
|
|
4325
|
+
"space_inaccessible",
|
|
4326
|
+
"scope_not_held",
|
|
4327
|
+
"app_not_accessible",
|
|
4328
|
+
"consent_required"
|
|
4329
|
+
].includes(String(payload.code)))) clearAuthorizationIntent(requestId);
|
|
3628
4330
|
const namespace = legacyRequestIds.has(requestId) ? "work" : "app";
|
|
3629
4331
|
const type = payload.type;
|
|
3630
4332
|
reply(requestId, {
|
|
3631
4333
|
...payload,
|
|
3632
4334
|
...typeof type === "string" && type.startsWith("cohub.app.") ? { type: type.replace("cohub.app.", `cohub.${namespace}.`) } : {}
|
|
3633
4335
|
});
|
|
3634
|
-
if (complete)
|
|
4336
|
+
if (complete) {
|
|
4337
|
+
legacyRequestIds.delete(requestId);
|
|
4338
|
+
authorizationRequests.delete(requestId);
|
|
4339
|
+
}
|
|
3635
4340
|
}
|
|
3636
4341
|
function toLegacyWorkContext(context) {
|
|
3637
4342
|
const permissions = context.permissions ? {
|
|
@@ -3710,12 +4415,11 @@ function createAppBridgeCore(config) {
|
|
|
3710
4415
|
return authorizationContext.surface === "background" || authorizationContext.surface === "app" || authorizationContext.surface === "overlay";
|
|
3711
4416
|
}
|
|
3712
4417
|
async function ensureBaseToken(forceRefresh = false) {
|
|
4418
|
+
const viewer = await getViewerUuid();
|
|
4419
|
+
synchronizeViewer(viewer);
|
|
3713
4420
|
if (appToken && !forceRefresh) return appToken;
|
|
3714
4421
|
const userToken = await getAccessToken({ forceRefresh });
|
|
3715
|
-
if (!userToken)
|
|
3716
|
-
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
3717
|
-
return null;
|
|
3718
|
-
}
|
|
4422
|
+
if (!userToken) return null;
|
|
3719
4423
|
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/session`, {
|
|
3720
4424
|
method: "POST",
|
|
3721
4425
|
headers: { Authorization: `Bearer ${userToken}` }
|
|
@@ -3723,6 +4427,7 @@ function createAppBridgeCore(config) {
|
|
|
3723
4427
|
if (!response.ok) throw new Error("Failed to create app session.");
|
|
3724
4428
|
const token = readTokenResponse(await response.json());
|
|
3725
4429
|
if (!token) throw new Error("Invalid app session response.");
|
|
4430
|
+
if (viewer !== await getViewerUuid()) return null;
|
|
3726
4431
|
appToken = token;
|
|
3727
4432
|
return appToken;
|
|
3728
4433
|
}
|
|
@@ -3733,11 +4438,14 @@ function createAppBridgeCore(config) {
|
|
|
3733
4438
|
* fresh dialog.
|
|
3734
4439
|
*/
|
|
3735
4440
|
async function authorize(scopes, spaceId, options) {
|
|
4441
|
+
const incremental = Boolean(state.pendingAuth && authorizationRequests.has(state.pendingAuth.requestId));
|
|
3736
4442
|
const userToken = await getAccessToken(options?.forceRefreshToken ? { forceRefresh: true } : void 0);
|
|
3737
4443
|
if (!userToken) {
|
|
3738
|
-
|
|
3739
|
-
|
|
4444
|
+
if (mintedSpace) throw new AppAuthorizationError("Sign in to finish authorization.", 401, "login_required");
|
|
4445
|
+
await startSignIn();
|
|
4446
|
+
throw new AppLoginRedirect();
|
|
3740
4447
|
}
|
|
4448
|
+
const authorizingViewer = await getViewerUuid();
|
|
3741
4449
|
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
|
|
3742
4450
|
method: "POST",
|
|
3743
4451
|
signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
|
|
@@ -3748,13 +4456,18 @@ function createAppBridgeCore(config) {
|
|
|
3748
4456
|
body: JSON.stringify({
|
|
3749
4457
|
scopes,
|
|
3750
4458
|
...spaceId ? { spaceId } : {},
|
|
3751
|
-
...options?.silent ? { silent: true } : {}
|
|
4459
|
+
...options?.silent ? { silent: true } : {},
|
|
4460
|
+
...incremental ? { scopeMode: "extend" } : {}
|
|
3752
4461
|
})
|
|
3753
4462
|
});
|
|
3754
4463
|
const payload = await response.json().catch(() => null);
|
|
3755
4464
|
if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status, typeof payload?.code === "string" ? payload.code : void 0);
|
|
4465
|
+
if (authorizingViewer !== await getViewerUuid()) throw new AppAuthorizationError("The signed-in account changed. Please try again.", 409, "session_changed");
|
|
4466
|
+
synchronizeViewer(authorizingViewer);
|
|
3756
4467
|
const token = readTokenResponse(payload);
|
|
3757
4468
|
if (!token) throw new Error("Invalid app authorization response.");
|
|
4469
|
+
const parsedGrant = appAuthorizationGrantSchema.safeParse(payload?.grant);
|
|
4470
|
+
if (parsedGrant.success) authoritativeGrants.set(parsedGrant.data.spaceId, parsedGrant.data);
|
|
3758
4471
|
const canonicalSpaceId = typeof payload?.grant?.spaceId === "string" && payload.grant.spaceId ? payload.grant.spaceId : spaceId ?? app.spaceId;
|
|
3759
4472
|
const grantedScopes = sanitizeRequestedScopes(payload?.grant?.scopes);
|
|
3760
4473
|
const canonicalScopes = grantedScopes.length > 0 ? grantedScopes : clonePermissionScopes(scopes);
|
|
@@ -3836,7 +4549,10 @@ function createAppBridgeCore(config) {
|
|
|
3836
4549
|
async function listViewerSpaces() {
|
|
3837
4550
|
const request = async (forceRefresh = false) => {
|
|
3838
4551
|
const userToken = await getAccessToken({ forceRefresh });
|
|
3839
|
-
if (!userToken)
|
|
4552
|
+
if (!userToken) {
|
|
4553
|
+
await startSignIn();
|
|
4554
|
+
throw new AppLoginRedirect();
|
|
4555
|
+
}
|
|
3840
4556
|
return fetch(`${apiOrigin}/api/spaces`, {
|
|
3841
4557
|
headers: { Authorization: `Bearer ${userToken}` },
|
|
3842
4558
|
signal: spaceListSignal()
|
|
@@ -3852,7 +4568,8 @@ function createAppBridgeCore(config) {
|
|
|
3852
4568
|
const option = toSpaceOption(space);
|
|
3853
4569
|
return option ? [option] : [];
|
|
3854
4570
|
});
|
|
3855
|
-
} catch {
|
|
4571
|
+
} catch (error) {
|
|
4572
|
+
if (error instanceof AppLoginRedirect) throw error;
|
|
3856
4573
|
return null;
|
|
3857
4574
|
}
|
|
3858
4575
|
}
|
|
@@ -4006,8 +4723,38 @@ function createAppBridgeCore(config) {
|
|
|
4006
4723
|
return (await response.json()).checkout ?? null;
|
|
4007
4724
|
}
|
|
4008
4725
|
async function handleMessage(event) {
|
|
4009
|
-
|
|
4726
|
+
let data = event.data;
|
|
4010
4727
|
if (!data?.requestId) return;
|
|
4728
|
+
if (data.type === "cohub.app.authorize.v2") {
|
|
4729
|
+
const parsed = appAuthorizationRequestSchema.safeParse(event.data);
|
|
4730
|
+
if (!parsed.success || parsed.data.scopes.some((scope) => !PERMISSIONS.includes(scope))) {
|
|
4731
|
+
replyForRequest(data.requestId, {
|
|
4732
|
+
type: "cohub.app.error",
|
|
4733
|
+
code: "invalid_request",
|
|
4734
|
+
message: "Invalid authorization request."
|
|
4735
|
+
}, true);
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
const input = parsed.data;
|
|
4739
|
+
if (input.target.kind === "account" && input.scopes.some((scope) => !isUserLevelPermission(scope))) {
|
|
4740
|
+
replyForRequest(data.requestId, {
|
|
4741
|
+
type: "cohub.app.error",
|
|
4742
|
+
code: "invalid_request",
|
|
4743
|
+
message: "Space permissions require a Space target."
|
|
4744
|
+
}, true);
|
|
4745
|
+
return;
|
|
4746
|
+
}
|
|
4747
|
+
authorizationRequests.set(data.requestId, input);
|
|
4748
|
+
data = {
|
|
4749
|
+
requestId: data.requestId,
|
|
4750
|
+
...input,
|
|
4751
|
+
type: "cohub.app.authorize",
|
|
4752
|
+
scopes: input.scopes,
|
|
4753
|
+
spaceId: input.target.kind === "space" ? input.target.spaceId : void 0,
|
|
4754
|
+
selectSpace: input.target.kind === "pick-space"
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
if (!data.requestId) return;
|
|
4011
4758
|
const isLegacyWork = data.type?.startsWith("cohub.work.") === true;
|
|
4012
4759
|
if (isLegacyWork) legacyRequestIds.add(data.requestId);
|
|
4013
4760
|
let reservedAuth = null;
|
|
@@ -4018,6 +4765,7 @@ function createAppBridgeCore(config) {
|
|
|
4018
4765
|
type: "cohub.app.context.result",
|
|
4019
4766
|
context: isLegacyWork ? toLegacyWorkContext(context) : context
|
|
4020
4767
|
}, true);
|
|
4768
|
+
await resumeAuthorization();
|
|
4021
4769
|
}
|
|
4022
4770
|
if (data.type === "cohub.app.token" || data.type === "cohub.work.token") {
|
|
4023
4771
|
const token = await ensureBaseToken(Boolean(data.forceRefresh));
|
|
@@ -4125,18 +4873,22 @@ function createAppBridgeCore(config) {
|
|
|
4125
4873
|
return;
|
|
4126
4874
|
}
|
|
4127
4875
|
if (state.pendingAuth) {
|
|
4128
|
-
if (
|
|
4876
|
+
if (canJoin(state.pendingAuth, {
|
|
4129
4877
|
scopes,
|
|
4130
4878
|
spaceId,
|
|
4131
4879
|
selectSpace,
|
|
4132
4880
|
createSpace
|
|
4133
|
-
})) {
|
|
4881
|
+
}, data.requestId)) {
|
|
4134
4882
|
state.pendingAuth.joinedRequestIds = [...state.pendingAuth.joinedRequestIds ?? [], data.requestId];
|
|
4135
4883
|
return;
|
|
4136
4884
|
}
|
|
4137
4885
|
dismissPendingAuth();
|
|
4138
4886
|
}
|
|
4139
4887
|
if (createSpace) {
|
|
4888
|
+
if (!await getViewerUuid()) {
|
|
4889
|
+
await startSignIn();
|
|
4890
|
+
return;
|
|
4891
|
+
}
|
|
4140
4892
|
mintedSpace = null;
|
|
4141
4893
|
const pendingCreate = {
|
|
4142
4894
|
requestId: data.requestId,
|
|
@@ -4154,7 +4906,7 @@ function createAppBridgeCore(config) {
|
|
|
4154
4906
|
notify();
|
|
4155
4907
|
return;
|
|
4156
4908
|
}
|
|
4157
|
-
if (!selectSpace && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
|
|
4909
|
+
if (!selectSpace && !authorizationRequests.has(data.requestId) && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
|
|
4158
4910
|
const result = await authorizeSilent(scopes, spaceId);
|
|
4159
4911
|
replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, result.spaceId === app.spaceId ? app.spaceName ?? null : null), true);
|
|
4160
4912
|
return;
|
|
@@ -4168,12 +4920,12 @@ function createAppBridgeCore(config) {
|
|
|
4168
4920
|
}
|
|
4169
4921
|
}
|
|
4170
4922
|
if (state.pendingAuth) {
|
|
4171
|
-
if (
|
|
4923
|
+
if (canJoin(state.pendingAuth, {
|
|
4172
4924
|
scopes,
|
|
4173
4925
|
spaceId,
|
|
4174
4926
|
selectSpace,
|
|
4175
4927
|
createSpace
|
|
4176
|
-
})) {
|
|
4928
|
+
}, data.requestId)) {
|
|
4177
4929
|
state.pendingAuth.joinedRequestIds = [...state.pendingAuth.joinedRequestIds ?? [], data.requestId];
|
|
4178
4930
|
return;
|
|
4179
4931
|
}
|
|
@@ -4194,6 +4946,11 @@ function createAppBridgeCore(config) {
|
|
|
4194
4946
|
state.authError = null;
|
|
4195
4947
|
const viewerUuid = await getViewerUuid();
|
|
4196
4948
|
if (state.pendingAuth !== reserved) return;
|
|
4949
|
+
if (!viewerUuid) {
|
|
4950
|
+
rememberAuthorization(data);
|
|
4951
|
+
await startSignIn();
|
|
4952
|
+
return;
|
|
4953
|
+
}
|
|
4197
4954
|
const spaceLevel = Boolean(spaceId) || selectSpace || scopes.some((scope) => !isUserLevelPermission(scope));
|
|
4198
4955
|
const lastPicked = readLastPickedSpace();
|
|
4199
4956
|
const contextSpaceId = resolveContextSpaceId();
|
|
@@ -4208,7 +4965,26 @@ function createAppBridgeCore(config) {
|
|
|
4208
4965
|
if (fallback) spaces = [fallback];
|
|
4209
4966
|
}
|
|
4210
4967
|
const candidates = spaces ?? null;
|
|
4968
|
+
if (spaceLevel && spaces === null) {
|
|
4969
|
+
reserved.spaces = null;
|
|
4970
|
+
state.authError = "Couldn't load your Spaces. Please try again.";
|
|
4971
|
+
state.authOpen = true;
|
|
4972
|
+
state.canChangeSpace = false;
|
|
4973
|
+
notify();
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4211
4976
|
const explicitAccessible = Boolean(spaceId) && candidates?.some((space) => space.id === spaceId) === true;
|
|
4977
|
+
const structuredRequest = authorizationRequests.get(data.requestId);
|
|
4978
|
+
if (spaceId && candidates && !explicitAccessible && structuredRequest?.fallback === "none") {
|
|
4979
|
+
replyPendingAuth(reserved, {
|
|
4980
|
+
type: "cohub.app.error",
|
|
4981
|
+
code: "space_inaccessible",
|
|
4982
|
+
message: "The requested Space is unavailable."
|
|
4983
|
+
});
|
|
4984
|
+
resetDialogState();
|
|
4985
|
+
safeNotify();
|
|
4986
|
+
return;
|
|
4987
|
+
}
|
|
4212
4988
|
const defaultSpaceId = explicitAccessible ? spaceId : resolveDefaultSpaceId(candidates);
|
|
4213
4989
|
const silentTargets = (!spaceLevel ? [] : [selectSpace ? lastPicked && candidates?.some((space) => space.id === lastPicked) ? lastPicked : null : explicitAccessible ? spaceId : spaceId === void 0 ? defaultSpaceId : null].filter((target) => Boolean(target) && hasGrantedAppScopes(viewerUuid, app.id, scopes, target ?? void 0))).slice(0, MAX_SILENT_SPACE_ATTEMPTS);
|
|
4214
4990
|
if (!alwaysAsk && await attemptSilentRenewal(reserved, viewerUuid, scopes, silentTargets)) return;
|
|
@@ -4230,11 +5006,19 @@ function createAppBridgeCore(config) {
|
|
|
4230
5006
|
notify();
|
|
4231
5007
|
}
|
|
4232
5008
|
} catch (error) {
|
|
5009
|
+
if (error instanceof AppLoginRedirect) {
|
|
5010
|
+
rememberAuthorization(data);
|
|
5011
|
+
resetDialogState();
|
|
5012
|
+
safeNotify();
|
|
5013
|
+
return;
|
|
5014
|
+
}
|
|
4233
5015
|
const message = error instanceof Error ? error.message : "Request failed.";
|
|
5016
|
+
const code = error instanceof AppAuthorizationError ? error.code : "request_failed";
|
|
4234
5017
|
if (reservedAuth && state.pendingAuth === reservedAuth) {
|
|
4235
5018
|
replyPendingAuth(reservedAuth, {
|
|
4236
5019
|
type: "cohub.app.error",
|
|
4237
|
-
message
|
|
5020
|
+
message,
|
|
5021
|
+
code
|
|
4238
5022
|
});
|
|
4239
5023
|
resetDialogState();
|
|
4240
5024
|
safeNotify();
|
|
@@ -4242,7 +5026,8 @@ function createAppBridgeCore(config) {
|
|
|
4242
5026
|
}
|
|
4243
5027
|
replyForRequest(data.requestId, {
|
|
4244
5028
|
type: "cohub.app.error",
|
|
4245
|
-
message
|
|
5029
|
+
message,
|
|
5030
|
+
code
|
|
4246
5031
|
}, true);
|
|
4247
5032
|
}
|
|
4248
5033
|
}
|
|
@@ -4262,7 +5047,14 @@ function createAppBridgeCore(config) {
|
|
|
4262
5047
|
if (!state.pendingAuth) return;
|
|
4263
5048
|
replyPendingAuth(state.pendingAuth, {
|
|
4264
5049
|
type: "cohub.app.authorize.result",
|
|
4265
|
-
token: null
|
|
5050
|
+
token: null,
|
|
5051
|
+
...mintedSpace ? {
|
|
5052
|
+
space: {
|
|
5053
|
+
id: mintedSpace.id,
|
|
5054
|
+
name: mintedSpace.name
|
|
5055
|
+
},
|
|
5056
|
+
stage: mintedSpace.provisioned ? "authorization" : "bootstrap"
|
|
5057
|
+
} : {}
|
|
4266
5058
|
});
|
|
4267
5059
|
resetDialogState();
|
|
4268
5060
|
mintedSpace = null;
|
|
@@ -4323,6 +5115,11 @@ function createAppBridgeCore(config) {
|
|
|
4323
5115
|
if (!state.pendingAuth || state.authSaving) return;
|
|
4324
5116
|
const pending = state.pendingAuth;
|
|
4325
5117
|
let requestedSpaceId = (state.canChangeSpace ? pickedSpaceId : void 0) ?? state.selectedSpaceId ?? void 0;
|
|
5118
|
+
if (pending.spaces === null) {
|
|
5119
|
+
state.authError = "Couldn't load your Spaces. Please try again.";
|
|
5120
|
+
notify();
|
|
5121
|
+
return;
|
|
5122
|
+
}
|
|
4326
5123
|
if (pending.spaces !== void 0) {
|
|
4327
5124
|
const candidates = pending.spaces;
|
|
4328
5125
|
if (!(requestedSpaceId !== void 0 && candidates?.some((space) => space.id === requestedSpaceId) === true)) {
|
|
@@ -4360,6 +5157,13 @@ function createAppBridgeCore(config) {
|
|
|
4360
5157
|
resetDialogState();
|
|
4361
5158
|
mintedSpace = null;
|
|
4362
5159
|
} catch (error) {
|
|
5160
|
+
if (error instanceof AppLoginRedirect) return;
|
|
5161
|
+
if (mintedSpace && error instanceof AppAuthorizationError && error.code === "login_required") {
|
|
5162
|
+
replyPendingAuth(pending, authorizeResult(null, mintedSpace.id, mintedSpace.name));
|
|
5163
|
+
resetDialogState();
|
|
5164
|
+
mintedSpace = null;
|
|
5165
|
+
return;
|
|
5166
|
+
}
|
|
4363
5167
|
if (error instanceof AppAuthorizationError) emitDiagnostic({
|
|
4364
5168
|
code: error.code ?? "authorize_failed",
|
|
4365
5169
|
message: error.message,
|
|
@@ -4626,4 +5430,4 @@ function createBoardExtensionRegistry() {
|
|
|
4626
5430
|
}
|
|
4627
5431
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
4628
5432
|
//#endregion
|
|
4629
|
-
export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectSpacePickerItems, setGrantedAppScopes, track };
|
|
5433
|
+
export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppRuntimeError, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, RUNTIME_ARCHIVE_SEGMENT_BYTES, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, RUNTIME_RECOVERY_BATCH_SIZE, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, contextToPiMessages, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeReadySchema, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, setGrantedAppScopes, track, validateArchiveBoundary };
|