@neta-art/cohub 8.19.0 → 8.20.1
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 +53 -4
- package/dist/chunks/environment.js +4 -1
- package/dist/chunks/http.d.ts +73 -15
- package/dist/chunks/http.js +22 -9
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/http.js +1 -1
- package/dist/index.d.ts +80 -3
- package/dist/index.js +829 -71
- package/docs/app-runtime-guide.md +49 -74
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
-
import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as
|
|
3
|
+
import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebBaseUrl, f as resolveWebsocketUrl, g as scopeListHasPermission, h as isUserLevelPermission, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, m as USER_LEVEL_PERMISSIONS, n as getCohubContext, o as resolveApiBaseUrl, p as 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";
|
|
@@ -14,6 +14,15 @@ const appAuthorizationTargetSchema = z.discriminatedUnion("kind", [
|
|
|
14
14
|
}),
|
|
15
15
|
z.object({ kind: z.literal("pick-space") })
|
|
16
16
|
]);
|
|
17
|
+
const APP_SILENT_SHELL_SCOPE_SET = /* @__PURE__ */ new Set([
|
|
18
|
+
"space.view",
|
|
19
|
+
"file.view",
|
|
20
|
+
"file.view.filtered",
|
|
21
|
+
"session.view",
|
|
22
|
+
"taskrun.view",
|
|
23
|
+
"checkpoint.view"
|
|
24
|
+
]);
|
|
25
|
+
const isAppSilentShellScope = (scope) => APP_SILENT_SHELL_SCOPE_SET.has(scope);
|
|
17
26
|
const appAuthorizationRequestSchema = z.object({
|
|
18
27
|
target: appAuthorizationTargetSchema,
|
|
19
28
|
scopes: z.array(z.string().min(1)).min(1).max(128),
|
|
@@ -1344,6 +1353,457 @@ function validateArchiveBoundary(index, parent) {
|
|
|
1344
1353
|
if (!index.segments.length && parent?.sha256 !== index.sha256) throw new Error("Unchanged archive checksum mismatch");
|
|
1345
1354
|
}
|
|
1346
1355
|
//#endregion
|
|
1356
|
+
//#region ../protocol/dist/runtime/projection.js
|
|
1357
|
+
const DEFAULT_TIMESTAMP = "1970-01-01T00:00:00.000Z";
|
|
1358
|
+
const asMeta = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1359
|
+
const safeId = (value) => value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
1360
|
+
const entryId = (messageId, ordinal) => `cohub_${safeId(messageId)}_${ordinal}`;
|
|
1361
|
+
const isProjectionCompaction = (message) => message.role === "system" && message.content.some((block) => block.type === "system_note" && block.note_type === "compacted");
|
|
1362
|
+
const isPendingGeneration = (message) => message.role === "assistant" && message.meta?.messageKind === "generation_result" && !["completed", "failed"].includes(String(message.meta.generationStatus));
|
|
1363
|
+
const messageMeta = (message) => asMeta(message.meta);
|
|
1364
|
+
const timestampOf = (message, fallback) => message.createdAt || fallback;
|
|
1365
|
+
function stableJson(value) {
|
|
1366
|
+
if (value === void 0) return "undefined";
|
|
1367
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
1368
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
1369
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
1370
|
+
}
|
|
1371
|
+
function fingerprint(value) {
|
|
1372
|
+
let hash = 14695981039346656037n;
|
|
1373
|
+
for (const character of stableJson(value)) {
|
|
1374
|
+
hash ^= BigInt(character.codePointAt(0) ?? 0);
|
|
1375
|
+
hash = BigInt.asUintN(64, hash * 1099511628211n);
|
|
1376
|
+
}
|
|
1377
|
+
return hash.toString(16).padStart(16, "0");
|
|
1378
|
+
}
|
|
1379
|
+
function sourceFingerprint(turns) {
|
|
1380
|
+
return `${turns.length}:${fingerprint(turns)}`;
|
|
1381
|
+
}
|
|
1382
|
+
function projectNativeMessageMeta(message) {
|
|
1383
|
+
const source = messageMeta(message);
|
|
1384
|
+
const safe = {
|
|
1385
|
+
sourceSessionId: message.sourceSessionId,
|
|
1386
|
+
sequence: message.sequence,
|
|
1387
|
+
createdAt: message.createdAt
|
|
1388
|
+
};
|
|
1389
|
+
for (const key of [
|
|
1390
|
+
"agentSessionEntryId",
|
|
1391
|
+
"messageKind",
|
|
1392
|
+
"nativeApi",
|
|
1393
|
+
"generationStatus"
|
|
1394
|
+
]) if (source[key] !== void 0) safe[key] = source[key];
|
|
1395
|
+
const compaction = asMeta(source.compaction);
|
|
1396
|
+
if (Object.keys(compaction).length) safe.compaction = Object.fromEntries([
|
|
1397
|
+
"compactionId",
|
|
1398
|
+
"compactedAt",
|
|
1399
|
+
"tokensBefore",
|
|
1400
|
+
"firstKeptEntryId"
|
|
1401
|
+
].filter((key) => compaction[key] !== void 0).map((key) => [key, compaction[key]]));
|
|
1402
|
+
return {
|
|
1403
|
+
...safe,
|
|
1404
|
+
messageId: message.id,
|
|
1405
|
+
turnId: message.turnId
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function projectPiBlock(block) {
|
|
1409
|
+
if (block.type === "text") return {
|
|
1410
|
+
type: "text",
|
|
1411
|
+
text: block.text
|
|
1412
|
+
};
|
|
1413
|
+
if (block.type === "thinking") return {
|
|
1414
|
+
type: "thinking",
|
|
1415
|
+
thinking: block.thinking,
|
|
1416
|
+
...block.signature ? { thinkingSignature: block.signature } : {}
|
|
1417
|
+
};
|
|
1418
|
+
if (block.type === "image" && block.source.type === "base64") return {
|
|
1419
|
+
type: "image",
|
|
1420
|
+
data: block.source.data,
|
|
1421
|
+
mimeType: block.source.media_type
|
|
1422
|
+
};
|
|
1423
|
+
if (block.type === "tool_use") return {
|
|
1424
|
+
type: "toolCall",
|
|
1425
|
+
id: block.id,
|
|
1426
|
+
name: block.name,
|
|
1427
|
+
arguments: block.input
|
|
1428
|
+
};
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
function containsExternalImage(content) {
|
|
1432
|
+
return content.some((block) => block.type === "image" && block.source.type === "url" || block.type === "tool_result" && Array.isArray(block.content) && containsExternalImage(block.content));
|
|
1433
|
+
}
|
|
1434
|
+
function projectPiMessage(message, warnings, toolNames) {
|
|
1435
|
+
const meta = projectNativeMessageMeta(message);
|
|
1436
|
+
if (containsExternalImage(message.content)) warnings.push({
|
|
1437
|
+
sourceMessageId: message.id,
|
|
1438
|
+
sourceTurnId: message.turnId,
|
|
1439
|
+
reason: "external image URL omitted from native projection"
|
|
1440
|
+
});
|
|
1441
|
+
if (message.role === "system") return [];
|
|
1442
|
+
if (message.role === "user") {
|
|
1443
|
+
const content = message.content.map(projectPiBlock).filter((block) => block !== null);
|
|
1444
|
+
return content.length ? [{
|
|
1445
|
+
role: "user",
|
|
1446
|
+
content,
|
|
1447
|
+
timestamp: Date.parse(message.createdAt) || 0,
|
|
1448
|
+
meta
|
|
1449
|
+
}] : [];
|
|
1450
|
+
}
|
|
1451
|
+
const calls = new Map(message.content.flatMap((block) => block.type === "tool_use" ? [[block.id, block.name]] : []));
|
|
1452
|
+
for (const [id, name] of calls) toolNames.set(id, name);
|
|
1453
|
+
const result = [];
|
|
1454
|
+
const content = message.content.map(projectPiBlock).filter((block) => block !== null);
|
|
1455
|
+
if (content.length) result.push({
|
|
1456
|
+
role: "assistant",
|
|
1457
|
+
content,
|
|
1458
|
+
api: typeof messageMeta(message).nativeApi === "string" ? messageMeta(message).nativeApi : "history",
|
|
1459
|
+
provider: message.provider ?? "history",
|
|
1460
|
+
model: message.model ?? "history",
|
|
1461
|
+
usage: message.usage ?? emptyUsage(),
|
|
1462
|
+
stopReason: message.stopReason === "tool_use" ? "toolUse" : message.stopReason ?? (calls.size ? "toolUse" : "stop"),
|
|
1463
|
+
...message.errorMessage ? { errorMessage: message.errorMessage } : {},
|
|
1464
|
+
timestamp: Date.parse(message.createdAt) || 0,
|
|
1465
|
+
meta
|
|
1466
|
+
});
|
|
1467
|
+
for (const block of message.content) {
|
|
1468
|
+
if (block.type !== "tool_result") continue;
|
|
1469
|
+
const toolName = calls.get(block.tool_use_id) ?? toolNames.get(block.tool_use_id);
|
|
1470
|
+
if (!toolName) continue;
|
|
1471
|
+
result.push({
|
|
1472
|
+
role: "toolResult",
|
|
1473
|
+
toolCallId: block.tool_use_id,
|
|
1474
|
+
toolName,
|
|
1475
|
+
content: typeof block.content === "string" ? [{
|
|
1476
|
+
type: "text",
|
|
1477
|
+
text: block.content
|
|
1478
|
+
}] : block.content.map(projectPiBlock).filter((item) => item !== null),
|
|
1479
|
+
isError: block.is_error ?? false,
|
|
1480
|
+
timestamp: Date.parse(message.createdAt) || 0,
|
|
1481
|
+
meta
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
return result;
|
|
1485
|
+
}
|
|
1486
|
+
function emptyUsage() {
|
|
1487
|
+
return {
|
|
1488
|
+
input: 0,
|
|
1489
|
+
output: 0,
|
|
1490
|
+
cacheRead: 0,
|
|
1491
|
+
cacheWrite: 0,
|
|
1492
|
+
totalTokens: 0,
|
|
1493
|
+
cost: null
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function projectionMetadata(input) {
|
|
1497
|
+
return {
|
|
1498
|
+
schema: "cohub.projection.v2",
|
|
1499
|
+
spaceId: input.spaceId,
|
|
1500
|
+
sessionId: input.sessionId
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function trimProjectionTurnsToCompaction(turns) {
|
|
1504
|
+
let boundaryTurn = -1;
|
|
1505
|
+
let boundaryMessage = -1;
|
|
1506
|
+
let boundaryOrder = Number.NEGATIVE_INFINITY;
|
|
1507
|
+
turns.forEach((turn, turnIndex) => {
|
|
1508
|
+
turn.messages.forEach((message, messageIndex) => {
|
|
1509
|
+
if (!isProjectionCompaction(message)) return;
|
|
1510
|
+
const compactedAt = asMeta(messageMeta(message).compaction).compactedAt;
|
|
1511
|
+
const parsed = typeof compactedAt === "string" ? Date.parse(compactedAt) : NaN;
|
|
1512
|
+
const order = Number.isFinite(parsed) ? parsed : turnIndex * 1e6 + messageIndex;
|
|
1513
|
+
if (order >= boundaryOrder) {
|
|
1514
|
+
boundaryTurn = turnIndex;
|
|
1515
|
+
boundaryMessage = messageIndex;
|
|
1516
|
+
boundaryOrder = order;
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1520
|
+
if (boundaryTurn < 0) return turns;
|
|
1521
|
+
return turns.slice(boundaryTurn).flatMap((turn, index) => {
|
|
1522
|
+
const messages = index === 0 ? turn.messages.slice(boundaryMessage).filter((message, messageIndex) => messageIndex === 0 || !isProjectionCompaction(message)) : turn.messages.filter((message) => !isProjectionCompaction(message));
|
|
1523
|
+
return messages.length ? [{
|
|
1524
|
+
...turn,
|
|
1525
|
+
messages
|
|
1526
|
+
}] : [];
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
function piProjection(input) {
|
|
1530
|
+
const sourceTurns = trimProjectionTurnsToCompaction(input.turns);
|
|
1531
|
+
const fallbackTimestamp = input.timestamp ?? DEFAULT_TIMESTAMP;
|
|
1532
|
+
const records = [{
|
|
1533
|
+
key: `header:${input.nativeSessionId}`,
|
|
1534
|
+
sourceTurnId: null,
|
|
1535
|
+
sourceMessageId: null,
|
|
1536
|
+
record: {
|
|
1537
|
+
type: "session",
|
|
1538
|
+
version: 3,
|
|
1539
|
+
id: input.nativeSessionId,
|
|
1540
|
+
timestamp: fallbackTimestamp,
|
|
1541
|
+
cwd: input.cwd,
|
|
1542
|
+
affinity: {
|
|
1543
|
+
sessionId: input.sessionId,
|
|
1544
|
+
threadId: input.nativeSessionId
|
|
1545
|
+
},
|
|
1546
|
+
cohub: projectionMetadata(input)
|
|
1547
|
+
}
|
|
1548
|
+
}];
|
|
1549
|
+
const warnings = [];
|
|
1550
|
+
const nativeIds = /* @__PURE__ */ new Map();
|
|
1551
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
1552
|
+
const compactions = [];
|
|
1553
|
+
let parentId = null;
|
|
1554
|
+
for (const turn of sourceTurns) for (const message of turn.messages) {
|
|
1555
|
+
if (isPendingGeneration(message)) continue;
|
|
1556
|
+
if (isProjectionCompaction(message)) {
|
|
1557
|
+
const id = `cohub_${safeId(message.id)}_compaction`;
|
|
1558
|
+
const record = {
|
|
1559
|
+
type: "compaction",
|
|
1560
|
+
id,
|
|
1561
|
+
parentId,
|
|
1562
|
+
timestamp: timestampOf(message, fallbackTimestamp),
|
|
1563
|
+
summary: message.content.flatMap((block) => block.type === "system_note" ? [block.text] : []).join("\n"),
|
|
1564
|
+
firstKeptEntryId: "",
|
|
1565
|
+
tokensBefore: Number(asMeta(messageMeta(message).compaction).tokensBefore) || 0
|
|
1566
|
+
};
|
|
1567
|
+
const projectionRecord = {
|
|
1568
|
+
key: `${message.id}:${message.turnId}:compaction`,
|
|
1569
|
+
sourceTurnId: message.turnId,
|
|
1570
|
+
sourceMessageId: message.id,
|
|
1571
|
+
record
|
|
1572
|
+
};
|
|
1573
|
+
records.push(projectionRecord);
|
|
1574
|
+
compactions.push({
|
|
1575
|
+
record: projectionRecord,
|
|
1576
|
+
firstKeptEntryId: typeof asMeta(messageMeta(message).compaction).firstKeptEntryId === "string" ? String(asMeta(messageMeta(message).compaction).firstKeptEntryId) : null
|
|
1577
|
+
});
|
|
1578
|
+
parentId = id;
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
const projected = projectPiMessage(message, warnings, toolNames);
|
|
1582
|
+
projected.forEach((nativeMessage, ordinal) => {
|
|
1583
|
+
const id = entryId(message.id, ordinal);
|
|
1584
|
+
const record = {
|
|
1585
|
+
type: "message",
|
|
1586
|
+
id,
|
|
1587
|
+
parentId,
|
|
1588
|
+
timestamp: timestampOf(message, fallbackTimestamp),
|
|
1589
|
+
message: nativeMessage
|
|
1590
|
+
};
|
|
1591
|
+
records.push({
|
|
1592
|
+
key: `${message.id}:${message.turnId}:${ordinal}`,
|
|
1593
|
+
sourceTurnId: message.turnId,
|
|
1594
|
+
sourceMessageId: message.id,
|
|
1595
|
+
record
|
|
1596
|
+
});
|
|
1597
|
+
if (!nativeIds.has(message.id)) nativeIds.set(message.id, id);
|
|
1598
|
+
const sourceEntry = messageMeta(message).agentSessionEntryId;
|
|
1599
|
+
if (typeof sourceEntry === "string" && !nativeIds.has(sourceEntry)) nativeIds.set(sourceEntry, id);
|
|
1600
|
+
parentId = id;
|
|
1601
|
+
});
|
|
1602
|
+
if (projected.length === 0 && message.content.length) warnings.push({
|
|
1603
|
+
sourceMessageId: message.id,
|
|
1604
|
+
sourceTurnId: message.turnId,
|
|
1605
|
+
reason: "message has no native Pi representation"
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
for (const compaction of compactions) compaction.record.record.firstKeptEntryId = compaction.firstKeptEntryId ? nativeIds.get(compaction.firstKeptEntryId) ?? "" : "";
|
|
1609
|
+
return {
|
|
1610
|
+
target: "pi",
|
|
1611
|
+
records,
|
|
1612
|
+
cursor: {
|
|
1613
|
+
sourceSequence: input.turns.at(-1)?.sequence ?? null,
|
|
1614
|
+
sourceTurnId: input.turns.at(-1)?.sourceTurnId ?? null,
|
|
1615
|
+
sourceFingerprint: sourceFingerprint(input.turns)
|
|
1616
|
+
},
|
|
1617
|
+
warnings
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function codexInputContent(block) {
|
|
1621
|
+
if (block.type === "text") return [{
|
|
1622
|
+
type: "input_text",
|
|
1623
|
+
text: block.text
|
|
1624
|
+
}];
|
|
1625
|
+
if (block.type === "image" && block.source.type === "base64") return [{
|
|
1626
|
+
type: "input_image",
|
|
1627
|
+
image_url: `data:${block.source.media_type};base64,${block.source.data}`
|
|
1628
|
+
}];
|
|
1629
|
+
return [];
|
|
1630
|
+
}
|
|
1631
|
+
function codexOutput(block) {
|
|
1632
|
+
return block.type === "text" ? [{
|
|
1633
|
+
type: "output_text",
|
|
1634
|
+
text: block.text
|
|
1635
|
+
}] : [];
|
|
1636
|
+
}
|
|
1637
|
+
function codexMessageRecords(input, message, warnings) {
|
|
1638
|
+
const timestamp = timestampOf(message, input.timestamp ?? DEFAULT_TIMESTAMP);
|
|
1639
|
+
const records = [];
|
|
1640
|
+
const metadata = { cohub: {
|
|
1641
|
+
...projectionMetadata(input),
|
|
1642
|
+
...projectNativeMessageMeta(message),
|
|
1643
|
+
turnId: message.turnId,
|
|
1644
|
+
messageId: message.id
|
|
1645
|
+
} };
|
|
1646
|
+
if (containsExternalImage(message.content)) warnings.push({
|
|
1647
|
+
sourceMessageId: message.id,
|
|
1648
|
+
sourceTurnId: message.turnId,
|
|
1649
|
+
reason: "external image URL omitted from native projection"
|
|
1650
|
+
});
|
|
1651
|
+
const push = (ordinal, payload) => records.push({
|
|
1652
|
+
key: `${message.id}:${message.turnId}:${ordinal}`,
|
|
1653
|
+
sourceTurnId: message.turnId,
|
|
1654
|
+
sourceMessageId: message.id,
|
|
1655
|
+
record: {
|
|
1656
|
+
timestamp,
|
|
1657
|
+
type: "response_item",
|
|
1658
|
+
payload,
|
|
1659
|
+
metadata
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
const plain = message.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
1663
|
+
for (const block of message.content) {
|
|
1664
|
+
if (block.type === "image" && block.source.type === "url") continue;
|
|
1665
|
+
if (block.type === "text") push(records.length, {
|
|
1666
|
+
type: "message",
|
|
1667
|
+
role: message.role,
|
|
1668
|
+
content: message.role === "assistant" ? codexOutput(block) : codexInputContent(block)
|
|
1669
|
+
});
|
|
1670
|
+
else if (block.type === "image") {
|
|
1671
|
+
const content = codexInputContent(block);
|
|
1672
|
+
if (content.length) push(records.length, {
|
|
1673
|
+
type: "message",
|
|
1674
|
+
role: "user",
|
|
1675
|
+
content
|
|
1676
|
+
});
|
|
1677
|
+
} else if (block.type === "thinking") push(records.length, {
|
|
1678
|
+
type: "reasoning",
|
|
1679
|
+
summary: [{
|
|
1680
|
+
type: "summary_text",
|
|
1681
|
+
text: block.thinking
|
|
1682
|
+
}],
|
|
1683
|
+
content: null,
|
|
1684
|
+
encrypted_content: null
|
|
1685
|
+
});
|
|
1686
|
+
else if (block.type === "tool_use") push(records.length, {
|
|
1687
|
+
type: "function_call",
|
|
1688
|
+
name: block.name,
|
|
1689
|
+
arguments: JSON.stringify(block.input),
|
|
1690
|
+
call_id: block.id
|
|
1691
|
+
});
|
|
1692
|
+
else if (block.type === "tool_result") push(records.length, {
|
|
1693
|
+
type: "function_call_output",
|
|
1694
|
+
call_id: block.tool_use_id,
|
|
1695
|
+
output: typeof block.content === "string" ? block.content : block.content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n")
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
if (plain) records.push({
|
|
1699
|
+
key: `${message.id}:${message.turnId}:event`,
|
|
1700
|
+
sourceTurnId: message.turnId,
|
|
1701
|
+
sourceMessageId: message.id,
|
|
1702
|
+
record: {
|
|
1703
|
+
timestamp,
|
|
1704
|
+
type: "event_msg",
|
|
1705
|
+
payload: {
|
|
1706
|
+
type: message.role === "user" ? "user_message" : "agent_message",
|
|
1707
|
+
message: plain,
|
|
1708
|
+
kind: "plain"
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
return records;
|
|
1713
|
+
}
|
|
1714
|
+
function codexTerminalPayload(turn) {
|
|
1715
|
+
const lastAgentMessage = turn.assistantContent?.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n") || null;
|
|
1716
|
+
if (turn.status === "completed") return {
|
|
1717
|
+
type: "turn_complete",
|
|
1718
|
+
turn_id: turn.id,
|
|
1719
|
+
last_agent_message: lastAgentMessage
|
|
1720
|
+
};
|
|
1721
|
+
return {
|
|
1722
|
+
type: "turn_aborted",
|
|
1723
|
+
turn_id: turn.id,
|
|
1724
|
+
reason: turn.status,
|
|
1725
|
+
last_agent_message: lastAgentMessage
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
function codexProjection(input) {
|
|
1729
|
+
const sourceTurns = trimProjectionTurnsToCompaction(input.turns);
|
|
1730
|
+
const fallbackTimestamp = input.timestamp ?? DEFAULT_TIMESTAMP;
|
|
1731
|
+
const records = [{
|
|
1732
|
+
key: `header:${input.nativeSessionId}`,
|
|
1733
|
+
sourceTurnId: null,
|
|
1734
|
+
sourceMessageId: null,
|
|
1735
|
+
record: {
|
|
1736
|
+
timestamp: fallbackTimestamp,
|
|
1737
|
+
type: "session_meta",
|
|
1738
|
+
payload: {
|
|
1739
|
+
id: input.nativeSessionId,
|
|
1740
|
+
session_id: input.nativeSessionId,
|
|
1741
|
+
timestamp: fallbackTimestamp,
|
|
1742
|
+
cwd: input.cwd,
|
|
1743
|
+
originator: "cohub",
|
|
1744
|
+
cli_version: "cohub-runtime",
|
|
1745
|
+
source: "cli",
|
|
1746
|
+
model_provider: input.provider ?? null,
|
|
1747
|
+
history_mode: "legacy",
|
|
1748
|
+
cohub: projectionMetadata(input)
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}];
|
|
1752
|
+
const warnings = [];
|
|
1753
|
+
for (const turn of sourceTurns) {
|
|
1754
|
+
const start = turn.startedAt ?? turn.createdAt;
|
|
1755
|
+
records.push({
|
|
1756
|
+
key: `turn:${turn.id}:started`,
|
|
1757
|
+
sourceTurnId: turn.sourceTurnId,
|
|
1758
|
+
sourceMessageId: null,
|
|
1759
|
+
record: {
|
|
1760
|
+
timestamp: start,
|
|
1761
|
+
type: "event_msg",
|
|
1762
|
+
payload: {
|
|
1763
|
+
type: "turn_started",
|
|
1764
|
+
turn_id: turn.id,
|
|
1765
|
+
started_at: Math.floor(Date.parse(start) / 1e3),
|
|
1766
|
+
model_context_window: null,
|
|
1767
|
+
collaboration_mode_kind: "default"
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
for (const message of turn.messages) if (!isPendingGeneration(message)) records.push(...codexMessageRecords(input, message, warnings));
|
|
1772
|
+
records.push({
|
|
1773
|
+
key: `turn:${turn.id}:terminal`,
|
|
1774
|
+
sourceTurnId: turn.sourceTurnId,
|
|
1775
|
+
sourceMessageId: null,
|
|
1776
|
+
record: {
|
|
1777
|
+
timestamp: turn.completedAt ?? turn.createdAt,
|
|
1778
|
+
type: "event_msg",
|
|
1779
|
+
payload: codexTerminalPayload(turn)
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
return {
|
|
1784
|
+
target: "codex",
|
|
1785
|
+
records,
|
|
1786
|
+
cursor: {
|
|
1787
|
+
sourceSequence: input.turns.at(-1)?.sequence ?? null,
|
|
1788
|
+
sourceTurnId: input.turns.at(-1)?.sourceTurnId ?? null,
|
|
1789
|
+
sourceFingerprint: sourceFingerprint(input.turns)
|
|
1790
|
+
},
|
|
1791
|
+
warnings
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
function projectNativeSession(input, target) {
|
|
1795
|
+
return target === "pi" ? piProjection(input) : codexProjection(input);
|
|
1796
|
+
}
|
|
1797
|
+
function serializeProjectionRecords(records) {
|
|
1798
|
+
return records.length ? `${records.map((record) => JSON.stringify(record.record)).join("\n")}\n` : "";
|
|
1799
|
+
}
|
|
1800
|
+
function serializeProjection(projection) {
|
|
1801
|
+
return serializeProjectionRecords(projection.records);
|
|
1802
|
+
}
|
|
1803
|
+
function fingerprintProjectionTurns(turns) {
|
|
1804
|
+
return `${turns.length}:${fingerprint(turns)}`;
|
|
1805
|
+
}
|
|
1806
|
+
//#endregion
|
|
1347
1807
|
//#region ../protocol/dist/runtime/context.js
|
|
1348
1808
|
const createEmptyUsage = () => ({
|
|
1349
1809
|
input: 0,
|
|
@@ -1510,6 +1970,7 @@ const runtimePendingExecutionSchema = z.object({
|
|
|
1510
1970
|
}).strict();
|
|
1511
1971
|
z.object({
|
|
1512
1972
|
connectionId: z.string().uuid(),
|
|
1973
|
+
runtimeId: z.string().uuid().optional(),
|
|
1513
1974
|
endpoint: z.url({ protocol: /^wss?$/ }),
|
|
1514
1975
|
ownerUserId: z.string().min(1),
|
|
1515
1976
|
capabilities: runtimeCapabilitiesSchema
|
|
@@ -1518,6 +1979,33 @@ const runtimeReadySchema = z.object({
|
|
|
1518
1979
|
type: z.literal("runtime.ready"),
|
|
1519
1980
|
connectionId: z.string().uuid()
|
|
1520
1981
|
});
|
|
1982
|
+
const runtimeTraceContextSchema = z.object({
|
|
1983
|
+
requestId: z.string().regex(/^[a-zA-Z0-9._:-]{1,128}$/).optional(),
|
|
1984
|
+
traceId: z.string().regex(/^[0-9a-f]{32}$/i).nullable().optional(),
|
|
1985
|
+
spanId: z.string().regex(/^[0-9a-f]{16}$/i).nullable().optional(),
|
|
1986
|
+
traceparent: z.string().regex(/^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/i).nullable().optional()
|
|
1987
|
+
}).strict();
|
|
1988
|
+
z.object({
|
|
1989
|
+
backend: z.enum([
|
|
1990
|
+
"fsevents",
|
|
1991
|
+
"fsnotify",
|
|
1992
|
+
"scan",
|
|
1993
|
+
"none"
|
|
1994
|
+
]),
|
|
1995
|
+
state: z.enum([
|
|
1996
|
+
"running",
|
|
1997
|
+
"degraded",
|
|
1998
|
+
"unavailable"
|
|
1999
|
+
]),
|
|
2000
|
+
reason: z.enum([
|
|
2001
|
+
"polling",
|
|
2002
|
+
"watch_error",
|
|
2003
|
+
"start_failed",
|
|
2004
|
+
"coverage_incomplete",
|
|
2005
|
+
"root_changed"
|
|
2006
|
+
]).optional(),
|
|
2007
|
+
observedAt: z.iso.datetime()
|
|
2008
|
+
});
|
|
1521
2009
|
z.object({
|
|
1522
2010
|
expectedTurnId: z.string().uuid(),
|
|
1523
2011
|
revision: z.string().min(1),
|
|
@@ -1584,8 +2072,7 @@ const runtimeEventSchema = z.discriminatedUnion("type", [
|
|
|
1584
2072
|
}),
|
|
1585
2073
|
z.object({
|
|
1586
2074
|
type: z.literal("context.required"),
|
|
1587
|
-
pendingTurnIds: z.array(id).max(2).optional()
|
|
1588
|
-
historyOnly: z.boolean().optional()
|
|
2075
|
+
pendingTurnIds: z.array(id).max(2).optional()
|
|
1589
2076
|
}),
|
|
1590
2077
|
z.object({ type: z.literal("turn.acknowledged") }),
|
|
1591
2078
|
z.object({
|
|
@@ -1650,7 +2137,8 @@ const runtimeCommandSchema = z.discriminatedUnion("type", [
|
|
|
1650
2137
|
sessionId: id,
|
|
1651
2138
|
turnId: id,
|
|
1652
2139
|
harness: z.enum(["pi", "codex"])
|
|
1653
|
-
})
|
|
2140
|
+
}),
|
|
2141
|
+
traceContext: runtimeTraceContextSchema.optional()
|
|
1654
2142
|
}),
|
|
1655
2143
|
z.object({
|
|
1656
2144
|
type: z.literal("turn.start"),
|
|
@@ -1672,6 +2160,8 @@ const runtimeCommandSchema = z.discriminatedUnion("type", [
|
|
|
1672
2160
|
provider: z.string().nullable().optional(),
|
|
1673
2161
|
model: z.string().nullable().optional(),
|
|
1674
2162
|
thinkingLevel: z.string().nullable().optional(),
|
|
2163
|
+
requestId: z.string().regex(/^[a-zA-Z0-9._:-]{1,128}$/).nullable().optional(),
|
|
2164
|
+
traceContext: runtimeTraceContextSchema.optional(),
|
|
1675
2165
|
accessMode: z.enum(["read_only", "full_access"])
|
|
1676
2166
|
}).refine((input) => {
|
|
1677
2167
|
const owner = input.messages.at(-1);
|
|
@@ -2834,50 +3324,97 @@ var ParentBridgeTransport = class {
|
|
|
2834
3324
|
});
|
|
2835
3325
|
}
|
|
2836
3326
|
};
|
|
2837
|
-
/**
|
|
2838
|
-
* Broker-mode transport for standalone-deployed apps. Opens a popup window to
|
|
2839
|
-
* the Cohub auth broker page, performs a ready-handshake, sends the request via
|
|
2840
|
-
* postMessage, and resolves with the broker's response. The popup is closed
|
|
2841
|
-
* after a single request is fulfilled (one-shot, per §7.2 of the plan).
|
|
2842
|
-
*
|
|
2843
|
-
* Non-interactive messages (`context`, `checkout-state`) are answered locally
|
|
2844
|
-
* without opening a popup — the app already knows its own appId, and
|
|
2845
|
-
* checkout state is not available on the app's own origin in broker mode.
|
|
2846
|
-
*/
|
|
2847
3327
|
var PopupBrokerTransport = class {
|
|
2848
3328
|
supportsNavigation = false;
|
|
3329
|
+
memoryOnly = true;
|
|
2849
3330
|
brokerOrigin;
|
|
2850
3331
|
appId;
|
|
2851
3332
|
getAppId;
|
|
3333
|
+
app;
|
|
3334
|
+
getApp;
|
|
3335
|
+
viewerGrants = [];
|
|
3336
|
+
viewerIdentity;
|
|
3337
|
+
getViewerIdentity() {
|
|
3338
|
+
return this.viewerIdentity;
|
|
3339
|
+
}
|
|
3340
|
+
observeViewerIdentity(next) {
|
|
3341
|
+
if (next !== void 0 && this.viewerIdentity !== void 0 && next !== this.viewerIdentity) this.viewerGrants = [];
|
|
3342
|
+
if (next !== void 0) this.viewerIdentity = next;
|
|
3343
|
+
}
|
|
2852
3344
|
constructor(config) {
|
|
2853
3345
|
this.brokerOrigin = config.brokerOrigin;
|
|
2854
3346
|
this.appId = config.appId;
|
|
2855
3347
|
this.getAppId = config.getAppId;
|
|
2856
|
-
|
|
3348
|
+
this.app = config.app;
|
|
3349
|
+
this.getApp = config.getApp;
|
|
3350
|
+
if (!this.app && this.getApp) this.getApp();
|
|
3351
|
+
else if (!this.appId && this.getAppId) this.getAppId();
|
|
3352
|
+
}
|
|
3353
|
+
async resolveApp() {
|
|
3354
|
+
if (this.app) return this.app;
|
|
3355
|
+
const resolved = await this.getApp?.();
|
|
3356
|
+
if (resolved) return resolved;
|
|
3357
|
+
const appId = this.appId ?? await this.getAppId?.() ?? null;
|
|
3358
|
+
return appId ? {
|
|
3359
|
+
id: appId,
|
|
3360
|
+
slug: "",
|
|
3361
|
+
url: null,
|
|
3362
|
+
homeSpace: {
|
|
3363
|
+
id: "",
|
|
3364
|
+
name: null
|
|
3365
|
+
},
|
|
3366
|
+
appScopes: []
|
|
3367
|
+
} : null;
|
|
2857
3368
|
}
|
|
2858
|
-
resolveAppId() {
|
|
2859
|
-
|
|
2860
|
-
if (this.getAppId) return this.getAppId();
|
|
2861
|
-
return Promise.resolve(null);
|
|
3369
|
+
async resolveAppId() {
|
|
3370
|
+
return (await this.resolveApp())?.id ?? null;
|
|
2862
3371
|
}
|
|
2863
3372
|
async request(message, options) {
|
|
2864
|
-
if (message.type === "cohub.app.context")
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
3373
|
+
if (message.type === "cohub.app.context") {
|
|
3374
|
+
const app = await this.resolveApp();
|
|
3375
|
+
if (!app) return null;
|
|
3376
|
+
const viewerScopes = Array.from(new Set(this.viewerGrants.flatMap((grant) => grant.scopes)));
|
|
3377
|
+
return {
|
|
3378
|
+
type: "cohub.app.context.result",
|
|
3379
|
+
context: {
|
|
3380
|
+
capabilities: {
|
|
3381
|
+
authorization: 2,
|
|
3382
|
+
serverGrants: true
|
|
3383
|
+
},
|
|
3384
|
+
mode: "broker",
|
|
3385
|
+
app: {
|
|
3386
|
+
id: app.id,
|
|
3387
|
+
slug: app.slug,
|
|
3388
|
+
url: app.url,
|
|
3389
|
+
homeSpace: { ...app.homeSpace }
|
|
3390
|
+
},
|
|
3391
|
+
space: {
|
|
3392
|
+
id: app.homeSpace.id,
|
|
3393
|
+
name: app.homeSpace.name
|
|
3394
|
+
},
|
|
3395
|
+
...this.viewerIdentity !== void 0 ? { viewer: this.viewerIdentity ? { userUuid: this.viewerIdentity } : null } : {},
|
|
3396
|
+
invocation: {
|
|
3397
|
+
surface: "broker",
|
|
3398
|
+
source: "route"
|
|
3399
|
+
},
|
|
3400
|
+
shell: {
|
|
3401
|
+
surface: "broker",
|
|
3402
|
+
space: null,
|
|
3403
|
+
session: null,
|
|
3404
|
+
turn: null
|
|
3405
|
+
},
|
|
3406
|
+
permissions: {
|
|
3407
|
+
scopes: Array.from(/* @__PURE__ */ new Set([...app.appScopes, ...viewerScopes])),
|
|
3408
|
+
appScopes: [...app.appScopes],
|
|
3409
|
+
viewerScopes,
|
|
3410
|
+
viewerGrants: this.viewerGrants.map((grant) => ({
|
|
3411
|
+
...grant,
|
|
3412
|
+
scopes: [...grant.scopes]
|
|
3413
|
+
}))
|
|
3414
|
+
}
|
|
2878
3415
|
}
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
2881
3418
|
if (message.type === "cohub.app.checkout-state") return {
|
|
2882
3419
|
type: "cohub.app.checkout-state.result",
|
|
2883
3420
|
status: null,
|
|
@@ -2894,7 +3431,7 @@ var PopupBrokerTransport = class {
|
|
|
2894
3431
|
}
|
|
2895
3432
|
const appOrigin = window.location.origin;
|
|
2896
3433
|
const brokerUrl = `${this.brokerOrigin}/app-auth?app=${encodeURIComponent(appId)}&origin=${encodeURIComponent(appOrigin)}`;
|
|
2897
|
-
const popup = window.open(brokerUrl,
|
|
3434
|
+
const popup = window.open(brokerUrl, `cohub-app-auth-${appId}`, "popup,width=480,height=640");
|
|
2898
3435
|
if (!popup) {
|
|
2899
3436
|
reject(/* @__PURE__ */ new Error("Failed to open authorization window. Please allow popups for this site."));
|
|
2900
3437
|
return;
|
|
@@ -2933,6 +3470,8 @@ var PopupBrokerTransport = class {
|
|
|
2933
3470
|
return;
|
|
2934
3471
|
}
|
|
2935
3472
|
if (data.type === "cohub.app.broker.ready" && !ready) {
|
|
3473
|
+
const brokerViewer = data.viewer;
|
|
3474
|
+
this.observeViewerIdentity(brokerViewer === null ? null : typeof brokerViewer?.userUuid === "string" ? brokerViewer.userUuid : void 0);
|
|
2936
3475
|
if (message.type === "cohub.app.authorize.v2" && data.authorizationVersion !== 2) {
|
|
2937
3476
|
finish(() => reject(new AppRuntimeError("unsupported", "This host does not support structured authorization.")));
|
|
2938
3477
|
return;
|
|
@@ -2956,6 +3495,29 @@ var PopupBrokerTransport = class {
|
|
|
2956
3495
|
reject(new AppRuntimeError(data.code ?? "request_failed", data.message, requestId));
|
|
2957
3496
|
return;
|
|
2958
3497
|
}
|
|
3498
|
+
const rawResult = data.result;
|
|
3499
|
+
if (message.type === "cohub.app.authorize.v2") {
|
|
3500
|
+
const parsedResult = appAuthorizationResultSchema.safeParse(rawResult);
|
|
3501
|
+
if (!parsedResult.success) {
|
|
3502
|
+
reject(new AppRuntimeError("invalid_response", "Invalid authorization result.", requestId));
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
const authorization = parsedResult.data;
|
|
3506
|
+
if (authorization.status === "granted") {
|
|
3507
|
+
const token = data.token;
|
|
3508
|
+
if (typeof token !== "string" || !token) {
|
|
3509
|
+
reject(new AppRuntimeError("invalid_response", "Authorization token is missing.", requestId));
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const knownScopes = new Set(PERMISSIONS);
|
|
3513
|
+
const scopes = authorization.grant.scopes.filter((scope) => knownScopes.has(scope));
|
|
3514
|
+
const existing = this.viewerGrants.filter((grant) => grant.spaceId !== authorization.grant.spaceId);
|
|
3515
|
+
this.viewerGrants = [...existing, {
|
|
3516
|
+
spaceId: authorization.grant.spaceId,
|
|
3517
|
+
scopes
|
|
3518
|
+
}];
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
2959
3521
|
resolve(data);
|
|
2960
3522
|
});
|
|
2961
3523
|
};
|
|
@@ -3123,7 +3685,7 @@ var AppRuntimeApi = class {
|
|
|
3123
3685
|
return this.storageKeysReady;
|
|
3124
3686
|
}
|
|
3125
3687
|
readStoredToken() {
|
|
3126
|
-
if (this.transport
|
|
3688
|
+
if (this.transport.memoryOnly) {
|
|
3127
3689
|
this.writeStoredToken(null);
|
|
3128
3690
|
return null;
|
|
3129
3691
|
}
|
|
@@ -3135,7 +3697,7 @@ var AppRuntimeApi = class {
|
|
|
3135
3697
|
}
|
|
3136
3698
|
}
|
|
3137
3699
|
writeStoredToken(token) {
|
|
3138
|
-
if (this.transport
|
|
3700
|
+
if (this.transport.memoryOnly) token = null;
|
|
3139
3701
|
if (!this.tokenStorageKey || typeof localStorage === "undefined") return;
|
|
3140
3702
|
try {
|
|
3141
3703
|
if (token) localStorage.setItem(this.tokenStorageKey, token);
|
|
@@ -3143,6 +3705,12 @@ var AppRuntimeApi = class {
|
|
|
3143
3705
|
} catch {}
|
|
3144
3706
|
}
|
|
3145
3707
|
readStoredGrants() {
|
|
3708
|
+
if (this.transport.memoryOnly) {
|
|
3709
|
+
if (this.grantsStorageKey && typeof localStorage !== "undefined") try {
|
|
3710
|
+
localStorage.removeItem(this.grantsStorageKey);
|
|
3711
|
+
} catch {}
|
|
3712
|
+
return null;
|
|
3713
|
+
}
|
|
3146
3714
|
if (!this.grantsStorageKey || typeof localStorage === "undefined") return null;
|
|
3147
3715
|
try {
|
|
3148
3716
|
const raw = localStorage.getItem(this.grantsStorageKey);
|
|
@@ -3155,6 +3723,7 @@ var AppRuntimeApi = class {
|
|
|
3155
3723
|
}
|
|
3156
3724
|
}
|
|
3157
3725
|
writeStoredGrants(grants) {
|
|
3726
|
+
if (this.transport.memoryOnly) return;
|
|
3158
3727
|
if (!this.grantsStorageKey || typeof localStorage === "undefined") return;
|
|
3159
3728
|
try {
|
|
3160
3729
|
if (grants && grants.length > 0) localStorage.setItem(this.grantsStorageKey, JSON.stringify(grants));
|
|
@@ -3163,12 +3732,11 @@ var AppRuntimeApi = class {
|
|
|
3163
3732
|
}
|
|
3164
3733
|
serverGrants = false;
|
|
3165
3734
|
viewerIdentity;
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
const viewer = context.viewer?.userUuid ?? null;
|
|
3735
|
+
observeTransportIdentity() {
|
|
3736
|
+
const viewer = this.transport.getViewerIdentity?.();
|
|
3737
|
+
if (viewer !== void 0) this.observeViewer(viewer);
|
|
3738
|
+
}
|
|
3739
|
+
observeViewer(viewer) {
|
|
3172
3740
|
if (this.viewerIdentity !== void 0 && viewer !== this.viewerIdentity) {
|
|
3173
3741
|
this.identityVersion++;
|
|
3174
3742
|
this.token = null;
|
|
@@ -3178,7 +3746,15 @@ var AppRuntimeApi = class {
|
|
|
3178
3746
|
}
|
|
3179
3747
|
this.viewerIdentity = viewer;
|
|
3180
3748
|
}
|
|
3749
|
+
identityVersion = 0;
|
|
3750
|
+
contextUnsubscribe = null;
|
|
3751
|
+
observeContext(context) {
|
|
3752
|
+
if (context.capabilities?.serverGrants) this.serverGrants = true;
|
|
3753
|
+
if (context.viewer === void 0) return;
|
|
3754
|
+
this.observeViewer(context.viewer?.userUuid ?? null);
|
|
3755
|
+
}
|
|
3181
3756
|
async context() {
|
|
3757
|
+
this.observeTransportIdentity();
|
|
3182
3758
|
const response = await this.transport.request({ type: "cohub.app.context" }, {
|
|
3183
3759
|
timeoutMs: 8e3,
|
|
3184
3760
|
retryIntervalMs: 250
|
|
@@ -3264,6 +3840,7 @@ var AppRuntimeApi = class {
|
|
|
3264
3840
|
}
|
|
3265
3841
|
async getAccessToken(options) {
|
|
3266
3842
|
await this.ensureStorageKeys();
|
|
3843
|
+
this.observeTransportIdentity();
|
|
3267
3844
|
const identityVersion = this.identityVersion;
|
|
3268
3845
|
if (this.token && !options?.forceRefresh) return this.token;
|
|
3269
3846
|
if (options?.forceRefresh) {
|
|
@@ -3279,14 +3856,16 @@ var AppRuntimeApi = class {
|
|
|
3279
3856
|
scopes: grant.scopes,
|
|
3280
3857
|
spaceId: grant.spaceId
|
|
3281
3858
|
}, { timeoutMs: 12e4 });
|
|
3282
|
-
|
|
3859
|
+
this.observeTransportIdentity();
|
|
3860
|
+
if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) return null;
|
|
3283
3861
|
const token = response?.token ?? null;
|
|
3284
3862
|
if (!token) continue;
|
|
3285
3863
|
this.token = token;
|
|
3286
3864
|
next = recordConsent(next, responseSpaceId(response?.space?.id) ?? grant.spaceId, grant.scopes);
|
|
3287
3865
|
refreshed = true;
|
|
3288
3866
|
} catch {
|
|
3289
|
-
|
|
3867
|
+
this.observeTransportIdentity();
|
|
3868
|
+
if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) return null;
|
|
3290
3869
|
next = recordConsent(next, grant.spaceId, grant.scopes);
|
|
3291
3870
|
}
|
|
3292
3871
|
this.authorizedGrants = next;
|
|
@@ -3300,7 +3879,8 @@ var AppRuntimeApi = class {
|
|
|
3300
3879
|
type: "cohub.app.token",
|
|
3301
3880
|
forceRefresh: Boolean(options?.forceRefresh)
|
|
3302
3881
|
}, { timeoutMs: 2e4 });
|
|
3303
|
-
|
|
3882
|
+
this.observeTransportIdentity();
|
|
3883
|
+
if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) return null;
|
|
3304
3884
|
this.token = response?.token ?? null;
|
|
3305
3885
|
this.writeStoredToken(this.token);
|
|
3306
3886
|
return this.token;
|
|
@@ -3317,7 +3897,8 @@ var AppRuntimeApi = class {
|
|
|
3317
3897
|
type: "cohub.app.authorize.v2",
|
|
3318
3898
|
...parsed.data
|
|
3319
3899
|
}, { timeoutMs: 12e4 });
|
|
3320
|
-
|
|
3900
|
+
this.observeTransportIdentity();
|
|
3901
|
+
if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) throw new AppRuntimeError("session_changed", "The signed-in account changed. Please try again.");
|
|
3321
3902
|
if (!response) throw new AppRuntimeError("host_timeout", "Authorization host did not respond. It may not support structured authorization.");
|
|
3322
3903
|
const result = appAuthorizationResultSchema.safeParse(response.result);
|
|
3323
3904
|
if (!result.success) throw new AppRuntimeError("invalid_response", "Invalid authorization result.");
|
|
@@ -3343,6 +3924,7 @@ var AppRuntimeApi = class {
|
|
|
3343
3924
|
*/
|
|
3344
3925
|
async requestAuthorization(input) {
|
|
3345
3926
|
await this.ensureStorageKeys();
|
|
3927
|
+
this.observeTransportIdentity();
|
|
3346
3928
|
const response = await this.transport.request({
|
|
3347
3929
|
type: "cohub.app.authorize",
|
|
3348
3930
|
scopes: input.scopes,
|
|
@@ -3350,6 +3932,7 @@ var AppRuntimeApi = class {
|
|
|
3350
3932
|
spaceId: input.spaceId,
|
|
3351
3933
|
alwaysAsk: input.alwaysAsk
|
|
3352
3934
|
}, { timeoutMs: 12e4 });
|
|
3935
|
+
this.observeTransportIdentity();
|
|
3353
3936
|
const token = response?.token ?? null;
|
|
3354
3937
|
if (token) {
|
|
3355
3938
|
this.token = token;
|
|
@@ -3368,6 +3951,7 @@ var AppRuntimeApi = class {
|
|
|
3368
3951
|
*/
|
|
3369
3952
|
async requestSpaceAuthorization(input) {
|
|
3370
3953
|
await this.ensureStorageKeys();
|
|
3954
|
+
this.observeTransportIdentity();
|
|
3371
3955
|
const response = await this.transport.request({
|
|
3372
3956
|
type: "cohub.app.authorize",
|
|
3373
3957
|
scopes: input.scopes,
|
|
@@ -3375,6 +3959,7 @@ var AppRuntimeApi = class {
|
|
|
3375
3959
|
selectSpace: true,
|
|
3376
3960
|
alwaysAsk: input.alwaysAsk
|
|
3377
3961
|
}, { timeoutMs: 12e4 });
|
|
3962
|
+
this.observeTransportIdentity();
|
|
3378
3963
|
const token = response?.token ?? null;
|
|
3379
3964
|
const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
|
|
3380
3965
|
const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
|
|
@@ -3404,12 +3989,14 @@ var AppRuntimeApi = class {
|
|
|
3404
3989
|
*/
|
|
3405
3990
|
async requestCreateSpaceAuthorization(input) {
|
|
3406
3991
|
await this.ensureStorageKeys();
|
|
3992
|
+
this.observeTransportIdentity();
|
|
3407
3993
|
const response = await this.transport.request({
|
|
3408
3994
|
type: "cohub.app.authorize",
|
|
3409
3995
|
scopes: input.scopes,
|
|
3410
3996
|
reason: input.reason,
|
|
3411
3997
|
createSpace: input.space
|
|
3412
3998
|
}, { timeoutMs: 12e4 });
|
|
3999
|
+
this.observeTransportIdentity();
|
|
3413
4000
|
const token = response?.token ?? null;
|
|
3414
4001
|
const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
|
|
3415
4002
|
const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
|
|
@@ -3444,6 +4031,83 @@ var AppRuntimeApi = class {
|
|
|
3444
4031
|
}) ?? null;
|
|
3445
4032
|
}
|
|
3446
4033
|
};
|
|
4034
|
+
/** Resolves complete App identity from the browser's current standalone origin. */
|
|
4035
|
+
function createOriginAppResolver(deps) {
|
|
4036
|
+
let cached = null;
|
|
4037
|
+
return () => {
|
|
4038
|
+
if (cached) return cached;
|
|
4039
|
+
const run = (async () => {
|
|
4040
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
4041
|
+
const origin = deps.origin?.() ?? (typeof window !== "undefined" ? window.location.origin : null);
|
|
4042
|
+
if (typeof doFetch !== "function" || !origin) return null;
|
|
4043
|
+
const response = await doFetch(`${deps.apiBaseUrl}/api/apps/by-origin`, {
|
|
4044
|
+
credentials: "omit",
|
|
4045
|
+
headers: { Accept: "application/json" }
|
|
4046
|
+
});
|
|
4047
|
+
if (response.status === 404) return null;
|
|
4048
|
+
if (!response.ok) throw new Error(`getByOrigin failed: ${response.status}`);
|
|
4049
|
+
const data = await response.json();
|
|
4050
|
+
const app = data?.app;
|
|
4051
|
+
const space = data?.space;
|
|
4052
|
+
if (typeof app?.id !== "string" || typeof app.slug !== "string" || typeof app.spaceId !== "string" || typeof space?.id !== "string" || space.id !== app.spaceId) return null;
|
|
4053
|
+
const knownScopes = new Set(PERMISSIONS);
|
|
4054
|
+
const appScopes = Array.isArray(app.appScopes) ? app.appScopes.filter((scope) => typeof scope === "string" && knownScopes.has(scope)) : [];
|
|
4055
|
+
return {
|
|
4056
|
+
id: app.id,
|
|
4057
|
+
slug: app.slug,
|
|
4058
|
+
url: origin,
|
|
4059
|
+
homeSpace: {
|
|
4060
|
+
id: space.id,
|
|
4061
|
+
name: typeof space.name === "string" ? space.name : null
|
|
4062
|
+
},
|
|
4063
|
+
appScopes
|
|
4064
|
+
};
|
|
4065
|
+
})().catch(() => {
|
|
4066
|
+
cached = null;
|
|
4067
|
+
return null;
|
|
4068
|
+
});
|
|
4069
|
+
cached = run;
|
|
4070
|
+
return run;
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
/**
|
|
4074
|
+
* Defers standalone origin discovery until the runtime is first used.
|
|
4075
|
+
* Apps should initialize `context()` before click-triggered authorization so
|
|
4076
|
+
* discovery does not consume the browser's transient user activation.
|
|
4077
|
+
*/
|
|
4078
|
+
var OriginBrokerTransport = class {
|
|
4079
|
+
brokerOrigin;
|
|
4080
|
+
resolveApp;
|
|
4081
|
+
supportsNavigation = false;
|
|
4082
|
+
memoryOnly = true;
|
|
4083
|
+
transport = null;
|
|
4084
|
+
resolvedTransport = null;
|
|
4085
|
+
getViewerIdentity() {
|
|
4086
|
+
return this.resolvedTransport?.getViewerIdentity();
|
|
4087
|
+
}
|
|
4088
|
+
constructor(brokerOrigin, resolveApp) {
|
|
4089
|
+
this.brokerOrigin = brokerOrigin;
|
|
4090
|
+
this.resolveApp = resolveApp;
|
|
4091
|
+
}
|
|
4092
|
+
resolveTransport() {
|
|
4093
|
+
if (this.transport) return this.transport;
|
|
4094
|
+
const attempt = this.resolveApp().then((app) => app ? new PopupBrokerTransport({
|
|
4095
|
+
brokerOrigin: this.brokerOrigin,
|
|
4096
|
+
app
|
|
4097
|
+
}) : null).then((transport) => {
|
|
4098
|
+
this.resolvedTransport = transport;
|
|
4099
|
+
return transport;
|
|
4100
|
+
}).catch(() => null);
|
|
4101
|
+
this.transport = attempt;
|
|
4102
|
+
attempt.then((transport) => {
|
|
4103
|
+
if (!transport && this.transport === attempt) this.transport = null;
|
|
4104
|
+
});
|
|
4105
|
+
return attempt;
|
|
4106
|
+
}
|
|
4107
|
+
async request(message, options) {
|
|
4108
|
+
return (await this.resolveTransport())?.request(message, options) ?? null;
|
|
4109
|
+
}
|
|
4110
|
+
};
|
|
3447
4111
|
/**
|
|
3448
4112
|
* Builds a memoized appId resolver that reverse-looks-up the appId from the
|
|
3449
4113
|
* public slug triple via `GET /api/apps/by-slug/:username/:spaceSlug/:appSlug`
|
|
@@ -3472,11 +4136,10 @@ function createSlugAppIdResolver(deps) {
|
|
|
3472
4136
|
};
|
|
3473
4137
|
}
|
|
3474
4138
|
/**
|
|
3475
|
-
* Resolves the appropriate transport
|
|
3476
|
-
*
|
|
3477
|
-
* broker; otherwise → bridge (returns null for non-app contexts).
|
|
4139
|
+
* Resolves the appropriate transport. Iframes use the parent bridge;
|
|
4140
|
+
* standalone pages use explicit legacy configuration or Origin discovery.
|
|
3478
4141
|
*/
|
|
3479
|
-
function resolveAppTransport(config, appIdResolver) {
|
|
4142
|
+
function resolveAppTransport(config, appIdResolver, originAppResolver, defaultBrokerOrigin) {
|
|
3480
4143
|
const explicitMode = config?.mode;
|
|
3481
4144
|
const brokerOrigin = config?.brokerOrigin;
|
|
3482
4145
|
const appId = config?.appId;
|
|
@@ -3490,7 +4153,9 @@ function resolveAppTransport(config, appIdResolver) {
|
|
|
3490
4153
|
if (explicitMode === "bridge") return new ParentBridgeTransport();
|
|
3491
4154
|
if (explicitMode === "broker") return createBroker();
|
|
3492
4155
|
if (typeof window !== "undefined" && window.parent !== window) return new ParentBridgeTransport();
|
|
3493
|
-
|
|
4156
|
+
if (hasBrokerConfig) return createBroker();
|
|
4157
|
+
if (originAppResolver && defaultBrokerOrigin) return new OriginBrokerTransport(defaultBrokerOrigin, originAppResolver);
|
|
4158
|
+
return new ParentBridgeTransport();
|
|
3494
4159
|
}
|
|
3495
4160
|
const createAppRuntime = (transport, appId, appIdResolver) => new AppRuntimeApi(transport, appId, appIdResolver);
|
|
3496
4161
|
//#endregion
|
|
@@ -3543,7 +4208,10 @@ var CohubClient = class {
|
|
|
3543
4208
|
spaceSlug: appRuntime.spaceSlug,
|
|
3544
4209
|
appSlug: appRuntime.appSlug
|
|
3545
4210
|
}) : void 0;
|
|
3546
|
-
const appTransport = resolveAppTransport(appRuntime, appIdResolver
|
|
4211
|
+
const appTransport = resolveAppTransport(appRuntime, appIdResolver, createOriginAppResolver({
|
|
4212
|
+
apiBaseUrl,
|
|
4213
|
+
fetch: options.fetch
|
|
4214
|
+
}), resolveWebBaseUrl({ env: options.env }));
|
|
3547
4215
|
this.appRuntime = createAppRuntime(appTransport, appRuntime?.appId, appIdResolver);
|
|
3548
4216
|
const executionToken = resolveExecutionToken();
|
|
3549
4217
|
const getAccessToken = options.getAccessToken ?? (executionToken ? () => executionToken : (tokenOptions) => this.appRuntime.getAccessToken(tokenOptions));
|
|
@@ -3980,6 +4648,20 @@ function clonePermissionScopes(scopes) {
|
|
|
3980
4648
|
return Array.from(scopes ?? []).filter((scope) => typeof scope === "string");
|
|
3981
4649
|
}
|
|
3982
4650
|
/**
|
|
4651
|
+
* Invocation contexts can originate in reactive host state. Copy nested
|
|
4652
|
+
* objects before putting the context on a postMessage payload: a shallow copy
|
|
4653
|
+
* of the invocation itself would leave `embedder` as a framework Proxy.
|
|
4654
|
+
*/
|
|
4655
|
+
function cloneInvocation(invocation) {
|
|
4656
|
+
return {
|
|
4657
|
+
...invocation,
|
|
4658
|
+
...invocation.embedder ? { embedder: {
|
|
4659
|
+
appId: invocation.embedder.appId,
|
|
4660
|
+
slug: invocation.embedder.slug
|
|
4661
|
+
} } : {}
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
/**
|
|
3983
4665
|
* Scopes arriving over postMessage are untrusted: keep only known permission
|
|
3984
4666
|
* names (in first-seen order, deduplicated) so a malicious app cannot push
|
|
3985
4667
|
* arbitrary strings, duplicates, or oversized arrays into the consent dialog.
|
|
@@ -4018,7 +4700,7 @@ const isDefinitiveAuthorizationFailure = (error) => error instanceof AppAuthoriz
|
|
|
4018
4700
|
401,
|
|
4019
4701
|
403,
|
|
4020
4702
|
404
|
|
4021
|
-
].includes(error.status);
|
|
4703
|
+
].includes(error.status) && error.code !== "host_unavailable";
|
|
4022
4704
|
/** A grant the server rejected (revoked / not found) — safe to drop from cache. */
|
|
4023
4705
|
const isGrantRejection = (error) => error instanceof AppAuthorizationError && [403, 404].includes(error.status);
|
|
4024
4706
|
/** A stale access token — never a revoked grant, so never clear the cache. */
|
|
@@ -4250,7 +4932,7 @@ function createAppBridgeCore(config) {
|
|
|
4250
4932
|
},
|
|
4251
4933
|
space: { id: app.spaceId },
|
|
4252
4934
|
viewer: viewerUuid ? { userUuid: viewerUuid } : null,
|
|
4253
|
-
...invocation ? { invocation:
|
|
4935
|
+
...invocation ? { invocation: cloneInvocation(invocation) } : {},
|
|
4254
4936
|
...shell ? { shell: {
|
|
4255
4937
|
...shell,
|
|
4256
4938
|
space: shell.space ? { ...shell.space } : null,
|
|
@@ -4446,20 +5128,25 @@ function createAppBridgeCore(config) {
|
|
|
4446
5128
|
throw new AppLoginRedirect();
|
|
4447
5129
|
}
|
|
4448
5130
|
const authorizingViewer = await getViewerUuid();
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
5131
|
+
let response;
|
|
5132
|
+
try {
|
|
5133
|
+
response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
|
|
5134
|
+
method: "POST",
|
|
5135
|
+
signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
|
|
5136
|
+
headers: {
|
|
5137
|
+
Authorization: `Bearer ${userToken}`,
|
|
5138
|
+
"Content-Type": "application/json"
|
|
5139
|
+
},
|
|
5140
|
+
body: JSON.stringify({
|
|
5141
|
+
scopes,
|
|
5142
|
+
...spaceId ? { spaceId } : {},
|
|
5143
|
+
...options?.silent ? { silent: true } : {},
|
|
5144
|
+
...incremental ? { scopeMode: "extend" } : {}
|
|
5145
|
+
})
|
|
5146
|
+
});
|
|
5147
|
+
} catch (error) {
|
|
5148
|
+
throw new AppAuthorizationError(error instanceof DOMException && error.name === "TimeoutError" ? "Authorization request timed out." : "Authorization request failed.", 503, "host_unavailable");
|
|
5149
|
+
}
|
|
4463
5150
|
const payload = await response.json().catch(() => null);
|
|
4464
5151
|
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
5152
|
if (authorizingViewer !== await getViewerUuid()) throw new AppAuthorizationError("The signed-in account changed. Please try again.", 409, "session_changed");
|
|
@@ -4500,6 +5187,55 @@ function createAppBridgeCore(config) {
|
|
|
4500
5187
|
}
|
|
4501
5188
|
}
|
|
4502
5189
|
/**
|
|
5190
|
+
* Silently renews the approved read-only scopes on the current Shell Space.
|
|
5191
|
+
*
|
|
5192
|
+
* Deciding that a request needs no dialog is the Host's job: it compares the
|
|
5193
|
+
* target against the Space it is actually showing. That check only decides
|
|
5194
|
+
* *whether to ask*; the server still decides *what may be granted* through
|
|
5195
|
+
* the silent path, so a revoked consent can never come back on its own.
|
|
5196
|
+
*/
|
|
5197
|
+
const shellAuthorizationInFlight = /* @__PURE__ */ new Map();
|
|
5198
|
+
const shellAuthorizationCache = /* @__PURE__ */ new Map();
|
|
5199
|
+
const SHELL_AUTH_CACHE_MS = 3e4;
|
|
5200
|
+
async function authorizeShellSilently(scopes, spaceId) {
|
|
5201
|
+
const shellContext = config.getShell?.() ?? config.shell;
|
|
5202
|
+
const shell = shellContext?.space ?? null;
|
|
5203
|
+
if (!shellContext || authorizationContext.surface === "broker" || !shell || shell.id !== spaceId) throw new AppAuthorizationError("Shell authorization is unavailable for this surface.", 403, "consent_required");
|
|
5204
|
+
const viewerUuid = await getViewerUuid();
|
|
5205
|
+
if (!viewerUuid) {
|
|
5206
|
+
await startSignIn();
|
|
5207
|
+
throw new AppLoginRedirect();
|
|
5208
|
+
}
|
|
5209
|
+
const cacheKey = `${viewerUuid}:${app.id}:${spaceId}:${[...scopes].sort().join(",")}`;
|
|
5210
|
+
const cached = shellAuthorizationCache.get(cacheKey);
|
|
5211
|
+
if (cached && cached.expiresAt > Date.now()) return cached.result;
|
|
5212
|
+
if (cached) shellAuthorizationCache.delete(cacheKey);
|
|
5213
|
+
const pending = shellAuthorizationInFlight.get(cacheKey);
|
|
5214
|
+
if (pending) return pending;
|
|
5215
|
+
const request = authorize(scopes, spaceId, { silent: true });
|
|
5216
|
+
shellAuthorizationInFlight.set(cacheKey, request);
|
|
5217
|
+
try {
|
|
5218
|
+
const result = await request;
|
|
5219
|
+
shellAuthorizationCache.set(cacheKey, {
|
|
5220
|
+
result,
|
|
5221
|
+
expiresAt: Date.now() + SHELL_AUTH_CACHE_MS
|
|
5222
|
+
});
|
|
5223
|
+
return result;
|
|
5224
|
+
} finally {
|
|
5225
|
+
shellAuthorizationInFlight.delete(cacheKey);
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
/**
|
|
5229
|
+
* Reads the Shell Space supplied by the current Host. Invocation context is
|
|
5230
|
+
* deliberately excluded: an App may be opened from one Space while the Shell
|
|
5231
|
+
* shows another; the Host/Shell broker remains the authorization authority.
|
|
5232
|
+
*/
|
|
5233
|
+
function resolveShellSpace() {
|
|
5234
|
+
const shell = config.getShell?.() ?? config.shell;
|
|
5235
|
+
if (!shell || shell.surface === "broker") return null;
|
|
5236
|
+
return shell.space;
|
|
5237
|
+
}
|
|
5238
|
+
/**
|
|
4503
5239
|
* Attempts silent renewal against each target in order. Returns true when the
|
|
4504
5240
|
* request was answered (success or terminal error); false when every target
|
|
4505
5241
|
* was a rejected grant, so the caller should fall back to consent.
|
|
@@ -4951,6 +5687,28 @@ function createAppBridgeCore(config) {
|
|
|
4951
5687
|
await startSignIn();
|
|
4952
5688
|
return;
|
|
4953
5689
|
}
|
|
5690
|
+
const shellSpace = resolveShellSpace();
|
|
5691
|
+
const shellTargetId = spaceId ?? shellSpace?.id;
|
|
5692
|
+
if (!alwaysAsk && !selectSpace && Boolean(shellTargetId && (!spaceId || shellSpace?.id === spaceId)) && scopes.every(isAppSilentShellScope) && shellTargetId) try {
|
|
5693
|
+
const result = await authorizeShellSilently(scopes, shellTargetId);
|
|
5694
|
+
if (state.pendingAuth !== reserved) return;
|
|
5695
|
+
setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
|
|
5696
|
+
replyPendingAuth(reserved, authorizeResult(result.token, result.spaceId, shellSpace?.name ?? null));
|
|
5697
|
+
resetDialogState();
|
|
5698
|
+
safeNotify();
|
|
5699
|
+
return;
|
|
5700
|
+
} catch (error) {
|
|
5701
|
+
if (error instanceof AppLoginRedirect) throw error;
|
|
5702
|
+
if (!isDefinitiveAuthorizationFailure(error)) {
|
|
5703
|
+
replyPendingAuth(reserved, {
|
|
5704
|
+
type: "cohub.app.error",
|
|
5705
|
+
message: error instanceof Error ? error.message : "Authorization failed."
|
|
5706
|
+
});
|
|
5707
|
+
resetDialogState();
|
|
5708
|
+
safeNotify();
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
4954
5712
|
const spaceLevel = Boolean(spaceId) || selectSpace || scopes.some((scope) => !isUserLevelPermission(scope));
|
|
4955
5713
|
const lastPicked = readLastPickedSpace();
|
|
4956
5714
|
const contextSpaceId = resolveContextSpaceId();
|
|
@@ -5430,4 +6188,4 @@ function createBoardExtensionRegistry() {
|
|
|
5430
6188
|
}
|
|
5431
6189
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
5432
6190
|
//#endregion
|
|
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 };
|
|
6191
|
+
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, OriginBrokerTransport, 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, createOriginAppResolver, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, fingerprintProjectionTurns, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isProjectionCompaction, 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, projectNativeMessageMeta, projectNativeSession, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebBaseUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeReadySchema, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, serializeProjection, serializeProjectionRecords, setGrantedAppScopes, track, trimProjectionTurnsToCompaction, validateArchiveBoundary };
|