@openchamber/sdk 1.23.2-preview.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.
Files changed (75) hide show
  1. package/API.md +529 -0
  2. package/DOCUMENTATION.md +81 -0
  3. package/GUEST_SERVICES.md +166 -0
  4. package/LICENSE +21 -0
  5. package/README.md +186 -0
  6. package/dist/api-version.d.ts +6 -0
  7. package/dist/api-version.js +5 -0
  8. package/dist/contract.d.ts +471 -0
  9. package/dist/contract.js +256 -0
  10. package/dist/host-version.d.ts +16 -0
  11. package/dist/host-version.js +50 -0
  12. package/dist/host.d.ts +101 -0
  13. package/dist/host.js +606 -0
  14. package/dist/index.d.ts +12 -0
  15. package/dist/index.js +7 -0
  16. package/dist/manifest.d.ts +298 -0
  17. package/dist/manifest.js +224 -0
  18. package/dist/parse.d.ts +279 -0
  19. package/dist/parse.js +379 -0
  20. package/dist/protocol.d.ts +1092 -0
  21. package/dist/protocol.js +450 -0
  22. package/dist/schemas.d.ts +4 -0
  23. package/dist/schemas.js +6 -0
  24. package/dist/ui/badge.d.ts +10 -0
  25. package/dist/ui/badge.js +26 -0
  26. package/dist/ui/banner.d.ts +13 -0
  27. package/dist/ui/banner.js +48 -0
  28. package/dist/ui/button.d.ts +14 -0
  29. package/dist/ui/button.js +49 -0
  30. package/dist/ui/checkbox.d.ts +12 -0
  31. package/dist/ui/checkbox.js +45 -0
  32. package/dist/ui/dom.d.ts +15 -0
  33. package/dist/ui/dom.js +60 -0
  34. package/dist/ui/empty.d.ts +11 -0
  35. package/dist/ui/empty.js +44 -0
  36. package/dist/ui/field.d.ts +18 -0
  37. package/dist/ui/field.js +49 -0
  38. package/dist/ui/icons.d.ts +10 -0
  39. package/dist/ui/icons.js +23 -0
  40. package/dist/ui/index.d.ts +35 -0
  41. package/dist/ui/index.js +17 -0
  42. package/dist/ui/list.d.ts +26 -0
  43. package/dist/ui/list.js +90 -0
  44. package/dist/ui/menu.d.ts +20 -0
  45. package/dist/ui/menu.js +111 -0
  46. package/dist/ui/navigation.d.ts +18 -0
  47. package/dist/ui/navigation.js +38 -0
  48. package/dist/ui/option.d.ts +16 -0
  49. package/dist/ui/option.js +35 -0
  50. package/dist/ui/popup.d.ts +5 -0
  51. package/dist/ui/popup.js +43 -0
  52. package/dist/ui/progress.d.ts +11 -0
  53. package/dist/ui/progress.js +44 -0
  54. package/dist/ui/search.d.ts +11 -0
  55. package/dist/ui/search.js +62 -0
  56. package/dist/ui/select.d.ts +22 -0
  57. package/dist/ui/select.js +164 -0
  58. package/dist/ui/separator.d.ts +6 -0
  59. package/dist/ui/separator.js +26 -0
  60. package/dist/ui/spinner.d.ts +8 -0
  61. package/dist/ui/spinner.js +29 -0
  62. package/dist/ui/style.d.ts +1 -0
  63. package/dist/ui/style.js +190 -0
  64. package/dist/ui/tabs.d.ts +15 -0
  65. package/dist/ui/tabs.js +61 -0
  66. package/dist/ui/text.d.ts +23 -0
  67. package/dist/ui/text.js +89 -0
  68. package/dist/ui/theme.d.ts +22 -0
  69. package/dist/ui/theme.js +69 -0
  70. package/dist/workspace-schemas.d.ts +136 -0
  71. package/dist/workspace-schemas.js +44 -0
  72. package/dist/workspace.d.ts +109 -0
  73. package/dist/workspace.js +4 -0
  74. package/package.json +55 -0
  75. package/scripts/bundle-guest.ts +44 -0
