@openchamber/sdk 2.0.4 → 2.1.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/host.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL } from "./api-version.js";
2
2
  import { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_VALUE_BYTES } from "./workspace.js";
3
+ import { isGuestStatusControls } from "./status-controls.js";
4
+ import { isGuestPopoverRequest } from "./popover.js";
3
5
  import { GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITOR_VERSION_MAX, fileEditorPayloadSize, sameFileEditorDocument, } from "./file-editor.js";
4
6
  import { GUEST_FILE_CONTENT_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_REQUEST_TIMEOUT_MS, GUEST_RESOLVE_ERROR_MAX, isGuestFilePath, isGuestRequestPath, clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, clampPromptRequest, clampStartSessionRequest, readHostMessage, isGenerateResult, isFileListResult, isFileReadResult, isFileStatResult, isJsonValue, isFileWriteResult, isServiceStatusResult, isGuestRequestResult, isPromptResult, isStartSessionResult, } from "./contract.js";
5
7
  // Structural, not `instanceof KeyboardEvent`: the frame's own realm owns that
@@ -18,6 +20,16 @@ export class HostRequestError extends Error {
18
20
  // path would otherwise surface only as HOST_TIMEOUT twenty seconds later.
19
21
  const rejectBadPath = () => Promise.reject(new HostRequestError('BAD_PATH', 'Request path must start with "/" and stay on the declared origin.'));
20
22
  const rejectBadFilePath = () => Promise.reject(new HostRequestError('BAD_PATH', `File path must be 1 to ${GUEST_FILE_PATH_MAX} characters without NUL or backslash.`));
23
+ const deviceStorageScope = (options) => {
24
+ // Typed callers supply GuestStorageOptions, but extensions may call this JavaScript API without TypeScript.
25
+ if (options === undefined)
26
+ return undefined;
27
+ if (Object(options) !== options || Array.isArray(options)
28
+ || (options.scope !== undefined && options.scope !== 'instance' && options.scope !== 'device')) {
29
+ throw new HostRequestError('HOST_REJECTED', 'Storage scope must be "instance" or "device".');
30
+ }
31
+ return options.scope === 'device' ? 'device' : undefined;
32
+ };
21
33
  const nextId = (n) => {
22
34
  n.value += 1;
23
35
  return `oc-${n.value}`;
@@ -36,6 +48,8 @@ export const connectHost = (options = {}) => {
36
48
  const connectionListeners = new Set();
37
49
  const settingsListeners = new Set();
38
50
  const itemListeners = new Set();
51
+ const statusControlListeners = new Set();
52
+ const popoverClosedListeners = new Set();
39
53
  let resolveHandler = null;
40
54
  let actionHandler = null;
41
55
  const fileOpenListeners = new Set();
@@ -49,6 +63,9 @@ export const connectHost = (options = {}) => {
49
63
  const ids = { value: 0 };
50
64
  let lastReady = null;
51
65
  let lastLifecycle = null;
66
+ let popoverPointerActive = false;
67
+ let popoverFocusActive = false;
68
+ let popoverActivityReported = null;
52
69
  const lifecycleFromSession = (session) => {
53
70
  if (!session)
54
71
  return null;
@@ -146,6 +163,14 @@ export const connectHost = (options = {}) => {
146
163
  emit(itemListeners, message.payload.item);
147
164
  return;
148
165
  }
166
+ if (message.type === 'status-control-event') {
167
+ emit(statusControlListeners, message.payload);
168
+ return;
169
+ }
170
+ if (message.type === 'popover-closed') {
171
+ emit(popoverClosedListeners, message.payload);
172
+ return;
173
+ }
149
174
  if (message.type === 'action') {
150
175
  const answer = (payload) => {
151
176
  if (!disposed)
@@ -274,6 +299,46 @@ export const connectHost = (options = {}) => {
274
299
  event.preventDefault();
275
300
  requestFileSave();
276
301
  };
302
+ const onPopoverEscape = (event) => {
303
+ if (!isKeyEvent(event) || event.key !== 'Escape' || lastReady?.surface !== 'popover' || !lastReady.popover)
304
+ return;
305
+ event.preventDefault();
306
+ void closePopover(lastReady.popover.id, 'escape').catch(() => undefined);
307
+ };
308
+ const reportPopoverActivity = () => {
309
+ const popover = lastReady?.surface === 'popover' ? lastReady.popover : undefined;
310
+ if (!popover)
311
+ return;
312
+ const active = popoverPointerActive || popoverFocusActive;
313
+ if (popoverActivityReported === active)
314
+ return;
315
+ popoverActivityReported = active;
316
+ void setPopoverAnchorActive(popover.id, active).catch(() => undefined);
317
+ };
318
+ const onPopoverPointerOver = () => {
319
+ if (lastReady?.surface !== 'popover' || popoverPointerActive)
320
+ return;
321
+ popoverPointerActive = true;
322
+ reportPopoverActivity();
323
+ };
324
+ const onPopoverPointerOut = (event) => {
325
+ if (lastReady?.surface !== 'popover' || event.relatedTarget !== null || !popoverPointerActive)
326
+ return;
327
+ popoverPointerActive = false;
328
+ reportPopoverActivity();
329
+ };
330
+ const onPopoverFocus = () => {
331
+ if (lastReady?.surface !== 'popover' || popoverFocusActive)
332
+ return;
333
+ popoverFocusActive = true;
334
+ reportPopoverActivity();
335
+ };
336
+ const onPopoverBlur = () => {
337
+ if (lastReady?.surface !== 'popover' || !popoverFocusActive)
338
+ return;
339
+ popoverFocusActive = false;
340
+ reportPopoverActivity();
341
+ };
277
342
  const requireIdentity = (value, maximum = 1024) => {
278
343
  if (!value.trim() || value.length > maximum)
279
344
  throw new HostRequestError('HOST_REJECTED', `Identity must contain 1 to ${maximum} characters.`);
@@ -317,11 +382,26 @@ export const connectHost = (options = {}) => {
317
382
  if (payload.op === 'set' && new TextEncoder().encode(JSON.stringify(payload.value)).length > GUEST_STORAGE_VALUE_BYTES) {
318
383
  throw new HostRequestError('HOST_REJECTED', 'Storage value exceeds 64 KiB.');
319
384
  }
385
+ if (payload.scope === 'device' && lastReady?.features?.deviceStorage !== true) {
386
+ throw new HostRequestError('UNSUPPORTED', 'This host does not support device storage.');
387
+ }
320
388
  const result = await send({ ...envelope, type: 'storage', id: nextId(ids), payload });
321
389
  if (!result || !('storage' in result) || result.op !== payload.op)
322
390
  throw new HostRequestError('HOST_REJECTED', 'Host did not return storage data.');
323
391
  return result;
324
392
  };
393
+ const closePopover = (id, reason) => (lastReady?.features?.popovers !== true && !(lastReady?.surface === 'popover' && lastReady.popover?.id === id)
394
+ ? Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'))
395
+ : request({ ...envelope, type: 'popover-close', id: nextId(ids), payload: reason ? { id, reason } : { id } }));
396
+ const setPopoverAnchorActive = (id, active) => (lastReady?.features?.popovers !== true && !(lastReady?.surface === 'popover' && lastReady.popover?.id === id)
397
+ ? Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'))
398
+ : request({ ...envelope, type: 'popover-anchor', id: nextId(ids), payload: { id, active } }));
399
+ target.addEventListener('keydown', onPopoverEscape, true);
400
+ target.addEventListener('pointerover', onPopoverPointerOver);
401
+ target.addEventListener('pointermove', onPopoverPointerOver);
402
+ target.addEventListener('pointerout', onPopoverPointerOut);
403
+ target.addEventListener('focus', onPopoverFocus);
404
+ target.addEventListener('blur', onPopoverBlur);
325
405
  return {
326
406
  onAction: (handler) => {
327
407
  actionHandler = handler;
@@ -357,14 +437,34 @@ export const connectHost = (options = {}) => {
357
437
  await request({ ...envelope, type: 'open-session', id: nextId(ids), payload: { sessionId } });
358
438
  },
359
439
  storage: {
360
- get: async (key) => {
361
- const result = await storage({ op: 'get', key });
440
+ get: async (key, options) => {
441
+ const scope = deviceStorageScope(options);
442
+ const payload = { op: 'get', key };
443
+ if (scope)
444
+ payload.scope = scope;
445
+ const result = await storage(payload);
362
446
  return result.op === 'get' && result.found ? result.value : undefined;
363
447
  },
364
- set: async (key, value) => { await storage({ op: 'set', key, value }); },
365
- delete: async (key) => { await storage({ op: 'delete', key }); },
366
- keys: async () => {
367
- const result = await storage({ op: 'keys' });
448
+ set: async (key, value, options) => {
449
+ const scope = deviceStorageScope(options);
450
+ const payload = { op: 'set', key, value };
451
+ if (scope)
452
+ payload.scope = scope;
453
+ await storage(payload);
454
+ },
455
+ delete: async (key, options) => {
456
+ const scope = deviceStorageScope(options);
457
+ const payload = { op: 'delete', key };
458
+ if (scope)
459
+ payload.scope = scope;
460
+ await storage(payload);
461
+ },
462
+ keys: async (options) => {
463
+ const scope = deviceStorageScope(options);
464
+ const payload = { op: 'keys' };
465
+ if (scope)
466
+ payload.scope = scope;
467
+ const result = await storage(payload);
368
468
  if (result.op !== 'keys')
369
469
  throw new HostRequestError('HOST_REJECTED', 'Expected storage keys.');
370
470
  return result.keys;
@@ -426,6 +526,36 @@ export const connectHost = (options = {}) => {
426
526
  itemListeners.delete(listener);
427
527
  };
428
528
  },
529
+ onStatusControl: (listener) => {
530
+ statusControlListeners.add(listener);
531
+ return () => {
532
+ statusControlListeners.delete(listener);
533
+ };
534
+ },
535
+ onPopoverClosed: (listener) => {
536
+ popoverClosedListeners.add(listener);
537
+ return () => { popoverClosedListeners.delete(listener); };
538
+ },
539
+ setStatusControls: (controls) => {
540
+ if (lastReady?.features?.statusControls !== true) {
541
+ return Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support status controls.'));
542
+ }
543
+ if (!isGuestStatusControls(controls)) {
544
+ return Promise.reject(new HostRequestError('HOST_REJECTED', 'Status controls must have unique bounded ids and valid selected options.'));
545
+ }
546
+ return request({ ...envelope, type: 'status-controls', id: nextId(ids), payload: { controls } });
547
+ },
548
+ openPopover: (payload) => {
549
+ if (lastReady?.features?.popovers !== true) {
550
+ return Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'));
551
+ }
552
+ if (!isGuestPopoverRequest(payload)) {
553
+ return Promise.reject(new HostRequestError('HOST_REJECTED', 'Popover requests need bounded geometry and JSON data.'));
554
+ }
555
+ return request({ ...envelope, type: 'popover-open', id: nextId(ids), payload });
556
+ },
557
+ closePopover,
558
+ setPopoverAnchorActive,
429
559
  onResolve: (handler) => {
430
560
  resolveHandler = handler;
431
561
  return () => {
@@ -735,6 +865,12 @@ export const connectHost = (options = {}) => {
735
865
  if (saveShortcutInstalled)
736
866
  target.removeEventListener('keydown', onSaveShortcut, true);
737
867
  target.removeEventListener('message', onMessage);
868
+ target.removeEventListener('keydown', onPopoverEscape, true);
869
+ target.removeEventListener('pointerover', onPopoverPointerOver);
870
+ target.removeEventListener('pointermove', onPopoverPointerOver);
871
+ target.removeEventListener('pointerout', onPopoverPointerOut);
872
+ target.removeEventListener('focus', onPopoverFocus);
873
+ target.removeEventListener('blur', onPopoverBlur);
738
874
  for (const waiter of pending.values()) {
739
875
  clearTimeout(waiter.timer);
740
876
  waiter.reject(new HostRequestError('HOST_UNAVAILABLE', 'Host client was disposed.'));
@@ -747,6 +883,8 @@ export const connectHost = (options = {}) => {
747
883
  connectionListeners.clear();
748
884
  settingsListeners.clear();
749
885
  itemListeners.clear();
886
+ statusControlListeners.clear();
887
+ popoverClosedListeners.clear();
750
888
  },
751
889
  };
752
890
  };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from './api-version.ts';
2
2
  export { GUEST_SCROLLBAR_CSS, GUEST_SCROLLBAR_SCRIPT } from './scrollbar-style.ts';
3
3
  export { guestFramePolicy } from './frame-policy.ts';
4
- export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity, GuestSessionRecord, GuestDirectoryCoverage, GuestProjectsSnapshot, GuestWorktreesSnapshot, GuestSessionsSnapshot, GuestWorkspaceSnapshot, GuestWorkspaceQuery, GuestWorkspaceSubscription, GuestWorkspaceUpdate, GuestStorageRequest, GuestStorageResult, GuestSessionWorktree } from './workspace.ts';
5
- export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from './workspace.ts';
4
+ export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity, GuestSessionRecord, GuestDirectoryCoverage, GuestProjectsSnapshot, GuestWorktreesSnapshot, GuestSessionsSnapshot, GuestWorkspaceSnapshot, GuestWorkspaceQuery, GuestWorkspaceSubscription, GuestWorkspaceUpdate, GuestStorageRequest, GuestStorageResult, GuestStorageOptions, GuestStorageScope, GuestSessionWorktree } from './workspace.ts';
5
+ export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES, GUEST_DEVICE_STORAGE_TOTAL_BYTES } from './workspace.ts';
6
+ export { GUEST_STATUS_CONTROLS_MAX, GUEST_STATUS_CONTROL_ID, GUEST_STATUS_CONTROL_LABEL_MAX, GUEST_STATUS_CONTROL_OPTIONS_MAX, GUEST_STATUS_CONTROL_VALUE_MAX } from './status-controls.ts';
7
+ export type { GuestStatusControl, GuestStatusControlEvent, GuestStatusControlOption } from './status-controls.ts';
6
8
  export type { OpenChamberManifestApiVersion } from './api-version.ts';
7
9
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from './host-version.ts';
8
10
  export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_DEFAULT, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, clampStatusSectionHeight, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveStatusSectionEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from './manifest.ts';
@@ -11,8 +13,10 @@ export { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITO
11
13
  export type { FileEditorChange, FileEditorContentKind, FileEditorContribution, FileEditorDocument, FileEditorSnapshot, FileSnapshotPurpose, FileSnapshotRequest, FileSnapshotResultPayload, } from './file-editor.ts';
12
14
  export { connectHost, HostRequestError } from './host.ts';
13
15
  export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
16
+ export { GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, GUEST_POPOVER_CLOSE_REASONS, isGuestPopoverRequest, } from './popover.ts';
17
+ export type { GuestPopoverAnchor, GuestPopoverClosedEvent, GuestPopoverContext, GuestPopoverRequest, GuestPopoverSide } from './popover.ts';
14
18
  export { clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, GUEST_COMMIT_SHA, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from './contract.ts';
15
- export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, HostFileOpenMessage, HostFileSnapshotMessage, HostFileSavedMessage, GuestFileSnapshotResultMessage, GuestFileChangeMessage, GuestFileSaveMessage, GuestFileUnsupportedMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, OpenCommitRequest, ResizeRequest, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, GuestFileEntryKind, GuestFileListMessage, GuestFileReadMessage, GuestFileScope, GuestFileStatKind, GuestFileStatMessage, GuestGenerateMessage, GuestFileWriteMessage, ServiceStatus, ServiceStatusResult, HostRequestErrorCode, AttachBranches, AttachIssueRequest, AttachThreadKind, JsonValue, HostResultPayload, PromptRequest, PromptResult, SessionLifecycleEvent, SessionLifecyclePhase, StartSessionRequest, StartSessionResult, StartSessionSent, ComposeRequest, GuestServiceRequestMessage, GuestServiceStatusMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
19
+ export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, HostFileOpenMessage, HostFileSnapshotMessage, HostFileSavedMessage, GuestFileSnapshotResultMessage, GuestFileChangeMessage, GuestFileSaveMessage, GuestFileUnsupportedMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, GuestStatusControlsMessage, GuestPopoverAnchorMessage, GuestPopoverCloseMessage, GuestPopoverOpenMessage, OpenCommitRequest, ResizeRequest, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, GuestFileEntryKind, GuestFileListMessage, GuestFileReadMessage, GuestFileScope, GuestFileStatKind, GuestFileStatMessage, GuestGenerateMessage, GuestFileWriteMessage, ServiceStatus, ServiceStatusResult, HostRequestErrorCode, AttachBranches, AttachIssueRequest, AttachThreadKind, JsonValue, HostResultPayload, PromptRequest, PromptResult, SessionLifecycleEvent, SessionLifecyclePhase, StartSessionRequest, StartSessionResult, StartSessionSent, ComposeRequest, GuestServiceRequestMessage, GuestServiceStatusMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostStatusControlEventMessage, HostPopoverClosedMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
16
20
  export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from './contract.ts';
17
21
  export { BROWSER_CONTROL_ACTIONS, BROWSER_PROVIDER_ACTION_TIMEOUT_MS, BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH, BROWSER_PROVIDER_RESPONSE_MAX, BROWSER_SCROLL_DIRECTIONS, BROWSER_VIEWPORT_MODES, isBrowserControlAction, readBrowserProviderRequest, } from './service-providers.ts';
18
22
  export type { BrowserCaptureData, BrowserCaptureParameters, BrowserClickData, BrowserClickParameters, BrowserConsoleProblem, BrowserControlAction, BrowserElementBounds, BrowserInspectData, BrowserInspectParameters, BrowserNavigationData, BrowserOpenData, BrowserOpenParameters, BrowserProviderContext, BrowserProviderData, BrowserProviderRequest, BrowserProviderResult, BrowserResizeData, BrowserResizeParameters, BrowserScrollData, BrowserScrollDirection, BrowserScrollParameters, BrowserSnapshotData, BrowserSnapshotElement, BrowserSnapshotParameters, BrowserTab, BrowserTabTarget, BrowserTypeData, BrowserTypeParameters, BrowserViewportMode, BrowserViewportSummary, } from './service-providers.ts';
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from "./api-version.js";
2
2
  export { GUEST_SCROLLBAR_CSS, GUEST_SCROLLBAR_SCRIPT } from "./scrollbar-style.js";
3
3
  export { guestFramePolicy } from "./frame-policy.js";
4
- export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from "./workspace.js";
4
+ export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES, GUEST_DEVICE_STORAGE_TOTAL_BYTES } from "./workspace.js";
5
+ export { GUEST_STATUS_CONTROLS_MAX, GUEST_STATUS_CONTROL_ID, GUEST_STATUS_CONTROL_LABEL_MAX, GUEST_STATUS_CONTROL_OPTIONS_MAX, GUEST_STATUS_CONTROL_VALUE_MAX } from "./status-controls.js";
5
6
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from "./host-version.js";
6
7
  export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_DEFAULT, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, clampStatusSectionHeight, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveStatusSectionEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from "./manifest.js";
7
8
  export { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITOR_PATTERNS_MAX, GUEST_FILE_EDITOR_PATTERN_MAX, GUEST_FILE_EDITOR_TITLE_MAX, GUEST_FILE_EDITOR_VERSION_MAX, createFileSaveTracker, fileEditorPayloadSize, isFileEditorPattern, matchesFileEditorPattern, } from "./file-editor.js";
8
9
  export { connectHost, HostRequestError } from "./host.js";
10
+ export { GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, GUEST_POPOVER_CLOSE_REASONS, isGuestPopoverRequest, } from "./popover.js";
9
11
  export { clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, GUEST_COMMIT_SHA, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from "./contract.js";
10
12
  export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from "./contract.js";
11
13
  export { BROWSER_CONTROL_ACTIONS, BROWSER_PROVIDER_ACTION_TIMEOUT_MS, BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH, BROWSER_PROVIDER_RESPONSE_MAX, BROWSER_SCROLL_DIRECTIONS, BROWSER_VIEWPORT_MODES, isBrowserControlAction, readBrowserProviderRequest, } from "./service-providers.js";
@@ -60,6 +60,8 @@ export type StatusSectionContribution = true | {
60
60
  entry: string;
61
61
  title?: string;
62
62
  height?: number;
63
+ defaultExpanded?: boolean;
64
+ requiresProject?: boolean;
63
65
  };
64
66
  /** Characters in a status section title. */
65
67
  export declare const GUEST_STATUS_SECTION_TITLE_MAX = 60;
package/dist/parse.d.ts CHANGED
@@ -12,10 +12,10 @@ export declare const openChamberManifestSchema: z.ZodObject<{
12
12
  icon: z.ZodString;
13
13
  entry: z.ZodOptional<z.ZodString>;
14
14
  dock: z.ZodOptional<z.ZodEnum<{
15
- top: "top";
16
- bottom: "bottom";
17
15
  left: "left";
18
16
  right: "right";
17
+ top: "top";
18
+ bottom: "bottom";
19
19
  }>>;
20
20
  size: z.ZodOptional<z.ZodNumber>;
21
21
  }, z.core.$strip>;
@@ -40,6 +40,8 @@ export declare const openChamberManifestSchema: z.ZodObject<{
40
40
  entry: z.ZodString;
41
41
  title: z.ZodOptional<z.ZodString>;
42
42
  height: z.ZodOptional<z.ZodNumber>;
43
+ defaultExpanded: z.ZodOptional<z.ZodBoolean>;
44
+ requiresProject: z.ZodOptional<z.ZodBoolean>;
43
45
  }, z.core.$strip>]>>;
44
46
  capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
45
47
  sessions: "sessions";
@@ -184,10 +186,10 @@ export declare const packageManifestSchema: z.ZodObject<{
184
186
  icon: z.ZodString;
185
187
  entry: z.ZodOptional<z.ZodString>;
186
188
  dock: z.ZodOptional<z.ZodEnum<{
187
- top: "top";
188
- bottom: "bottom";
189
189
  left: "left";
190
190
  right: "right";
191
+ top: "top";
192
+ bottom: "bottom";
191
193
  }>>;
192
194
  size: z.ZodOptional<z.ZodNumber>;
193
195
  }, z.core.$strip>;
@@ -212,6 +214,8 @@ export declare const packageManifestSchema: z.ZodObject<{
212
214
  entry: z.ZodString;
213
215
  title: z.ZodOptional<z.ZodString>;
214
216
  height: z.ZodOptional<z.ZodNumber>;
217
+ defaultExpanded: z.ZodOptional<z.ZodBoolean>;
218
+ requiresProject: z.ZodOptional<z.ZodBoolean>;
215
219
  }, z.core.$strip>]>>;
216
220
  capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
217
221
  sessions: "sessions";
package/dist/parse.js CHANGED
@@ -215,6 +215,8 @@ const contributesSchema = z.object({
215
215
  entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')),
216
216
  title: z.string().trim().min(1).max(GUEST_STATUS_SECTION_TITLE_MAX).optional(),
217
217
  height: z.number().int().min(GUEST_STATUS_SECTION_HEIGHT_MIN).max(GUEST_STATUS_SECTION_HEIGHT_MAX).optional(),
218
+ defaultExpanded: z.boolean().optional(),
219
+ requiresProject: z.boolean().optional(),
218
220
  })]).optional(),
219
221
  capabilities: z.array(z.enum(DECLARED_GUEST_CAPABILITIES)).max(8).optional(),
220
222
  integration: integrationSchema.optional(),
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+ import type { JsonValue } from './contract.ts';
3
+ export declare const guestPopoverDataSchema: z.ZodCustom<JsonValue, JsonValue>;
4
+ export declare const guestPopoverRequestSchema: z.ZodObject<{
5
+ id: z.ZodString;
6
+ anchor: z.ZodObject<{
7
+ x: z.ZodNumber;
8
+ y: z.ZodNumber;
9
+ width: z.ZodNumber;
10
+ height: z.ZodNumber;
11
+ }, z.core.$strict>;
12
+ width: z.ZodNumber;
13
+ height: z.ZodNumber;
14
+ side: z.ZodOptional<z.ZodEnum<{
15
+ left: "left";
16
+ right: "right";
17
+ top: "top";
18
+ bottom: "bottom";
19
+ }>>;
20
+ focus: z.ZodOptional<z.ZodBoolean>;
21
+ data: z.ZodCustom<JsonValue, JsonValue>;
22
+ }, z.core.$strict>;
23
+ export declare const guestPopoverClosedEventSchema: z.ZodObject<{
24
+ id: z.ZodString;
25
+ reason: z.ZodEnum<{
26
+ escape: "escape";
27
+ outside: "outside";
28
+ anchor: "anchor";
29
+ owner: "owner";
30
+ closed: "closed";
31
+ replaced: "replaced";
32
+ }>;
33
+ }, z.core.$strict>;
@@ -0,0 +1,74 @@
1
+ import { z } from 'zod';
2
+ import { GUEST_POPOVER_CLOSE_REASONS, GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, } from "./popover.js";
3
+ const jsonPrimitive = z.union([z.string().max(GUEST_POPOVER_DATA_MAX), z.number(), z.boolean(), z.null()]);
4
+ const jsonRecord = z.record(z.string().max(GUEST_POPOVER_DATA_MAX), z.unknown());
5
+ // Parse one level at a time: recursive z.json() would traverse a hostile cycle
6
+ // or deeply nested structured-clone payload before its size/depth refinement.
7
+ export const guestPopoverDataSchema = z.custom((value) => {
8
+ const pending = [{ value, depth: 0 }];
9
+ let size = 0;
10
+ while (pending.length > 0) {
11
+ const current = pending.pop();
12
+ if (!current)
13
+ break;
14
+ const item = current.value;
15
+ if (current.depth >= GUEST_POPOVER_DATA_DEPTH_MAX)
16
+ return false;
17
+ if (Array.isArray(item)) {
18
+ if (item.length > GUEST_POPOVER_DATA_MAX)
19
+ return false;
20
+ if (Object.keys(item).some((key) => !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= item.length))
21
+ return false;
22
+ size += 2 + Math.max(0, item.length - 1);
23
+ for (const child of item)
24
+ pending.push({ value: child, depth: current.depth + 1 });
25
+ }
26
+ else {
27
+ const primitive = jsonPrimitive.safeParse(item);
28
+ if (primitive.success) {
29
+ size += JSON.stringify(primitive.data).length;
30
+ }
31
+ else {
32
+ const record = jsonRecord.safeParse(item);
33
+ if (!record.success)
34
+ return false;
35
+ const prototype = Object.getPrototypeOf(item);
36
+ if (prototype !== null && prototype !== Object.prototype)
37
+ return false;
38
+ // z.record normalizes prototype-named keys. Validate the original
39
+ // structured-clone values so those keys cannot escape the budget.
40
+ const entries = Object.entries(Object.getOwnPropertyDescriptors(item)).filter(([, descriptor]) => descriptor.enumerable);
41
+ size += 2 + Math.max(0, entries.length - 1);
42
+ for (const [key, descriptor] of entries) {
43
+ if (!('value' in descriptor))
44
+ return false;
45
+ size += JSON.stringify(key).length + 1;
46
+ if (size > GUEST_POPOVER_DATA_MAX)
47
+ return false;
48
+ pending.push({ value: descriptor.value, depth: current.depth + 1 });
49
+ }
50
+ }
51
+ }
52
+ if (size > GUEST_POPOVER_DATA_MAX)
53
+ return false;
54
+ }
55
+ return true;
56
+ }, 'Popover data must be finite JSON within its size and depth limits.');
57
+ export const guestPopoverRequestSchema = z.object({
58
+ id: z.string().regex(GUEST_POPOVER_ID),
59
+ anchor: z.object({
60
+ x: z.number().finite().min(-GUEST_POPOVER_COORDINATE_MAX).max(GUEST_POPOVER_COORDINATE_MAX),
61
+ y: z.number().finite().min(-GUEST_POPOVER_COORDINATE_MAX).max(GUEST_POPOVER_COORDINATE_MAX),
62
+ width: z.number().finite().positive().max(GUEST_POPOVER_COORDINATE_MAX),
63
+ height: z.number().finite().positive().max(GUEST_POPOVER_COORDINATE_MAX),
64
+ }).strict(),
65
+ width: z.number().int().min(GUEST_POPOVER_WIDTH_MIN).max(GUEST_POPOVER_WIDTH_MAX),
66
+ height: z.number().int().min(GUEST_POPOVER_HEIGHT_MIN).max(GUEST_POPOVER_HEIGHT_MAX),
67
+ side: z.enum(GUEST_POPOVER_SIDES).optional(),
68
+ focus: z.boolean().optional(),
69
+ data: guestPopoverDataSchema,
70
+ }).strict();
71
+ export const guestPopoverClosedEventSchema = z.object({
72
+ id: z.string().regex(GUEST_POPOVER_ID),
73
+ reason: z.enum(GUEST_POPOVER_CLOSE_REASONS),
74
+ }).strict();
@@ -0,0 +1,37 @@
1
+ import type { JsonValue } from './contract.ts';
2
+ export declare const GUEST_POPOVER_ID: RegExp;
3
+ export declare const GUEST_POPOVER_COORDINATE_MAX = 100000;
4
+ export declare const GUEST_POPOVER_WIDTH_MIN = 160;
5
+ export declare const GUEST_POPOVER_WIDTH_MAX = 640;
6
+ export declare const GUEST_POPOVER_HEIGHT_MIN = 48;
7
+ export declare const GUEST_POPOVER_HEIGHT_MAX = 480;
8
+ export declare const GUEST_POPOVER_DATA_MAX = 16000;
9
+ export declare const GUEST_POPOVER_DATA_DEPTH_MAX = 32;
10
+ export declare const GUEST_POPOVER_SIDES: readonly ["left", "right", "top", "bottom"];
11
+ export type GuestPopoverSide = (typeof GUEST_POPOVER_SIDES)[number];
12
+ export type GuestPopoverAnchor = {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ };
18
+ export type GuestPopoverRequest = {
19
+ id: string;
20
+ anchor: GuestPopoverAnchor;
21
+ width: number;
22
+ height: number;
23
+ side?: GuestPopoverSide;
24
+ focus?: boolean;
25
+ data: JsonValue;
26
+ };
27
+ export type GuestPopoverContext = {
28
+ id: string;
29
+ data: JsonValue;
30
+ };
31
+ export declare const GUEST_POPOVER_CLOSE_REASONS: readonly ["escape", "outside", "anchor", "owner", "closed", "replaced"];
32
+ export type GuestPopoverClosedEvent = {
33
+ id: string;
34
+ reason: (typeof GUEST_POPOVER_CLOSE_REASONS)[number];
35
+ };
36
+ /** Guest-side fast-fail guard. The host schema remains authoritative for untyped callers. */
37
+ export declare const isGuestPopoverRequest: (request: GuestPopoverRequest) => boolean;
@@ -0,0 +1,57 @@
1
+ export const GUEST_POPOVER_ID = /^[A-Za-z0-9_-]{1,80}$/;
2
+ export const GUEST_POPOVER_COORDINATE_MAX = 100_000;
3
+ export const GUEST_POPOVER_WIDTH_MIN = 160;
4
+ export const GUEST_POPOVER_WIDTH_MAX = 640;
5
+ export const GUEST_POPOVER_HEIGHT_MIN = 48;
6
+ export const GUEST_POPOVER_HEIGHT_MAX = 480;
7
+ export const GUEST_POPOVER_DATA_MAX = 16_000;
8
+ export const GUEST_POPOVER_DATA_DEPTH_MAX = 32;
9
+ export const GUEST_POPOVER_SIDES = ['left', 'right', 'top', 'bottom'];
10
+ export const GUEST_POPOVER_CLOSE_REASONS = ['escape', 'outside', 'anchor', 'owner', 'closed', 'replaced'];
11
+ const hasBoundedJsonTree = (value, depth = 0, budget = { remaining: GUEST_POPOVER_DATA_MAX }) => {
12
+ if (--budget.remaining < 0 || depth >= GUEST_POPOVER_DATA_DEPTH_MAX)
13
+ return false;
14
+ if (value === null)
15
+ return true;
16
+ const children = Array.isArray(value) ? value : Object(value) === value ? Object.values(value) : [];
17
+ for (const item of children) {
18
+ if (!hasBoundedJsonTree(item, depth + 1, budget))
19
+ return false;
20
+ }
21
+ return true;
22
+ };
23
+ // Used only after the depth bound above, including for JavaScript consumers
24
+ // whose values need not satisfy the public JsonValue type.
25
+ const isFiniteJson = (value) => {
26
+ if (value === null || value === true || value === false)
27
+ return true;
28
+ if (Array.isArray(value))
29
+ return Object.keys(value).every((key) => /^(0|[1-9]\d*)$/.test(key) && Number(key) < value.length)
30
+ && value.every(isFiniteJson);
31
+ if (Object(value) === value) {
32
+ const prototype = Object.getPrototypeOf(value);
33
+ return (prototype === null || prototype === Object.prototype) && Object.values(value).every(isFiniteJson);
34
+ }
35
+ return String(value) === value || Number.isFinite(value);
36
+ };
37
+ /** Guest-side fast-fail guard. The host schema remains authoritative for untyped callers. */
38
+ export const isGuestPopoverRequest = (request) => {
39
+ try {
40
+ if (String(request.id) !== request.id || !GUEST_POPOVER_ID.test(request.id)
41
+ || !Number.isFinite(request.anchor.x) || Math.abs(request.anchor.x) > GUEST_POPOVER_COORDINATE_MAX
42
+ || !Number.isFinite(request.anchor.y) || Math.abs(request.anchor.y) > GUEST_POPOVER_COORDINATE_MAX
43
+ || !Number.isFinite(request.anchor.width) || !Number.isFinite(request.anchor.height)
44
+ || request.anchor.width <= 0 || request.anchor.height <= 0
45
+ || request.anchor.width > GUEST_POPOVER_COORDINATE_MAX || request.anchor.height > GUEST_POPOVER_COORDINATE_MAX
46
+ || !Number.isInteger(request.width) || request.width < GUEST_POPOVER_WIDTH_MIN || request.width > GUEST_POPOVER_WIDTH_MAX
47
+ || !Number.isInteger(request.height) || request.height < GUEST_POPOVER_HEIGHT_MIN || request.height > GUEST_POPOVER_HEIGHT_MAX
48
+ || (request.side !== undefined && !GUEST_POPOVER_SIDES.includes(request.side))
49
+ || (request.focus !== undefined && request.focus !== true && request.focus !== false)
50
+ || !hasBoundedJsonTree(request.data) || !isFiniteJson(request.data))
51
+ return false;
52
+ return JSON.stringify(request.data).length <= GUEST_POPOVER_DATA_MAX;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ };