@nekro-nxt/contracts 0.1.0 → 0.3.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.
@@ -1,5 +1,9 @@
1
1
  import { z } from 'zod'
2
- import { ExtensionCapabilitiesSchema } from './extension-capabilities.js'
2
+ import {
3
+ ExtensionCapabilitiesSchema,
4
+ HostLayerCapabilitiesSchema,
5
+ hostLayerAsCapabilities,
6
+ } from './extension-capabilities.js'
3
7
  import { ExtensionIdSchema, ExtensionRevisionIdSchema, HostUiPageInstanceIdSchema } from './domain.js'
4
8
  import { ConfigSchemaDocumentSchema } from './config-schema.js'
5
9
 
@@ -132,11 +136,23 @@ export const HostPageContributionSchema = z
132
136
  .string()
133
137
  .trim()
134
138
  .regex(/^(?:[a-z0-9][a-z0-9/_-]*)?$/u),
139
+ /** Registers a navigation rail entry for this page; at most one page of an extension may declare it. */
140
+ rail: z
141
+ .object({ order: z.number().int().min(0).max(999).optional() })
142
+ .strict()
143
+ .optional(),
135
144
  })
136
145
  .strict()
137
146
 
138
147
  export type HostPageContribution = z.output<typeof HostPageContributionSchema>
139
148
 
149
+ /** Rail position of a page that registers a rail entry without an explicit order. */
150
+ export const HOST_PAGE_DEFAULT_RAIL_ORDER = 100
151
+
152
+ /** The stored rail position of a page; `null` when the page has no rail entry. */
153
+ export const hostPageRailOrder = (page: Pick<HostPageContribution, 'rail'>): number | null =>
154
+ page.rail === undefined ? null : (page.rail.order ?? HOST_PAGE_DEFAULT_RAIL_ORDER)
155
+
140
156
  export const HostUiNavigationItemSchema = z
141
157
  .object({
142
158
  id: z.string().trim().min(1).max(80),
@@ -189,32 +205,77 @@ export const HostUiNetworkOriginSchema = z
189
205
  return url.origin
190
206
  })
191
207
 
208
+ const checkBrowserPermissions = (
209
+ value: { readonly permissions: readonly HostUiPermission[]; readonly networkOrigins: readonly string[] },
210
+ context: z.RefinementCtx,
211
+ ): void => {
212
+ if (new Set(value.permissions).size !== value.permissions.length) {
213
+ context.addIssue({ code: 'custom', path: ['permissions'], message: 'Host UI permissions must be unique.' })
214
+ }
215
+ if (new Set(value.networkOrigins).size !== value.networkOrigins.length) {
216
+ context.addIssue({ code: 'custom', path: ['networkOrigins'], message: 'Host UI network origins must be unique.' })
217
+ }
218
+ if (value.networkOrigins.length > 0 && !value.permissions.includes('network.request')) {
219
+ context.addIssue({
220
+ code: 'custom',
221
+ path: ['networkOrigins'],
222
+ message: 'Host UI network origins require network.request.',
223
+ })
224
+ }
225
+ }
226
+
227
+ /**
228
+ * One approval: browser-side permissions plus the capabilities of one layer. Grants, digests and the approval page
229
+ * use this shape for both the host approval and each agent approval.
230
+ */
192
231
  export const HostUiPermissionDeclarationSchema = z
193
232
  .object({
194
233
  permissions: z.array(HostUiPermissionSchema).max(HostUiPermissionSchema.options.length),
195
234
  networkOrigins: z.array(HostUiNetworkOriginSchema).max(32).default([]),
196
- /** Host capabilities of an agent-scope Revision; absent keeps the pre-capability digest unchanged. */
197
235
  capabilities: ExtensionCapabilitiesSchema.optional(),
198
236
  })
199
237
  .strict()
200
- .superRefine((value, context) => {
201
- if (new Set(value.permissions).size !== value.permissions.length) {
202
- context.addIssue({ code: 'custom', path: ['permissions'], message: 'Host UI permissions must be unique.' })
203
- }
204
- if (new Set(value.networkOrigins).size !== value.networkOrigins.length) {
205
- context.addIssue({ code: 'custom', path: ['networkOrigins'], message: 'Host UI network origins must be unique.' })
206
- }
207
- if (value.networkOrigins.length > 0 && !value.permissions.includes('network.request')) {
208
- context.addIssue({
209
- code: 'custom',
210
- path: ['networkOrigins'],
211
- message: 'Host UI network origins require network.request.',
212
- })
213
- }
214
- })
238
+ .superRefine(checkBrowserPermissions)
215
239
 
216
240
  export type HostUiPermissionDeclaration = z.output<typeof HostUiPermissionDeclarationSchema>
217
241
 
