@openchamber/sdk 2.5.0 → 2.5.2

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/parse.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from './api-version.js';
3
3
  import { OPENCHAMBER_ENGINE_PATTERN } from './host-version.js';
4
- import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, 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, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, } from './manifest.js';
4
+ import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, 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, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, SOURCE_CONTROL_DEFAULT_INSTANCES_MAX, SOURCE_CONTROL_GUEST_ID_MAX, SOURCE_CONTROL_URL_TEMPLATES_MAX, SOURCE_CONTROL_URL_TEMPLATE_MAX, SOURCE_CONTROL_URL_TOKENS, } from './manifest.js';
5
+ import { isGuestRequestPath } from './contract.js';
6
+ import { GUEST_STATUS_SOURCE_INTERVAL_MAX, GUEST_STATUS_SOURCE_INTERVAL_MIN } from './status-view.js';
5
7
  import { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_PATTERNS_MAX, GUEST_FILE_EDITOR_TITLE_MAX, isFileEditorPattern, } from './file-editor.js';
6
8
  const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
7
9
  const isHttpsUrl = (value) => {
@@ -156,6 +158,148 @@ const serviceSchema = z.object({
156
158
  .optional(),
157
159
  surface: z.literal(true).optional(),
158
160
  });
161
+ // SVG path data only, the same rule the UI applies to a provider's icon path:
162
+ // commands, numbers and separators, nothing that could close the attribute.
163
+ const SVG_PATH_DATA = /^[MmLlHhVvCcSsQqTtAaZz0-9eE.,\s+-]+$/;
164
+ // A literal URL template segment: no placeholder braces, no regex syntax.
165
+ const URL_TEMPLATE_LITERAL = /^[A-Za-z0-9._~-]+$/;
166
+ const URL_TEMPLATE_TOKENS = new Set(SOURCE_CONTROL_URL_TOKENS);
167
+ /**
168
+ * A web path template of a change request or issue: `/`, then segments that
169
+ * are each a literal or one of the known placeholders. `{repo}` and
170
+ * `{number}` appear exactly once, and the repository's owner exactly once as
171
+ * `{owner}` (`owner/name` paths) or `{namespace+}` (`namespace/name` paths).
172
+ */
173
+ const isUrlTemplate = (value, repositoryPath) => {
174
+ if (!value.startsWith('/') || value.length > SOURCE_CONTROL_URL_TEMPLATE_MAX)
175
+ return false;
176
+ const segments = value.slice(1).split('/');
177
+ // A normalized URL never has a `.` or `..` segment, so a template with one matches nothing.
178
+ if (segments.some((segment) => segment === '.' || segment === '..'))
179
+ return false;
180
+ if (segments.some((segment) => !URL_TEMPLATE_TOKENS.has(segment) && !URL_TEMPLATE_LITERAL.test(segment)))
181
+ return false;
182
+ const count = (token) => segments.filter((segment) => segment === token).length;
183
+ const owner = repositoryPath === 'owner/name' ? '{owner}' : '{namespace+}';
184
+ const other = repositoryPath === 'owner/name' ? '{namespace+}' : '{owner}';
185
+ return count('{repo}') === 1 && count('{number}') === 1 && count(owner) === 1 && count(other) === 0;
186
+ };
187
+ // A file inside the repository, as a change request template names it:
188
+ // relative, no `.` or `..` segment, dot-folders like `.gitea/` allowed.
189
+ const REPOSITORY_FILE_SEGMENT = /^[A-Za-z0-9._-]+$/;
190
+ const isRepositoryFilePath = (value) => value.length > 0 && value.split('/')
191
+ .every((segment) => REPOSITORY_FILE_SEGMENT.test(segment) && segment !== '.' && segment !== '..');
192
+ const prefixSchema = z.string().min(1).max(4).regex(/^\S+$/);
193
+ const literalSchema = z.string().trim().min(1).max(64);
194
+ const sourceControlCapabilitiesSchema = z.object({
195
+ projects: z.boolean().default(false),
196
+ issues: z.boolean().default(false),
197
+ changeRequests: z.boolean().default(false),
198
+ draftChangeRequests: z.boolean().default(false),
199
+ merge: z.union([
200
+ z.literal(false),
201
+ z.object({
202
+ methods: z.array(z.enum(['merge', 'squash', 'rebase', 'submit'])).min(1).max(3)
203
+ .refine((methods) => new Set(methods).size === methods.length, { message: 'merge methods must be unique' })
204
+ // A host that submits a change merges it the one way its project is set up.
205
+ .refine((methods) => !methods.includes('submit') || methods.length === 1, { message: '"submit" is the only merge method when present' }),
206
+ }).strict(),
207
+ ]).default(false),
208
+ ci: z.boolean().default(false),
209
+ comments: z.boolean().default(false),
210
+ reviews: z.boolean().default(false),
211
+ state: z.boolean().default(false),
212
+ labels: z.boolean().default(false),
213
+ reviewers: z.boolean().default(false),
214
+ changeRequestPatch: z.boolean().default(false),
215
+ liveSummaries: z.boolean().default(false),
216
+ // Left out rather than defaulted, so a manifest that does not use them parses as before.
217
+ reviewModel: z.enum(['verdict', 'votes']).optional(),
218
+ revisions: z.boolean().optional(),
219
+ }).strict();
220
+ // A Git username OpenChamber puts before the token in an HTTPS credential.
221
+ const FIXED_USERNAME = /^[A-Za-z0-9._-]{1,64}$/;
222
+ const urlTemplatesSchema = z.array(z.string().trim().min(1)).max(SOURCE_CONTROL_URL_TEMPLATES_MAX);
223
+ const sourceControlSchema = z.object({
224
+ wire: z.literal(1),
225
+ label: literalSchema,
226
+ terminology: z.object({
227
+ changeRequest: z.enum(['pullRequest', 'mergeRequest', 'change']),
228
+ ci: z.enum(['checks', 'pipeline']),
229
+ }).strict(),
230
+ referencePrefix: z.object({ changeRequest: prefixSchema, issue: prefixSchema }).strict(),
231
+ repositoryPath: z.enum(['owner/name', 'namespace/name']),
232
+ instances: z.object({
233
+ defaultInstances: z.array(z.string().trim().refine(isHttpsOrigin, { message: 'must be an https origin with no path, like https://git.example.com' }))
234
+ .max(SOURCE_CONTROL_DEFAULT_INSTANCES_MAX)
235
+ .refine((origins) => new Set(origins).size === origins.length, { message: 'default instances must be unique' }),
236
+ custom: z.boolean(),
237
+ allowLoopbackHttp: z.boolean(),
238
+ allowPort: z.boolean(),
239
+ placeholder: z.string().trim().max(256),
240
+ }).strict().refine((instances) => instances.custom || instances.defaultInstances.length > 0, { message: 'without custom instances, defaultInstances names at least one' }),
241
+ capabilities: sourceControlCapabilitiesSchema,
242
+ token: z.object({
243
+ header: z.enum(['authorization', 'private-token']),
244
+ scheme: z.enum(['bearer', 'token', 'basic', 'none']),
245
+ username: z.boolean().optional(),
246
+ scopes: z.array(z.string().trim().min(1).max(64)).max(32),
247
+ createPath: z.string().trim().max(256).refine(isSafeApiPath, { message: 'must be a path on the instance starting with "/", with no "." or ".." segment' }).optional(),
248
+ }).strict().refine((token) => (token.header === 'private-token') === (token.scheme === 'none'), { message: 'private-token takes the bare token; authorization takes a bearer, token or basic scheme' }).refine(
249
+ // Basic sends `login:token`, so Settings must ask for the login; no other scheme uses one.
250
+ (token) => (token.scheme === 'basic') === (token.username === true), { message: 'the basic scheme needs username: true, and username is only for the basic scheme' }),
251
+ transport: z.object({
252
+ httpsUsername: z.union([
253
+ z.object({ kind: z.literal('login') }).strict(),
254
+ z.object({ kind: z.literal('fixed'), value: z.string().regex(FIXED_USERNAME) }).strict(),
255
+ ]),
256
+ appendDotGitToHttpsClone: z.boolean(),
257
+ }).strict(),
258
+ limits: z.object({
259
+ commentMaxLength: z.number().int().min(1).max(1_000_000),
260
+ reviewerId: z.enum(['numeric', 'login']),
261
+ labelsMax: z.number().int().min(0).max(100),
262
+ reviewersMax: z.number().int().min(0).max(50),
263
+ }).strict(),
264
+ trackedItems: z.object({ batchLimit: z.number().int().min(1).max(50) }).strict(),
265
+ presentation: z.object({
266
+ abbreviation: z.string().trim().min(1).max(8),
267
+ promptNoun: literalSchema,
268
+ sharedNumberSpace: z.boolean(),
269
+ iconPath: z.string().max(8192).regex(SVG_PATH_DATA).optional(),
270
+ urls: z.object({ changeRequest: urlTemplatesSchema, issue: urlTemplatesSchema }).strict(),
271
+ features: z.object({
272
+ authorCanReview: z.boolean().default(false),
273
+ issueCloseReason: z.boolean().default(false),
274
+ submitRequirements: z.boolean().optional(),
275
+ }).strict(),
276
+ changeRequestTemplates: z.array(z.string().trim().max(256).refine(isRepositoryFilePath, {
277
+ message: 'must be a file path inside the repository, like .gitea/pull_request_template.md, with no "." or ".." segment',
278
+ })).max(8),
279
+ }).strict(),
280
+ }).strict().superRefine((value, ctx) => {
281
+ for (const kind of ['changeRequest', 'issue']) {
282
+ value.presentation.urls[kind].forEach((template, index) => {
283
+ if (isUrlTemplate(template, value.repositoryPath))
284
+ return;
285
+ ctx.addIssue({
286
+ code: 'custom', path: ['presentation', 'urls', kind, index],
287
+ message: `templates start with "/" and use literal segments and ${SOURCE_CONTROL_URL_TOKENS.join(', ')}: {repo} and {number} once, and ${value.repositoryPath === 'owner/name' ? '{owner}' : '{namespace+}'} once for ${value.repositoryPath} paths.`,
288
+ });
289
+ });
290
+ }
291
+ // Change request patterns are matched first, so an issue template that is
292
+ // also a change request template would read every issue link as one.
293
+ const changeRequestTemplates = new Set(value.presentation.urls.changeRequest);
294
+ value.presentation.urls.issue.forEach((template, index) => {
295
+ if (!changeRequestTemplates.has(template))
296
+ return;
297
+ ctx.addIssue({
298
+ code: 'custom', path: ['presentation', 'urls', 'issue', index],
299
+ message: 'is also a changeRequest template; a link names either a change request or an issue.',
300
+ });
301
+ });
302
+ });
159
303
  const uniqueBy = (items, key) => (new Set(items.map(key)).size === items.length);
160
304
  // `roles` only means something on a message action and `payload` only on a
161
305
  // session action; a dead field is a misconfiguration and fails closed.
@@ -212,12 +356,16 @@ const contributesSchema = z.object({
212
356
  title: z.string().trim().min(1).max(200).optional(),
213
357
  })]).optional(),
