@openchamber/sdk 2.5.3 → 2.6.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/parse.d.ts CHANGED
@@ -118,7 +118,9 @@ export declare const openChamberManifestSchema: z.ZodObject<{
118
118
  }>>>;
119
119
  provides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
120
120
  browser: "browser";
121
+ notifications: "notifications";
121
122
  "source-control": "source-control";
123
+ tracker: "tracker";
122
124
  }>>>;
123
125
  surface: z.ZodOptional<z.ZodLiteral<true>>;
124
126
  }, z.core.$strip>>;
@@ -284,6 +286,97 @@ export declare const openChamberManifestSchema: z.ZodObject<{
284
286
  changeRequestTemplates: z.ZodArray<z.ZodString>;
285
287
  }, z.core.$strict>;
286
288
  }, z.core.$strict>>;
289
+ sessionEvents: z.ZodOptional<z.ZodObject<{
290
+ events: z.ZodArray<z.ZodEnum<{
291
+ "permission-asked": "permission-asked";
292
+ "question-asked": "question-asked";
293
+ "turn-failed": "turn-failed";
294
+ "turn-finished": "turn-finished";
295
+ }>>;
296
+ content: z.ZodOptional<z.ZodLiteral<true>>;
297
+ }, z.core.$strict>>;
298
+ notifications: z.ZodOptional<z.ZodObject<{
299
+ label: z.ZodString;
300
+ instances: z.ZodUnion<readonly [z.ZodObject<{
301
+ kind: z.ZodLiteral<"fixed">;
302
+ origin: z.ZodString;
303
+ }, z.core.$strict>, z.ZodObject<{
304
+ kind: z.ZodLiteral<"user">;
305
+ label: z.ZodString;
306
+ placeholder: z.ZodOptional<z.ZodString>;
307
+ }, z.core.$strict>]>;
308
+ token: z.ZodObject<{
309
+ header: z.ZodEnum<{
310
+ authorization: "authorization";
311
+ "private-token": "private-token";
312
+ }>;
313
+ scheme: z.ZodEnum<{
314
+ basic: "basic";
315
+ bearer: "bearer";
316
+ none: "none";
317
+ token: "token";
318
+ }>;
319
+ username: z.ZodOptional<z.ZodBoolean>;
320
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
321
+ createPath: z.ZodOptional<z.ZodString>;
322
+ }, z.core.$strict>;
323
+ }, z.core.$strict>>;
324
+ tracker: z.ZodOptional<z.ZodObject<{
325
+ wire: z.ZodLiteral<1>;
326
+ label: z.ZodString;
327
+ instances: z.ZodUnion<readonly [z.ZodObject<{
328
+ kind: z.ZodLiteral<"fixed">;
329
+ origin: z.ZodString;
330
+ }, z.core.$strict>, z.ZodObject<{
331
+ kind: z.ZodLiteral<"user">;
332
+ label: z.ZodString;
333
+ placeholder: z.ZodOptional<z.ZodString>;
334
+ }, z.core.$strict>]>;
335
+ auth: z.ZodObject<{
336
+ token: z.ZodOptional<z.ZodObject<{
337
+ header: z.ZodEnum<{
338
+ authorization: "authorization";
339
+ "private-token": "private-token";
340
+ }>;
341
+ scheme: z.ZodEnum<{
342
+ basic: "basic";
343
+ bearer: "bearer";
344
+ none: "none";
345
+ token: "token";
346
+ }>;
347
+ username: z.ZodOptional<z.ZodBoolean>;
348
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
349
+ createPath: z.ZodOptional<z.ZodString>;
350
+ }, z.core.$strict>>;
351
+ oauth: z.ZodOptional<z.ZodObject<{
352
+ authorizeUrl: z.ZodString;
353
+ tokenUrl: z.ZodString;
354
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
355
+ }, z.core.$strict>>;
356
+ }, z.core.$strict>;
357
+ capabilities: z.ZodObject<{
358
+ multipleAccounts: z.ZodDefault<z.ZodBoolean>;
359
+ stateUpdate: z.ZodDefault<z.ZodBoolean>;
360
+ children: z.ZodDefault<z.ZodBoolean>;
361
+ labels: z.ZodDefault<z.ZodBoolean>;
362
+ priority: z.ZodDefault<z.ZodBoolean>;
363
+ filters: z.ZodDefault<z.ZodObject<{
364
+ people: z.ZodDefault<z.ZodBoolean>;
365
+ priority: z.ZodDefault<z.ZodBoolean>;
366
+ }, z.core.$strict>>;
367
+ projectMapping: z.ZodDefault<z.ZodBoolean>;
368
+ sessionComments: z.ZodDefault<z.ZodBoolean>;
369
+ }, z.core.$strict>;
370
+ stateFilters: z.ZodArray<z.ZodString>;
371
+ identifier: z.ZodEnum<{
372
+ "key-number": "key-number";
373
+ number: "number";
374
+ "short-code": "short-code";
375
+ }>;
376
+ urls: z.ZodObject<{
377
+ item: z.ZodArray<z.ZodString>;
378
+ }, z.core.$strict>;
379
+ }, z.core.$strict>>;
287
380
  }, z.core.$strip>;
