@brydio/manifest 0.1.0-alpha.36 → 0.1.0-alpha.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brydio/manifest",
3
- "version": "0.1.0-alpha.36",
3
+ "version": "0.1.0-alpha.37",
4
4
  "description": "The shape of a Brydio app's manifest, its collections' schemas, and the checks that say what is wrong with one.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,7 +10,7 @@
10
10
  "url": "https://github.com/nakel-ola/brydio-sdk",
11
11
  "directory": "packages/manifest"
12
12
  },
13
- "gitHead": "2d8e769d02c74fe201ce734f6d77def59ea99d53",
13
+ "gitHead": "c0364642e34a602b75477af366aa61582e5598f7",
14
14
  "exports": {
15
15
  ".": {
16
16
  "types": "./src/index.d.ts",
package/src/grants.d.ts CHANGED
@@ -6,8 +6,16 @@
6
6
  * `apps/api/src/apps/manifest/grants.ts`, and the `grant_unknown` sentence in
7
7
  * `apps/api/src/apps/publishing/app-publish.service.ts`.
8
8
  */
9
- /** The host grants with a name of their own. A connection's is `connection:<name>`. */
10
- export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects", "files", "chats", "model", "secrets", "notify", "approvals"];
9
+ /**
10
+ * The host grants with a name of their own. A connection's is `connection:<name>`.
11
+ *
12
+ * `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
13
+ * person's assistant conversations; `chat` is a handler's `chat.post`, the
14
+ * app posting one of its cards into a workspace room (FO03). `directory` is
15
+ * a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
16
+ * is a handler's `webhooks.send` (FO07).
17
+ */
18
+ export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects", "files", "chats", "model", "secrets", "notify", "approvals", "chat", "directory", "webhooks"];
11
19
  /** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
12
20
  export declare const KNOWN_HOST_GRANTS: readonly string[];
13
21
  /** True for a host grant Brydio knows how to honour: a named capability, or one connection. */
package/src/grants.js CHANGED
@@ -7,8 +7,30 @@
7
7
  * `apps/api/src/apps/publishing/app-publish.service.ts`.
8
8
  */
9
9
  import { BUNDLE_MANIFEST } from "./bundle.js";
10
- /** The host grants with a name of their own. A connection's is `connection:<name>`. */
11
- export const HOST_CAPABILITIES = ['navigate', 'message', 'members', 'projects', 'files', 'chats', 'model', 'secrets', 'notify', 'approvals'];
10
+ /**
11
+ * The host grants with a name of their own. A connection's is `connection:<name>`.
12
+ *
13
+ * `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
14
+ * person's assistant conversations; `chat` is a handler's `chat.post`, the
15
+ * app posting one of its cards into a workspace room (FO03). `directory` is
16
+ * a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
17
+ * is a handler's `webhooks.send` (FO07).
18
+ */
19
+ export const HOST_CAPABILITIES = [
20
+ 'navigate',
21
+ 'message',
22
+ 'members',
23
+ 'projects',
24
+ 'files',
25
+ 'chats',
26
+ 'model',
27
+ 'secrets',
28
+ 'notify',
29
+ 'approvals',
30
+ 'chat',
31
+ 'directory',
32
+ 'webhooks',
33
+ ];
12
34
  /** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
13
35
  export const KNOWN_HOST_GRANTS = HOST_CAPABILITIES;
14
36
  const CONNECTION = /^connection:[a-z0-9][a-z0-9_-]{0,59}$/;
package/src/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, br
7
7
  export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, type BrandField, type RequirementCode, type RequirementFiles, type RequirementProblem, } from './requirements.js';
8
8
  export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, type BundleFiles, type BundleProblem, type BundleProblemCode, } from './bundle.js';
9
9
  export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, type DocumentOf, type FieldKind, type FieldType, type FieldValue, type FieldsOf, } from './field-types.js';
10
- export { appManifestSchema, collectionOf, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type DataProblemOptions, type HomeCardSize, type ManifestExtensions, type SecretSpec, } from './schema.js';
10
+ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type DataProblemOptions, type HomeCardSize, type ManifestExtensions, type SecretSpec, } from './schema.js';
11
11
  export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, type Moved, type OpenField, type OpenFieldChange, type OpenFieldValue, type OpenProblem, type OpenProblemCode, type OpenSchemaFlag, type OpenType, } from './open-schema.js';
12
12
  export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, type ConfirmEmailDeclared, type ConfirmEmailSpec, type ConfirmProblemCode, } from './confirm-email.js';
13
13
  export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, type AnonymousDeclared, type AnonymousProblemCode, type AnonymousSpec, } from './anonymous.js';
package/src/index.js CHANGED
@@ -7,7 +7,7 @@ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, br
7
7
  export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, } from "./requirements.js";
8
8
  export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, } from "./bundle.js";
9
9
  export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, } from "./field-types.js";
10
- export { appManifestSchema, collectionOf, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, } from "./schema.js";
10
+ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, } from "./schema.js";
11
11
  export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, } from "./open-schema.js";
12
12
  export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, } from "./confirm-email.js";
13
13
  export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, } from "./anonymous.js";
package/src/schema.d.ts CHANGED
@@ -102,6 +102,7 @@ declare const extensionShape: {
102
102
  "project-sidebar": "project-sidebar";
103
103
  "workspace-sidebar": "workspace-sidebar";
104
104
  "public-page": "public-page";
105
+ "chat-card": "chat-card";
105
106
  }>;
106
107
  screen: z.ZodString;
107
108
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -240,7 +241,7 @@ export interface DataProblem {
240
241
  field?: string;
241
242
  message: string;
242
243
  }
243
- export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | 'data_too_many_collections' | 'data_too_many_fields' | 'data_collection_name_format' | 'data_field_name_format' | 'data_field_reserved' | 'data_label_format' | 'data_label_taken' | 'data_project_field_twice' | 'data_search_unknown_field' | 'data_search_not_text' | 'grant_collection_missing' | 'grant_tool_missing' | 'custom_name_taken' | 'custom_collection_unknown' | 'custom_input_invalid' | 'placement_key_taken' | 'placement_screen_unknown' | 'placement_home_sizes' | 'placement_sizes_not_home' | 'placement_widget_sizes' | 'placement_tab_retired' | 'placement_public_shape' | 'placement_public_nothing' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
244
+ export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | 'data_too_many_collections' | 'data_too_many_fields' | 'data_collection_name_format' | 'data_field_name_format' | 'data_field_reserved' | 'data_label_format' | 'data_label_taken' | 'data_project_field_twice' | 'data_search_unknown_field' | 'data_search_not_text' | 'grant_collection_missing' | 'grant_tool_missing' | 'custom_name_taken' | 'custom_collection_unknown' | 'custom_input_invalid' | 'placement_key_taken' | 'placement_screen_unknown' | 'placement_home_sizes' | 'placement_sizes_not_home' | 'placement_widget_sizes' | 'placement_tab_retired' | 'placement_public_shape' | 'placement_public_nothing' | 'placement_chat_card_shape' | 'grant_tool_cross_app_self' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
244
245
  type Additions = z.infer<z.ZodObject<typeof extensionShape>>;
245
246
  /**
246
247
  * Everything wrong with an app's additions that a type cannot say.
@@ -256,6 +257,17 @@ export interface DataProblemOptions {
256
257
  retired?: boolean;
257
258
  }
258
259
  export declare function dataProblems(additions: Additions, options?: DataProblemOptions): DataProblem[];
260
+ /**
261
+ * Another installed app's tool, as `grants.tools` names it (FO07): the name
262
+ * the assistant knows it by, `<slug>__<tool>`. Null for anything else.
263
+ * A handler calls it with `tools.call('<slug>__<tool>', input)`.
264
+ */
265
+ export declare function crossAppTool(name: string): {
266
+ app: string;
267
+ tool: string;
268
+ } | null;
269
+ /** An app's name as the front of its tools' assistant names: `issue-tracker` → `issue_tracker`. */
270
+ export declare const appToolPrefix: (name: string) => string;
259
271
  /** The additions alone, for code that has the rest of the manifest already. */
260
272
  export declare const manifestExtensionsSchema: z.ZodObject<{
261
273
  placements: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -267,6 +279,7 @@ export declare const manifestExtensionsSchema: z.ZodObject<{
267
279
  "project-sidebar": "project-sidebar";
268
280
  "workspace-sidebar": "workspace-sidebar";
269
281
  "public-page": "public-page";
282
+ "chat-card": "chat-card";
270
283
  }>;
271
284
  screen: z.ZodString;
272
285
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -402,6 +415,7 @@ export declare const storedExtensionsSchema: z.ZodObject<{
402
415
  "project-sidebar": "project-sidebar";
403
416
  "workspace-sidebar": "workspace-sidebar";
404
417
  "public-page": "public-page";
418
+ "chat-card": "chat-card";
405
419
  }>;
406
420
  screen: z.ZodString;
407
421
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -572,6 +586,7 @@ export declare const appManifestSchema: z.ZodObject<{
572
586
  "project-sidebar": "project-sidebar";
573
587
  "workspace-sidebar": "workspace-sidebar";
574
588
  "public-page": "public-page";
589
+ "chat-card": "chat-card";
575
590
  }>;
576
591
  screen: z.ZodString;
577
592
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -746,6 +761,7 @@ export declare const newAppManifestSchema: z.ZodObject<{
746
761
  "project-sidebar": "project-sidebar";
747
762
  "workspace-sidebar": "workspace-sidebar";
748
763
  "public-page": "public-page";
764
+ "chat-card": "chat-card";
749
765
  }>;
750
766
  screen: z.ZodString;
751
767
  sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
package/src/schema.js CHANGED
@@ -72,8 +72,12 @@ const placementSchema = z.object({
72
72
  * `children` or `settings` (`placement_public_shape`), and it reaches only
73
73
  * collections marked `publicRead` or `publicSubmit` and tools marked
74
74
  * `public`.
75
+ * `chat-card`: a screen drawn inside a chat message the app posted with
76
+ * a handler's `chat.post` (FO03). Nobody places it by hand, so it has no
77
+ * `sizes`, `children` or `settings` (`placement_chat_card_shape`); its
78
+ * screen sees `placement.kind === 'chat-card'` and the card's `route`.
75
79
  */
76
- kind: z.enum(['project-tab', 'project-widget', 'project-sidebar', 'workspace-sidebar', 'home', 'public-page']),
80
+ kind: z.enum(['project-tab', 'project-widget', 'project-sidebar', 'workspace-sidebar', 'home', 'public-page', 'chat-card']),
77
81
  screen: z.string().min(1).max(FIELD_LIMITS.nameChars),
78
82
  /** For `home` and `project-widget` only, and required there: the sizes the screen draws well at. */
79
83
  sizes: z.array(z.enum(HOME_CARD_SIZES)).max(3).optional(),
@@ -454,6 +458,14 @@ export function dataProblems(additions, options = {}) {
454
458
  message: `The public page "${placement.screen}" can have no children or settings: it is one screen at its own address.`,
455
459
  });
456
460
  }
461
+ // A chat card is drawn at the message's own size, in a message nobody
462
+ // places by hand (FO03): nothing to size, configure or list.
463
+ if (placement.kind === 'chat-card' && (placement.sizes?.length || placement.children || placement.settings)) {
464
+ problems.push({
465
+ code: 'placement_chat_card_shape',
466
+ message: `The chat card "${placement.screen}" cannot have sizes, children or settings.`,
467
+ });
468
+ }
457
469
  }
458
470
  // A public page with nothing public could show a visitor nothing at all.
459
471
  if ((additions.placements ?? []).some(placement => placement.kind === 'public-page')) {
@@ -551,6 +563,20 @@ function customToolProblems(additions, options) {
551
563
  }));
552
564
  const seen = new Set();
553
565
  const granted = additions.grants?.tools ?? [];
566
+ // Another app's tool, asked for by its assistant name `<slug>__<tool>`
567
+ // (FO07): never an unknown custom tool of this app, but naming the app
568
+ // itself would be a way round its own grants.
569
+ const name = additions.name;
570
+ const own = typeof name === 'string' ? appToolPrefix(name) : null;
571
+ for (const grant of granted) {
572
+ const qualified = crossAppTool(grant);
573
+ if (qualified && own !== null && qualified.app === own) {
574
+ problems.push({
575
+ code: 'grant_tool_cross_app_self',
576
+ message: `${grant} names this app's own tool: ask for ${qualified.tool} instead.`,
577
+ });
578
+ }
579
+ }
554
580
  for (const tool of custom) {
555
581
  if (generated.has(tool.name) || seen.has(tool.name)) {
556
582
  problems.push({
@@ -587,6 +613,22 @@ function customToolProblems(additions, options) {
587
613
  }
588
614
  return problems;
589
615
  }
616
+ /**
617
+ * Another installed app's tool, as `grants.tools` names it (FO07): the name
618
+ * the assistant knows it by, `<slug>__<tool>`. Null for anything else.
619
+ * A handler calls it with `tools.call('<slug>__<tool>', input)`.
620
+ */
621
+ export function crossAppTool(name) {
622
+ const at = name.indexOf('__');
623
+ if (at <= 0 || at + 2 >= name.length)
624
+ return null;
625
+ return { app: name.slice(0, at), tool: name.slice(at + 2) };
626
+ }
627
+ /** An app's name as the front of its tools' assistant names: `issue-tracker` → `issue_tracker`. */
628
+ export const appToolPrefix = (name) => name
629
+ .replace(/[^A-Za-z0-9_]+/g, '_')
630
+ .replace(/_+/g, '_')
631
+ .replace(/^_+|_+$/g, '');
590
632
  /**
591
633
  * Each secret declared once, and asked for (ADR-A24): an app that declares
592
634
  * secrets without the `secrets` host grant could never read one.
@@ -619,7 +661,7 @@ const refuse = (additions, ctx, options = {}) => {
619
661
  ctx.addIssue({
620
662
  code: 'custom',
621
663
  message: problem.message,
622
- path: problem.code === 'grant_tool_missing'
664
+ path: problem.code === 'grant_tool_missing' || problem.code === 'grant_tool_cross_app_self'
623
665
  ? ['grants', 'tools']
624
666
  : problem.code === 'grant_secrets_missing'
625
667
  ? ['grants', 'host']