214
358
  statusSection: z.union([z.literal(true), z.object({
215
- entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')),
359
+ entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')).optional(),
360
+ source: z.object({
361
+ path: z.string().trim().refine(isGuestRequestPath),
362
+ interval: z.number().int().min(GUEST_STATUS_SOURCE_INTERVAL_MIN).max(GUEST_STATUS_SOURCE_INTERVAL_MAX).optional(),
363
+ }).strict().optional(),
216
364
  title: z.string().trim().min(1).max(GUEST_STATUS_SECTION_TITLE_MAX).optional(),
217
365
  height: z.number().int().min(GUEST_STATUS_SECTION_HEIGHT_MIN).max(GUEST_STATUS_SECTION_HEIGHT_MAX).optional(),
218
366
  defaultExpanded: z.boolean().optional(),
219
367
  requiresProject: z.boolean().optional(),
220
- })]).optional(),
368
+ }).refine((section) => section.entry !== undefined || section.source !== undefined)]).optional(),
221
369
  capabilities: z.array(z.enum(DECLARED_GUEST_CAPABILITIES)).max(8).optional(),
222
370
  integration: integrationSchema.optional(),
223
371
  service: serviceSchema.optional(),
@@ -229,10 +377,15 @@ const contributesSchema = z.object({
229
377
  commands: commandsSchema.optional(),
230
378
  tools: toolsSchema.optional(),
231
379
  fileEditors: fileEditorsSchema.optional(),
380
+ sourceControl: sourceControlSchema.optional(),
232
381
  });