242
+ /**
243
+ * Manifest V7 `permissions`: browser-side permissions of pages and panels (`host.call`), host-layer capabilities used
244
+ * by the single host instance, and agent-layer capabilities used inside each enabled agent.
245
+ */
246
+ export const ExtensionPermissionsSchema = z
247
+ .object({
248
+ permissions: z.array(HostUiPermissionSchema).max(HostUiPermissionSchema.options.length).default([]),
249
+ networkOrigins: z.array(HostUiNetworkOriginSchema).max(32).default([]),
250
+ host: HostLayerCapabilitiesSchema.optional(),
251
+ agent: ExtensionCapabilitiesSchema.optional(),
252
+ })
253
+ .strict()
254
+ .superRefine(checkBrowserPermissions)
255
+
256
+ export type ExtensionPermissions = z.output<typeof ExtensionPermissionsSchema>
257
+
258
+ export const EMPTY_EXTENSION_PERMISSIONS: ExtensionPermissions = { permissions: [], networkOrigins: [] }
259
+
260
+ /** What installing the extension on this machine asks the user to approve. */
261
+ export const hostPermissionDeclaration = (permissions: ExtensionPermissions): HostUiPermissionDeclaration => {
262
+ const capabilities = hostLayerAsCapabilities(permissions.host)
263
+ return {
264
+ permissions: [...permissions.permissions],
265
+ networkOrigins: [...permissions.networkOrigins],
266
+ ...(capabilities === undefined ? {} : { capabilities }),
267
+ }
268
+ }
269
+
270
+ /** What enabling the extension for one agent asks the user to approve. */
271
+ export const agentPermissionDeclaration = (permissions: ExtensionPermissions): HostUiPermissionDeclaration => ({
272
+ permissions: [],
273
+ networkOrigins: [],
274
+ ...(permissions.agent === undefined || Object.keys(permissions.agent).length === 0
275
+ ? {}
276
+ : { capabilities: permissions.agent }),
277
+ })
278
+
218
279
  export const HostUiOwnerSchema = z.discriminatedUnion('kind', [
219
280
  z
220
281
  .object({
@@ -246,6 +307,11 @@ export const HostUiPageEntrySchema = z
246
307
  startPath: z.string(),
247
308
  visible: z.boolean(),
248
309
  sortOrder: z.number().int().nonnegative(),
310
+ /** Present when the page registered a navigation rail entry; lower orders come first. */
311
+ rail: z
312
+ .object({ order: z.number().int().min(0).max(999) })
313
+ .strict()
314
+ .optional(),
249
315
  routeBase: z.string().regex(/^\/apps\/hup_[0-9A-Za-z]+$/u),
250
316
  client: z
251
317
  .object({
@@ -308,8 +374,17 @@ export type DshNxtHostUi = z.output<typeof DshNxtHostUiSchema>
308
374
  export const ExtensionPanelAnchorSchema = z.enum(['agent', 'channel', 'extension', 'connection'])
309
375
  export type ExtensionPanelAnchor = z.output<typeof ExtensionPanelAnchorSchema>
310
376
 
311
- export const AGENT_PANEL_ANCHORS = ['agent', 'channel', 'extension'] as const satisfies readonly ExtensionPanelAnchor[]
312
- export const ADAPTER_PANEL_ANCHORS = ['connection', 'channel'] as const satisfies readonly ExtensionPanelAnchor[]
377
+ /**
378
+ * Which layer a panel belongs to. Agent and channel panels follow the agents the extension is enabled for (a channel
379
+ * panel also shows on channels of the extension's own adapter); extension and connection panels belong to the host
380
+ * instance.
381
+ */
382
+ export const PANEL_ANCHOR_LAYER = {
383
+ agent: 'agent',
384
+ channel: 'agent',
385
+ extension: 'host',
386
+ connection: 'host',
387
+ } as const satisfies Readonly<Record<ExtensionPanelAnchor, 'agent' | 'host'>>
313
388
 
314
389
  export const PanelDensitySchema = z.enum(['compact', 'full'])
315
390
  export type PanelDensity = z.output<typeof PanelDensitySchema>
@@ -402,10 +477,22 @@ export const MessageRendererContributionSchema = z
402
477
  .strict()
403
478
  export type MessageRendererContribution = z.output<typeof MessageRendererContributionSchema>
404
479
 
405
- /** Optional configuration surface shared by every extension scope. */
480
+ /** What an extension provides, derived from its Manifest; for listing and filtering, never a lifecycle switch. */
481
+ export const EXTENSION_PROVIDES = ['agent', 'page', 'adapter', 'mcp'] as const
482
+ export const ExtensionProvideSchema = z.enum(EXTENSION_PROVIDES)
483
+ export type ExtensionProvide = z.output<typeof ExtensionProvideSchema>
484
+
485
+ /** One configuration form: a serialized Schemastery document. */
406
486
  export const ExtensionConfigDeclarationSchema = z.object({ schema: ConfigSchemaDocumentSchema }).strict()
407
487
  export type ExtensionConfigDeclaration = z.output<typeof ExtensionConfigDeclarationSchema>
408
488
 
489
+ /** Manifest V7 `config`: the host instance's configuration and each enabled agent's configuration. */
490
+ export const ExtensionLayeredConfigSchema = z
491
+ .object({ host: ExtensionConfigDeclarationSchema.optional(), agent: ExtensionConfigDeclarationSchema.optional() })
492
+ .strict()
493
+ export type ExtensionLayeredConfig = z.output<typeof ExtensionLayeredConfigSchema>
494
+ export type ExtensionConfigLayer = 'host' | 'agent'
495
+
409
496
  /** Product projection of what one Revision contributes to the shell. */
410
497
  export const ExtensionUiContributionsSchema = z
411
498
  .object({