288
381
  }, z.core.$strip>;
289
382
  export declare const PACKAGE_VERSION_PATTERN: RegExp;
@@ -407,7 +500,9 @@ export declare const packageManifestSchema: z.ZodObject<{
407
500
  }>>>;
408
501
  provides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
409
502
  browser: "browser";
503
+ notifications: "notifications";
410
504
  "source-control": "source-control";
505
+ tracker: "tracker";
411
506
  }>>>;
412
507
  surface: z.ZodOptional<z.ZodLiteral<true>>;
413
508
  }, z.core.$strip>>;
@@ -573,6 +668,97 @@ export declare const packageManifestSchema: z.ZodObject<{
573
668
  changeRequestTemplates: z.ZodArray<z.ZodString>;
574
669
  }, z.core.$strict>;
575
670
  }, z.core.$strict>>;
671
+ sessionEvents: z.ZodOptional<z.ZodObject<{
672
+ events: z.ZodArray<z.ZodEnum<{
673
+ "permission-asked": "permission-asked";
674
+ "question-asked": "question-asked";
675
+ "turn-failed": "turn-failed";
676
+ "turn-finished": "turn-finished";
677
+ }>>;
678
+ content: z.ZodOptional<z.ZodLiteral<true>>;
679
+ }, z.core.$strict>>;
680
+ notifications: z.ZodOptional<z.ZodObject<{
681
+ label: z.ZodString;
682
+ instances: z.ZodUnion<readonly [z.ZodObject<{
683
+ kind: z.ZodLiteral<"fixed">;
684
+ origin: z.ZodString;
685
+ }, z.core.$strict>, z.ZodObject<{
686
+ kind: z.ZodLiteral<"user">;
687
+ label: z.ZodString;
688
+ placeholder: z.ZodOptional<z.ZodString>;
689
+ }, z.core.$strict>]>;
690
+ token: z.ZodObject<{
691
+ header: z.ZodEnum<{
692
+ authorization: "authorization";
693
+ "private-token": "private-token";
694
+ }>;
695
+ scheme: z.ZodEnum<{
696
+ basic: "basic";
697
+ bearer: "bearer";
698
+ none: "none";
699
+ token: "token";
700
+ }>;
701
+ username: z.ZodOptional<z.ZodBoolean>;
702
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
703
+ createPath: z.ZodOptional<z.ZodString>;
704
+ }, z.core.$strict>;
705
+ }, z.core.$strict>>;
706
+ tracker: z.ZodOptional<z.ZodObject<{
707
+ wire: z.ZodLiteral<1>;
708
+ label: z.ZodString;
709
+ instances: z.ZodUnion<readonly [z.ZodObject<{
710
+ kind: z.ZodLiteral<"fixed">;
711
+ origin: z.ZodString;
712
+ }, z.core.$strict>, z.ZodObject<{
713
+ kind: z.ZodLiteral<"user">;
714
+ label: z.ZodString;
715
+ placeholder: z.ZodOptional<z.ZodString>;
716
+ }, z.core.$strict>]>;
717
+ auth: z.ZodObject<{
718
+ token: z.ZodOptional<z.ZodObject<{
719
+ header: z.ZodEnum<{
720
+ authorization: "authorization";
721
+ "private-token": "private-token";
722
+ }>;
723
+ scheme: z.ZodEnum<{
724
+ basic: "basic";
725
+ bearer: "bearer";
726
+ none: "none";
727
+ token: "token";
728
+ }>;
729
+ username: z.ZodOptional<z.ZodBoolean>;
730
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
731
+ createPath: z.ZodOptional<z.ZodString>;
732
+ }, z.core.$strict>>;
733
+ oauth: z.ZodOptional<z.ZodObject<{
734
+ authorizeUrl: z.ZodString;
735
+ tokenUrl: z.ZodString;
736
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
737
+ }, z.core.$strict>>;
738
+ }, z.core.$strict>;
739
+ capabilities: z.ZodObject<{
740
+ multipleAccounts: z.ZodDefault<z.ZodBoolean>;
741
+ stateUpdate: z.ZodDefault<z.ZodBoolean>;
742
+ children: z.ZodDefault<z.ZodBoolean>;
743
+ labels: z.ZodDefault<z.ZodBoolean>;
744
+ priority: z.ZodDefault<z.ZodBoolean>;
745
+ filters: z.ZodDefault<z.ZodObject<{
746
+ people: z.ZodDefault<z.ZodBoolean>;
747
+ priority: z.ZodDefault<z.ZodBoolean>;
748
+ }, z.core.$strict>>;
749
+ projectMapping: z.ZodDefault<z.ZodBoolean>;
750
+ sessionComments: z.ZodDefault<z.ZodBoolean>;
751
+ }, z.core.$strict>;
752
+ stateFilters: z.ZodArray<z.ZodString>;
753
+ identifier: z.ZodEnum<{
754
+ "key-number": "key-number";
755
+ number: "number";
756
+ "short-code": "short-code";
757
+ }>;
758
+ urls: z.ZodObject<{
759
+ item: z.ZodArray<z.ZodString>;
760
+ }, z.core.$strict>;
761
+ }, z.core.$strict>>;
576
762
  }, z.core.$strip>;
