@neta-art/cohub 8.18.0 → 8.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,
@@ -1483,6 +1943,7 @@ function contextToPiMessages(messages, options = {}) {
1483
1943
  //#region ../protocol/dist/runtime/index.js
1484
1944
  const RUNTIME_PROTOCOL_VERSION = 1;
1485
1945
  const RUNTIME_MAX_FRAME_BYTES = 33554432;
1946
+ const RUNTIME_RECOVERY_BATCH_SIZE = 64;
1486
1947
  z.enum([
1487
1948
  "cohub",
1488
1949
  "pi",
@@ -1502,8 +1963,14 @@ const runtimeCapabilitiesSchema = z.object({
1502
1963
  name: z.string().max(255)
1503
1964
  })).max(2e3)
1504
1965
  });
1966
+ const runtimePendingExecutionSchema = z.object({
1967
+ sessionId: z.string().uuid(),
1968
+ turnId: z.string().uuid(),
1969
+ harness: z.enum(["pi", "codex"])
1970
+ }).strict();
1505
1971
  z.object({
1506
1972
  connectionId: z.string().uuid(),
1973
+ runtimeId: z.string().uuid().optional(),
1507
1974
  endpoint: z.url({ protocol: /^wss?$/ }),
1508
1975
  ownerUserId: z.string().min(1),
1509
1976
  capabilities: runtimeCapabilitiesSchema
@@ -1512,7 +1979,35 @@ const runtimeReadySchema = z.object({
1512
1979
  type: z.literal("runtime.ready"),
1513
1980
  connectionId: z.string().uuid()
1514
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
+ });
1515
2009
  z.object({
2010
+ expectedTurnId: z.string().uuid(),
1516
2011
  revision: z.string().min(1),
1517
2012
  confirmed: z.literal(true)
1518
2013
  }).strict();
@@ -1577,8 +2072,7 @@ const runtimeEventSchema = z.discriminatedUnion("type", [
1577
2072
  }),
1578
2073
  z.object({
1579
2074
  type: z.literal("context.required"),
1580
- pendingTurnIds: z.array(id).max(2).optional(),
1581
- historyOnly: z.boolean().optional()
2075
+ pendingTurnIds: z.array(id).max(2).optional()
1582
2076
  }),
1583
2077
  z.object({ type: z.literal("turn.acknowledged") }),
1584
2078
  z.object({
@@ -1594,7 +2088,11 @@ z.discriminatedUnion("type", [
1594
2088
  spaceId: id,
1595
2089
  token: z.string().min(1).max(16384),
1596
2090
  capabilities: runtimeCapabilitiesSchema
1597
- }),
2091
+ }).strict(),
2092
+ z.object({
2093
+ type: z.literal("runtime.recovery"),
2094
+ executions: z.array(runtimePendingExecutionSchema).min(1).max(64)
2095
+ }).strict(),
1598
2096
  z.object({ type: z.literal("runtime.heartbeat") }),
1599
2097
  z.object({
1600
2098
  type: z.literal("runtime.auth"),
@@ -1639,7 +2137,8 @@ const runtimeCommandSchema = z.discriminatedUnion("type", [
1639
2137
  sessionId: id,
1640
2138
  turnId: id,
1641
2139
  harness: z.enum(["pi", "codex"])
1642
- })
2140
+ }),
2141
+ traceContext: runtimeTraceContextSchema.optional()
1643
2142
  }),
