@openchamber/sdk 2.1.0 → 2.2.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/host.js CHANGED
@@ -1,7 +1,10 @@
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";
7
+ import { GUEST_SHELL_ID_MAX, GUEST_SHELL_OUTPUT_TAIL_MAX } from "./shells.js";
5
8
  // Structural, not `instanceof KeyboardEvent`: the frame's own realm owns that
6
9
  // constructor, and non-browser runtimes have none.
7
10
  const isKeyEvent = (event) => 'key' in event && 'metaKey' in event && 'ctrlKey' in event;
@@ -18,6 +21,16 @@ export class HostRequestError extends Error {
18
21
  // path would otherwise surface only as HOST_TIMEOUT twenty seconds later.
19
22
  const rejectBadPath = () => Promise.reject(new HostRequestError('BAD_PATH', 'Request path must start with "/" and stay on the declared origin.'));
20
23
  const rejectBadFilePath = () => Promise.reject(new HostRequestError('BAD_PATH', `File path must be 1 to ${GUEST_FILE_PATH_MAX} characters without NUL or backslash.`));
24
+ const deviceStorageScope = (options) => {
25
+ // Typed callers supply GuestStorageOptions, but extensions may call this JavaScript API without TypeScript.
26
+ if (options === undefined)
27
+ return undefined;
28
+ if (Object(options) !== options || Array.isArray(options)
29
+ || (options.scope !== undefined && options.scope !== 'instance' && options.scope !== 'device')) {
30
+ throw new HostRequestError('HOST_REJECTED', 'Storage scope must be "instance" or "device".');
31
+ }
32
+ return options.scope === 'device' ? 'device' : undefined;
33
+ };
21
34
  const nextId = (n) => {
22
35
  n.value += 1;
23
36
  return `oc-${n.value}`;
@@ -36,6 +49,8 @@ export const connectHost = (options = {}) => {
36
49
  const connectionListeners = new Set();
37
50
  const settingsListeners = new Set();
38
51
  const itemListeners = new Set();
52
+ const statusControlListeners = new Set();
53
+ const popoverClosedListeners = new Set();
39
54
  let resolveHandler = null;
40
55
  let actionHandler = null;
41
56
  const fileOpenListeners = new Set();
@@ -45,10 +60,14 @@ export const connectHost = (options = {}) => {
45
60
  let saveShortcutInstalled = false;
46
61
  const pending = new Map();
47
62
  const workspaceListeners = new Map();
63
+ const shellsListeners = new Map();
48
64
  let disposed = false;
49
65
  const ids = { value: 0 };
50
66
  let lastReady = null;
51
67
  let lastLifecycle = null;
68
+ let popoverPointerActive = false;
69
+ let popoverFocusActive = false;
70
+ let popoverActivityReported = null;
52
71
  const lifecycleFromSession = (session) => {
53
72
  if (!session)
54
73
  return null;
@@ -86,6 +105,12 @@ export const connectHost = (options = {}) => {
86
105
  emit([listener], message.payload.snapshot);
87
106
  return;
88
107
  }
108
+ if (message.type === 'shells') {
109
+ const listener = shellsListeners.get(message.payload.subscriptionId);
110
+ if (listener)
111
+ emit([listener], message.payload.snapshot);
112
+ return;
113
+ }
89
114
  if (message.type === 'ready') {
90
115
  lastReady = message.payload;
91
116
  lastLifecycle = lifecycleFromSession(message.payload.session);
@@ -146,6 +171,14 @@ export const connectHost = (options = {}) => {
146
171
  emit(itemListeners, message.payload.item);
147
172
  return;
148
173
  }
174
+ if (message.type === 'status-control-event') {
175
+ emit(statusControlListeners, message.payload);
176
+ return;
177
+ }
178
+ if (message.type === 'popover-closed') {
179
+ emit(popoverClosedListeners, message.payload);
180
+ return;
181
+ }
149
182
  if (message.type === 'action') {
150
183
  const answer = (payload) => {
151
184
  if (!disposed)
@@ -274,6 +307,46 @@ export const connectHost = (options = {}) => {
274
307
  event.preventDefault();
275
308
  requestFileSave();
276
309
  };
310
+ const onPopoverEscape = (event) => {
311
+ if (!isKeyEvent(event) || event.key !== 'Escape' || lastReady?.surface !== 'popover' || !lastReady.popover)
312
+ return;
313
+ event.preventDefault();
314
+ void closePopover(lastReady.popover.id, 'escape').catch(() => undefined);
315
+ };
316
+ const reportPopoverActivity = () => {
317
+ const popover = lastReady?.surface === 'popover' ? lastReady.popover : undefined;
318
+ if (!popover)
319
+ return;
320
+ const active = popoverPointerActive || popoverFocusActive;
321
+ if (popoverActivityReported === active)
322
+ return;
323
+ popoverActivityReported = active;
324
+ void setPopoverAnchorActive(popover.id, active).catch(() => undefined);
325
+ };
326
+ const onPopoverPointerOver = () => {
327
+ if (lastReady?.surface !== 'popover' || popoverPointerActive)
328
+ return;
329
+ popoverPointerActive = true;
330
+ reportPopoverActivity();
331
+ };
332
+ const onPopoverPointerOut = (event) => {
333
+ if (lastReady?.surface !== 'popover' || event.relatedTarget !== null || !popoverPointerActive)
334
+ return;
335
+ popoverPointerActive = false;
336
+ reportPopoverActivity();
337
+ };
338
+ const onPopoverFocus = () => {
339
+ if (lastReady?.surface !== 'popover' || popoverFocusActive)
340
+ return;
341
+ popoverFocusActive = true;
342
+ reportPopoverActivity();
343
+ };
344
+ const onPopoverBlur = () => {
345
+ if (lastReady?.surface !== 'popover' || !popoverFocusActive)
346
+ return;
347
+ popoverFocusActive = false;
348
+ reportPopoverActivity();
349
+ };
277
350
  const requireIdentity = (value, maximum = 1024) => {
278
351
  if (!value.trim() || value.length > maximum)
279
352
  throw new HostRequestError('HOST_REJECTED', `Identity must contain 1 to ${maximum} characters.`);
@@ -307,6 +380,28 @@ export const connectHost = (options = {}) => {
307
380
  post({ ...envelope, type: 'workspace-unsubscribe', id: nextId(ids), payload: { subscriptionId } });
308
381
  };
309
382
  };
383
+ const subscribeShells = async (scope, listener) => {
384
+ if (scope.kind === 'session')
385
+ requireIdentity(scope.sessionId);
386
+ if (scope.kind === 'project')
387
+ requireIdentity(scope.projectId);
388
+ const subscriptionId = nextId(ids);
389
+ shellsListeners.set(subscriptionId, listener);
390
+ try {
391
+ await request({ ...envelope, type: 'shells-subscribe', id: nextId(ids), payload: { subscriptionId, scope } });
392
+ }
393
+ catch (error) {
394
+ shellsListeners.delete(subscriptionId);
395
+ if (!disposed)
396
+ post({ ...envelope, type: 'shells-unsubscribe', id: nextId(ids), payload: { subscriptionId } });
397
+ throw error;
398
+ }
399
+ return () => {
400
+ if (!shellsListeners.delete(subscriptionId) || disposed)
401
+ return;
402
+ post({ ...envelope, type: 'shells-unsubscribe', id: nextId(ids), payload: { subscriptionId } });
403
+ };
404
+ };
310
405
  const storage = async (payload) => {
311
406
  if ('key' in payload && (payload.key.length === 0 || payload.key.length > GUEST_STORAGE_KEY_MAX)) {
312
407
  throw new HostRequestError('HOST_REJECTED', 'Storage key must contain 1 to 128 characters.');
@@ -317,11 +412,26 @@ export const connectHost = (options = {}) => {
317
412
  if (payload.op === 'set' && new TextEncoder().encode(JSON.stringify(payload.value)).length > GUEST_STORAGE_VALUE_BYTES) {
318
413
  throw new HostRequestError('HOST_REJECTED', 'Storage value exceeds 64 KiB.');
319
414
  }
415
+ if (payload.scope === 'device' && lastReady?.features?.deviceStorage !== true) {
416
+ throw new HostRequestError('UNSUPPORTED', 'This host does not support device storage.');
417
+ }
320
418
  const result = await send({ ...envelope, type: 'storage', id: nextId(ids), payload });
321
419
  if (!result || !('storage' in result) || result.op !== payload.op)
322
420
  throw new HostRequestError('HOST_REJECTED', 'Host did not return storage data.');
323
421
  return result;
324
422
  };
423
+ const closePopover = (id, reason) => (lastReady?.features?.popovers !== true && !(lastReady?.surface === 'popover' && lastReady.popover?.id === id)
424
+ ? Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'))
425
+ : request({ ...envelope, type: 'popover-close', id: nextId(ids), payload: reason ? { id, reason } : { id } }));
426
+ const setPopoverAnchorActive = (id, active) => (lastReady?.features?.popovers !== true && !(lastReady?.surface === 'popover' && lastReady.popover?.id === id)
427
+ ? Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'))
428
+ : request({ ...envelope, type: 'popover-anchor', id: nextId(ids), payload: { id, active } }));
429
+ target.addEventListener('keydown', onPopoverEscape, true);
430
+ target.addEventListener('pointerover', onPopoverPointerOver);
431
+ target.addEventListener('pointermove', onPopoverPointerOver);
432
+ target.addEventListener('pointerout', onPopoverPointerOut);
433
+ target.addEventListener('focus', onPopoverFocus);
434
+ target.addEventListener('blur', onPopoverBlur);
325
435
  return {
326
436
  onAction: (handler) => {
327
437
  actionHandler = handler;
@@ -352,19 +462,64 @@ export const connectHost = (options = {}) => {
352
462
  listener(snapshot); }),
353
463
  onSessions: (projectId, listener) => subscribeWorkspace({ kind: 'sessions', projectId }, (snapshot) => { if (snapshot.kind === 'sessions')
354
464
  listener(snapshot); }),
465
+ onRunningShells: (scope, listener) => subscribeShells(scope, listener),
466
+ readShellOutput: async (shellId, options) => {
467
+ requireIdentity(shellId, GUEST_SHELL_ID_MAX);
468
+ const cursor = options?.cursor;
469
+ if (cursor !== undefined && (!Number.isInteger(cursor) || cursor < 0)) {
470
+ throw new HostRequestError('HOST_REJECTED', 'Shell output cursor must be a non-negative integer.');
471
+ }
472
+ const tailBytes = options?.tailBytes;
473
+ if (tailBytes !== undefined && (!Number.isInteger(tailBytes) || tailBytes < 1 || tailBytes > GUEST_SHELL_OUTPUT_TAIL_MAX)) {
474
+ throw new HostRequestError('HOST_REJECTED', `Shell output tailBytes must be an integer between 1 and ${GUEST_SHELL_OUTPUT_TAIL_MAX}.`);
475
+ }
476
+ const result = await send({ ...envelope, type: 'shell-output', id: nextId(ids), payload: { shellId, cursor, tailBytes } });
477
+ const output = result;
478
+ if (!output || typeof output.output !== 'string' || !Number.isFinite(output.cursor)) {
479
+ throw new HostRequestError('HOST_REJECTED', 'Host did not return shell output.');
480
+ }
481
+ return output;
482
+ },
483
+ stopShell: async (shellId) => {
484
+ requireIdentity(shellId, GUEST_SHELL_ID_MAX);
485
+ const result = (await send({ ...envelope, type: 'shell-stop', id: nextId(ids), payload: { shellId } }));
486
+ if (result?.stopped !== true)
487
+ throw new HostRequestError('HOST_REJECTED', 'Host did not stop the shell.');
488
+ return { stopped: true };
489
+ },
355
490
  openSession: async (sessionId) => {
356
491
  requireIdentity(sessionId);
357
492
  await request({ ...envelope, type: 'open-session', id: nextId(ids), payload: { sessionId } });
358
493
  },
359
494
  storage: {
360
- get: async (key) => {
361
- const result = await storage({ op: 'get', key });
495
+ get: async (key, options) => {
496
+ const scope = deviceStorageScope(options);
497
+ const payload = { op: 'get', key };
498
+ if (scope)
499
+ payload.scope = scope;
500
+ const result = await storage(payload);
362
501
  return result.op === 'get' && result.found ? result.value : undefined;
363
502
  },
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' });
503
+ set: async (key, value, options) => {
504
+ const scope = deviceStorageScope(options);
505
+ const payload = { op: 'set', key, value };
506
+ if (scope)
507
+ payload.scope = scope;
508
+ await storage(payload);
509
+ },
510
+ delete: async (key, options) => {
511
+ const scope = deviceStorageScope(options);
512
+ const payload = { op: 'delete', key };
513
+ if (scope)
514
+ payload.scope = scope;
515
+ await storage(payload);
516
+ },
517
+ keys: async (options) => {
518
+ const scope = deviceStorageScope(options);
519
+ const payload = { op: 'keys' };
520
+ if (scope)
521
+ payload.scope = scope;
522
+ const result = await storage(payload);
368
523
  if (result.op !== 'keys')
369
524
  throw new HostRequestError('HOST_REJECTED', 'Expected storage keys.');
370
525
  return result.keys;
@@ -426,6 +581,36 @@ export const connectHost = (options = {}) => {
426
581
  itemListeners.delete(listener);
427
582
  };
428
583
  },
584
+ onStatusControl: (listener) => {
585
+ statusControlListeners.add(listener);
586
+ return () => {
587
+ statusControlListeners.delete(listener);
588
+ };
589
+ },
590
+ onPopoverClosed: (listener) => {
591
+ popoverClosedListeners.add(listener);
592
+ return () => { popoverClosedListeners.delete(listener); };
593
+ },
594
+ setStatusControls: (controls) => {
595
+ if (lastReady?.features?.statusControls !== true) {
596
+ return Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support status controls.'));
597
+ }
598
+ if (!isGuestStatusControls(controls)) {
599
+ return Promise.reject(new HostRequestError('HOST_REJECTED', 'Status controls must have unique bounded ids and valid selected options.'));
600
+ }
601
+ return request({ ...envelope, type: 'status-controls', id: nextId(ids), payload: { controls } });
602
+ },
603
+ openPopover: (payload) => {
604
+ if (lastReady?.features?.popovers !== true) {
605
+ return Promise.reject(new HostRequestError('UNSUPPORTED', 'This host does not support popovers.'));
606
+ }
607
+ if (!isGuestPopoverRequest(payload)) {
608
+ return Promise.reject(new HostRequestError('HOST_REJECTED', 'Popover requests need bounded geometry and JSON data.'));
609
+ }
610
+ return request({ ...envelope, type: 'popover-open', id: nextId(ids), payload });
611
+ },
612
+ closePopover,
613
+ setPopoverAnchorActive,
429
614
  onResolve: (handler) => {
430
615
  resolveHandler = handler;
431
616
  return () => {
@@ -726,6 +911,10 @@ export const connectHost = (options = {}) => {
726
911
  post({ ...envelope, type: 'workspace-unsubscribe', id: nextId(ids), payload: { subscriptionId } });
727
912
  }
728
913
  workspaceListeners.clear();
914
+ for (const subscriptionId of shellsListeners.keys()) {
915
+ post({ ...envelope, type: 'shells-unsubscribe', id: nextId(ids), payload: { subscriptionId } });
916
+ }
917
+ shellsListeners.clear();
729
918
  disposed = true;
730
919
  resolveHandler = null;
731
920
  actionHandler = null;
@@ -735,6 +924,12 @@ export const connectHost = (options = {}) => {
735
924
  if (saveShortcutInstalled)
736
925
  target.removeEventListener('keydown', onSaveShortcut, true);
737
926
  target.removeEventListener('message', onMessage);
927
+ target.removeEventListener('keydown', onPopoverEscape, true);
928
+ target.removeEventListener('pointerover', onPopoverPointerOver);
929
+ target.removeEventListener('pointermove', onPopoverPointerOver);
930
+ target.removeEventListener('pointerout', onPopoverPointerOut);
931
+ target.removeEventListener('focus', onPopoverFocus);
932
+ target.removeEventListener('blur', onPopoverBlur);
738
933
  for (const waiter of pending.values()) {
739
934
  clearTimeout(waiter.timer);
740
935
  waiter.reject(new HostRequestError('HOST_UNAVAILABLE', 'Host client was disposed.'));
@@ -747,6 +942,8 @@ export const connectHost = (options = {}) => {
747
942
  connectionListeners.clear();
748
943
  settingsListeners.clear();
749
944
  itemListeners.clear();
945
+ statusControlListeners.clear();
946
+ popoverClosedListeners.clear();
750
947
  },
751
948
  };
752
949
  };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
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 type { GuestRunningShell, GuestEndedShell, GuestShellEndStatus, GuestRunningShellsSnapshot, GuestShellsScope, GuestShellsSubscription, GuestShellOutputRequest, GuestShellOutputResult, GuestShellStopResult, } from './shells.ts';
7
+ export { GUEST_SHELL_END_STATUSES, GUEST_SHELL_ID_MAX, GUEST_SHELLS_MAX, GUEST_SHELL_OUTPUT_TAIL_MAX, } from './shells.ts';
8
+ 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';
9
+ export type { GuestStatusControl, GuestStatusControlEvent, GuestStatusControlOption } from './status-controls.ts';
6
10
  export type { OpenChamberManifestApiVersion } from './api-version.ts';
7
11
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from './host-version.ts';
8
12
  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 +15,10 @@ export { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITO
11
15
  export type { FileEditorChange, FileEditorContentKind, FileEditorContribution, FileEditorDocument, FileEditorSnapshot, FileSnapshotPurpose, FileSnapshotRequest, FileSnapshotResultPayload, } from './file-editor.ts';
12
16
  export { connectHost, HostRequestError } from './host.ts';
13
17
  export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
18
+ 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';
19
+ export type { GuestPopoverAnchor, GuestPopoverClosedEvent, GuestPopoverContext, GuestPopoverRequest, GuestPopoverSide } from './popover.ts';
14
20
  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';
21
+ 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, GuestShellOutputMessage, GuestShellsSubscribeMessage, GuestShellsUnsubscribeMessage, GuestShellStopMessage, 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, HostShellsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
16
22
  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
23
  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
24
  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,14 @@
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_SHELL_END_STATUSES, GUEST_SHELL_ID_MAX, GUEST_SHELLS_MAX, GUEST_SHELL_OUTPUT_TAIL_MAX, } from "./shells.js";
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.js";
5
7
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from "./host-version.js";
6
8
  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
9
  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
10
  export { connectHost, HostRequestError } from "./host.js";
11
+ 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
12
  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
13
  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
14
  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;
@@ -271,15 +273,15 @@ export declare const serviceProvides: (service: Pick<ServiceContribution, "provi
271
273
  /**
272
274
  * What a guest may do beyond drawing its own panel. The user approves the
273
275
  * full list once, when the package is installed; a later package that asks
274
- * for more is re-approved. `prompt`, `sessions`, `files`, and `model` are
275
- * declared under `contributes.capabilities`; `service`, `network`,
276
+ * for more is re-approved. `prompt`, `sessions`, `files`, `model`, and
277
+ * `shells` are declared under `contributes.capabilities`; `service`, `network`,
276
278
  * `filesystem` and `origins` follow from `contributes.service`,
277
279
  * `contributes.integration`, `contributes.filesystem` and `contributes.origins`. `model` is one-off text generation with the
278
280
  * user's Small Model (`host.generate`), outside any session.
279
281
  */
280
- export declare const GUEST_CAPABILITIES: readonly ["prompt", "sessions", "files", "model", "conversation", "service", "network", "filesystem", "origins"];
282
+ export declare const GUEST_CAPABILITIES: readonly ["prompt", "sessions", "files", "model", "shells", "conversation", "service", "network", "filesystem", "origins"];
281
283
  export type GuestCapability = (typeof GUEST_CAPABILITIES)[number];
282
- export declare const DECLARED_GUEST_CAPABILITIES: readonly ["prompt", "sessions", "files", "model"];
284
+ export declare const DECLARED_GUEST_CAPABILITIES: readonly ["prompt", "sessions", "files", "model", "shells"];
283
285
  /** The capabilities a manifest may ask for directly. */
284
286
  export type DeclaredGuestCapability = (typeof DECLARED_GUEST_CAPABILITIES)[number];
285
287
  /** How many `contributes.origins` a package may declare. */
package/dist/manifest.js CHANGED
@@ -96,14 +96,14 @@ export const serviceProvides = (service, role) => Boolean(service?.provides?.inc
96
96
  /**
97
97
  * What a guest may do beyond drawing its own panel. The user approves the
98
98
  * full list once, when the package is installed; a later package that asks
99
- * for more is re-approved. `prompt`, `sessions`, `files`, and `model` are
100
- * declared under `contributes.capabilities`; `service`, `network`,
99
+ * for more is re-approved. `prompt`, `sessions`, `files`, `model`, and
100
+ * `shells` are declared under `contributes.capabilities`; `service`, `network`,
101
101
  * `filesystem` and `origins` follow from `contributes.service`,
102
102
  * `contributes.integration`, `contributes.filesystem` and `contributes.origins`. `model` is one-off text generation with the
103
103
  * user's Small Model (`host.generate`), outside any session.
104
104
  */
105
- export const GUEST_CAPABILITIES = ['prompt', 'sessions', 'files', 'model', 'conversation', 'service', 'network', 'filesystem', 'origins'];
106
- export const DECLARED_GUEST_CAPABILITIES = ['prompt', 'sessions', 'files', 'model'];
105
+ export const GUEST_CAPABILITIES = ['prompt', 'sessions', 'files', 'model', 'shells', 'conversation', 'service', 'network', 'filesystem', 'origins'];
106
+ export const DECLARED_GUEST_CAPABILITIES = ['prompt', 'sessions', 'files', 'model', 'shells'];
107
107
  /** How many `contributes.origins` a package may declare. */
108
108
  export const GUEST_ORIGINS_MAX = 8;
109
109
  /** How many `contributes.filesystem` patterns a package may declare. */
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,8 +40,11 @@ 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<{
47
+ shells: "shells";
45
48
  sessions: "sessions";
46
49
  prompt: "prompt";
47
50
  files: "files";
@@ -121,8 +124,8 @@ export declare const openChamberManifestSchema: z.ZodObject<{
121
124
  label: z.ZodString;
122
125
  icon: z.ZodOptional<z.ZodString>;
123
126
  where: z.ZodEnum<{
124
- message: "message";
125
127
  session: "session";
128
+ message: "message";
126
129
  }>;
127
130
  mode: z.ZodOptional<z.ZodEnum<{
128
131
  background: "background";
@@ -184,10 +187,10 @@ export declare const packageManifestSchema: z.ZodObject<{
184
187
  icon: z.ZodString;
185
188
  entry: z.ZodOptional<z.ZodString>;
186
189
  dock: z.ZodOptional<z.ZodEnum<{
187
- top: "top";
188
- bottom: "bottom";
189
190
  left: "left";
190
191
  right: "right";
192
+ top: "top";
193
+ bottom: "bottom";
191
194
  }>>;
192
195
  size: z.ZodOptional<z.ZodNumber>;
193
196
  }, z.core.$strip>;
@@ -212,8 +215,11 @@ export declare const packageManifestSchema: z.ZodObject<{
212
215
  entry: z.ZodString;
213
216
  title: z.ZodOptional<z.ZodString>;
214
217
  height: z.ZodOptional<z.ZodNumber>;
218
+ defaultExpanded: z.ZodOptional<z.ZodBoolean>;
219
+ requiresProject: z.ZodOptional<z.ZodBoolean>;
215
220
  }, z.core.$strip>]>>;
216
221
  capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
222
+ shells: "shells";
217
223
  sessions: "sessions";
218
224
  prompt: "prompt";
219
225
  files: "files";
@@ -293,8 +299,8 @@ export declare const packageManifestSchema: z.ZodObject<{
293
299
  label: z.ZodString;
294
300
  icon: z.ZodOptional<z.ZodString>;
295
301
  where: z.ZodEnum<{
296
- message: "message";
297
302
  session: "session";
303
+ message: "message";
298
304
  }>;
299
305
  mode: z.ZodOptional<z.ZodEnum<{
300
306
  background: "background";
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();