577
763
  }, z.core.$strip>;
578
764
  }, z.core.$strip>;
package/dist/parse.js CHANGED
@@ -1,8 +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, 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';
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, NOTIFICATIONS_GUEST_ID_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, TRACKER_BASE_STATE_FILTERS, TRACKER_GUEST_ID_MAX, TRACKER_IDENTIFIER_KINDS, TRACKER_STATE_FILTERS_MAX, TRACKER_URL_TEMPLATES_MAX, TRACKER_URL_TOKENS, isTrackerUrlTemplate, } from './manifest.js';
5
5
  import { isGuestRequestPath } from './contract.js';
6
+ import { GUEST_SESSION_EVENT_TYPES } from './session-events.js';
6
7
  import { GUEST_STATUS_SOURCE_INTERVAL_MAX, GUEST_STATUS_SOURCE_INTERVAL_MIN } from './status-view.js';
7
8
  import { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_PATTERNS_MAX, GUEST_FILE_EDITOR_TITLE_MAX, isFileEditorPattern, } from './file-editor.js';
8
9
  const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
@@ -300,7 +301,88 @@ const sourceControlSchema = z.object({
300
301
  });
301
302
  });
302
303
  });
304
+ // The token block of a notification channel follows the Git host's rules: a
305
+ // bare token travels only as `private-token`, and `basic` needs the login.
306
+ const notificationsTokenSchema = z.object({
307
+ header: z.enum(['authorization', 'private-token']),
308
+ scheme: z.enum(['bearer', 'token', 'basic', 'none']),
309
+ username: z.boolean().optional(),
310
+ scopes: z.array(z.string().trim().min(1).max(64)).max(32).optional(),
311
+ createPath: z.string().trim().max(256).refine(isSafeApiPath, { message: 'must be a path on the instance starting with "/", with no "." or ".." segment' }).optional(),
312
+ }).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((token) => (token.scheme === 'basic') === (token.username === true), { message: 'the basic scheme needs username: true, and username is only for the basic scheme' });
313
+ const notificationsSchema = z.object({
314
+ label: literalSchema,
315
+ instances: z.union([
316
+ z.object({
317
+ kind: z.literal('fixed'),
318
+ origin: z.string().trim().refine(isHttpsOrigin, { message: 'must be an https origin with no path, like https://slack.com' }),
319
+ }).strict(),
320
+ z.object({
321
+ kind: z.literal('user'),
322
+ label: literalSchema,
323
+ placeholder: z.string().trim().max(256).optional(),
324
+ }).strict(),
325
+ ]),
326
+ token: notificationsTokenSchema,
327
+ }).strict();
303
328
  const uniqueBy = (items, key) => (new Set(items.map(key)).size === items.length);