1644
2143
  z.object({
1645
2144
  type: z.literal("turn.start"),
@@ -1661,6 +2160,8 @@ const runtimeCommandSchema = z.discriminatedUnion("type", [
1661
2160
  provider: z.string().nullable().optional(),
1662
2161
  model: z.string().nullable().optional(),
1663
2162
  thinkingLevel: z.string().nullable().optional(),
2163
+ requestId: z.string().regex(/^[a-zA-Z0-9._:-]{1,128}$/).nullable().optional(),
2164
+ traceContext: runtimeTraceContextSchema.optional(),
1664
2165
  accessMode: z.enum(["read_only", "full_access"])
1665
2166
  }).refine((input) => {
1666
2167
  const owner = input.messages.at(-1);
@@ -2823,50 +3324,97 @@ var ParentBridgeTransport = class {
2823
3324
  });
2824
3325
  }
2825
3326
  };
2826
- /**
2827
- * Broker-mode transport for standalone-deployed apps. Opens a popup window to
2828
- * the Cohub auth broker page, performs a ready-handshake, sends the request via
2829
- * postMessage, and resolves with the broker's response. The popup is closed
2830
- * after a single request is fulfilled (one-shot, per §7.2 of the plan).
2831
- *
2832
- * Non-interactive messages (`context`, `checkout-state`) are answered locally
2833
- * without opening a popup — the app already knows its own appId, and
2834
- * checkout state is not available on the app's own origin in broker mode.
2835
- */
2836
3327
  var PopupBrokerTransport = class {
2837
3328
  supportsNavigation = false;
3329
+ memoryOnly = true;
2838
3330
  brokerOrigin;
2839
3331
  appId;
2840
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
+ }
2841
3344
  constructor(config) {
2842
3345
  this.brokerOrigin = config.brokerOrigin;
2843
3346
  this.appId = config.appId;
2844
3347
  this.getAppId = config.getAppId;
2845
- 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;
2846
3368
  }
2847
- resolveAppId() {
2848
- if (this.appId) return Promise.resolve(this.appId);
2849
- if (this.getAppId) return this.getAppId();
2850
- return Promise.resolve(null);
3369
+ async resolveAppId() {
3370
+ return (await this.resolveApp())?.id ?? null;
2851
3371
  }
2852
3372
  async request(message, options) {
2853
- if (message.type === "cohub.app.context") return {
2854
- type: "cohub.app.context.result",
2855
- context: {
2856
- mode: "broker",
2857
- app: {
2858
- id: await this.resolveAppId() ?? "",
2859
- slug: "",
2860
- url: null
2861
- },
2862
- space: { id: "" },
2863
- permissions: {
2864
- scopes: [],
2865
- appScopes: [],
2866
- 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
+ }
2867
3415
  }
2868
- }
2869
- };
3416
+ };
3417
+ }
2870
3418
  if (message.type === "cohub.app.checkout-state") return {
2871
3419
  type: "cohub.app.checkout-state.result",
2872
3420
  status: null,
@@ -2883,7 +3431,7 @@ var PopupBrokerTransport = class {
2883
3431
  }
2884
3432
  const appOrigin = window.location.origin;
2885
3433
  const brokerUrl = `${this.brokerOrigin}/app-auth?app=${encodeURIComponent(appId)}&origin=${encodeURIComponent(appOrigin)}`;
2886
- 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");
2887
3435
  if (!popup) {
2888
3436
  reject(/* @__PURE__ */ new Error("Failed to open authorization window. Please allow popups for this site."));
2889
3437
  return;
@@ -2922,6 +3470,8 @@ var PopupBrokerTransport = class {
2922
3470
  return;
2923
3471
  }
2924
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);
2925
3475
  if (message.type === "cohub.app.authorize.v2" && data.authorizationVersion !== 2) {
2926
3476
  finish(() => reject(new AppRuntimeError("unsupported", "This host does not support structured authorization.")));
2927
3477
  return;
@@ -2945,6 +3495,29 @@ var PopupBrokerTransport = class {
2945
3495
  reject(new AppRuntimeError(data.code ?? "request_failed", data.message, requestId));
2946
3496
  return;
2947
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
+ }
2948
3521
  resolve(data);
2949
3522
  });
2950
3523
  };
@@ -3112,7 +3685,7 @@ var AppRuntimeApi = class {
3112
3685
  return this.storageKeysReady;
3113
3686
  }
