@nekro-nxt/contracts 0.2.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.
- package/dist/index.mjs +689 -126
- package/dist/index.mjs.map +1 -1
- package/lib/types/community-personas.d.ts +139 -0
- package/lib/types/community-personas.d.ts.map +1 -0
- package/lib/types/community.d.ts +269 -148
- package/lib/types/community.d.ts.map +1 -1
- package/lib/types/extension-capabilities.d.ts +36 -7
- package/lib/types/extension-capabilities.d.ts.map +1 -1
- package/lib/types/extension-ui.d.ts +194 -3
- package/lib/types/extension-ui.d.ts.map +1 -1
- package/lib/types/host-api.d.ts +1674 -272
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/community-personas.ts +81 -0
- package/src/community.ts +233 -169
- package/src/extension-capabilities.ts +54 -8
- package/src/extension-ui.ts +107 -20
- package/src/host-api.ts +382 -34
- package/src/index.ts +1 -0
|
@@ -2,10 +2,10 @@ import { z } from 'zod'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Host capability level this build implements. A Revision that declares `requires.sdk` above it is rejected at
|
|
5
|
-
* import with an "upgrade NekroNXT" message instead of an opaque schema error. Bump
|
|
6
|
-
*
|
|
5
|
+
* import with an "upgrade NekroNXT" message instead of an opaque schema error. Bump when a Manifest capability
|
|
6
|
+
* ships; never reuse a level for a different meaning. Level 7 is Manifest V7 (host instance + agent attachments).
|
|
7
7
|
*/
|
|
8
|
-
export const EXTENSION_SDK_LEVEL =
|
|
8
|
+
export const EXTENSION_SDK_LEVEL = 7
|
|
9
9
|
|
|
10
10
|
export const ExtensionRequiresSchema = z.object({ sdk: z.number().int().min(1).max(1000) }).strict()
|
|
11
11
|
export type ExtensionRequires = z.output<typeof ExtensionRequiresSchema>
|
|
@@ -252,8 +252,8 @@ const mcpTarget = (server: ExtensionMcpServer): string =>
|
|
|
252
252
|
: JSON.stringify(['streamable-http', server.name, server.url])
|
|
253
253
|
|
|
254
254
|
/**
|
|
255
|
-
*
|
|
256
|
-
* means the
|
|
255
|
+
* Agent-layer capabilities (`permissions.agent`): what the extension may do inside the agents it is enabled for. An
|
|
256
|
+
* absent field means the extension cannot use that capability.
|
|
257
257
|
*/
|
|
258
258
|
export const ExtensionCapabilitiesSchema = z
|
|
259
259
|
.object({
|
|
@@ -281,6 +281,37 @@ export const ExtensionCapabilitiesSchema = z
|
|
|
281
281
|
.strict()
|
|
282
282
|
export type ExtensionCapabilities = z.output<typeof ExtensionCapabilitiesSchema>
|
|
283
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Host-layer capabilities (`permissions.host`): what the extension's single host instance may do through the `nxt`
|
|
286
|
+
* service its factory receives. Host storage is the extension's `shared` partition.
|
|
287
|
+
*/
|
|
288
|
+
export const HostLayerCapabilitiesSchema = z
|
|
289
|
+
.object({
|
|
290
|
+
network: ExtensionNetworkCapabilitySchema.optional(),
|
|
291
|
+
storage: z
|
|
292
|
+
.object({ quotaBytes: z.number().int().min(1024).max(EXTENSION_STORAGE_MAX_QUOTA_BYTES).optional() })
|
|
293
|
+
.strict()
|
|
294
|
+
.optional(),
|
|
295
|
+
})
|
|
296
|
+
.strict()
|
|
297
|
+
export type HostLayerCapabilities = z.output<typeof HostLayerCapabilitiesSchema>
|
|
298
|
+
|
|
299
|
+
/** Host-layer capabilities in the agent-capability shape, so digests, expansion and summaries share one implementation. */
|
|
300
|
+
export const hostLayerAsCapabilities = (host: HostLayerCapabilities | undefined): ExtensionCapabilities | undefined =>
|
|
301
|
+
host === undefined || Object.keys(host).length === 0
|
|
302
|
+
? undefined
|
|
303
|
+
: {
|
|
304
|
+
...(host.network === undefined ? {} : { network: host.network }),
|
|
305
|
+
...(host.storage === undefined
|
|
306
|
+
? {}
|
|
307
|
+
: {
|
|
308
|
+
storage: {
|
|
309
|
+
scopes: ['shared'],
|
|
310
|
+
...(host.storage.quotaBytes === undefined ? {} : { quotaBytes: host.storage.quotaBytes }),
|
|
311
|
+
},
|
|
312
|
+
}),
|
|
313
|
+
}
|
|
314
|
+
|
|
284
315
|
const NETWORK_MODE_RANK = { domains: 0, config: 1, unrestricted: 2 } as const
|
|
285
316
|
|
|
286
317
|
const networkExpands = (
|
|
@@ -360,9 +391,17 @@ export interface ExtensionCapabilitySummaryItem {
|
|
|
360
391
|
readonly detail?: string
|
|
361
392
|
}
|
|
362
393
|
|
|
363
|
-
/**
|
|
394
|
+
/**
|
|
395
|
+
* User-facing summary of declared capabilities, in a stable order, for approval and inspection pages. `layer: 'host'`
|
|
396
|
+
* phrases host-layer capabilities (passed through {@link hostLayerAsCapabilities}) for the host instance.
|
|
397
|
+
*/
|
|
364
398
|
export const summarizeExtensionCapabilities = (
|
|
365
399
|
capabilities: ExtensionCapabilities | undefined,
|
|
400
|
+
options: {
|
|
401
|
+
readonly layer?: 'host' | 'agent'
|
|
402
|
+
/** Display name of a config field, so `network.mode: 'config'` names the fields as the user knows them. */
|
|
403
|
+
readonly fieldTitle?: (key: string) => string
|
|
404
|
+
} = {},
|
|
366
405
|
): readonly ExtensionCapabilitySummaryItem[] => {
|
|
367
406
|
if (capabilities === undefined) return []
|
|
368
407
|
const items: ExtensionCapabilitySummaryItem[] = []
|
|
@@ -370,11 +409,18 @@ export const summarizeExtensionCapabilities = (
|
|
|
370
409
|
if (network?.mode === 'domains') {
|
|
371
410
|
items.push({ key: 'network', risk: 'normal', label: '访问指定网站', detail: network.domains.join('、') })
|
|
372
411
|
} else if (network?.mode === 'config') {
|
|
373
|
-
items.push({
|
|
412
|
+
items.push({
|
|
413
|
+
key: 'network',
|
|
414
|
+
risk: 'normal',
|
|
415
|
+
label: '访问你在配置中填写的地址',
|
|
416
|
+
detail: network.fields.map((field) => options.fieldTitle?.(field) ?? field).join('、'),
|
|
417
|
+
})
|
|
374
418
|
} else if (network?.mode === 'unrestricted') {
|
|
375
419
|
items.push({ key: 'network', risk: 'high', label: '访问任意公网地址', detail: network.purpose })
|
|
376
420
|
}
|
|
377
|
-
if (capabilities.storage !== undefined) {
|
|
421
|
+
if (capabilities.storage !== undefined && options.layer === 'host') {
|
|
422
|
+
items.push({ key: 'storage', risk: 'normal', label: '在本机保存扩展数据,页面与启用它的智能体共用' })
|
|
423
|
+
} else if (capabilities.storage !== undefined) {
|
|
378
424
|
const shared = capabilities.storage.scopes.includes('shared')
|
|
379
425
|
items.push({
|
|
380
426
|
key: 'storage',
|
package/src/extension-ui.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import {
|
|
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(
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
/**
|
|
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({
|