329
+ // A state filter id: the neutral categories and a tracker's own (`inReview`).
330
+ const TRACKER_STATE_FILTER = /^[a-z][a-zA-Z0-9-]{0,31}$/;
331
+ const trackerTokenSchema = notificationsTokenSchema;
332
+ // An OAuth endpoint: an https URL, or a path on the instance the user names.
333
+ const trackerOauthEndpointSchema = z.string().trim().min(1).max(2_048).refine((value) => isHttpsUrl(value) || isSafeApiPath(value), { message: 'must be an https URL, or a path on the instance starting with "/"' });
334
+ const trackerSchema = z.object({
335
+ wire: z.literal(1),
336
+ label: literalSchema,
337
+ instances: z.union([
338
+ z.object({
339
+ kind: z.literal('fixed'),
340
+ origin: z.string().trim().refine(isHttpsOrigin, { message: 'must be an https origin with no path, like https://tracker.example.com' }),
341
+ }).strict(),
342
+ z.object({
343
+ kind: z.literal('user'),
344
+ label: literalSchema,
345
+ placeholder: z.string().trim().max(256).optional(),
346
+ }).strict(),
347
+ ]),
348
+ auth: z.object({
349
+ token: trackerTokenSchema.optional(),
350
+ oauth: z.object({
351
+ authorizeUrl: trackerOauthEndpointSchema,
352
+ tokenUrl: trackerOauthEndpointSchema,
353
+ scopes: z.array(z.string().trim().min(1).max(64)).max(32).optional(),
354
+ }).strict().optional(),
355
+ }).strict().refine((auth) => auth.token !== undefined || auth.oauth !== undefined, { message: 'declare token, oauth or both' }),
356
+ capabilities: z.object({
357
+ multipleAccounts: z.boolean().default(false),
358
+ stateUpdate: z.boolean().default(false),
359
+ children: z.boolean().default(false),
360
+ labels: z.boolean().default(false),
361
+ priority: z.boolean().default(false),
362
+ filters: z.object({ people: z.boolean().default(false), priority: z.boolean().default(false) }).strict().default({ people: false, priority: false }),
363
+ projectMapping: z.boolean().default(false),
364
+ sessionComments: z.boolean().default(false),
365
+ }).strict(),
366
+ stateFilters: z.array(z.string().trim().regex(TRACKER_STATE_FILTER)).min(TRACKER_BASE_STATE_FILTERS.length).max(TRACKER_STATE_FILTERS_MAX)
367
+ .refine((filters) => new Set(filters).size === filters.length, { message: 'state filters must be unique' })
368
+ .refine((filters) => TRACKER_BASE_STATE_FILTERS.every((filter, index) => filters[index] === filter), {
369
+ message: `state filters start with ${TRACKER_BASE_STATE_FILTERS.map((filter) => `"${filter}"`).join(', ')}`,
370
+ }),
371
+ identifier: z.enum(TRACKER_IDENTIFIER_KINDS),
372
+ urls: z.object({
373
+ item: z.array(z.string().trim().min(1)).min(1).max(TRACKER_URL_TEMPLATES_MAX)
374
+ .refine((templates) => new Set(templates).size === templates.length, { message: 'link templates must be unique' }),
375
+ }).strict(),
376
+ }).strict().superRefine((value, ctx) => {
377
+ value.urls.item.forEach((template, index) => {
378
+ if (isTrackerUrlTemplate(template))
379
+ return;
380
+ ctx.addIssue({
381
+ code: 'custom', path: ['urls', 'item', index],
382
+ message: `templates start with "/" and use literal segments and ${TRACKER_URL_TOKENS.join(', ')}, with {id} once, in the path or as "?name={id}".`,
383
+ });
384
+ });
385
+ });
304
386
  // `roles` only means something on a message action and `payload` only on a