233
382
  /**
234
383
  * Contributions that need either the panel or background execution frame.
235
384
  */
385
+ /** A section the host draws from the service's answers: the host is the service's caller. */
386
+ const hasStatusSource = (contributes) => (Boolean(contributes.statusSection && contributes.statusSection !== true && contributes.statusSection.source));
387
+ /** A section the host loads as a frame: `true`, or an object with an `entry`. */
388
+ const hasStatusFrame = (contributes) => (contributes.statusSection === true || Boolean(contributes.statusSection?.entry));
236
389
  const runtimeContributions = (contributes) => {
237
390
  const declared = [];
238
391
  if (contributes.page !== undefined)
@@ -246,7 +399,8 @@ const runtimeContributions = (contributes) => {
246
399
  // A service the host starts itself (it provides a role or a surface) needs
247
400
  // no frame; one that only answers a panel's `serviceRequest` has no caller
248
401
  // without one.
249
- if (contributes.service !== undefined && !contributes.service.provides?.length && !contributes.service.surface)
402
+ if (contributes.service !== undefined && !contributes.service.provides?.length && !contributes.service.surface
403
+ && !hasStatusSource(contributes))
250
404
  declared.push('service');
251
405
  if (contributes.filesystem !== undefined)
252
406
  declared.push('filesystem');
@@ -264,6 +418,23 @@ export const openChamberManifestSchema = z.object({
264
418
  openchamber: z.string().trim().regex(OPENCHAMBER_ENGINE_PATTERN),
265
419
  }).strict().optional(),
266
420
  contributes: contributesSchema.superRefine((contributes, ctx) => {
421
+ // The Git host and the role that serves it come together, and the host
422
+ // names it `ext-<panel.id>` within the 32-character provider id.
423
+ const providesSourceControl = contributes.service?.provides?.includes('source-control') === true;
424
+ if (providesSourceControl !== (contributes.sourceControl !== undefined)) {
425
+ ctx.addIssue({
426
+ code: 'custom', path: ['service', 'provides'],
427
+ message: 'service.provides "source-control" and contributes.sourceControl come together: declare both or neither.',
428
+ });
429
+ return;
430
+ }
431
+ if (providesSourceControl && contributes.panel.id.length > SOURCE_CONTROL_GUEST_ID_MAX) {
432
+ ctx.addIssue({
433
+ code: 'custom', path: ['service', 'provides'],
434
+ message: `A source-control extension's panel.id is at most ${SOURCE_CONTROL_GUEST_ID_MAX} characters: OpenChamber names its Git host "ext-<panel.id>".`,
435
+ });
436
+ return;
437
+ }
267
438
  // Docking only means something for a page beside a shared surface.
268
439
  const docked = contributes.panel.dock !== undefined || contributes.panel.size !== undefined;
269
440
  if (docked && !(contributes.service?.surface && hasGuestPage(contributes))) {
@@ -281,6 +452,14 @@ export const openChamberManifestSchema = z.object({
281
452
  });
282
453
  return;
283
454
  }
455
+ // The host reads a source from the extension's service; without one it has nothing to ask.
456
+ if (hasStatusSource(contributes) && !contributes.service) {
457
+ ctx.addIssue({
458
+ code: 'custom', path: ['statusSection', 'source'],
459
+ message: 'contributes.statusSection.source is read from the extension\'s service; declare contributes.service.',
460
+ });
461
+ return;
462
+ }
284
463
  if (hasGuestPage(contributes))
285
464
  return;
286
465
  if (contributes.background) {
@@ -298,7 +477,7 @@ export const openChamberManifestSchema = z.object({
298
477
  });
299
478
  return;
300
479
  }
301
- if (contributes.statusSection || contributes.fileEditors) {
480
+ if (hasStatusFrame(contributes) || contributes.fileEditors) {
302
481
  // A status section or file editor frame runs code and may use what the
303
482
  // package is granted, but it only hosts itself: things the host opens or
304
483
  // invokes elsewhere need a panel or background entry.
@@ -343,6 +522,9 @@ const fail = (code, message) => ({
343
522
  code,
344
523
  message,
345
524
  });
525
+ /** `contributes.sourceControl.instances.defaultInstances[0]`, relative to the `openchamber` block. */
526
+ const fieldPath = (issuePath) => (issuePath[0] === 'openchamber' ? issuePath.slice(1) : issuePath)
527
+ .reduce((field, segment) => (Number.isInteger(segment) ? `${field}[${String(segment)}]` : `${field ? `${field}.` : ''}${String(segment)}`), '');
346
528
  const failureFromIssue = (issue) => {
347
529
  // Paths are reported relative to the `openchamber` block whether the
348
530
  // document was a bare manifest or a package.json envelope.
@@ -395,7 +577,7 @@ const failureFromIssue = (issue) => {
395
577
  if (path === 'contributes.statusSection' || path.startsWith('contributes.statusSection.')) {
396
578
  return fail('invalid-status-section', issue.code === 'custom'
397
579
  ? issue.message
398
- : `contributes.statusSection must be true or { entry: "<package .html>", title?: "1 to ${GUEST_STATUS_SECTION_TITLE_MAX} characters", height?: ${GUEST_STATUS_SECTION_HEIGHT_MIN} to ${GUEST_STATUS_SECTION_HEIGHT_MAX} }.`);
580
+ : `contributes.statusSection must be true or { entry?: "<package .html>", source?: { path: "/status", interval?: ${GUEST_STATUS_SOURCE_INTERVAL_MIN} to ${GUEST_STATUS_SOURCE_INTERVAL_MAX} }, title?: "1 to ${GUEST_STATUS_SECTION_TITLE_MAX} characters", height?: ${GUEST_STATUS_SECTION_HEIGHT_MIN} to ${GUEST_STATUS_SECTION_HEIGHT_MAX} } with entry, source, or both.`);
399
581
  }
400
582
  if (path === 'contributes.background' || path.startsWith('contributes.background.')) {
401
583
  return fail('invalid-background', 'contributes.background needs an entry ending in .html inside the package.');
@@ -427,8 +609,17 @@ const failureFromIssue = (issue) => {
427
609
  if (path.startsWith('contributes.integration')) {
428
610
  return fail('invalid-integration', 'contributes.integration needs a name, description, and oauth, token, or host.');
429
611
  }
612
+ if (path.startsWith('contributes.sourceControl')) {
613
+ // The field is named either way: a refine's message alone does not say which entry failed.
614
+ const field = fieldPath(issue.path);
615
+ return fail('invalid-service', issue.code === 'custom'
616
+ ? `${field}: ${issue.message}`
617
+ : `${field} is invalid: see "contributes.sourceControl" in GUEST_SERVICES.md for every field.`);
618
+ }
430
619
  if (path.startsWith('contributes.service')) {
431
- return fail('invalid-service', 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser"), and optional surface (true).');
620
+ return fail('invalid-service', issue.code === 'custom'
621
+ ? issue.message
622
+ : 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser", "source-control"), and optional surface (true).');
432
623
  }
433
624
  return fail('missing-panel', 'contributes.panel is required.');
434
625
  };
@@ -89,6 +89,15 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
89
89
  id: z.ZodString;
90
90
  data: z.ZodOptional<z.ZodJSONSchema>;
91
91
  }, z.core.$strip>>;
92
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
93
+ title: z.ZodString;
94
+ url: z.ZodString;
95
+ kind: z.ZodEnum<{
96
+ change: "change";
97
+ issue: "issue";
98
+ }>;
99
+ linkedAt: z.ZodNumber;
100
+ }, z.core.$strip>>>;
92
101
  }, z.core.$strip>>;
93
102
  }, z.core.$strip>], "kind">;
94
103
  }, z.core.$strip>;
@@ -503,6 +512,15 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
503
512
  id: z.ZodString;
504
513
  data: z.ZodOptional<z.ZodJSONSchema>;
505
514
  }, z.core.$strip>>;
515
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
516
+ title: z.ZodString;
517
+ url: z.ZodString;
518
+ kind: z.ZodEnum<{
519
+ change: "change";
520
+ issue: "issue";
521
+ }>;
522
+ linkedAt: z.ZodNumber;
523
+ }, z.core.$strip>>>;
506
524
  }, z.core.$strip>>;
507
525
  }, z.core.$strip>], "kind">, z.ZodObject<{
508
526
  output: z.ZodString;
@@ -645,6 +663,12 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
645
663
  id: string;
646
664
  data?: z.util.JSONType | undefined;
647
665
  }[];