3114
3687
  readStoredToken() {
3115
- if (this.transport instanceof PopupBrokerTransport) {
3688
+ if (this.transport.memoryOnly) {
3116
3689
  this.writeStoredToken(null);
3117
3690
  return null;
3118
3691
  }
@@ -3124,7 +3697,7 @@ var AppRuntimeApi = class {
3124
3697
  }
3125
3698
  }
3126
3699
  writeStoredToken(token) {
3127
- if (this.transport instanceof PopupBrokerTransport) token = null;
3700
+ if (this.transport.memoryOnly) token = null;
3128
3701
  if (!this.tokenStorageKey || typeof localStorage === "undefined") return;
3129
3702
  try {
3130
3703
  if (token) localStorage.setItem(this.tokenStorageKey, token);
@@ -3132,6 +3705,12 @@ var AppRuntimeApi = class {
3132
3705
  } catch {}
3133
3706
  }
3134
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
+ }
3135
3714
  if (!this.grantsStorageKey || typeof localStorage === "undefined") return null;
3136
3715
  try {
3137
3716
  const raw = localStorage.getItem(this.grantsStorageKey);
@@ -3144,6 +3723,7 @@ var AppRuntimeApi = class {
3144
3723
  }
3145
3724
  }
3146
3725
  writeStoredGrants(grants) {
3726
+ if (this.transport.memoryOnly) return;
3147
3727
  if (!this.grantsStorageKey || typeof localStorage === "undefined") return;
3148
3728
  try {
3149
3729
  if (grants && grants.length > 0) localStorage.setItem(this.grantsStorageKey, JSON.stringify(grants));
@@ -3152,12 +3732,11 @@ var AppRuntimeApi = class {
3152
3732
  }
3153
3733
  serverGrants = false;
3154
3734
  viewerIdentity;
3155
- identityVersion = 0;
3156
- contextUnsubscribe = null;
3157
- observeContext(context) {
3158
- if (context.capabilities?.serverGrants) this.serverGrants = true;
3159
- if (context.viewer === void 0) return;
3160
- const viewer = context.viewer?.userUuid ?? null;
3735
+ observeTransportIdentity() {
3736
+ const viewer = this.transport.getViewerIdentity?.();
3737
+ if (viewer !== void 0) this.observeViewer(viewer);
3738
+ }
3739
+ observeViewer(viewer) {
3161
3740
  if (this.viewerIdentity !== void 0 && viewer !== this.viewerIdentity) {
3162
3741
  this.identityVersion++;
3163
3742
  this.token = null;
@@ -3167,7 +3746,15 @@ var AppRuntimeApi = class {
3167
3746
  }
3168
3747
  this.viewerIdentity = viewer;
3169
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
+ }
3170
3756
  async context() {
3757
+ this.observeTransportIdentity();
3171
3758
  const response = await this.transport.request({ type: "cohub.app.context" }, {
3172
3759
  timeoutMs: 8e3,
3173
3760
  retryIntervalMs: 250
@@ -3253,6 +3840,7 @@ var AppRuntimeApi = class {
3253
3840
  }
3254
3841
  async getAccessToken(options) {
3255
3842
  await this.ensureStorageKeys();
3843
+ this.observeTransportIdentity();
3256
3844
  const identityVersion = this.identityVersion;
3257
3845
  if (this.token && !options?.forceRefresh) return this.token;
3258
3846
  if (options?.forceRefresh) {
@@ -3268,14 +3856,16 @@ var AppRuntimeApi = class {
3268
3856
  scopes: grant.scopes,
3269
3857
  spaceId: grant.spaceId
3270
3858
  }, { timeoutMs: 12e4 });
3271
- if (identityVersion !== this.identityVersion) return null;
3859
+ this.observeTransportIdentity();
3860
+ if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) return null;
3272
3861
  const token = response?.token ?? null;
3273
3862
  if (!token) continue;
3274
3863
  this.token = token;
3275
3864
  next = recordConsent(next, responseSpaceId(response?.space?.id) ?? grant.spaceId, grant.scopes);
3276
3865
  refreshed = true;
3277
3866
  } catch {
3278
- if (identityVersion !== this.identityVersion) return null;
3867
+ this.observeTransportIdentity();
3868
+ if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) return null;
3279
3869
  next = recordConsent(next, grant.spaceId, grant.scopes);
3280
3870
  }
3281
3871
  this.authorizedGrants = next;
@@ -3289,7 +3879,8 @@ var AppRuntimeApi = class {
3289
3879
  type: "cohub.app.token",
3290
3880
  forceRefresh: Boolean(options?.forceRefresh)
3291
3881
  }, { timeoutMs: 2e4 });
3292
- if (identityVersion !== this.identityVersion) return null;
3882
+ this.observeTransportIdentity();
3883
+ if (identityVersion !== this.identityVersion && !this.transport.memoryOnly) return null;
3293
3884
  this.token = response?.token ?? null;
3294
3885
  this.writeStoredToken(this.token);
3295
3886
  return this.token;
@@ -3306,7 +3897,8 @@ var AppRuntimeApi = class {
3306
3897
  type: "cohub.app.authorize.v2",
3307
3898
  ...parsed.data
3308
3899
  }, { timeoutMs: 12e4 });
3309
- 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.");
3310
3902
  if (!response) throw new AppRuntimeError("host_timeout", "Authorization host did not respond. It may not support structured authorization.");
3311
3903
  const result = appAuthorizationResultSchema.safeParse(response.result);
3312
3904
  if (!result.success) throw new AppRuntimeError("invalid_response", "Invalid authorization result.");
@@ -3332,6 +3924,7 @@ var AppRuntimeApi = class {
3332
3924
  */
3333
3925
  async requestAuthorization(input) {
3334
3926
  await this.ensureStorageKeys();
3927
+ this.observeTransportIdentity();
3335
3928
  const response = await this.transport.request({
3336
3929
  type: "cohub.app.authorize",
3337
3930
  scopes: input.scopes,
@@ -3339,6 +3932,7 @@ var AppRuntimeApi = class {
3339
3932
  spaceId: input.spaceId,
3340
3933
  alwaysAsk: input.alwaysAsk
3341
3934
  }, { timeoutMs: 12e4 });
3935
+ this.observeTransportIdentity();
3342
3936
  const token = response?.token ?? null;
3343
3937
  if (token) {
3344
3938
  this.token = token;
@@ -3357,6 +3951,7 @@ var AppRuntimeApi = class {
3357
3951
  */
3358
3952
  async requestSpaceAuthorization(input) {
3359
3953
  await this.ensureStorageKeys();
3954
+ this.observeTransportIdentity();
3360
3955
  const response = await this.transport.request({
3361
3956
  type: "cohub.app.authorize",
3362
3957
  scopes: input.scopes,
@@ -3364,6 +3959,7 @@ var AppRuntimeApi = class {
3364
3959
  selectSpace: true,
3365
3960
  alwaysAsk: input.alwaysAsk
3366
3961
  }, { timeoutMs: 12e4 });
3962
+ this.observeTransportIdentity();
3367
3963
  const token = response?.token ?? null;
3368
3964
  const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
3369
3965
  const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
@@ -3393,12 +3989,14 @@ var AppRuntimeApi = class {
3393
3989
  */
3394
3990
  async requestCreateSpaceAuthorization(input) {
3395
3991
  await this.ensureStorageKeys();
3992
+ this.observeTransportIdentity();
3396
3993
  const response = await this.transport.request({
3397
3994
  type: "cohub.app.authorize",
3398
3995
  scopes: input.scopes,
3399
3996
  reason: input.reason,
3400
3997
  createSpace: input.space
3401
3998
  }, { timeoutMs: 12e4 });
3999
+ this.observeTransportIdentity();
3402
4000
  const token = response?.token ?? null;
3403
4001
  const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
3404
4002
  const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
@@ -3433,6 +4031,83 @@ var AppRuntimeApi = class {
3433
4031
  }) ?? null;
3434
4032
  }
3435
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
+ };
3436
4111
  /**
3437
4112
  * Builds a memoized appId resolver that reverse-looks-up the appId from the
3438
4113
  * public slug triple via `GET /api/apps/by-slug/:username/:spaceSlug/:appSlug`
@@ -3461,11 +4136,10 @@ function createSlugAppIdResolver(deps) {
3461
4136
  };
3462
4137
  }
3463
4138
  /**
3464
- * Resolves the appropriate transport based on the app mode configuration.
3465
- * Auto-detection: inside an iframe → bridge; standalone with broker config →
3466
- * 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.
3467
4141
  */
3468
- function resolveAppTransport(config, appIdResolver) {
4142
+ function resolveAppTransport(config, appIdResolver, originAppResolver, defaultBrokerOrigin) {
3469
4143
  const explicitMode = config?.mode;
3470
4144
  const brokerOrigin = config?.brokerOrigin;
3471
4145
  const appId = config?.appId;
@@ -3479,7 +4153,9 @@ function resolveAppTransport(config, appIdResolver) {
3479
4153
  if (explicitMode === "bridge") return new ParentBridgeTransport();
3480
4154
  if (explicitMode === "broker") return createBroker();
3481
4155
  if (typeof window !== "undefined" && window.parent !== window) return new ParentBridgeTransport();
3482
- return hasBrokerConfig ? createBroker() : new ParentBridgeTransport();
4156
+ if (hasBrokerConfig) return createBroker();
4157
+ if (originAppResolver && defaultBrokerOrigin) return new OriginBrokerTransport(defaultBrokerOrigin, originAppResolver);
4158
+ return new ParentBridgeTransport();
3483
4159
  }
3484
4160
  const createAppRuntime = (transport, appId, appIdResolver) => new AppRuntimeApi(transport, appId, appIdResolver);
3485
4161
  //#endregion
@@ -3532,7 +4208,10 @@ var CohubClient = class {
3532
4208
  spaceSlug: appRuntime.spaceSlug,
3533
4209
  appSlug: appRuntime.appSlug
3534
4210
  }) : void 0;
3535
- const appTransport = resolveAppTransport(appRuntime, appIdResolver);
4211
+ const appTransport = resolveAppTransport(appRuntime, appIdResolver, createOriginAppResolver({
4212
+ apiBaseUrl,
4213
+ fetch: options.fetch
4214
+ }), resolveWebBaseUrl({ env: options.env }));
3536
4215
  this.appRuntime = createAppRuntime(appTransport, appRuntime?.appId, appIdResolver);
3537
4216
  const executionToken = resolveExecutionToken();
3538
4217
  const getAccessToken = options.getAccessToken ?? (executionToken ? () => executionToken : (tokenOptions) => this.appRuntime.getAccessToken(tokenOptions));
@@ -4007,7 +4686,7 @@ const isDefinitiveAuthorizationFailure = (error) => error instanceof AppAuthoriz
4007
4686
  401,
4008
4687
  403,
4009
4688
  404
4010
- ].includes(error.status);
4689
+ ].includes(error.status) && error.code !== "host_unavailable";
4011
4690
  /** A grant the server rejected (revoked / not found) — safe to drop from cache. */
4012
4691
  const isGrantRejection = (error) => error instanceof AppAuthorizationError && [403, 404].includes(error.status);
4013
4692
  /** A stale access token — never a revoked grant, so never clear the cache. */
@@ -4435,20 +5114,25 @@ function createAppBridgeCore(config) {
4435
5114
  throw new AppLoginRedirect();
4436
5115
  }
4437
5116
  const authorizingViewer = await getViewerUuid();
4438
- const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
4439
- method: "POST",
4440
- signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
4441
- headers: {
4442
- Authorization: `Bearer ${userToken}`,
4443
- "Content-Type": "application/json"
4444
- },
4445
- body: JSON.stringify({
4446
- scopes,
4447
- ...spaceId ? { spaceId } : {},
4448
- ...options?.silent ? { silent: true } : {},
4449
- ...incremental ? { scopeMode: "extend" } : {}
4450
- })
4451
- });
5117
+ let response;
5118
+ try {
5119
+ response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
5120
+ method: "POST",
5121
+ signal: timeoutSignal(AUTHORIZE_TIMEOUT_MS),
5122
+ headers: {
5123
+ Authorization: `Bearer ${userToken}`,
5124
+ "Content-Type": "application/json"
5125
+ },
5126
+ body: JSON.stringify({
5127
+ scopes,
5128
+ ...spaceId ? { spaceId } : {},
5129
+ ...options?.silent ? { silent: true } : {},
5130
+ ...incremental ? { scopeMode: "extend" } : {}
5131
+ })
5132
+ });
5133
+ } catch (error) {
5134
+ throw new AppAuthorizationError(error instanceof DOMException && error.name === "TimeoutError" ? "Authorization request timed out." : "Authorization request failed.", 503, "host_unavailable");
5135
+ }
4452
5136
  const payload = await response.json().catch(() => null);
4453
5137
  if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status, typeof payload?.code === "string" ? payload.code : void 0);
4454
5138
  if (authorizingViewer !== await getViewerUuid()) throw new AppAuthorizationError("The signed-in account changed. Please try again.", 409, "session_changed");
@@ -4489,6 +5173,55 @@ function createAppBridgeCore(config) {
4489
5173
  }
4490
5174
  }
4491
5175
  /**
5176
+ * Silently renews the approved read-only scopes on the current Shell Space.
5177
+ *
5178
+ * Deciding that a request needs no dialog is the Host's job: it compares the
5179
+ * target against the Space it is actually showing. That check only decides
5180
+ * *whether to ask*; the server still decides *what may be granted* through
5181
+ * the silent path, so a revoked consent can never come back on its own.
5182
+ */
5183
+ const shellAuthorizationInFlight = /* @__PURE__ */ new Map();
5184
+ const shellAuthorizationCache = /* @__PURE__ */ new Map();
5185
+ const SHELL_AUTH_CACHE_MS = 3e4;
5186
+ async function authorizeShellSilently(scopes, spaceId) {
5187
+ const shellContext = config.getShell?.() ?? config.shell;
5188
+ const shell = shellContext?.space ?? null;
5189
+ if (!shellContext || authorizationContext.surface === "broker" || !shell || shell.id !== spaceId) throw new AppAuthorizationError("Shell authorization is unavailable for this surface.", 403, "consent_required");
5190
+ const viewerUuid = await getViewerUuid();
5191
+ if (!viewerUuid) {
5192
+ await startSignIn();
5193
+ throw new AppLoginRedirect();
5194
+ }
5195
+ const cacheKey = `${viewerUuid}:${app.id}:${spaceId}:${[...scopes].sort().join(",")}`;
5196
+ const cached = shellAuthorizationCache.get(cacheKey);
5197
+ if (cached && cached.expiresAt > Date.now()) return cached.result;
5198
+ if (cached) shellAuthorizationCache.delete(cacheKey);
5199
+ const pending = shellAuthorizationInFlight.get(cacheKey);
5200
+ if (pending) return pending;
5201
+ const request = authorize(scopes, spaceId, { silent: true });
5202
+ shellAuthorizationInFlight.set(cacheKey, request);
5203
+ try {
5204
+ const result = await request;
5205
+ shellAuthorizationCache.set(cacheKey, {
5206
+ result,
5207
+ expiresAt: Date.now() + SHELL_AUTH_CACHE_MS
5208
+ });
5209
+ return result;
5210
+ } finally {
5211
+ shellAuthorizationInFlight.delete(cacheKey);
5212
+ }
5213
+ }
5214
+ /**
5215
+ * Reads the Shell Space supplied by the current Host. Invocation context is
5216
+ * deliberately excluded: an App may be opened from one Space while the Shell
5217
+ * shows another; the Host/Shell broker remains the authorization authority.
5218
+ */
5219
+ function resolveShellSpace() {
5220
+ const shell = config.getShell?.() ?? config.shell;
5221
+ if (!shell || shell.surface === "broker") return null;
5222
+ return shell.space;
5223
+ }
5224
+ /**
4492
5225
  * Attempts silent renewal against each target in order. Returns true when the
4493
5226
  * request was answered (success or terminal error); false when every target
4494
5227
  * was a rejected grant, so the caller should fall back to consent.
@@ -4940,6 +5673,28 @@ function createAppBridgeCore(config) {
4940
5673
  await startSignIn();
4941
5674
  return;
4942
5675
  }
5676
+ const shellSpace = resolveShellSpace();
5677
+ const shellTargetId = spaceId ?? shellSpace?.id;
5678
+ if (!alwaysAsk && !selectSpace && Boolean(shellTargetId && (!spaceId || shellSpace?.id === spaceId)) && scopes.every(isAppSilentShellScope) && shellTargetId) try {
5679
+ const result = await authorizeShellSilently(scopes, shellTargetId);
5680
+ if (state.pendingAuth !== reserved) return;
5681
+ setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
5682
+ replyPendingAuth(reserved, authorizeResult(result.token, result.spaceId, shellSpace?.name ?? null));
5683
+ resetDialogState();
5684
+ safeNotify();
5685
+ return;
5686
+ } catch (error) {
5687
+ if (error instanceof AppLoginRedirect) throw error;
5688
+ if (!isDefinitiveAuthorizationFailure(error)) {
5689
+ replyPendingAuth(reserved, {
5690
+ type: "cohub.app.error",
5691
+ message: error instanceof Error ? error.message : "Authorization failed."
5692
+ });
5693
+ resetDialogState();
5694
+ safeNotify();
5695
+ return;
5696
+ }
5697
+ }
4943
5698
  const spaceLevel = Boolean(spaceId) || selectSpace || scopes.some((scope) => !isUserLevelPermission(scope));
4944
5699
  const lastPicked = readLastPickedSpace();
4945
5700
  const contextSpaceId = resolveContextSpaceId();
@@ -5419,4 +6174,4 @@ function createBoardExtensionRegistry() {
5419
6174
  }
5420
6175
  const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
5421
6176
  //#endregion
5422
- export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppRuntimeError, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, RUNTIME_ARCHIVE_SEGMENT_BYTES, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, contextToPiMessages, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeReadySchema, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, setGrantedAppScopes, track, validateArchiveBoundary };
6177
+ 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 };