305
387
  // session action; a dead field is a misconfiguration and fails closed.
306
388
  const actionSchema = z.object({
@@ -378,6 +460,13 @@ const contributesSchema = z.object({
378
460
  tools: toolsSchema.optional(),
379
461
  fileEditors: fileEditorsSchema.optional(),
380
462
  sourceControl: sourceControlSchema.optional(),
463
+ sessionEvents: z.object({
464
+ events: z.array(z.enum(GUEST_SESSION_EVENT_TYPES)).min(1).max(GUEST_SESSION_EVENT_TYPES.length)
465
+ .refine((events) => new Set(events).size === events.length, { message: 'event types must be unique' }),
466
+ content: z.literal(true).optional(),
467
+ }).strict().optional(),
468
+ notifications: notificationsSchema.optional(),
469
+ tracker: trackerSchema.optional(),
381
470
  });
382
471
  /**
383
472
  * Contributions that need either the panel or background execution frame.
@@ -396,11 +485,11 @@ const runtimeContributions = (contributes) => {
396
485
  declared.push('capabilities');
397
486
  if (contributes.integration !== undefined)
398
487
  declared.push('integration');
399
- // A service the host starts itself (it provides a role or a surface) needs
400
- // no frame; one that only answers a panel's `serviceRequest` has no caller
401
- // without one.
488
+ // A service the host starts itself (it provides a role or a surface, or
489
+ // receives session events) needs no frame; one that only answers a panel's
490
+ // `serviceRequest` has no caller without one.
402
491
  if (contributes.service !== undefined && !contributes.service.provides?.length && !contributes.service.surface
403
- && !hasStatusSource(contributes))
492
+ && !hasStatusSource(contributes) && contributes.sessionEvents === undefined)
404
493
  declared.push('service');
405
494
  if (contributes.filesystem !== undefined)
406
495
  declared.push('filesystem');
@@ -435,6 +524,47 @@ export const openChamberManifestSchema = z.object({
435
524
  });
436
525
  return;
437
526
  }
527
+ // The notification channel and the role that serves it come together, named `ext-<panel.id>` as well.
528
+ const providesNotifications = contributes.service?.provides?.includes('notifications') === true;
529
+ if (providesNotifications !== (contributes.notifications !== undefined)) {
530
+ ctx.addIssue({
531
+ code: 'custom', path: ['service', 'provides'],
532
+ message: 'service.provides "notifications" and contributes.notifications come together: declare both or neither.',
533
+ });
534
+ return;
535
+ }
536
+ if (providesNotifications && contributes.panel.id.length > NOTIFICATIONS_GUEST_ID_MAX) {
537
+ ctx.addIssue({
538
+ code: 'custom', path: ['service', 'provides'],
539
+ message: `A notifications extension's panel.id is at most ${NOTIFICATIONS_GUEST_ID_MAX} characters: OpenChamber names its channel "ext-<panel.id>".`,
540
+ });
541
+ return;
542
+ }
543
+ // The tracker and the role that serves it come together, named `ext-<panel.id>` as well.
544
+ const providesTracker = contributes.service?.provides?.includes('tracker') === true;
545
+ if (providesTracker !== (contributes.tracker !== undefined)) {
546
+ ctx.addIssue({
547
+ code: 'custom', path: ['service', 'provides'],
548
+ message: 'service.provides "tracker" and contributes.tracker come together: declare both or neither.',
549
+ });
550
+ return;
551
+ }
552
+ if (providesTracker && contributes.panel.id.length > TRACKER_GUEST_ID_MAX) {
553
+ ctx.addIssue({
554
+ code: 'custom', path: ['service', 'provides'],
555
+ message: `A tracker extension's panel.id is at most ${TRACKER_GUEST_ID_MAX} characters: OpenChamber names its tracker "ext-<panel.id>".`,
556
+ });
557
+ return;
558
+ }
559
+ // Tracker and Git host ids share one namespace (tracked items, the read
560
+ // cache, session links), and both would be `ext-<panel.id>`.
561
+ if (providesTracker && providesSourceControl) {
562
+ ctx.addIssue({
563
+ code: 'custom', path: ['service', 'provides'],
564
+ message: 'An extension is a Git host ("source-control") or a tracker ("tracker"), never both: the two roles would share the id "ext-<panel.id>".',
565
+ });
566
+ return;
567
+ }
438
568
  // Docking only means something for a page beside a shared surface.
439
569
  const docked = contributes.panel.dock !== undefined || contributes.panel.size !== undefined;
440
570
  if (docked && !(contributes.service?.surface && hasGuestPage(contributes))) {
@@ -460,6 +590,14 @@ export const openChamberManifestSchema = z.object({
460
590
  });
461
591
  return;
462
592
  }
593
+ // Session events are posted to the extension's service; without one there is nothing to post to.
594
+ if (contributes.sessionEvents !== undefined && !contributes.service) {
595
+ ctx.addIssue({
596
+ code: 'custom', path: ['sessionEvents'],
597
+ message: 'contributes.sessionEvents are posted to the extension\'s service; declare contributes.service.',
598
+ });
599
+ return;
600
+ }
463
601
  if (hasGuestPage(contributes))
464
602
  return;
465
603
  if (contributes.background) {
@@ -606,6 +744,11 @@ const failureFromIssue = (issue) => {
606
744
  if (path.startsWith('contributes.fileEditors')) {
607
745
  return fail('invalid-file-editors', `contributes.fileEditors lists 1 to ${GUEST_FILE_EDITORS_MAX} editors with a unique kebab-case id, a title of 1 to ${GUEST_FILE_EDITOR_TITLE_MAX} characters, match: 1 to ${GUEST_FILE_EDITOR_PATTERNS_MAX} file-name patterns (no "/", not only wildcards, like "*.excalidraw"), an entry ending in .html inside the package, and optional content "text" or "binary".`);
608
746
  }
747
+ if (path === 'contributes.sessionEvents' || path.startsWith('contributes.sessionEvents.')) {
748
+ return fail('invalid-session-events', issue.code === 'custom' && path === 'contributes.sessionEvents'
749
+ ? issue.message
750
+ : `contributes.sessionEvents is { events: [1 to ${GUEST_SESSION_EVENT_TYPES.length} distinct of ${GUEST_SESSION_EVENT_TYPES.map((type) => `"${type}"`).join(', ')}], content?: true }.`);
751
+ }
609
752
  if (path.startsWith('contributes.integration')) {
610
753
  return fail('invalid-integration', 'contributes.integration needs a name, description, and oauth, token, or host.');
611
754
  }
@@ -616,10 +759,22 @@ const failureFromIssue = (issue) => {
616
759
  ? `${field}: ${issue.message}`
617
760
  : `${field} is invalid: see "contributes.sourceControl" in GUEST_SERVICES.md for every field.`);
618
761
  }
762
+ if (path.startsWith('contributes.notifications')) {
763
+ const field = fieldPath(issue.path);
764
+ return fail('invalid-service', issue.code === 'custom'
765
+ ? `${field}: ${issue.message}`
766
+ : `${field} is invalid: see "contributes.notifications" in GUEST_SERVICES.md for every field.`);
767
+ }
768
+ if (path.startsWith('contributes.tracker')) {
769
+ const field = fieldPath(issue.path);
770
+ return fail('invalid-service', issue.code === 'custom'
771
+ ? `${field}: ${issue.message}`
772
+ : `${field} is invalid: see "contributes.tracker" in GUEST_SERVICES.md for every field.`);
773
+ }
619
774
  if (path.startsWith('contributes.service')) {
620
775
  return fail('invalid-service', issue.code === 'custom'
621
776
  ? issue.message
622
- : 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser", "source-control"), and optional surface (true).');
777
+ : 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser", "source-control", "notifications", "tracker"), and optional surface (true).');
623
778
  }
624
779
  return fail('missing-panel', 'contributes.panel is required.');
625
780
  };
@@ -58,6 +58,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
58
58
  title: z.ZodString;
59
59
  projectId: z.ZodString;
60
60
  directory: z.ZodString;
61
+ agent: z.ZodOptional<z.ZodString>;
61
62
  parentId: z.ZodNullable<z.ZodString>;
62
63
  createdAt: z.ZodNumber;
63
64
  updatedAt: z.ZodNumber;
@@ -481,6 +482,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
481
482
  title: z.ZodString;
482
483
  projectId: z.ZodString;
483
484
  directory: z.ZodString;
485
+ agent: z.ZodOptional<z.ZodString>;
484
486
  parentId: z.ZodNullable<z.ZodString>;
485
487
  createdAt: z.ZodNumber;
486
488
  updatedAt: z.ZodNumber;
@@ -647,6 +649,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
647
649
  title: string;
648
650
  projectId: string;
649
651
  directory: string;
652
+ agent?: string | undefined;
650
653
  parentId: string | null;
651
654
  createdAt: number;
652
655
  updatedAt: number;
@@ -796,6 +799,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
796
799
  title: string;
797
800
  projectId: string;
798
801
  directory: string;
802
+ agent?: string | undefined;
799
803
  parentId: string | null;
800
804
  createdAt: number;
801
805
  updatedAt: number;
package/dist/schemas.d.ts CHANGED
@@ -5,6 +5,6 @@ export { guestStatusControlsSchema, guestStatusControlEventSchema } from './stat
5
5
  export { guestStatusViewSchema } from './status-view-schemas.ts';
6
6
  export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.ts';
7
7
  export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.ts';
8
- export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.ts';
8
+ export { browserControlActionSchema, browserProviderResultSchema, notificationsProviderMethodSchema, notificationsProviderSchemasV1, sessionEventResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, trackerProviderMethodSchema, trackerProviderSchemasV1, } from './service-provider-schemas.ts';
9
9
  export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.ts';
10
10
  export type { SurfaceHostMessage, SurfaceViewerMessage } from './service-surface-schemas.ts';
package/dist/schemas.js CHANGED
@@ -7,5 +7,5 @@ export { guestStatusControlsSchema, guestStatusControlEventSchema } from './stat
7
7
  export { guestStatusViewSchema } from './status-view-schemas.js';
8
8
  export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.js';
9
9
  export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.js';
10
- export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.js';
10
+ export { browserControlActionSchema, browserProviderResultSchema, notificationsProviderMethodSchema, notificationsProviderSchemasV1, sessionEventResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, trackerProviderMethodSchema, trackerProviderSchemasV1, } from './service-provider-schemas.js';
11
11
  export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.js';