@neta-art/cohub 8.17.0 → 8.18.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/dist/chunks/environment.d.ts +510 -3
- package/dist/chunks/http.d.ts +47 -2
- package/dist/chunks/http.js +972 -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 +823 -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 +9 -9
- 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,440 @@ 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
|
+
z.enum([
|
|
1487
|
+
"cohub",
|
|
1488
|
+
"pi",
|
|
1489
|
+
"codex"
|
|
1490
|
+
]);
|
|
1491
|
+
const isLocalHarness = (value) => value === "pi" || value === "codex";
|
|
1492
|
+
const resolveHarness = (meta) => {
|
|
1493
|
+
const value = meta && typeof meta === "object" ? meta.harness : null;
|
|
1494
|
+
return isLocalHarness(value) ? value : "cohub";
|
|
1495
|
+
};
|
|
1496
|
+
const runtimeCapabilitiesSchema = z.object({
|
|
1497
|
+
harnesses: z.array(z.enum(["pi", "codex"])).min(1).max(2),
|
|
1498
|
+
models: z.array(z.object({
|
|
1499
|
+
harness: z.enum(["pi", "codex"]),
|
|
1500
|
+
provider: z.string().max(100),
|
|
1501
|
+
id: z.string().min(1).max(255),
|
|
1502
|
+
name: z.string().max(255)
|
|
1503
|
+
})).max(2e3)
|
|
1504
|
+
});
|
|
1505
|
+
z.object({
|
|
1506
|
+
connectionId: z.string().uuid(),
|
|
1507
|
+
endpoint: z.url({ protocol: /^wss?$/ }),
|
|
1508
|
+
ownerUserId: z.string().min(1),
|
|
1509
|
+
capabilities: runtimeCapabilitiesSchema
|
|
1510
|
+
});
|
|
1511
|
+
const runtimeReadySchema = z.object({
|
|
1512
|
+
type: z.literal("runtime.ready"),
|
|
1513
|
+
connectionId: z.string().uuid()
|
|
1514
|
+
});
|
|
1515
|
+
z.object({
|
|
1516
|
+
revision: z.string().min(1),
|
|
1517
|
+
confirmed: z.literal(true)
|
|
1518
|
+
}).strict();
|
|
1519
|
+
const id = z.string().uuid();
|
|
1520
|
+
const ordinal = z.number().int().min(0).max(1e5);
|
|
1521
|
+
const content = z.array(contentBlockSchema).max(1e5);
|
|
1522
|
+
const count = z.number().finite().nonnegative().optional();
|
|
1523
|
+
const usageSchema = z.object({
|
|
1524
|
+
input: count,
|
|
1525
|
+
output: count,
|
|
1526
|
+
cacheRead: count,
|
|
1527
|
+
cacheWrite: count,
|
|
1528
|
+
totalTokens: count,
|
|
1529
|
+
cost: z.object({
|
|
1530
|
+
input: count,
|
|
1531
|
+
output: count,
|
|
1532
|
+
cacheRead: count,
|
|
1533
|
+
cacheWrite: count,
|
|
1534
|
+
total: count
|
|
1535
|
+
}).nullable().optional()
|
|
1536
|
+
});
|
|
1537
|
+
const runtimeMessageSchema = z.object({
|
|
1538
|
+
ordinal,
|
|
1539
|
+
content,
|
|
1540
|
+
provider: z.string().nullable().optional(),
|
|
1541
|
+
model: z.string().nullable().optional(),
|
|
1542
|
+
usage: usageSchema.nullable().optional(),
|
|
1543
|
+
stopReason: z.string().max(64).nullable().optional(),
|
|
1544
|
+
errorMessage: z.string().max(16384).nullable().optional()
|
|
1545
|
+
});
|
|
1546
|
+
const runtimeEventSchema = z.discriminatedUnion("type", [
|
|
1547
|
+
z.object({
|
|
1548
|
+
type: z.literal("message.start"),
|
|
1549
|
+
ordinal
|
|
1550
|
+
}),
|
|
1551
|
+
z.object({
|
|
1552
|
+
type: z.literal("text.delta"),
|
|
1553
|
+
ordinal,
|
|
1554
|
+
index: ordinal,
|
|
1555
|
+
kind: z.enum(["text", "thinking"]),
|
|
1556
|
+
delta: z.string()
|
|
1557
|
+
}),
|
|
1558
|
+
z.object({
|
|
1559
|
+
type: z.literal("content.replace"),
|
|
1560
|
+
ordinal,
|
|
1561
|
+
content
|
|
1562
|
+
}),
|
|
1563
|
+
z.object({
|
|
1564
|
+
type: z.literal("message.commit"),
|
|
1565
|
+
message: runtimeMessageSchema
|
|
1566
|
+
}),
|
|
1567
|
+
z.object({
|
|
1568
|
+
type: z.literal("turn.end"),
|
|
1569
|
+
message: runtimeMessageSchema,
|
|
1570
|
+
archive: harnessArchiveSchema.nullable().optional(),
|
|
1571
|
+
resume: z.enum([
|
|
1572
|
+
"native",
|
|
1573
|
+
"restored",
|
|
1574
|
+
"handoff",
|
|
1575
|
+
"new"
|
|
1576
|
+
])
|
|
1577
|
+
}),
|
|
1578
|
+
z.object({
|
|
1579
|
+
type: z.literal("context.required"),
|
|
1580
|
+
pendingTurnIds: z.array(id).max(2).optional(),
|
|
1581
|
+
historyOnly: z.boolean().optional()
|
|
1582
|
+
}),
|
|
1583
|
+
z.object({ type: z.literal("turn.acknowledged") }),
|
|
1584
|
+
z.object({
|
|
1585
|
+
type: z.literal("turn.error"),
|
|
1586
|
+
message: z.string().max(16384),
|
|
1587
|
+
uncertain: z.boolean().optional()
|
|
1588
|
+
})
|
|
1589
|
+
]);
|
|
1590
|
+
z.discriminatedUnion("type", [
|
|
1591
|
+
z.object({
|
|
1592
|
+
type: z.literal("runtime.hello"),
|
|
1593
|
+
version: z.literal(1),
|
|
1594
|
+
spaceId: id,
|
|
1595
|
+
token: z.string().min(1).max(16384),
|
|
1596
|
+
capabilities: runtimeCapabilitiesSchema
|
|
1597
|
+
}),
|
|
1598
|
+
z.object({ type: z.literal("runtime.heartbeat") }),
|
|
1599
|
+
z.object({
|
|
1600
|
+
type: z.literal("runtime.auth"),
|
|
1601
|
+
token: z.string().min(1).max(16384)
|
|
1602
|
+
}),
|
|
1603
|
+
z.object({
|
|
1604
|
+
type: z.literal("runtime.event"),
|
|
1605
|
+
requestId: id,
|
|
1606
|
+
event: runtimeEventSchema
|
|
1607
|
+
})
|
|
1608
|
+
]);
|
|
1609
|
+
const runtimeContextSchema = z.object({
|
|
1610
|
+
complete: z.boolean().optional(),
|
|
1611
|
+
revision: z.string(),
|
|
1612
|
+
throughTurnId: id.nullable(),
|
|
1613
|
+
messages: z.array(z.object({
|
|
1614
|
+
id: z.string(),
|
|
1615
|
+
turnId: id,
|
|
1616
|
+
role: z.enum([
|
|
1617
|
+
"user",
|
|
1618
|
+
"assistant",
|
|
1619
|
+
"system"
|
|
1620
|
+
]),
|
|
1621
|
+
content,
|
|
1622
|
+
provider: z.string().nullable().optional(),
|
|
1623
|
+
model: z.string().nullable().optional(),
|
|
1624
|
+
usage: usageSchema.nullable().optional(),
|
|
1625
|
+
stopReason: z.string().nullable().optional(),
|
|
1626
|
+
errorMessage: z.string().nullable().optional(),
|
|
1627
|
+
meta: z.record(z.string(), z.unknown()).nullable().optional()
|
|
1628
|
+
})),
|
|
1629
|
+
archive: harnessArchiveSchema.nullable().optional(),
|
|
1630
|
+
resolvedTurnIds: z.array(id).max(2).optional(),
|
|
1631
|
+
settledTurnIds: z.array(id).max(2).optional()
|
|
1632
|
+
});
|
|
1633
|
+
const runtimeCommandSchema = z.discriminatedUnion("type", [
|
|
1634
|
+
z.object({
|
|
1635
|
+
type: z.literal("turn.recover"),
|
|
1636
|
+
requestId: id,
|
|
1637
|
+
execution: z.object({
|
|
1638
|
+
spaceId: id,
|
|
1639
|
+
sessionId: id,
|
|
1640
|
+
turnId: id,
|
|
1641
|
+
harness: z.enum(["pi", "codex"])
|
|
1642
|
+
})
|
|
1643
|
+
}),
|
|
1644
|
+
z.object({
|
|
1645
|
+
type: z.literal("turn.start"),
|
|
1646
|
+
requestId: id,
|
|
1647
|
+
resumeOnly: z.boolean().optional(),
|
|
1648
|
+
input: z.object({
|
|
1649
|
+
spaceId: id,
|
|
1650
|
+
sessionId: id,
|
|
1651
|
+
turnId: id,
|
|
1652
|
+
userMessageId: id,
|
|
1653
|
+
harness: z.enum(["pi", "codex"]),
|
|
1654
|
+
messages: z.array(z.object({
|
|
1655
|
+
turnId: id,
|
|
1656
|
+
userMessageId: id,
|
|
1657
|
+
userId: z.string().nullable(),
|
|
1658
|
+
content
|
|
1659
|
+
})).min(1).max(64),
|
|
1660
|
+
context: runtimeContextSchema,
|
|
1661
|
+
provider: z.string().nullable().optional(),
|
|
1662
|
+
model: z.string().nullable().optional(),
|
|
1663
|
+
thinkingLevel: z.string().nullable().optional(),
|
|
1664
|
+
accessMode: z.enum(["read_only", "full_access"])
|
|
1665
|
+
}).refine((input) => {
|
|
1666
|
+
const owner = input.messages.at(-1);
|
|
1667
|
+
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;
|
|
1668
|
+
}, "Runtime batch owner or message identity mismatch")
|
|
1669
|
+
}),
|
|
1670
|
+
z.object({
|
|
1671
|
+
type: z.literal("session.context"),
|
|
1672
|
+
requestId: id,
|
|
1673
|
+
context: runtimeContextSchema
|
|
1674
|
+
}),
|
|
1675
|
+
z.object({
|
|
1676
|
+
type: z.literal("turn.abort"),
|
|
1677
|
+
requestId: id
|
|
1678
|
+
}),
|
|
1679
|
+
z.object({
|
|
1680
|
+
type: z.literal("turn.ack"),
|
|
1681
|
+
requestId: id,
|
|
1682
|
+
revision: z.string(),
|
|
1683
|
+
turnId: id
|
|
1684
|
+
})
|
|
1685
|
+
]);
|
|
1686
|
+
//#endregion
|
|
1208
1687
|
//#region ../protocol/dist/public-identifiers.js
|
|
1209
1688
|
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
1210
1689
|
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
@@ -2187,6 +2666,18 @@ function attachAppEmbed(frame, options) {
|
|
|
2187
2666
|
}
|
|
2188
2667
|
//#endregion
|
|
2189
2668
|
//#region src/app-runtime.ts
|
|
2669
|
+
var AppRuntimeError = class extends Error {
|
|
2670
|
+
code;
|
|
2671
|
+
requestId;
|
|
2672
|
+
retryable;
|
|
2673
|
+
constructor(code, message, requestId) {
|
|
2674
|
+
super(message);
|
|
2675
|
+
this.code = code;
|
|
2676
|
+
this.requestId = requestId;
|
|
2677
|
+
this.name = "AppRuntimeError";
|
|
2678
|
+
this.retryable = code === "host_timeout" || code === "login_timeout" || code === "request_failed";
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2190
2681
|
const isBrowser$1 = () => typeof window !== "undefined" && typeof window.parent !== "undefined";
|
|
2191
2682
|
const hasParent = () => isBrowser$1() && window.parent !== window;
|
|
2192
2683
|
const getParentOrigin = () => {
|
|
@@ -2258,7 +2749,7 @@ var ParentBridgeTransport = class {
|
|
|
2258
2749
|
};
|
|
2259
2750
|
}
|
|
2260
2751
|
subscribeContextChanged(listener) {
|
|
2261
|
-
if (!hasParent()) return () => {};
|
|
2752
|
+
if (!hasParent() || typeof window.addEventListener !== "function") return () => {};
|
|
2262
2753
|
this.contextListeners.add(listener);
|
|
2263
2754
|
if (!this.contextListener) {
|
|
2264
2755
|
this.contextListener = (event) => {
|
|
@@ -2321,7 +2812,7 @@ var ParentBridgeTransport = class {
|
|
|
2321
2812
|
cleanup();
|
|
2322
2813
|
this.trustedParentOrigin = event.origin;
|
|
2323
2814
|
if (data.type === "cohub.app.error") {
|
|
2324
|
-
reject(new
|
|
2815
|
+
reject(new AppRuntimeError(data.code ?? "request_failed", data.message, requestId));
|
|
2325
2816
|
return;
|
|
2326
2817
|
}
|
|
2327
2818
|
resolve(data);
|
|
@@ -2362,6 +2853,7 @@ var PopupBrokerTransport = class {
|
|
|
2362
2853
|
if (message.type === "cohub.app.context") return {
|
|
2363
2854
|
type: "cohub.app.context.result",
|
|
2364
2855
|
context: {
|
|
2856
|
+
mode: "broker",
|
|
2365
2857
|
app: {
|
|
2366
2858
|
id: await this.resolveAppId() ?? "",
|
|
2367
2859
|
slug: "",
|
|
@@ -2397,6 +2889,7 @@ var PopupBrokerTransport = class {
|
|
|
2397
2889
|
return;
|
|
2398
2890
|
}
|
|
2399
2891
|
let ready = false;
|
|
2892
|
+
let loginPending = false;
|
|
2400
2893
|
let settled = false;
|
|
2401
2894
|
let timer = null;
|
|
2402
2895
|
let closeChecker = null;
|
|
@@ -2422,8 +2915,20 @@ var PopupBrokerTransport = class {
|
|
|
2422
2915
|
if (event.origin !== this.brokerOrigin) return;
|
|
2423
2916
|
const data = event.data;
|
|
2424
2917
|
if (!data) return;
|
|
2918
|
+
if (data.type === "cohub.app.broker.progress" && !loginPending && !ready) {
|
|
2919
|
+
loginPending = true;
|
|
2920
|
+
if (timer) clearTimeout(timer);
|
|
2921
|
+
timer = setTimeout(() => finish(() => reject(new AppRuntimeError("login_timeout", "Login did not complete in time."))), 6e5);
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2425
2924
|
if (data.type === "cohub.app.broker.ready" && !ready) {
|
|
2925
|
+
if (message.type === "cohub.app.authorize.v2" && data.authorizationVersion !== 2) {
|
|
2926
|
+
finish(() => reject(new AppRuntimeError("unsupported", "This host does not support structured authorization.")));
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2426
2929
|
ready = true;
|
|
2930
|
+
if (timer) clearTimeout(timer);
|
|
2931
|
+
timer = setTimeout(() => finish(() => reject(new AppRuntimeError("host_timeout", "Authorization timed out."))), timeoutMs);
|
|
2427
2932
|
try {
|
|
2428
2933
|
popup.postMessage({
|
|
2429
2934
|
...message,
|
|
@@ -2437,7 +2942,7 @@ var PopupBrokerTransport = class {
|
|
|
2437
2942
|
if (data.requestId !== requestId) return;
|
|
2438
2943
|
finish(() => {
|
|
2439
2944
|
if (data.type === "cohub.app.error") {
|
|
2440
|
-
reject(new
|
|
2945
|
+
reject(new AppRuntimeError(data.code ?? "request_failed", data.message, requestId));
|
|
2441
2946
|
return;
|
|
2442
2947
|
}
|
|
2443
2948
|
resolve(data);
|
|
@@ -2446,7 +2951,8 @@ var PopupBrokerTransport = class {
|
|
|
2446
2951
|
window.addEventListener("message", onMessage);
|
|
2447
2952
|
closeChecker = setInterval(() => {
|
|
2448
2953
|
if (popup.closed) finish(() => {
|
|
2449
|
-
if (
|
|
2954
|
+
if (message.type === "cohub.app.authorize.v2") resolve({ result: { status: "cancelled" } });
|
|
2955
|
+
else if (!ready) reject(/* @__PURE__ */ new Error("Authorization window was closed."));
|
|
2450
2956
|
else resolve(null);
|
|
2451
2957
|
});
|
|
2452
2958
|
}, 500);
|
|
@@ -2564,6 +3070,7 @@ var AppRuntimeApi = class {
|
|
|
2564
3070
|
}
|
|
2565
3071
|
constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
|
|
2566
3072
|
this.transport = transport;
|
|
3073
|
+
this.contextUnsubscribe = transport.subscribeContextChanged?.((context) => this.observeContext(context)) ?? null;
|
|
2567
3074
|
this.appIdResolver = appIdResolver;
|
|
2568
3075
|
this.diagnosticUnsubscribe = transport.subscribeDiagnostics?.((diagnostic) => {
|
|
2569
3076
|
console.warn(`[cohub] ${diagnostic.message} (${diagnostic.code})`, diagnostic.detail ?? {});
|
|
@@ -2605,6 +3112,10 @@ var AppRuntimeApi = class {
|
|
|
2605
3112
|
return this.storageKeysReady;
|
|
2606
3113
|
}
|
|
2607
3114
|
readStoredToken() {
|
|
3115
|
+
if (this.transport instanceof PopupBrokerTransport) {
|
|
3116
|
+
this.writeStoredToken(null);
|
|
3117
|
+
return null;
|
|
3118
|
+
}
|
|
2608
3119
|
if (!this.tokenStorageKey || typeof localStorage === "undefined") return null;
|
|
2609
3120
|
try {
|
|
2610
3121
|
return localStorage.getItem(this.tokenStorageKey);
|
|
@@ -2613,6 +3124,7 @@ var AppRuntimeApi = class {
|
|
|
2613
3124
|
}
|
|
2614
3125
|
}
|
|
2615
3126
|
writeStoredToken(token) {
|
|
3127
|
+
if (this.transport instanceof PopupBrokerTransport) token = null;
|
|
2616
3128
|
if (!this.tokenStorageKey || typeof localStorage === "undefined") return;
|
|
2617
3129
|
try {
|
|
2618
3130
|
if (token) localStorage.setItem(this.tokenStorageKey, token);
|
|
@@ -2638,11 +3150,30 @@ var AppRuntimeApi = class {
|
|
|
2638
3150
|
else localStorage.removeItem(this.grantsStorageKey);
|
|
2639
3151
|
} catch {}
|
|
2640
3152
|
}
|
|
3153
|
+
serverGrants = false;
|
|
3154
|
+
viewerIdentity;
|
|
3155
|
+
identityVersion = 0;
|
|
3156
|
+
contextUnsubscribe = null;
|
|
3157
|
+
observeContext(context) {
|
|
3158
|
+
if (context.capabilities?.serverGrants) this.serverGrants = true;
|
|
3159
|
+
if (context.viewer === void 0) return;
|
|
3160
|
+
const viewer = context.viewer?.userUuid ?? null;
|
|
3161
|
+
if (this.viewerIdentity !== void 0 && viewer !== this.viewerIdentity) {
|
|
3162
|
+
this.identityVersion++;
|
|
3163
|
+
this.token = null;
|
|
3164
|
+
this.authorizedGrants = null;
|
|
3165
|
+
this.writeStoredToken(null);
|
|
3166
|
+
this.writeStoredGrants(null);
|
|
3167
|
+
}
|
|
3168
|
+
this.viewerIdentity = viewer;
|
|
3169
|
+
}
|
|
2641
3170
|
async context() {
|
|
2642
|
-
|
|
3171
|
+
const response = await this.transport.request({ type: "cohub.app.context" }, {
|
|
2643
3172
|
timeoutMs: 8e3,
|
|
2644
3173
|
retryIntervalMs: 250
|
|
2645
|
-
})
|
|
3174
|
+
});
|
|
3175
|
+
if (response?.context) this.observeContext(response.context);
|
|
3176
|
+
return response?.context ?? null;
|
|
2646
3177
|
}
|
|
2647
3178
|
onContextChanged(listener) {
|
|
2648
3179
|
return this.transport.subscribeContextChanged?.(listener) ?? (() => {});
|
|
@@ -2659,6 +3190,8 @@ var AppRuntimeApi = class {
|
|
|
2659
3190
|
const unsubscribe = this.diagnosticUnsubscribe;
|
|
2660
3191
|
this.diagnosticUnsubscribe = null;
|
|
2661
3192
|
unsubscribe?.();
|
|
3193
|
+
this.contextUnsubscribe?.();
|
|
3194
|
+
this.contextUnsubscribe = null;
|
|
2662
3195
|
this.pointerForwarding = false;
|
|
2663
3196
|
this.pointerPending = null;
|
|
2664
3197
|
if (this.pointerFrame !== null) {
|
|
@@ -2720,12 +3253,13 @@ var AppRuntimeApi = class {
|
|
|
2720
3253
|
}
|
|
2721
3254
|
async getAccessToken(options) {
|
|
2722
3255
|
await this.ensureStorageKeys();
|
|
3256
|
+
const identityVersion = this.identityVersion;
|
|
2723
3257
|
if (this.token && !options?.forceRefresh) return this.token;
|
|
2724
3258
|
if (options?.forceRefresh) {
|
|
2725
3259
|
this.token = null;
|
|
2726
3260
|
this.writeStoredToken(null);
|
|
2727
3261
|
}
|
|
2728
|
-
if (options?.forceRefresh && this.authorizedGrants && this.authorizedGrants.length > 0) {
|
|
3262
|
+
if (!this.serverGrants && options?.forceRefresh && this.authorizedGrants && this.authorizedGrants.length > 0) {
|
|
2729
3263
|
let refreshed = false;
|
|
2730
3264
|
let next = [];
|
|
2731
3265
|
for (const grant of this.authorizedGrants) try {
|
|
@@ -2734,12 +3268,14 @@ var AppRuntimeApi = class {
|
|
|
2734
3268
|
scopes: grant.scopes,
|
|
2735
3269
|
spaceId: grant.spaceId
|
|
2736
3270
|
}, { timeoutMs: 12e4 });
|
|
3271
|
+
if (identityVersion !== this.identityVersion) return null;
|
|
2737
3272
|
const token = response?.token ?? null;
|
|
2738
3273
|
if (!token) continue;
|
|
2739
3274
|
this.token = token;
|
|
2740
3275
|
next = recordConsent(next, responseSpaceId(response?.space?.id) ?? grant.spaceId, grant.scopes);
|
|
2741
3276
|
refreshed = true;
|
|
2742
3277
|
} catch {
|
|
3278
|
+
if (identityVersion !== this.identityVersion) return null;
|
|
2743
3279
|
next = recordConsent(next, grant.spaceId, grant.scopes);
|
|
2744
3280
|
}
|
|
2745
3281
|
this.authorizedGrants = next;
|
|
@@ -2753,11 +3289,37 @@ var AppRuntimeApi = class {
|
|
|
2753
3289
|
type: "cohub.app.token",
|
|
2754
3290
|
forceRefresh: Boolean(options?.forceRefresh)
|
|
2755
3291
|
}, { timeoutMs: 2e4 });
|
|
3292
|
+
if (identityVersion !== this.identityVersion) return null;
|
|
2756
3293
|
this.token = response?.token ?? null;
|
|
2757
3294
|
this.writeStoredToken(this.token);
|
|
2758
3295
|
return this.token;
|
|
2759
3296
|
}
|
|
3297
|
+
/** Structured authorization. Legacy hosts cannot execute this message. */
|
|
3298
|
+
async authorize(input) {
|
|
3299
|
+
const parsed = appAuthorizationRequestSchema.safeParse(input);
|
|
3300
|
+
if (!parsed.success) throw new AppRuntimeError("invalid_request", "Invalid authorization request.");
|
|
3301
|
+
await this.ensureStorageKeys();
|
|
3302
|
+
const context = await this.context();
|
|
3303
|
+
if (context?.mode !== "broker" && context?.capabilities?.authorization !== 2) throw new AppRuntimeError("unsupported", "This host does not support structured authorization.");
|
|
3304
|
+
const identityVersion = this.identityVersion;
|
|
3305
|
+
const response = await this.transport.request({
|
|
3306
|
+
type: "cohub.app.authorize.v2",
|
|
3307
|
+
...parsed.data
|
|
3308
|
+
}, { timeoutMs: 12e4 });
|
|
3309
|
+
if (identityVersion !== this.identityVersion) throw new AppRuntimeError("session_changed", "The signed-in account changed. Please try again.");
|
|
3310
|
+
if (!response) throw new AppRuntimeError("host_timeout", "Authorization host did not respond. It may not support structured authorization.");
|
|
3311
|
+
const result = appAuthorizationResultSchema.safeParse(response.result);
|
|
3312
|
+
if (!result.success) throw new AppRuntimeError("invalid_response", "Invalid authorization result.");
|
|
3313
|
+
this.serverGrants = true;
|
|
3314
|
+
if (result.data.status === "granted") {
|
|
3315
|
+
if (typeof response.token !== "string" || !response.token) throw new AppRuntimeError("invalid_response", "Authorization token is missing.");
|
|
3316
|
+
this.token = response.token;
|
|
3317
|
+
this.writeStoredToken(this.token);
|
|
3318
|
+
}
|
|
3319
|
+
return result.data;
|
|
3320
|
+
}
|
|
2760
3321
|
/**
|
|
3322
|
+
* @deprecated Use authorize() to receive the actual target and grant.
|
|
2761
3323
|
* Requests viewer consent. With an accessible `spaceId` the grant targets
|
|
2762
3324
|
* that Space; otherwise the host resolves a viewer-controlled Space (the
|
|
2763
3325
|
* invocation or embedding Space, then the viewer's last picked Space, then
|
|
@@ -2788,6 +3350,7 @@ var AppRuntimeApi = class {
|
|
|
2788
3350
|
return Boolean(token);
|
|
2789
3351
|
}
|
|
2790
3352
|
/**
|
|
3353
|
+
* @deprecated Use authorize() with a pick-space target.
|
|
2791
3354
|
* Asks the viewer to pick a Space and grant the scopes on it — one consent
|
|
2792
3355
|
* dialog covers both. Resolves with the picked space so the app knows where
|
|
2793
3356
|
* it may act; `space` is null when the viewer denied.
|
|
@@ -2887,7 +3450,8 @@ function createSlugAppIdResolver(deps) {
|
|
|
2887
3450
|
if (typeof doFetch !== "function") return null;
|
|
2888
3451
|
const response = await doFetch(`${deps.apiBaseUrl}/api/apps/by-slug/${encodeURIComponent(deps.ownerUsername)}/${encodeURIComponent(deps.spaceSlug)}/${encodeURIComponent(deps.appSlug)}`);
|
|
2889
3452
|
if (!response.ok) throw new Error(`getBySlug failed: ${response.status}`);
|
|
2890
|
-
|
|
3453
|
+
const data = await response.json();
|
|
3454
|
+
return data?.app?.id ?? data?.work?.id ?? null;
|
|
2891
3455
|
})().catch(() => {
|
|
2892
3456
|
cached = null;
|
|
2893
3457
|
return null;
|
|
@@ -3027,14 +3591,16 @@ var CohubClient = class {
|
|
|
3027
3591
|
this.appRuntime.dispose();
|
|
3028
3592
|
}
|
|
3029
3593
|
auth = {
|
|
3594
|
+
authorize: (input) => this.appRuntime.authorize(input),
|
|
3030
3595
|
/**
|
|
3596
|
+
* @deprecated Use authorize() for the actual target and structured result.
|
|
3031
3597
|
* Ensure the app holds these scopes. Without an accessible `spaceId` the
|
|
3032
3598
|
* host targets a viewer-controlled Space (never the app author's home
|
|
3033
3599
|
* Space) — use `requestSpace` when the app needs to know which Space.
|
|
3034
3600
|
* Silent when a grant already covers them; `alwaysAsk` forces the dialog.
|
|
3035
3601
|
*/
|
|
3036
3602
|
request: (input) => this.appRuntime.requestAuthorization(input),
|
|
3037
|
-
/**
|
|
3603
|
+
/** @deprecated Use authorize({ target: { kind: "pick-space" }, scopes }). */
|
|
3038
3604
|
requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input),
|
|
3039
3605
|
/** One consent: create a viewer-owned Space and grant the scopes on it. `space` is `CreateSpaceInput`. */
|
|
3040
3606
|
requestCreateSpace: (input) => this.appRuntime.requestCreateSpaceAuthorization(input)
|
|
@@ -3393,6 +3959,7 @@ function clearGrantedAppScopes(userUuid, appId, spaceId) {
|
|
|
3393
3959
|
}
|
|
3394
3960
|
//#endregion
|
|
3395
3961
|
//#region src/app-bridge-core.ts
|
|
3962
|
+
var AppLoginRedirect = class extends Error {};
|
|
3396
3963
|
function readTokenResponse(value) {
|
|
3397
3964
|
if (!value || typeof value !== "object") return null;
|
|
3398
3965
|
const token = value.token;
|
|
@@ -3572,18 +4139,95 @@ function createAppBridgeCore(config) {
|
|
|
3572
4139
|
const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
|
|
3573
4140
|
const onStateChange = config.onStateChange;
|
|
3574
4141
|
let appToken = null;
|
|
4142
|
+
let tokenViewer;
|
|
4143
|
+
function synchronizeViewer(viewer) {
|
|
4144
|
+
if (tokenViewer !== void 0 && tokenViewer !== viewer) {
|
|
4145
|
+
appToken = null;
|
|
4146
|
+
sessionViewerGrants.clear();
|
|
4147
|
+
authoritativeGrants.clear();
|
|
4148
|
+
}
|
|
4149
|
+
tokenViewer = viewer;
|
|
4150
|
+
}
|
|
3575
4151
|
/** Consents made through this host, keyed by target space. */
|
|
3576
4152
|
const sessionViewerGrants = /* @__PURE__ */ new Map();
|
|
3577
4153
|
let activeInvocation;
|
|
3578
4154
|
let contextChangeVersion = 0;
|
|
3579
4155
|
const legacyRequestIds = /* @__PURE__ */ new Set();
|
|
4156
|
+
const authorizationRequests = /* @__PURE__ */ new Map();
|
|
4157
|
+
const authoritativeGrants = /* @__PURE__ */ new Map();
|
|
4158
|
+
function canJoin(pending, next, requestId) {
|
|
4159
|
+
const previous = authorizationRequests.get(pending.requestId);
|
|
4160
|
+
const current = authorizationRequests.get(requestId);
|
|
4161
|
+
return Boolean(previous) === Boolean(current) && previous?.fallback === current?.fallback && isSameConsent(pending, next);
|
|
4162
|
+
}
|
|
4163
|
+
let signInFlight = null;
|
|
4164
|
+
const pendingLoginKey = `cohub:app-login:${app.id}`;
|
|
4165
|
+
function rememberAuthorization(data) {
|
|
4166
|
+
if (data.createSpace) return;
|
|
4167
|
+
try {
|
|
4168
|
+
const structured = typeof data.requestId === "string" ? authorizationRequests.get(data.requestId) : void 0;
|
|
4169
|
+
sessionStorage.setItem(pendingLoginKey, JSON.stringify({
|
|
4170
|
+
at: Date.now(),
|
|
4171
|
+
data: {
|
|
4172
|
+
...structured ? {
|
|
4173
|
+
...structured,
|
|
4174
|
+
type: "cohub.app.authorize.v2"
|
|
4175
|
+
} : { type: data.type },
|
|
4176
|
+
requestId: data.requestId,
|
|
4177
|
+
scopes: data.scopes,
|
|
4178
|
+
reason: sanitizeReason(data.reason),
|
|
4179
|
+
spaceId: data.spaceId,
|
|
4180
|
+
selectSpace: data.selectSpace,
|
|
4181
|
+
alwaysAsk: data.alwaysAsk
|
|
4182
|
+
}
|
|
4183
|
+
}));
|
|
4184
|
+
} catch {}
|
|
4185
|
+
}
|
|
4186
|
+
function clearAuthorizationIntent(requestId) {
|
|
4187
|
+
try {
|
|
4188
|
+
const raw = sessionStorage.getItem(pendingLoginKey);
|
|
4189
|
+
if (!raw) return;
|
|
4190
|
+
const saved = JSON.parse(raw);
|
|
4191
|
+
if (isRecord(saved) && isRecord(saved.data) && saved.data.requestId === requestId) sessionStorage.removeItem(pendingLoginKey);
|
|
4192
|
+
} catch {}
|
|
4193
|
+
}
|
|
4194
|
+
async function resumeAuthorization() {
|
|
4195
|
+
if (state.pendingAuth || !await getViewerUuid()) return;
|
|
4196
|
+
try {
|
|
4197
|
+
const raw = sessionStorage.getItem(pendingLoginKey);
|
|
4198
|
+
if (!raw) return;
|
|
4199
|
+
const saved = JSON.parse(raw);
|
|
4200
|
+
if (!isRecord(saved) || typeof saved.at !== "number" || Date.now() - saved.at > 6e5 || !isRecord(saved.data)) {
|
|
4201
|
+
sessionStorage.removeItem(pendingLoginKey);
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4204
|
+
await handleMessage({ data: saved.data });
|
|
4205
|
+
} catch {}
|
|
4206
|
+
}
|
|
4207
|
+
async function startSignIn() {
|
|
4208
|
+
if (state.pendingAuth) rememberAuthorization({
|
|
4209
|
+
...state.pendingAuth,
|
|
4210
|
+
type: "cohub.app.authorize"
|
|
4211
|
+
});
|
|
4212
|
+
signInFlight ??= config.requestSignIn(typeof location !== "undefined" ? location.pathname + location.search + location.hash : "/").catch((error) => {
|
|
4213
|
+
signInFlight = null;
|
|
4214
|
+
throw error;
|
|
4215
|
+
});
|
|
4216
|
+
await signInFlight;
|
|
4217
|
+
}
|
|
3580
4218
|
async function getContext() {
|
|
3581
4219
|
const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
|
|
3582
4220
|
const shell = config.getShell?.() ?? config.shell;
|
|
3583
4221
|
const appScopes = clonePermissionScopes(app.appScopes);
|
|
3584
4222
|
const viewerUuid = await getViewerUuid();
|
|
4223
|
+
synchronizeViewer(viewerUuid);
|
|
3585
4224
|
const viewerGrants = mergeViewerGrants(viewerUuid, Array.from(sessionViewerGrants.values()));
|
|
3586
4225
|
return {
|
|
4226
|
+
capabilities: {
|
|
4227
|
+
authorization: 2,
|
|
4228
|
+
serverGrants: true
|
|
4229
|
+
},
|
|
4230
|
+
mode: authorizationContext.surface === "broker" ? "broker" : "bridge",
|
|
3587
4231
|
app: {
|
|
3588
4232
|
id: app.id,
|
|
3589
4233
|
slug: app.slug,
|
|
@@ -3625,13 +4269,63 @@ function createAppBridgeCore(config) {
|
|
|
3625
4269
|
};
|
|
3626
4270
|
}
|
|
3627
4271
|
function replyForRequest(requestId, payload, complete = false) {
|
|
4272
|
+
const request = authorizationRequests.get(requestId);
|
|
4273
|
+
if (request && payload.type === "cohub.app.error" && [
|
|
4274
|
+
"space_inaccessible",
|
|
4275
|
+
"scope_not_held",
|
|
4276
|
+
"app_not_accessible",
|
|
4277
|
+
"consent_required"
|
|
4278
|
+
].includes(String(payload.code))) payload = {
|
|
4279
|
+
...payload,
|
|
4280
|
+
type: "cohub.app.authorize.result",
|
|
4281
|
+
token: null,
|
|
4282
|
+
result: {
|
|
4283
|
+
status: "denied",
|
|
4284
|
+
code: payload.code
|
|
4285
|
+
}
|
|
4286
|
+
};
|
|
4287
|
+
if (request && payload.type === "cohub.app.authorize.result" && !payload.result) {
|
|
4288
|
+
const space = payload.space;
|
|
4289
|
+
const grant = space ? authoritativeGrants.get(space.id) : void 0;
|
|
4290
|
+
let result = { status: "cancelled" };
|
|
4291
|
+
if (payload.token && space && grant) result = {
|
|
4292
|
+
status: "granted",
|
|
4293
|
+
requestedTarget: request.target,
|
|
4294
|
+
target: request.target.kind === "account" ? { kind: "account" } : {
|
|
4295
|
+
kind: "space",
|
|
4296
|
+
spaceId: space.id,
|
|
4297
|
+
name: space.name
|
|
4298
|
+
},
|
|
4299
|
+
resolution: request.target.kind === "pick-space" ? "selected" : request.target.kind === "space" && request.target.spaceId !== space.id ? "fallback" : "requested",
|
|
4300
|
+
grant
|
|
4301
|
+
};
|
|
4302
|
+
else if (payload.token) payload = {
|
|
4303
|
+
type: "cohub.app.error",
|
|
4304
|
+
code: "invalid_response",
|
|
4305
|
+
message: "Missing authoritative grant."
|
|
4306
|
+
};
|
|
4307
|
+
if (payload.type !== "cohub.app.error") payload = {
|
|
4308
|
+
...payload,
|
|
4309
|
+
result
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
if (complete && (payload.type === "cohub.app.authorize.result" || payload.type === "cohub.app.error" && [
|
|
4313
|
+
"invalid_request",
|
|
4314
|
+
"space_inaccessible",
|
|
4315
|
+
"scope_not_held",
|
|
4316
|
+
"app_not_accessible",
|
|
4317
|
+
"consent_required"
|
|
4318
|
+
].includes(String(payload.code)))) clearAuthorizationIntent(requestId);
|
|
3628
4319
|
const namespace = legacyRequestIds.has(requestId) ? "work" : "app";
|
|
3629
4320
|
const type = payload.type;
|
|
3630
4321
|
reply(requestId, {
|
|
3631
4322
|
...payload,
|
|
3632
4323
|
...typeof type === "string" && type.startsWith("cohub.app.") ? { type: type.replace("cohub.app.", `cohub.${namespace}.`) } : {}
|
|
3633
4324
|
});
|
|
3634
|
-
if (complete)
|
|
4325
|
+
if (complete) {
|
|
4326
|
+
legacyRequestIds.delete(requestId);
|
|
4327
|
+
authorizationRequests.delete(requestId);
|
|
4328
|
+
}
|
|
3635
4329
|
}
|
|
3636
4330
|
function toLegacyWorkContext(context) {
|
|
3637
4331
|
const permissions = context.permissions ? {
|
|
@@ -3710,12 +4404,11 @@ function createAppBridgeCore(config) {
|
|
|
3710
4404
|
return authorizationContext.surface === "background" || authorizationContext.surface === "app" || authorizationContext.surface === "overlay";
|
|
3711
4405
|
}
|
|
3712
4406
|
async function ensureBaseToken(forceRefresh = false) {
|
|
4407
|
+
const viewer = await getViewerUuid();
|
|
4408
|
+
synchronizeViewer(viewer);
|
|
3713
4409
|
if (appToken && !forceRefresh) return appToken;
|
|
3714
4410
|
const userToken = await getAccessToken({ forceRefresh });
|
|
3715
|
-
if (!userToken)
|
|
3716
|
-
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
3717
|
-
return null;
|
|
3718
|
-
}
|
|
4411
|
+
if (!userToken) return null;
|
|
3719
4412
|
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/session`, {
|
|
3720
4413
|
method: "POST",
|
|
3721
4414
|
headers: { Authorization: `Bearer ${userToken}` }
|
|
@@ -3723,6 +4416,7 @@ function createAppBridgeCore(config) {
|
|
|
3723
4416
|
if (!response.ok) throw new Error("Failed to create app session.");
|
|
3724
4417
|
const token = readTokenResponse(await response.json());
|
|
3725
4418
|
if (!token) throw new Error("Invalid app session response.");
|
|
4419
|
+
if (viewer !== await getViewerUuid()) return null;
|
|
3726
4420
|
appToken = token;
|
|
3727
4421
|
return appToken;
|
|
3728
4422
|
}
|
|
@@ -3733,11 +4427,14 @@ function createAppBridgeCore(config) {
|
|
|
3733
4427
|
* fresh dialog.
|
|
3734
4428
|
*/
|
|
3735
4429
|
async function authorize(scopes, spaceId, options) {
|
|
4430
|
+
const incremental = Boolean(state.pendingAuth && authorizationRequests.has(state.pendingAuth.requestId));
|
|
3736
4431
|
const userToken = await getAccessToken(options?.forceRefreshToken ? { forceRefresh: true } : void 0);
|
|
3737
4432
|
if (!userToken) {
|
|
3738
|
-
|
|
3739
|
-
|
|
4433
|
+
if (mintedSpace) throw new AppAuthorizationError("Sign in to finish authorization.", 401, "login_required");
|
|
4434
|
+
await startSignIn();
|
|
4435
|
+
throw new AppLoginRedirect();
|
|
3740
4436
|
}
|
|
4437
|
+
const authorizingViewer = await getViewerUuid();
|
|
3741
4438
|
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
|
|
3742
4439
|
method: "POST",
|
|
3743
4440
|
signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
|
|
@@ -3748,13 +4445,18 @@ function createAppBridgeCore(config) {
|
|
|
3748
4445
|
body: JSON.stringify({
|
|
3749
4446
|
scopes,
|
|
3750
4447
|
...spaceId ? { spaceId } : {},
|
|
3751
|
-
...options?.silent ? { silent: true } : {}
|
|
4448
|
+
...options?.silent ? { silent: true } : {},
|
|
4449
|
+
...incremental ? { scopeMode: "extend" } : {}
|
|
3752
4450
|
})
|
|
3753
4451
|
});
|
|
3754
4452
|
const payload = await response.json().catch(() => null);
|
|
3755
4453
|
if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status, typeof payload?.code === "string" ? payload.code : void 0);
|
|
4454
|
+
if (authorizingViewer !== await getViewerUuid()) throw new AppAuthorizationError("The signed-in account changed. Please try again.", 409, "session_changed");
|
|
4455
|
+
synchronizeViewer(authorizingViewer);
|
|
3756
4456
|
const token = readTokenResponse(payload);
|
|
3757
4457
|
if (!token) throw new Error("Invalid app authorization response.");
|
|
4458
|
+
const parsedGrant = appAuthorizationGrantSchema.safeParse(payload?.grant);
|
|
4459
|
+
if (parsedGrant.success) authoritativeGrants.set(parsedGrant.data.spaceId, parsedGrant.data);
|
|
3758
4460
|
const canonicalSpaceId = typeof payload?.grant?.spaceId === "string" && payload.grant.spaceId ? payload.grant.spaceId : spaceId ?? app.spaceId;
|
|
3759
4461
|
const grantedScopes = sanitizeRequestedScopes(payload?.grant?.scopes);
|
|
3760
4462
|
const canonicalScopes = grantedScopes.length > 0 ? grantedScopes : clonePermissionScopes(scopes);
|
|
@@ -3836,7 +4538,10 @@ function createAppBridgeCore(config) {
|
|
|
3836
4538
|
async function listViewerSpaces() {
|
|
3837
4539
|
const request = async (forceRefresh = false) => {
|
|
3838
4540
|
const userToken = await getAccessToken({ forceRefresh });
|
|
3839
|
-
if (!userToken)
|
|
4541
|
+
if (!userToken) {
|
|
4542
|
+
await startSignIn();
|
|
4543
|
+
throw new AppLoginRedirect();
|
|
4544
|
+
}
|
|
3840
4545
|
return fetch(`${apiOrigin}/api/spaces`, {
|
|
3841
4546
|
headers: { Authorization: `Bearer ${userToken}` },
|
|
3842
4547
|
signal: spaceListSignal()
|
|
@@ -3852,7 +4557,8 @@ function createAppBridgeCore(config) {
|
|
|
3852
4557
|
const option = toSpaceOption(space);
|
|
3853
4558
|
return option ? [option] : [];
|
|
3854
4559
|
});
|
|
3855
|
-
} catch {
|
|
4560
|
+
} catch (error) {
|
|
4561
|
+
if (error instanceof AppLoginRedirect) throw error;
|
|
3856
4562
|
return null;
|
|
3857
4563
|
}
|
|
3858
4564
|
}
|
|
@@ -4006,8 +4712,38 @@ function createAppBridgeCore(config) {
|
|
|
4006
4712
|
return (await response.json()).checkout ?? null;
|
|
4007
4713
|
}
|
|
4008
4714
|
async function handleMessage(event) {
|
|
4009
|
-
|
|
4715
|
+
let data = event.data;
|
|
4010
4716
|
if (!data?.requestId) return;
|
|
4717
|
+
if (data.type === "cohub.app.authorize.v2") {
|
|
4718
|
+
const parsed = appAuthorizationRequestSchema.safeParse(event.data);
|
|
4719
|
+
if (!parsed.success || parsed.data.scopes.some((scope) => !PERMISSIONS.includes(scope))) {
|
|
4720
|
+
replyForRequest(data.requestId, {
|
|
4721
|
+
type: "cohub.app.error",
|
|
4722
|
+
code: "invalid_request",
|
|
4723
|
+
message: "Invalid authorization request."
|
|
4724
|
+
}, true);
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4727
|
+
const input = parsed.data;
|
|
4728
|
+
if (input.target.kind === "account" && input.scopes.some((scope) => !isUserLevelPermission(scope))) {
|
|
4729
|
+
replyForRequest(data.requestId, {
|
|
4730
|
+
type: "cohub.app.error",
|
|
4731
|
+
code: "invalid_request",
|
|
4732
|
+
message: "Space permissions require a Space target."
|
|
4733
|
+
}, true);
|
|
4734
|
+
return;
|
|
4735
|
+
}
|
|
4736
|
+
authorizationRequests.set(data.requestId, input);
|
|
4737
|
+
data = {
|
|
4738
|
+
requestId: data.requestId,
|
|
4739
|
+
...input,
|
|
4740
|
+
type: "cohub.app.authorize",
|
|
4741
|
+
scopes: input.scopes,
|
|
4742
|
+
spaceId: input.target.kind === "space" ? input.target.spaceId : void 0,
|
|
4743
|
+
selectSpace: input.target.kind === "pick-space"
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
if (!data.requestId) return;
|
|
4011
4747
|
const isLegacyWork = data.type?.startsWith("cohub.work.") === true;
|
|
4012
4748
|
if (isLegacyWork) legacyRequestIds.add(data.requestId);
|
|
4013
4749
|
let reservedAuth = null;
|
|
@@ -4018,6 +4754,7 @@ function createAppBridgeCore(config) {
|
|
|
4018
4754
|
type: "cohub.app.context.result",
|
|
4019
4755
|
context: isLegacyWork ? toLegacyWorkContext(context) : context
|
|
4020
4756
|
}, true);
|
|
4757
|
+
await resumeAuthorization();
|
|
4021
4758
|
}
|
|
4022
4759
|
if (data.type === "cohub.app.token" || data.type === "cohub.work.token") {
|
|
4023
4760
|
const token = await ensureBaseToken(Boolean(data.forceRefresh));
|
|
@@ -4125,18 +4862,22 @@ function createAppBridgeCore(config) {
|
|
|
4125
4862
|
return;
|
|
4126
4863
|
}
|
|
4127
4864
|
if (state.pendingAuth) {
|
|
4128
|
-
if (
|
|
4865
|
+
if (canJoin(state.pendingAuth, {
|
|
4129
4866
|
scopes,
|
|
4130
4867
|
spaceId,
|
|
4131
4868
|
selectSpace,
|
|
4132
4869
|
createSpace
|
|
4133
|
-
})) {
|
|
4870
|
+
}, data.requestId)) {
|
|
4134
4871
|
state.pendingAuth.joinedRequestIds = [...state.pendingAuth.joinedRequestIds ?? [], data.requestId];
|
|
4135
4872
|
return;
|
|
4136
4873
|
}
|
|
4137
4874
|
dismissPendingAuth();
|
|
4138
4875
|
}
|
|
4139
4876
|
if (createSpace) {
|
|
4877
|
+
if (!await getViewerUuid()) {
|
|
4878
|
+
await startSignIn();
|
|
4879
|
+
return;
|
|
4880
|
+
}
|
|
4140
4881
|
mintedSpace = null;
|
|
4141
4882
|
const pendingCreate = {
|
|
4142
4883
|
requestId: data.requestId,
|
|
@@ -4154,7 +4895,7 @@ function createAppBridgeCore(config) {
|
|
|
4154
4895
|
notify();
|
|
4155
4896
|
return;
|
|
4156
4897
|
}
|
|
4157
|
-
if (!selectSpace && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
|
|
4898
|
+
if (!selectSpace && !authorizationRequests.has(data.requestId) && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
|
|
4158
4899
|
const result = await authorizeSilent(scopes, spaceId);
|
|
4159
4900
|
replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, result.spaceId === app.spaceId ? app.spaceName ?? null : null), true);
|
|
4160
4901
|
return;
|
|
@@ -4168,12 +4909,12 @@ function createAppBridgeCore(config) {
|
|
|
4168
4909
|
}
|
|
4169
4910
|
}
|
|
4170
4911
|
if (state.pendingAuth) {
|
|
4171
|
-
if (
|
|
4912
|
+
if (canJoin(state.pendingAuth, {
|
|
4172
4913
|
scopes,
|
|
4173
4914
|
spaceId,
|
|
4174
4915
|
selectSpace,
|
|
4175
4916
|
createSpace
|
|
4176
|
-
})) {
|
|
4917
|
+
}, data.requestId)) {
|
|
4177
4918
|
state.pendingAuth.joinedRequestIds = [...state.pendingAuth.joinedRequestIds ?? [], data.requestId];
|
|
4178
4919
|
return;
|
|
4179
4920
|
}
|
|
@@ -4194,6 +4935,11 @@ function createAppBridgeCore(config) {
|
|
|
4194
4935
|
state.authError = null;
|
|
4195
4936
|
const viewerUuid = await getViewerUuid();
|
|
4196
4937
|
if (state.pendingAuth !== reserved) return;
|
|
4938
|
+
if (!viewerUuid) {
|
|
4939
|
+
rememberAuthorization(data);
|
|
4940
|
+
await startSignIn();
|
|
4941
|
+
return;
|
|
4942
|
+
}
|
|
4197
4943
|
const spaceLevel = Boolean(spaceId) || selectSpace || scopes.some((scope) => !isUserLevelPermission(scope));
|
|
4198
4944
|
const lastPicked = readLastPickedSpace();
|
|
4199
4945
|
const contextSpaceId = resolveContextSpaceId();
|
|
@@ -4208,7 +4954,26 @@ function createAppBridgeCore(config) {
|
|
|
4208
4954
|
if (fallback) spaces = [fallback];
|
|
4209
4955
|
}
|
|
4210
4956
|
const candidates = spaces ?? null;
|
|
4957
|
+
if (spaceLevel && spaces === null) {
|
|
4958
|
+
reserved.spaces = null;
|
|
4959
|
+
state.authError = "Couldn't load your Spaces. Please try again.";
|
|
4960
|
+
state.authOpen = true;
|
|
4961
|
+
state.canChangeSpace = false;
|
|
4962
|
+
notify();
|
|
4963
|
+
return;
|
|
4964
|
+
}
|
|
4211
4965
|
const explicitAccessible = Boolean(spaceId) && candidates?.some((space) => space.id === spaceId) === true;
|
|
4966
|
+
const structuredRequest = authorizationRequests.get(data.requestId);
|
|
4967
|
+
if (spaceId && candidates && !explicitAccessible && structuredRequest?.fallback === "none") {
|
|
4968
|
+
replyPendingAuth(reserved, {
|
|
4969
|
+
type: "cohub.app.error",
|
|
4970
|
+
code: "space_inaccessible",
|
|
4971
|
+
message: "The requested Space is unavailable."
|
|
4972
|
+
});
|
|
4973
|
+
resetDialogState();
|
|
4974
|
+
safeNotify();
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4212
4977
|
const defaultSpaceId = explicitAccessible ? spaceId : resolveDefaultSpaceId(candidates);
|
|
4213
4978
|
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
4979
|
if (!alwaysAsk && await attemptSilentRenewal(reserved, viewerUuid, scopes, silentTargets)) return;
|
|
@@ -4230,11 +4995,19 @@ function createAppBridgeCore(config) {
|
|
|
4230
4995
|
notify();
|
|
4231
4996
|
}
|
|
4232
4997
|
} catch (error) {
|
|
4998
|
+
if (error instanceof AppLoginRedirect) {
|
|
4999
|
+
rememberAuthorization(data);
|
|
5000
|
+
resetDialogState();
|
|
5001
|
+
safeNotify();
|
|
5002
|
+
return;
|
|
5003
|
+
}
|
|
4233
5004
|
const message = error instanceof Error ? error.message : "Request failed.";
|
|
5005
|
+
const code = error instanceof AppAuthorizationError ? error.code : "request_failed";
|
|
4234
5006
|
if (reservedAuth && state.pendingAuth === reservedAuth) {
|
|
4235
5007
|
replyPendingAuth(reservedAuth, {
|
|
4236
5008
|
type: "cohub.app.error",
|
|
4237
|
-
message
|
|
5009
|
+
message,
|
|
5010
|
+
code
|
|
4238
5011
|
});
|
|
4239
5012
|
resetDialogState();
|
|
4240
5013
|
safeNotify();
|
|
@@ -4242,7 +5015,8 @@ function createAppBridgeCore(config) {
|
|
|
4242
5015
|
}
|
|
4243
5016
|
replyForRequest(data.requestId, {
|
|
4244
5017
|
type: "cohub.app.error",
|
|
4245
|
-
message
|
|
5018
|
+
message,
|
|
5019
|
+
code
|
|
4246
5020
|
}, true);
|
|
4247
5021
|
}
|
|
4248
5022
|
}
|
|
@@ -4262,7 +5036,14 @@ function createAppBridgeCore(config) {
|
|
|
4262
5036
|
if (!state.pendingAuth) return;
|
|
4263
5037
|
replyPendingAuth(state.pendingAuth, {
|
|
4264
5038
|
type: "cohub.app.authorize.result",
|
|
4265
|
-
token: null
|
|
5039
|
+
token: null,
|
|
5040
|
+
...mintedSpace ? {
|
|
5041
|
+
space: {
|
|
5042
|
+
id: mintedSpace.id,
|
|
5043
|
+
name: mintedSpace.name
|
|
5044
|
+
},
|
|
5045
|
+
stage: mintedSpace.provisioned ? "authorization" : "bootstrap"
|
|
5046
|
+
} : {}
|
|
4266
5047
|
});
|
|
4267
5048
|
resetDialogState();
|
|
4268
5049
|
mintedSpace = null;
|
|
@@ -4323,6 +5104,11 @@ function createAppBridgeCore(config) {
|
|
|
4323
5104
|
if (!state.pendingAuth || state.authSaving) return;
|
|
4324
5105
|
const pending = state.pendingAuth;
|
|
4325
5106
|
let requestedSpaceId = (state.canChangeSpace ? pickedSpaceId : void 0) ?? state.selectedSpaceId ?? void 0;
|
|
5107
|
+
if (pending.spaces === null) {
|
|
5108
|
+
state.authError = "Couldn't load your Spaces. Please try again.";
|
|
5109
|
+
notify();
|
|
5110
|
+
return;
|
|
5111
|
+
}
|
|
4326
5112
|
if (pending.spaces !== void 0) {
|
|
4327
5113
|
const candidates = pending.spaces;
|
|
4328
5114
|
if (!(requestedSpaceId !== void 0 && candidates?.some((space) => space.id === requestedSpaceId) === true)) {
|
|
@@ -4360,6 +5146,13 @@ function createAppBridgeCore(config) {
|
|
|
4360
5146
|
resetDialogState();
|
|
4361
5147
|
mintedSpace = null;
|
|
4362
5148
|
} catch (error) {
|
|
5149
|
+
if (error instanceof AppLoginRedirect) return;
|
|
5150
|
+
if (mintedSpace && error instanceof AppAuthorizationError && error.code === "login_required") {
|
|
5151
|
+
replyPendingAuth(pending, authorizeResult(null, mintedSpace.id, mintedSpace.name));
|
|
5152
|
+
resetDialogState();
|
|
5153
|
+
mintedSpace = null;
|
|
5154
|
+
return;
|
|
5155
|
+
}
|
|
4363
5156
|
if (error instanceof AppAuthorizationError) emitDiagnostic({
|
|
4364
5157
|
code: error.code ?? "authorize_failed",
|
|
4365
5158
|
message: error.message,
|
|
@@ -4626,4 +5419,4 @@ function createBoardExtensionRegistry() {
|
|
|
4626
5419
|
}
|
|
4627
5420
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
4628
5421
|
//#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 };
|
|
5422
|
+
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, 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 };
|