@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/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 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";
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
- if (!this.appId && this.getAppId) this.getAppId();
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
- if (this.appId) return Promise.resolve(this.appId);
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") return {
2865
- type: "cohub.app.context.result",
2866
- context: {
2867
- mode: "broker",
2868
- app: {
2869
- id: await this.resolveAppId() ?? "",
2870
- slug: "",
2871
- url: null
2872
- },
2873
- space: { id: "" },
2874
- permissions: {
2875
- scopes: [],
2876
- appScopes: [],
2877
- viewerScopes: []
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, "cohub-app-auth", "popup,width=480,height=640");
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 instanceof PopupBrokerTransport) {
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 instanceof PopupBrokerTransport) token = null;
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
- identityVersion = 0;
3167
- contextUnsubscribe = null;
3168
- observeContext(context) {
3169
- if (context.capabilities?.serverGrants) this.serverGrants = true;
3170
- if (context.viewer === void 0) return;
3171
- const viewer = context.viewer?.userUuid ?? null;
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
- if (identityVersion !== this.identityVersion) return null;
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
- if (identityVersion !== this.identityVersion) return null;
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
- if (identityVersion !== this.identityVersion) return null;
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
- if (identityVersion !== this.identityVersion) throw new AppRuntimeError("session_changed", "The signed-in account changed. Please try again.");
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 based on the app mode configuration.
3476
- * Auto-detection: inside an iframe → bridge; standalone with broker config →
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
- return hasBrokerConfig ? createBroker() : new ParentBridgeTransport();
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: { ...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
- const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
4450
- method: "POST",
4451
- signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
4452
- headers: {
4453
- Authorization: `Bearer ${userToken}`,
4454
- "Content-Type": "application/json"
4455
- },
4456
- body: JSON.stringify({
4457
- scopes,
4458
- ...spaceId ? { spaceId } : {},
4459
- ...options?.silent ? { silent: true } : {},
4460
- ...incremental ? { scopeMode: "extend" } : {}
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 };