@@ -0,0 +1,298 @@
1
+ import type { OpenChamberManifestApiVersion } from './api-version.ts';
2
+ export declare const PANEL_ID: RegExp;
3
+ /**
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`).
7
+ */
8
+ export type PanelContribution = {
9
+ id: string;
10
+ name: string;
11
+ icon: string;
12
+ entry?: string;
13
+ };
14
+ export type AttachMode = 'panel' | 'dialog';
15
+ /**
16
+ * Object form of `contributes.attach`. `entry` is an HTML page inside the
17
+ * package that the attach dialog loads instead of `panel.entry`; it is only
18
+ * meaningful with `mode: "dialog"`.
19
+ */
20
+ export type AttachContributionObject = {
21
+ mode: AttachMode;
22
+ entry?: string;
23
+ };
24
+ export type AttachContribution = boolean | AttachMode | AttachContributionObject;
25
+ /** A user-opened full-screen page, optionally with its own HTML and title. */
26
+ export type PageContribution = true | {
27
+ entry: string;
28
+ title?: string;
29
+ };
30
+ export type GuestActionWhere = 'message' | 'session';
31
+ export type GuestActionRole = 'user' | 'assistant';
32
+ /** What a session action wants alongside the session id and title. */
33
+ export type GuestActionPayload = 'messages';
34
+ /** How many `contributes.actions` entries a package may declare. */
35
+ export declare const GUEST_ACTIONS_MAX = 8;
36
+ /** Characters in an action label. */
37
+ export declare const GUEST_ACTION_LABEL_MAX = 40;
38
+ /** How many `contributes.commands` entries a package may declare. */
39
+ export declare const GUEST_COMMANDS_MAX = 8;
40
+ /** Characters in a command description. */
41
+ export declare const GUEST_COMMAND_DESCRIPTION_MAX = 80;
42
+ /** A slash command name: lower-case, digits and dashes, up to 24 characters. */
43
+ export declare const GUEST_COMMAND_NAME: RegExp;
44
+ /**
45
+ * A menu entry on a message or a session. Clicking it opens the guest with
46
+ * the message or session as `ready.item`. `roles` narrows a message action
47
+ * to user or assistant messages (default both); `payload: ["messages"]` on
48
+ * a session action asks for the conversation and needs the `conversation`
49
+ * capability.
50
+ */
51
+ export type GuestActionContribution = {
52
+ id: string;
53
+ label: string;
54
+ /** Remixicon name or package `.svg` path, same as `panel.icon`. Falls back to the panel icon. */
55
+ icon?: string;
56
+ where: GuestActionWhere;
57
+ roles?: GuestActionRole[];
58
+ payload?: GuestActionPayload[];
59
+ };
60
+ /** A composer slash command the guest resolves into a chip through `host.onResolve`. */
61
+ export type GuestCommandContribution = {
62
+ name: string;
63
+ description?: string;
64
+ };
65
+ /** How many `contributes.tools` entries a package may declare. */
66
+ export declare const GUEST_TOOLS_MAX = 16;
67
+ /** Characters in a `contributes.tools` `match`. */
68
+ export declare const GUEST_TOOL_MATCH_MAX = 128;
69
+ /**
70
+ * A tool name as OpenCode reports it (`mcp.jira.search`, `jira_search`),
71
+ * with `*` allowed once, at the end, as a suffix wildcard (`mcp.jira.*`).
72
+ */
73
+ export declare const GUEST_TOOL_MATCH: RegExp;
74
+ /** Characters in a tool `name`. */
75
+ export declare const GUEST_TOOL_NAME_MAX = 40;
76
+ /** Characters in a `title` or `subtitle` template. */
77
+ export declare const GUEST_TOOL_TEMPLATE_MAX = 200;
78
+ /** Characters a substituted `{input.path}` value may take in a rendered template. */
79
+ export declare const GUEST_TOOL_TEMPLATE_VALUE_MAX = 200;
80
+ /** Characters in a `code` language id. */
81
+ export declare const GUEST_TOOL_LANGUAGE_MAX = 32;
82
+ /** How many `columns` a `table` presentation may name. */
83
+ export declare const GUEST_TOOL_COLUMNS_MAX = 16;
84
+ /** Characters in one column path. */
85
+ export declare const GUEST_TOOL_COLUMN_MAX = 64;
86
+ export declare const GUEST_TOOL_OUTPUTS: readonly ["auto", "text", "json", "markdown", "code", "table"];
87
+ /** How the expanded body of a matched tool call renders. `auto` keeps the host's own detection. */
88
+ export type GuestToolOutput = (typeof GUEST_TOOL_OUTPUTS)[number];
89
+ /**
90
+ * How a tool call looks in the chat, declared without code. `match` is the
91
+ * full tool name OpenCode reports, or a prefix ending in `*`. `title` and
92
+ * `subtitle` are templates with `{input.path}`, `{output.path}`, and
93
+ * `{metadata.path}` placeholders; a missing path renders as an empty string.
94
+ * `language` only means something for `output: "code"` and `columns` only
95
+ * for `output: "table"` (rows are the output array or `output.items`).
96
+ */
97
+ export type GuestToolContribution = {
98
+ match: string;
99
+ /** Header title when `title` is absent or renders empty. */
100
+ name?: string;
101
+ /** Remixicon name or package `.svg` path, same rules as `panel.icon`. */
102
+ icon?: string;
103
+ title?: string;
104
+ subtitle?: string;
105
+ output?: GuestToolOutput;
106
+ language?: string;
107
+ columns?: string[];
108
+ };
109
+ export type IntegrationSettingField = {
110
+ id: string;
111
+ label: string;
112
+ };
113
+ export type IntegrationOAuthAccount = {
114
+ path: string;
115
+ name: string;
116
+ };
117
+ export type IntegrationOAuth = {
118
+ authorizeUrl: string;
119
+ tokenUrl: string;
120
+ apiOrigin: string;
121
+ scopes?: string[];
122
+ account?: IntegrationOAuthAccount;
123
+ };
124
+ export type IntegrationTokenScheme = 'raw' | 'bearer' | 'basic';
125
+ export type IntegrationToken = {
126
+ apiOrigin: string;
127
+ account?: IntegrationOAuthAccount;
128
+ /**
129
+ * How the pasted token travels: `raw` puts it in `Authorization` as is (default),
130
+ * `bearer` prefixes `Bearer `, `basic` asks for a username too and sends
131
+ * `Basic base64(username:token)` (Jira Cloud, Bitbucket, most Atlassian APIs).
132
+ */
133
+ scheme?: IntegrationTokenScheme;
134
+ /** Label of the username field on the Integrations card for `basic`. Defaults to "Username". */
135
+ usernameLabel?: string;
136
+ };
137
+ export type IntegrationHostProvider = 'linear';
138
+ export type IntegrationHost = {
139
+ provider: IntegrationHostProvider;
140
+ };
141
+ export type IntegrationAuth = 'oauth' | 'token' | 'host';
142
+ export type GuestAuthorization = 'bearer' | 'basic' | 'header';
143
+ export type ResolvedGuestApi = {
144
+ apiOrigin: string;
145
+ account?: IntegrationOAuthAccount;
146
+ authorization: GuestAuthorization;
147
+ };
148
+ export declare const HOST_LINEAR_API_ORIGIN = "https://api.linear.app";
149
+ export type IntegrationContribution = {
150
+ name: string;
151
+ description: string;
152
+ oauth?: IntegrationOAuth;
153
+ token?: IntegrationToken;
154
+ host?: IntegrationHost;
155
+ settings?: IntegrationSettingField[];
156
+ };
157
+ /** Catalog card. Drops oauth URLs and token apiOrigin. */
158
+ export type PublicIntegrationToken = {
159
+ scheme: IntegrationTokenScheme;
160
+ usernameLabel?: string;
161
+ };
162
+ export type PublicIntegration = {
163
+ name: string;
164
+ description: string;
165
+ auth: IntegrationAuth;
166
+ /** Present for `auth: 'token'`. Tells the Integrations card which fields to draw. */
167
+ token?: PublicIntegrationToken;
168
+ /** The one origin `request` may call. Shown in the approval dialog; not a secret. */
169
+ apiOrigin?: string;
170
+ settings?: IntegrationSettingField[];
171
+ };
172
+ export declare const resolveIntegrationAuth: (integration: Pick<IntegrationContribution, "oauth" | "token" | "host">) => IntegrationAuth | null;
173
+ export declare const resolveIntegrationApi: (integration: IntegrationContribution) => ResolvedGuestApi | null;
174
+ export type SocketPlatform = 'linux' | 'darwin' | 'win32';
175
+ /** Declared socket the service may dial. Candidates are per host platform. */
176
+ export type SocketBinding = {
177
+ id: string;
178
+ candidatesByPlatform: Partial<Record<SocketPlatform, string[]>>;
179
+ };
180
+ export type ServicePermissions = {
181
+ sockets?: SocketBinding[];
182
+ exec?: string[];
183
+ };
184
+ /** Catalog grant chip: ids only. Paths live on `socketBindings`. */
185
+ export type PublicServicePermissions = {
186
+ sockets?: string[];
187
+ exec?: string[];
188
+ };
189
+ /** Resolved socket for this host after override + candidate scan. */
190
+ export type PublicSocketBinding = {
191
+ id: string;
192
+ candidates: string[];
193
+ resolved: string | null;
194
+ override: string | null;
195
+ };
196
+ export type ServiceContribution = {
197
+ entry: string;
198
+ runtime: 'host';
199
+ permissions?: ServicePermissions;
200
+ };
201
+ /** Catalog card for a local service. Drops nothing secret; grant is host state. */
202
+ export type PublicService = {
203
+ runtime: 'host';
204
+ permissions?: PublicServicePermissions;
205
+ socketBindings?: PublicSocketBinding[];
206
+ granted: boolean;
207
+ };
208
+ /**
209
+ * What a guest may do beyond drawing its own panel. The user approves the
210
+ * full list once, when the package is installed; a later package that asks
211
+ * for more is re-approved. `prompt`, `sessions`, `files`, and `model` are
212
+ * declared under `contributes.capabilities`; `service`, `network`, and
213
+ * `filesystem` follow from `contributes.service`, `contributes.integration`,
214
+ * and `contributes.filesystem`. `model` is one-off text generation with the
215
+ * user's Small Model (`host.generate`), outside any session.
216
+ */
217
+ export declare const GUEST_CAPABILITIES: readonly ["prompt", "sessions", "files", "model", "conversation", "service", "network", "filesystem"];
218
+ export type GuestCapability = (typeof GUEST_CAPABILITIES)[number];
219
+ export declare const DECLARED_GUEST_CAPABILITIES: readonly ["prompt", "sessions", "files", "model"];
220
+ /** The capabilities a manifest may ask for directly. */
221
+ export type DeclaredGuestCapability = (typeof DECLARED_GUEST_CAPABILITIES)[number];
222
+ /** How many `contributes.filesystem` patterns a package may declare. */
223
+ export declare const GUEST_FILESYSTEM_PATTERNS_MAX = 16;
224
+ /** Characters in one `contributes.filesystem` pattern. */
225
+ export declare const GUEST_FILESYSTEM_PATTERN_MAX = 256;
226
+ /**
227
+ * A `contributes.filesystem` entry: an absolute glob (`/…`) or one under the
228
+ * user's home (`~/…`). `**` spans directories, `*` and `?` stay inside one
229
+ * segment. No `..`, no empty segment, no NUL, no backslash.
230
+ */
231
+ export declare const isGuestFilesystemPattern: (value: string) => boolean;
232
+ export type OpenChamberContributes = {
233
+ panel: PanelContribution;
234
+ attach?: AttachContribution;
235
+ page?: PageContribution;
236
+ capabilities?: DeclaredGuestCapability[];
237
+ integration?: IntegrationContribution;
238
+ service?: ServiceContribution;
239
+ /** Paths outside the project the panel may read and write. Grants `filesystem`. */
240
+ filesystem?: string[];
241
+ /** Menu entries on messages and sessions. */
242
+ actions?: GuestActionContribution[];
243
+ /** Composer slash commands that attach a chip. */
244
+ commands?: GuestCommandContribution[];
245
+ /** How the extension's tool calls look in the chat. */
246
+ tools?: GuestToolContribution[];
247
+ };
248
+ /** Whether any declared action asks for a session's messages, which needs `conversation`. */
249
+ export declare const guestActionsNeedConversation: (actions: readonly GuestActionContribution[] | undefined) => boolean;
250
+ /** Catalog view of the approval: what the package asks for and what the user allowed. */
251
+ export type PublicGuestCapabilities = {
252
+ requested: GuestCapability[];
253
+ granted: GuestCapability[];
254
+ };
255
+ export declare const requestedGuestCapabilities: (contributes: Pick<OpenChamberContributes, "capabilities" | "integration" | "service" | "filesystem" | "actions">) => GuestCapability[];
256
+ /**
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.
261
+ */
262
+ export declare const hasGuestPage: (contributes: Pick<OpenChamberContributes, "panel">) => boolean;
263
+ export declare const isGuestApproved: (capabilities: PublicGuestCapabilities) => boolean;
264
+ export declare const hasGuestCapability: (capabilities: PublicGuestCapabilities, capability: GuestCapability) => boolean;
265
+ export declare const resolveAttachMode: (attach: AttachContribution | undefined) => AttachMode | null;
266
+ /**
267
+ * The page the attach dialog loads, or `null` when the dialog reuses
268
+ * `panel.entry`. Only the object form with `mode: "dialog"` can name one.
269
+ */
270
+ export declare const resolveAttachEntry: (contributes: Pick<OpenChamberContributes, "attach">) => string | null;
271
+ export declare const resolvePageEntry: (contributes: Pick<OpenChamberContributes, "panel" | "page">) => string | null;
272
+ export type OpenChamberEngines = {
273
+ openchamber: string;
274
+ };
275
+ export type OpenChamberManifest = {
276
+ apiVersion: OpenChamberManifestApiVersion;
277
+ engines?: OpenChamberEngines;
278
+ contributes: OpenChamberContributes;
279
+ };
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';
281
+ export type ParseManifestFailure = {
282
+ ok: false;
283
+ code: ParseManifestErrorCode;
284
+ message: string;
285
+ };
286
+ export type ParseManifestSuccess = {
287
+ ok: true;
288
+ manifest: OpenChamberManifest;
289
+ /** npm `package.json` version when parsing a package envelope. */
290
+ version?: string;
291
+ };
292
+ export type ParseManifestResult = ParseManifestSuccess | ParseManifestFailure;
293
+ export declare const isSafeAssetPath: (value: string) => boolean;
294
+ /** Package SVG path for the rail, e.g. `icon.svg`. Remixicon names use `PANEL_ID`. */
295
+ export declare const isGuestPackageSvgIcon: (value: string) => boolean;
296
+ export declare const toPublicIntegration: (integration: IntegrationContribution) => PublicIntegration;
297
+ export declare const toPublicService: (service: ServiceContribution | undefined, granted: boolean, socketBindings?: PublicSocketBinding[]) => PublicService | undefined;
298
+ /** Guest package version: `1.2.3`, optional prerelease / build. */
@@ -0,0 +1,224 @@
1
+ export const PANEL_ID = /^[a-z][a-z0-9-]*$/;
2
+ /** How many `contributes.actions` entries a package may declare. */
3
+ export const GUEST_ACTIONS_MAX = 8;
4
+ /** Characters in an action label. */
5
+ export const GUEST_ACTION_LABEL_MAX = 40;
6
+ /** How many `contributes.commands` entries a package may declare. */
7
+ export const GUEST_COMMANDS_MAX = 8;
8
+ /** Characters in a command description. */
9
+ export const GUEST_COMMAND_DESCRIPTION_MAX = 80;
10
+ /** A slash command name: lower-case, digits and dashes, up to 24 characters. */
11
+ export const GUEST_COMMAND_NAME = /^[a-z][a-z0-9-]{0,23}$/;
12
+ /** How many `contributes.tools` entries a package may declare. */
13
+ export const GUEST_TOOLS_MAX = 16;
14
+ /** Characters in a `contributes.tools` `match`. */
15
+ export const GUEST_TOOL_MATCH_MAX = 128;
16
+ /**
17
+ * A tool name as OpenCode reports it (`mcp.jira.search`, `jira_search`),
18
+ * with `*` allowed once, at the end, as a suffix wildcard (`mcp.jira.*`).
19
+ */
20
+ export const GUEST_TOOL_MATCH = /^[A-Za-z0-9_.:-]+\*?$/;
21
+ /** Characters in a tool `name`. */
22
+ export const GUEST_TOOL_NAME_MAX = 40;
23
+ /** Characters in a `title` or `subtitle` template. */
24
+ export const GUEST_TOOL_TEMPLATE_MAX = 200;
25
+ /** Characters a substituted `{input.path}` value may take in a rendered template. */
26
+ export const GUEST_TOOL_TEMPLATE_VALUE_MAX = 200;
27
+ /** Characters in a `code` language id. */
28
+ export const GUEST_TOOL_LANGUAGE_MAX = 32;
29
+ /** How many `columns` a `table` presentation may name. */
30
+ export const GUEST_TOOL_COLUMNS_MAX = 16;
31
+ /** Characters in one column path. */
32
+ export const GUEST_TOOL_COLUMN_MAX = 64;
33
+ export const GUEST_TOOL_OUTPUTS = ['auto', 'text', 'json', 'markdown', 'code', 'table'];
34
+ export const HOST_LINEAR_API_ORIGIN = 'https://api.linear.app';
35
+ export const resolveIntegrationAuth = (integration) => {
36
+ const kinds = [Boolean(integration.oauth), Boolean(integration.token), Boolean(integration.host)]
37
+ .filter(Boolean).length;
38
+ if (kinds !== 1) {
39
+ return null;
40
+ }
41
+ if (integration.host)
42
+ return 'host';
43
+ return integration.oauth ? 'oauth' : 'token';
44
+ };
45
+ const resolveTokenAuthorization = (scheme) => {
46
+ if (scheme === 'bearer' || scheme === 'basic') {
47
+ return scheme;
48
+ }
49
+ return 'header';
50
+ };
51
+ export const resolveIntegrationApi = (integration) => {
52
+ if (integration.oauth) {
53
+ return {
54
+ apiOrigin: integration.oauth.apiOrigin,
55
+ account: integration.oauth.account,
56
+ authorization: 'bearer',
57
+ };
58
+ }
59
+ if (integration.token) {
60
+ return {
61
+ apiOrigin: integration.token.apiOrigin,
62
+ account: integration.token.account,
63
+ authorization: resolveTokenAuthorization(integration.token.scheme),
64
+ };
65
+ }
66
+ if (integration.host?.provider === 'linear') {
67
+ return {
68
+ apiOrigin: HOST_LINEAR_API_ORIGIN,
69
+ authorization: 'bearer',
70
+ };
71
+ }
72
+ return null;
73
+ };
74
+ /**
75
+ * What a guest may do beyond drawing its own panel. The user approves the
76
+ * full list once, when the package is installed; a later package that asks
77
+ * for more is re-approved. `prompt`, `sessions`, `files`, and `model` are
78
+ * declared under `contributes.capabilities`; `service`, `network`, and
79
+ * `filesystem` follow from `contributes.service`, `contributes.integration`,
80
+ * and `contributes.filesystem`. `model` is one-off text generation with the
81
+ * user's Small Model (`host.generate`), outside any session.
82
+ */
83
+ export const GUEST_CAPABILITIES = ['prompt', 'sessions', 'files', 'model', 'conversation', 'service', 'network', 'filesystem'];
84
+ export const DECLARED_GUEST_CAPABILITIES = ['prompt', 'sessions', 'files', 'model'];
85
+ /** How many `contributes.filesystem` patterns a package may declare. */
86
+ export const GUEST_FILESYSTEM_PATTERNS_MAX = 16;
87
+ /** Characters in one `contributes.filesystem` pattern. */
88
+ export const GUEST_FILESYSTEM_PATTERN_MAX = 256;
89
+ /**
90
+ * A `contributes.filesystem` entry: an absolute glob (`/…`) or one under the
91
+ * user's home (`~/…`). `**` spans directories, `*` and `?` stay inside one
92
+ * segment. No `..`, no empty segment, no NUL, no backslash.
93
+ */
94
+ export const isGuestFilesystemPattern = (value) => {
95
+ if (value.length === 0 || value.length > GUEST_FILESYSTEM_PATTERN_MAX) {
96
+ return false;
97
+ }
98
+ if (value.includes('\0') || value.includes('\\')) {
99
+ return false;
100
+ }
101
+ if (!value.startsWith('/') && !value.startsWith('~/')) {
102
+ return false;
103
+ }
104
+ const segments = value.split('/').slice(1);
105
+ return !segments.some((segment) => segment === '' || segment === '..');
106
+ };
107
+ /** Whether any declared action asks for a session's messages, which needs `conversation`. */
108
+ export const guestActionsNeedConversation = (actions) => Boolean(actions?.some((action) => action.payload?.includes('messages')));
109
+ export const requestedGuestCapabilities = (contributes) => {
110
+ const declared = new Set(contributes.capabilities ?? []);
111
+ if (guestActionsNeedConversation(contributes.actions))
112
+ declared.add('conversation');
113
+ if (contributes.service)
114
+ declared.add('service');
115
+ if (contributes.integration)
116
+ declared.add('network');
117
+ if (contributes.filesystem && contributes.filesystem.length > 0)
118
+ declared.add('filesystem');
119
+ return GUEST_CAPABILITIES.filter((capability) => declared.has(capability));
120
+ };
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.
126
+ */
127
+ export const hasGuestPage = (contributes) => Boolean(contributes.panel.entry);
128
+ export const isGuestApproved = (capabilities) => (capabilities.requested.every((capability) => capabilities.granted.includes(capability)));
129
+ export const hasGuestCapability = (capabilities, capability) => capabilities.granted.includes(capability);
130
+ // Everything that is not one of the scalar spellings is the object form;
131
+ // the parser has already refused anything else.
132
+ const isAttachObject = (attach) => (attach !== undefined && attach !== true && attach !== false && attach !== 'panel' && attach !== 'dialog');
133
+ export const resolveAttachMode = (attach) => {
134
+ if (attach === true || attach === 'panel')
135
+ return 'panel';
136
+ if (attach === 'dialog')
137
+ return 'dialog';
138
+ if (isAttachObject(attach))
139
+ return attach.mode;
140
+ return null;
141
+ };
142
+ /**
143
+ * The page the attach dialog loads, or `null` when the dialog reuses
144
+ * `panel.entry`. Only the object form with `mode: "dialog"` can name one.
145
+ */
146
+ export const resolveAttachEntry = (contributes) => {
147
+ const attach = contributes.attach;
148
+ if (!isAttachObject(attach) || attach.mode !== 'dialog')
149
+ return null;
150
+ return attach.entry ?? null;
151
+ };
152
+ export const resolvePageEntry = (contributes) => {
153
+ if (!contributes.page || !contributes.panel.entry)
154
+ return null;
155
+ return contributes.page === true ? contributes.panel.entry : contributes.page.entry;
156
+ };
157
+ export const isSafeAssetPath = (value) => {
158
+ if (value.includes('\0') || value.includes('\\') || value.startsWith('/') || value.includes('://')) {
159
+ return false;
160
+ }
161
+ const segments = value.split('/');
162
+ if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) {
163
+ return false;
164
+ }
165
+ return /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(value);
166
+ };
167
+ /** Package SVG path for the rail, e.g. `icon.svg`. Remixicon names use `PANEL_ID`. */
168
+ export const isGuestPackageSvgIcon = (value) => (isSafeAssetPath(value) && value.toLowerCase().endsWith('.svg'));
169
+ export const toPublicIntegration = (integration) => {
170
+ const auth = resolveIntegrationAuth(integration) ?? 'oauth';
171
+ const next = {
172
+ name: integration.name,
173
+ description: integration.description,
174
+ auth,
175
+ };
176
+ const api = resolveIntegrationApi(integration);
177
+ if (api?.apiOrigin) {
178
+ next.apiOrigin = api.apiOrigin;
179
+ }
180
+ if (auth === 'token' && integration.token) {
181
+ next.token = { scheme: integration.token.scheme ?? 'raw' };
182
+ if (integration.token.usernameLabel) {
183
+ next.token.usernameLabel = integration.token.usernameLabel;
184
+ }
185
+ }
186
+ if (integration.settings && integration.settings.length > 0) {
187
+ next.settings = integration.settings.map((field) => ({
188
+ id: field.id,
189
+ label: field.label,
190
+ }));
191
+ }
192
+ return next;
193
+ };
194
+ export const toPublicService = (service, granted, socketBindings) => {
195
+ if (!service) {
196
+ return undefined;
197
+ }
198
+ const next = {
199
+ runtime: service.runtime,
200
+ granted,
201
+ };
202
+ if (service.permissions) {
203
+ const permissions = {};
204
+ if (service.permissions.sockets && service.permissions.sockets.length > 0) {
205
+ permissions.sockets = service.permissions.sockets.map((binding) => binding.id);
206
+ }
207
+ if (service.permissions.exec && service.permissions.exec.length > 0) {
208
+ permissions.exec = [...service.permissions.exec];
209
+ }
210
+ if (permissions.sockets || permissions.exec) {
211
+ next.permissions = permissions;
212
+ }
213
+ }
214
+ if (socketBindings && socketBindings.length > 0) {
215
+ next.socketBindings = socketBindings.map((binding) => ({
216
+ id: binding.id,
217
+ candidates: [...binding.candidates],
218
+ resolved: binding.resolved,
219
+ override: binding.override,
220
+ }));
221
+ }
222
+ return next;
223
+ };
224
+ /** Guest package version: `1.2.3`, optional prerelease / build. */