666
+ links?: {
667
+ title: string;
668
+ url: string;
669
+ kind: "change" | "issue";
670
+ linkedAt: number;
671
+ }[] | undefined;
648
672
  }[];
649
673
  } | {
650
674
  status: number;
@@ -788,6 +812,12 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
788
812
  id: string;
789
813
  data?: z.util.JSONType | undefined;
790
814
  }[];
815
+ links?: {
816
+ title: string;
817
+ url: string;
818
+ kind: "change" | "issue";
819
+ linkedAt: number;
820
+ }[] | undefined;
791
821
  }[];
792
822
  } | {
793
823
  status: number;
package/dist/schemas.d.ts CHANGED
@@ -2,8 +2,9 @@ export { openChamberManifestSchema, PACKAGE_VERSION_PATTERN, packageManifestSche
2
2
  export type { ManifestDocument } from './parse.ts';
3
3
  export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnapshotSchema } from './workspace-schemas.ts';
4
4
  export { guestStatusControlsSchema, guestStatusControlEventSchema } from './status-control-schemas.ts';
5
+ export { guestStatusViewSchema } from './status-view-schemas.ts';
5
6
  export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.ts';
6
7
  export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.ts';
7
- export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.ts';
8
+ export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.ts';
8
9
  export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.ts';
9
10
  export type { SurfaceHostMessage, SurfaceViewerMessage } from './service-surface-schemas.ts';
package/dist/schemas.js CHANGED
@@ -4,7 +4,8 @@
4
4
  export { openChamberManifestSchema, PACKAGE_VERSION_PATTERN, packageManifestSchema, parseManifest, parseManifestJson, } from './parse.js';
5
5
  export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnapshotSchema } from './workspace-schemas.js';
6
6
  export { guestStatusControlsSchema, guestStatusControlEventSchema } from './status-control-schemas.js';
7
+ export { guestStatusViewSchema } from './status-view-schemas.js';
7
8
  export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.js';
8
9
  export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.js';
9
- export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.js';
10
+ export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.js';
10
11
  export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.js';