@openchamber/sdk 1.23.2-preview.1 → 1.24.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,6 +1,6 @@
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 { GUEST_FILE_CONTENT_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, clampPromptRequest, clampStartSessionRequest, readHostMessage, isGenerateResult, isFileListResult, isFileReadResult, isFileStatResult, isJsonValue, isFileWriteResult, isServiceStatusResult, isGuestRequestResult, isPromptResult, isStartSessionResult, } from "./contract.js";
3
+ 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, clampPromptRequest, clampStartSessionRequest, readHostMessage, isGenerateResult, isFileListResult, isFileReadResult, isFileStatResult, isJsonValue, isFileWriteResult, isServiceStatusResult, isGuestRequestResult, isPromptResult, isStartSessionResult, } from "./contract.js";
4
4
  export class HostRequestError extends Error {
5
5
  code;
6
6
  constructor(code, message) {
@@ -32,6 +32,7 @@ export const connectHost = (options = {}) => {
32
32
  const settingsListeners = new Set();
33
33
  const itemListeners = new Set();
34
34
  let resolveHandler = null;
35
+ let actionHandler = null;
35
36
  const pending = new Map();
36
37
  const workspaceListeners = new Map();
37
38
  let disposed = false;
@@ -135,6 +136,23 @@ export const connectHost = (options = {}) => {
135
136
  emit(itemListeners, message.payload.item);
136
137
  return;
137
138
  }
139
+ if (message.type === 'action') {
140
+ const answer = (payload) => {
141
+ if (!disposed)
142
+ post({ channel: OPENCHAMBER_SDK_CHANNEL, v: OPENCHAMBER_SDK_API_VERSION,
143
+ type: 'action-result', id: message.id, payload });
144
+ };
145
+ const handler = actionHandler;
146
+ if (!handler) {
147
+ answer({ ok: false, error: 'This extension does not handle background actions.' });
148
+ return;
149
+ }
150
+ Promise.resolve().then(() => handler(message.payload)).then(() => answer({ ok: true }), (error) => {
151
+ const text = (error instanceof Error ? error.message : String(error)).trim();
152
+ answer({ ok: false, error: (text || 'Action failed.').slice(0, GUEST_RESOLVE_ERROR_MAX) });
153
+ });
154
+ return;
155
+ }
138
156
  if (message.type === 'resolve') {
139
157
  const answer = (payload) => {
140
158
  post({
@@ -239,6 +257,11 @@ export const connectHost = (options = {}) => {
239
257
  return result;
240
258
  };
241
259
  return {
260
+ onAction: (handler) => {
261
+ actionHandler = handler;
262
+ return () => { if (actionHandler === handler)
263
+ actionHandler = null; };
264
+ },
242
265
  listProjects: async () => {
243
266
  const result = await readWorkspace({ kind: 'projects' });
244
267
  if (result.kind !== 'projects')
@@ -344,13 +367,22 @@ export const connectHost = (options = {}) => {
344
367
  resolveHandler = null;
345
368
  };
346
369
  },
347
- toast: (payload) => request({
348
- channel: OPENCHAMBER_SDK_CHANNEL,
349
- v: OPENCHAMBER_SDK_API_VERSION,
350
- type: 'toast',
351
- id: nextId(ids),
352
- payload,
353
- }),
370
+ toast: (payload) => {
371
+ const message = payload.message.trim();
372
+ if (!message || message.length > GUEST_TOAST_MAX) {
373
+ return Promise.reject(new HostRequestError('HOST_REJECTED', `Toast message must contain 1 to ${GUEST_TOAST_MAX} characters.`));
374
+ }
375
+ if (payload.copy && payload.copy !== true && (!payload.copy.text.length || payload.copy.text.length > GUEST_CLIPBOARD_TEXT_MAX)) {
376
+ return Promise.reject(new HostRequestError('HOST_REJECTED', `Toast copy text must contain 1 to ${GUEST_CLIPBOARD_TEXT_MAX} characters.`));
377
+ }
378
+ return request({
379
+ channel: OPENCHAMBER_SDK_CHANNEL,
380
+ v: OPENCHAMBER_SDK_API_VERSION,
381
+ type: 'toast',
382
+ id: nextId(ids),
383
+ payload: { ...payload, message },
384
+ });
385
+ },
354
386
  openUrl: (url) => request({
355
387
  channel: OPENCHAMBER_SDK_CHANNEL,
356
388
  v: OPENCHAMBER_SDK_API_VERSION,
@@ -588,6 +620,7 @@ export const connectHost = (options = {}) => {
588
620
  workspaceListeners.clear();
589
621
  disposed = true;
590
622
  resolveHandler = null;
623
+ actionHandler = null;
591
624
  target.removeEventListener('message', onMessage);
592
625
  for (const waiter of pending.values()) {
593
626
  clearTimeout(waiter.timer);
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from './api-version.ts';
2
+ export { GUEST_SCROLLBAR_CSS } from './scrollbar-style.ts';
2
3
  export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity, GuestSessionRecord, GuestDirectoryCoverage, GuestProjectsSnapshot, GuestWorktreesSnapshot, GuestSessionsSnapshot, GuestWorkspaceSnapshot, GuestWorkspaceQuery, GuestWorkspaceSubscription, GuestWorkspaceUpdate, GuestStorageRequest, GuestStorageResult, GuestSessionWorktree } from './workspace.ts';
3
4
  export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from './workspace.ts';
4
5
  export type { OpenChamberManifestApiVersion } from './api-version.ts';
5
6
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from './host-version.ts';
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, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, toPublicService, toPublicIntegration, } from './manifest.ts';
7
- export type { ServiceContribution, ServicePermissions, GuestActionContribution, GuestActionPayload, GuestActionRole, GuestActionWhere, GuestCommandContribution, GuestToolContribution, GuestToolOutput, AttachContribution, AttachContributionObject, AttachMode, DeclaredGuestCapability, GuestAuthorization, GuestCapability, IntegrationAuth, IntegrationContribution, IntegrationHost, IntegrationHostProvider, IntegrationOAuth, IntegrationOAuthAccount, IntegrationSettingField, IntegrationToken, IntegrationTokenScheme, OpenChamberContributes, OpenChamberEngines, OpenChamberManifest, PanelContribution, PageContribution, ParseManifestErrorCode, ParseManifestFailure, ParseManifestResult, ParseManifestSuccess, PublicService, PublicServicePermissions, PublicGuestCapabilities, PublicIntegration, PublicIntegrationToken, PublicSocketBinding, ResolvedGuestApi, SocketBinding, SocketPlatform, } from './manifest.ts';
8
+ export type { ServiceContribution, ServicePermissions, GuestActionContribution, GuestActionPayload, GuestActionRole, GuestActionWhere, GuestCommandContribution, GuestToolContribution, GuestToolOutput, AttachContribution, AttachContributionObject, AttachMode, DeclaredGuestCapability, GuestAuthorization, GuestCapability, IntegrationAuth, IntegrationContribution, IntegrationHost, IntegrationHostProvider, IntegrationOAuth, IntegrationOAuthAccount, IntegrationSettingField, IntegrationToken, IntegrationTokenScheme, OpenChamberContributes, OpenChamberEngines, OpenChamberManifest, PanelContribution, BackgroundContribution, PageContribution, ParseManifestErrorCode, ParseManifestFailure, ParseManifestResult, ParseManifestSuccess, PublicService, PublicServicePermissions, PublicGuestCapabilities, PublicIntegration, PublicIntegrationToken, PublicSocketBinding, ResolvedGuestApi, SocketBinding, SocketPlatform, } from './manifest.ts';
8
9
  export { connectHost, HostRequestError } from './host.ts';
9
10
  export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
10
11
  export { clampAttachRequest, clampBadgeCount, 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';
11
- export type { BadgeRequest, GuestBadgeMessage, 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';
12
+ export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, GuestBadgeMessage, 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';
12
13
  export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_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';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from "./api-version.js";
2
+ export { GUEST_SCROLLBAR_CSS } from "./scrollbar-style.js";
2
3
  export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from "./workspace.js";
3
4
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from "./host-version.js";
4
5
  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, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, toPublicService, toPublicIntegration, } from "./manifest.js";
@@ -2,8 +2,8 @@ import type { OpenChamberManifestApiVersion } from './api-version.ts';
2
2
  export declare const PANEL_ID: RegExp;
3
3
  /**
4
4
  * The extension's identity on the rail, the Extensions card, and the approval
5
- * dialog. `entry` is the panel page; without it the extension has no page and
6
- * may only declare `tools` (see `hasGuestPage`).
5
+ * dialog. `entry` is the optional visible panel page. Code can instead run
6
+ * from `background.entry` without adding a rail icon.
7
7
  */
8
8
  export type PanelContribution = {
9
9
  id: string;
@@ -11,6 +11,10 @@ export type PanelContribution = {
11
11
  icon: string;
12
12
  entry?: string;
13
13
  };
14
+ /** Sandboxed HTML loaded on demand for background actions and slash commands. */
15
+ export type BackgroundContribution = {
16
+ entry: string;
17
+ };
14
18
  export type AttachMode = 'panel' | 'dialog';
15
19
  /**
16
20
  * Object form of `contributes.attach`. `entry` is an HTML page inside the
@@ -42,7 +46,7 @@ export declare const GUEST_COMMAND_DESCRIPTION_MAX = 80;
42
46
  /** A slash command name: lower-case, digits and dashes, up to 24 characters. */
43
47
  export declare const GUEST_COMMAND_NAME: RegExp;
44
48
  /**
45
- * A menu entry on a message or a session. Clicking it opens the guest with
49
+ * A menu entry on a message or a session. By default it opens the guest with
46
50
  * the message or session as `ready.item`. `roles` narrows a message action
47
51
  * to user or assistant messages (default both); `payload: ["messages"]` on
48
52
  * a session action asks for the conversation and needs the `conversation`
@@ -54,6 +58,8 @@ export type GuestActionContribution = {
54
58
  /** Remixicon name or package `.svg` path, same as `panel.icon`. Falls back to the panel icon. */
55
59
  icon?: string;
56
60
  where: GuestActionWhere;
61
+ /** `background` calls `host.onAction` in a temporary hidden frame. Default: `open`. */
62
+ mode?: 'open' | 'background';
57
63
  roles?: GuestActionRole[];
58
64
  payload?: GuestActionPayload[];
59
65
  };
@@ -231,6 +237,7 @@ export declare const GUEST_FILESYSTEM_PATTERN_MAX = 256;
231
237
  export declare const isGuestFilesystemPattern: (value: string) => boolean;
232
238
  export type OpenChamberContributes = {
233
239
  panel: PanelContribution;
240
+ background?: BackgroundContribution;
234
241
  attach?: AttachContribution;
235
242
  page?: PageContribution;
236
243
  capabilities?: DeclaredGuestCapability[];
@@ -254,10 +261,8 @@ export type PublicGuestCapabilities = {
254
261
  };
255
262
  export declare const requestedGuestCapabilities: (contributes: Pick<OpenChamberContributes, "capabilities" | "integration" | "service" | "filesystem" | "actions">) => GuestCapability[];
256
263
  /**
257
- * Whether the package ships a panel page. A page-less package (no
258
- * `panel.entry`) never mounts an iframe: no rail surface, attach row, action,
259
- * command, service, integration, or capability; parse refuses those without
260
- * an entry. `tools` is the only contribution it may carry.
264
+ * Whether the package ships a visible panel. Background-only extensions can
265
+ * execute code but have no rail surface, attach picker, or full-screen page.
261
266
  */
262
267
  export declare const hasGuestPage: (contributes: Pick<OpenChamberContributes, "panel">) => boolean;
263
268
  export declare const isGuestApproved: (capabilities: PublicGuestCapabilities) => boolean;
@@ -277,7 +282,7 @@ export type OpenChamberManifest = {
277
282
  engines?: OpenChamberEngines;
278
283
  contributes: OpenChamberContributes;
279
284
  };
280
- export type ParseManifestErrorCode = 'not-object' | 'missing-openchamber' | 'unsupported-api-version' | 'invalid-engines' | 'invalid-version' | 'missing-panel' | 'invalid-panel' | 'invalid-panel-id' | 'invalid-panel-name' | 'invalid-panel-icon' | 'invalid-panel-entry' | 'invalid-attach' | 'invalid-page' | 'invalid-capabilities' | 'invalid-integration' | 'invalid-service' | 'invalid-filesystem' | 'invalid-actions' | 'invalid-commands' | 'invalid-tools';
285
+ export type ParseManifestErrorCode = 'not-object' | 'missing-openchamber' | 'unsupported-api-version' | 'invalid-engines' | 'invalid-version' | 'missing-panel' | 'invalid-panel' | 'invalid-panel-id' | 'invalid-panel-name' | 'invalid-panel-icon' | 'invalid-panel-entry' | 'invalid-background' | 'invalid-attach' | 'invalid-page' | 'invalid-capabilities' | 'invalid-integration' | 'invalid-service' | 'invalid-filesystem' | 'invalid-actions' | 'invalid-commands' | 'invalid-tools';
281
286
  export type ParseManifestFailure = {
282
287
  ok: false;
283
288
  code: ParseManifestErrorCode;
package/dist/manifest.js CHANGED
@@ -119,10 +119,8 @@ export const requestedGuestCapabilities = (contributes) => {
119
119
  return GUEST_CAPABILITIES.filter((capability) => declared.has(capability));
120
120
  };
121
121
  /**
122
- * Whether the package ships a panel page. A page-less package (no
123
- * `panel.entry`) never mounts an iframe: no rail surface, attach row, action,
124
- * command, service, integration, or capability; parse refuses those without
125
- * an entry. `tools` is the only contribution it may carry.
122
+ * Whether the package ships a visible panel. Background-only extensions can
123
+ * execute code but have no rail surface, attach picker, or full-screen page.
126
124
  */
127
125
  export const hasGuestPage = (contributes) => Boolean(contributes.panel.entry);
128
126
  export const isGuestApproved = (capabilities) => (capabilities.requested.every((capability) => capabilities.granted.includes(capability)));
package/dist/parse.d.ts CHANGED
@@ -12,6 +12,9 @@ export declare const openChamberManifestSchema: z.ZodObject<{
12
12
  icon: z.ZodString;
13
13
  entry: z.ZodOptional<z.ZodString>;
14
14
  }, z.core.$strip>;
15
+ background: z.ZodOptional<z.ZodObject<{
16
+ entry: z.ZodString;
17
+ }, z.core.$strip>>;
15
18
  attach: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
16
19
  panel: "panel";
17
20
  dialog: "dialog";
@@ -104,6 +107,10 @@ export declare const openChamberManifestSchema: z.ZodObject<{
104
107
  message: "message";
105
108
  session: "session";
106
109
  }>;
110
+ mode: z.ZodOptional<z.ZodEnum<{
111
+ background: "background";
112
+ open: "open";
113
+ }>>;
107
114
  roles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
108
115
  user: "user";
109
116
  assistant: "assistant";
@@ -150,6 +157,9 @@ export declare const packageManifestSchema: z.ZodObject<{
150
157
  icon: z.ZodString;
151
158
  entry: z.ZodOptional<z.ZodString>;
152
159
  }, z.core.$strip>;
160
+ background: z.ZodOptional<z.ZodObject<{
161
+ entry: z.ZodString;
162
+ }, z.core.$strip>>;
153
163
  attach: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodEnum<{
154
164
  panel: "panel";
155
165
  dialog: "dialog";
@@ -242,6 +252,10 @@ export declare const packageManifestSchema: z.ZodObject<{
242
252
  message: "message";
243
253
  session: "session";
244
254
  }>;
255
+ mode: z.ZodOptional<z.ZodEnum<{
256
+ background: "background";
257
+ open: "open";
258
+ }>>;
245
259
  roles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
246
260
  user: "user";
247
261
  assistant: "assistant";
package/dist/parse.js CHANGED
@@ -149,6 +149,7 @@ const actionSchema = z.object({
149
149
  label: z.string().trim().min(1).max(GUEST_ACTION_LABEL_MAX),
150
150
  icon: z.string().trim().refine(isPanelIcon).optional(),
151
151
  where: z.enum(['message', 'session']),
152
+ mode: z.enum(['open', 'background']).optional(),
152
153
  roles: z.array(z.enum(['user', 'assistant'])).min(1).max(2).optional(),
153
154
  payload: z.array(z.enum(['messages'])).max(1).optional(),
154
155
  }).refine((value) => value.where === 'message' || value.roles === undefined, { path: ['roles'] })
@@ -178,6 +179,9 @@ const toolSchema = z.object({
178
179
  const toolsSchema = z.array(toolSchema).min(1).max(GUEST_TOOLS_MAX);
179
180
  const contributesSchema = z.object({
180
181
  panel: panelSchema,
182
+ background: z.object({
183
+ entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')),
184
+ }).optional(),
181
185
  attach: attachSchema.optional(),
182
186
  page: z.union([z.literal(true), z.object({
183
187
  entry: z.string().trim().refine(isSafeAssetPath),
@@ -192,12 +196,9 @@ const contributesSchema = z.object({
192
196
  tools: toolsSchema.optional(),
193
197
  });
194
198
  /**
195
- * Everything that only makes sense with an iframe to mount. Without
196
- * `panel.entry` there is nothing to open from the rail, the + menu, a menu
197
- * action, or a slash command, and nothing to hand a capability, a service,
198
- * an integration, or a filesystem grant to; only `tools` stays meaningful.
199
+ * Contributions that need either the panel or background execution frame.
199
200
  */
200
- const pageOnlyContributions = (contributes) => {
201
+ const runtimeContributions = (contributes) => {
201
202
  const declared = [];
202
203
  if (contributes.page !== undefined)
203
204
  declared.push('page');
@@ -225,13 +226,28 @@ export const openChamberManifestSchema = z.object({
225
226
  contributes: contributesSchema.superRefine((contributes, ctx) => {
226
227
  if (hasGuestPage(contributes))
227
228
  return;
228
- const needsPage = pageOnlyContributions(contributes);
229
- if (needsPage.length === 0)
229
+ if (contributes.background) {
230
+ const needsPanel = [];
231
+ if (contributes.page !== undefined)
232
+ needsPanel.push('page');
233
+ if (contributes.attach !== undefined && contributes.attach !== false)
234
+ needsPanel.push('attach');
235
+ if (contributes.actions?.some((action) => action.mode !== 'background'))
236
+ needsPanel.push('actions with mode "open"');
237
+ if (needsPanel.length > 0)
238
+ ctx.addIssue({
239
+ code: 'custom', path: ['panel'],
240
+ message: `${needsPanel.join(', ')} needs panel.entry; background-only actions must declare mode "background".`,
241
+ });
242
+ return;
243
+ }
244
+ const needsRuntime = runtimeContributions(contributes);
245
+ if (needsRuntime.length === 0)
230
246
  return;
231
247
  ctx.addIssue({
232
248
  code: 'custom',
233
249
  path: ['panel'],
234
- message: `${needsPage.map((key) => `contributes.${key}`).join(', ')} needs panel.entry; an extension without a page may only declare tools.`,
250
+ message: `${needsRuntime.map((key) => `contributes.${key}`).join(', ')} needs panel.entry or background.entry; an extension without either may only declare tools.`,
235
251
  });
236
252
  }),
237
253
  });
@@ -293,6 +309,9 @@ const failureFromIssue = (issue) => {
293
309
  if (path === 'contributes.page' || path.startsWith('contributes.page.')) {
294
310
  return fail('invalid-page', 'contributes.page must be true or { entry: "<package HTML>", title?: "Page title" }.');
295
311
  }
312
+ if (path === 'contributes.background' || path.startsWith('contributes.background.')) {
313
+ return fail('invalid-background', 'contributes.background needs an entry ending in .html inside the package.');
314
+ }
296
315
  if (path === 'contributes.attach' || path.startsWith('contributes.attach.')) {
297
316
  return fail('invalid-attach', 'contributes.attach must be true, false, "panel", "dialog", or { "mode": "panel" | "dialog", "entry"?: "<html inside the package, dialog only>" }.');
298
317
  }
@@ -116,6 +116,11 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
116
116
  active: z.ZodString;
117
117
  selectionForeground: z.ZodString;
118
118
  primaryForeground: z.ZodString;
119
+ primaryText: z.ZodString;
120
+ successText: z.ZodString;
121
+ warningText: z.ZodString;
122
+ errorText: z.ZodString;
123
+ infoText: z.ZodString;
119
124
  success: z.ZodString;
120
125
  warning: z.ZodString;
121
126
  error: z.ZodString;
@@ -138,6 +143,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
138
143
  panel: "panel";
139
144
  dialog: "dialog";
140
145
  page: "page";
146
+ background: "background";
141
147
  }>;
142
148
  connection: z.ZodObject<{
143
149
  connected: z.ZodBoolean;
@@ -746,8 +752,53 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
746
752
  } | {
747
753
  text: string;
748
754
  } | undefined;
749
- }>>]>;
755
+ }>>, z.ZodObject<{
756
+ type: z.ZodLiteral<"action">;
757
+ id: z.ZodString;
758
+ payload: z.ZodUnion<readonly [z.ZodObject<{
759
+ kind: z.ZodLiteral<"message">;
760
+ action: z.ZodString;
761
+ sessionId: z.ZodString;
762
+ sessionTitle: z.ZodString;
763
+ directory: z.ZodNullable<z.ZodString>;
764
+ messageId: z.ZodString;
765
+ role: z.ZodEnum<{
766
+ user: "user";
767
+ assistant: "assistant";
768
+ }>;
769
+ text: z.ZodString;
770
+ }, z.core.$strip>, z.ZodObject<{
771
+ kind: z.ZodLiteral<"session">;
772
+ action: z.ZodString;
773
+ sessionId: z.ZodString;
774
+ sessionTitle: z.ZodString;
775
+ directory: z.ZodNullable<z.ZodString>;
776
+ messages: z.ZodOptional<z.ZodArray<z.ZodObject<{
777
+ id: z.ZodString;
778
+ role: z.ZodEnum<{
779
+ user: "user";
780
+ assistant: "assistant";
781
+ }>;
782
+ text: z.ZodString;
783
+ createdAt: z.ZodNumber;
784
+ }, z.core.$strip>>>;
785
+ truncated: z.ZodOptional<z.ZodBoolean>;
786
+ }, z.core.$strip>]>;
787
+ channel: z.ZodLiteral<"openchamber.sdk">;
788
+ v: z.ZodLiteral<1>;
789
+ }, z.core.$strip>]>;
750
790
  export declare const guestMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
791
+ type: z.ZodLiteral<"action-result">;
792
+ id: z.ZodString;
793
+ payload: z.ZodDiscriminatedUnion<[z.ZodObject<{
794
+ ok: z.ZodLiteral<true>;
795
+ }, z.core.$strip>, z.ZodObject<{
796
+ ok: z.ZodLiteral<false>;
797
+ error: z.ZodString;
798
+ }, z.core.$strip>], "ok">;
799
+ channel: z.ZodLiteral<"openchamber.sdk">;
800
+ v: z.ZodLiteral<1>;
801
+ }, z.core.$strip>, z.ZodObject<{
751
802
  type: z.ZodLiteral<"workspace-read">;
752
803
  id: z.ZodString;
753
804
  payload: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -826,6 +877,11 @@ export declare const guestMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
826
877
  success: "success";
827
878
  }>;
828
879
  message: z.ZodString;
880
+ copy: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
881
+ text: z.ZodString;
882
+ }, z.core.$strip>]>>;
883
+ dismiss: z.ZodOptional<z.ZodBoolean>;
884
+ persistent: z.ZodOptional<z.ZodBoolean>;
829
885
  }, z.core.$strip>;
830
886
  channel: z.ZodLiteral<"openchamber.sdk">;
831
887
  v: z.ZodLiteral<1>;
package/dist/protocol.js CHANGED
@@ -22,6 +22,11 @@ const themeTokensSchema = z.object({
22
22
  active: z.string().min(1),
23
23
  selectionForeground: z.string().min(1),
24
24
  primaryForeground: z.string().min(1),
25
+ primaryText: z.string().min(1),
26
+ successText: z.string().min(1),
27
+ warningText: z.string().min(1),
28
+ errorText: z.string().min(1),
29
+ infoText: z.string().min(1),
25
30
  success: z.string().min(1),
26
31
  warning: z.string().min(1),
27
32
  error: z.string().min(1),
@@ -141,7 +146,7 @@ const readyPayloadSchema = z.object({
141
146
  locale: z.string().min(1),
142
147
  directory: z.string().nullable(),
143
148
  session: sessionSnapshotSchema,
144
- surface: z.enum(['panel', 'dialog', 'page']),
149
+ surface: z.enum(['panel', 'dialog', 'page', 'background']),
145
150
  connection: guestConnectionSchema,
146
151
  settings: guestSettingsSchema,
147
152
  item: guestItemSchema,
@@ -248,9 +253,24 @@ export const hostMessageSchema = z.union([
248
253
  }),
249
254
  }),
250
255
  hostResultSchema,
256
+ z.object({
257
+ ...envelope,
258
+ type: z.literal('action'),
259
+ id: z.string().min(1),
260
+ payload: z.union([messageItemSchema, sessionItemSchema]),
261
+ }),
251
262
  ]);
252
263
  const filePathSchema = z.string().min(1).max(GUEST_FILE_PATH_MAX).refine(isGuestFilePath);
253
264
  export const guestMessageSchema = z.discriminatedUnion('type', [
265
+ z.object({
266
+ ...envelope,
267
+ type: z.literal('action-result'),
268
+ id: z.string().min(1),
269
+ payload: z.discriminatedUnion('ok', [
270
+ z.object({ ok: z.literal(true) }),
271
+ z.object({ ok: z.literal(false), error: z.string().trim().min(1).max(GUEST_RESOLVE_ERROR_MAX) }),
272
+ ]),
273
+ }),
254
274
  z.object({ ...envelope, type: z.literal('workspace-read'), id: z.string().min(1), payload: guestWorkspaceQuerySchema }),
255
275
  z.object({ ...envelope, type: z.literal('workspace-subscribe'), id: z.string().min(1), payload: z.object({ subscriptionId: z.string().min(1).max(128), query: guestWorkspaceQuerySchema }) }),
256
276
  z.object({ ...envelope, type: z.literal('workspace-unsubscribe'), id: z.string().min(1), payload: z.object({ subscriptionId: z.string().min(1).max(128) }) }),
@@ -267,6 +287,9 @@ export const guestMessageSchema = z.discriminatedUnion('type', [
267
287
  payload: z.object({
268
288
  kind: z.enum(['info', 'success', 'error']),
269
289
  message: z.string().trim().min(1).max(GUEST_TOAST_MAX),
290
+ copy: z.union([z.boolean(), z.object({ text: z.string().min(1).max(GUEST_CLIPBOARD_TEXT_MAX) })]).optional(),
291
+ dismiss: z.boolean().optional(),
292
+ persistent: z.boolean().optional(),
270
293
  }),
271
294
  }),
272
295
  z.object({
@@ -0,0 +1,2 @@
1
+ /** Shared by served extension documents and the UI kit, including older guest bundles. */
2
+ export declare const GUEST_SCROLLBAR_CSS = "\n:root {\n --oc-scrollbar-thumb: color-mix(in srgb, var(--oc-muted, currentColor) 40%, transparent);\n --oc-scrollbar-thumb-hover: color-mix(in srgb, var(--oc-muted, currentColor) 65%, transparent);\n scrollbar-gutter: stable;\n}\n* {\n scrollbar-width: thin;\n scrollbar-color: var(--oc-scrollbar-thumb) transparent;\n}\n/* Chromium's standard scrollbar properties otherwise override its pseudo-elements. */\n@supports selector(::-webkit-scrollbar) {\n * { scrollbar-width: auto; scrollbar-color: auto; }\n ::-webkit-scrollbar { width: 6px; height: 6px; background: transparent; }\n :root::-webkit-scrollbar, body::-webkit-scrollbar { background: var(--oc-bg, inherit); }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb {\n background: var(--oc-scrollbar-thumb);\n border-radius: 999px;\n min-width: 24px;\n min-height: 24px;\n }\n ::-webkit-scrollbar-thumb:hover { background: var(--oc-scrollbar-thumb-hover); }\n ::-webkit-scrollbar-corner { background: transparent; }\n ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }\n}\n@media (forced-colors: active) {\n * { scrollbar-color: auto; }\n ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover { background: CanvasText; }\n}\n";
@@ -0,0 +1,32 @@
1
+ /** Shared by served extension documents and the UI kit, including older guest bundles. */
2
+ export const GUEST_SCROLLBAR_CSS = `
3
+ :root {
4
+ --oc-scrollbar-thumb: color-mix(in srgb, var(--oc-muted, currentColor) 40%, transparent);
5
+ --oc-scrollbar-thumb-hover: color-mix(in srgb, var(--oc-muted, currentColor) 65%, transparent);
6
+ scrollbar-gutter: stable;
7
+ }
8
+ * {
9
+ scrollbar-width: thin;
10
+ scrollbar-color: var(--oc-scrollbar-thumb) transparent;
11
+ }
12
+ /* Chromium's standard scrollbar properties otherwise override its pseudo-elements. */
13
+ @supports selector(::-webkit-scrollbar) {
14
+ * { scrollbar-width: auto; scrollbar-color: auto; }
15
+ ::-webkit-scrollbar { width: 6px; height: 6px; background: transparent; }
16
+ :root::-webkit-scrollbar, body::-webkit-scrollbar { background: var(--oc-bg, inherit); }
17
+ ::-webkit-scrollbar-track { background: transparent; }
18
+ ::-webkit-scrollbar-thumb {
19
+ background: var(--oc-scrollbar-thumb);
20
+ border-radius: 999px;
21
+ min-width: 24px;
22
+ min-height: 24px;
23
+ }
24
+ ::-webkit-scrollbar-thumb:hover { background: var(--oc-scrollbar-thumb-hover); }
25
+ ::-webkit-scrollbar-corner { background: transparent; }
26
+ ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
27
+ }
28
+ @media (forced-colors: active) {
29
+ * { scrollbar-color: auto; }
30
+ ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover { background: CanvasText; }
31
+ }
32
+ `;
package/dist/ui/menu.js CHANGED
@@ -51,8 +51,12 @@ export const mountMenu = (root, initial) => {
51
51
  setActive(actions(props.items).some((item) => item.id === activeId) ? activeId : null);
52
52
  };
53
53
  const close = () => {
54
- closePopup?.();
54
+ // Removing a focused row fires `focusout` synchronously from inside the disposer, and
55
+ // that handler calls `close` again. Clearing the slot first makes the re-entry a no-op
56
+ // instead of a second `popup.remove()` that throws before the pick reaches `onChange`.
57
+ const dispose = closePopup;
55
58
  closePopup = null;
59
+ dispose?.();
56
60
  activeId = null;
57
61
  triggerNode?.setAttribute('aria-expanded', 'false');
58
62
  };
package/dist/ui/select.js CHANGED
@@ -63,8 +63,12 @@ export const mountSelect = (root, initial) => {
63
63
  setActive(options.some((option) => option.id === activeId) ? activeId : options[0]?.id ?? null);
64
64
  };
65
65
  const close = () => {
66
- closePopup?.();
66
+ // Removing a focused row fires `focusout` synchronously from inside the disposer, and
67
+ // that handler calls `close` again. Clearing the slot first makes the re-entry a no-op
68
+ // instead of a second `popup.remove()` that throws before the pick reaches `onChange`.
69
+ const dispose = closePopup;
67
70
  closePopup = null;
71
+ dispose?.();
68
72
  query = '';
69
73
  search.value = '';
70
74
  trigger.setAttribute('aria-expanded', 'false');