@native-knowledge/cli 0.1.0-rc.1 → 0.1.0-rc.3

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.
Files changed (94) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +29 -0
  3. package/dist/agent-entry-cli.js +90 -0
  4. package/dist/auth-commands.js +102 -0
  5. package/dist/cli.js +106 -4874
  6. package/dist/commands.js +41 -0
  7. package/dist/download.js +45 -0
  8. package/dist/errors.js +7 -0
  9. package/dist/input.js +89 -0
  10. package/dist/internal/contracts/artifact.js +103 -0
  11. package/dist/internal/contracts/collection-control.js +69 -0
  12. package/dist/internal/contracts/collection.js +174 -0
  13. package/dist/internal/contracts/common.js +16 -0
  14. package/dist/internal/contracts/currentness.js +221 -0
  15. package/dist/internal/contracts/event.js +35 -0
  16. package/dist/internal/contracts/evidence.js +171 -0
  17. package/dist/internal/contracts/index.js +55 -0
  18. package/dist/internal/contracts/material-capability.js +8 -0
  19. package/dist/internal/contracts/material-carrier.js +15 -0
  20. package/dist/internal/contracts/material-context.js +9 -0
  21. package/dist/internal/contracts/material-read.js +12 -0
  22. package/dist/internal/contracts/observation.js +39 -0
  23. package/dist/internal/contracts/persistence.js +239 -0
  24. package/dist/internal/contracts/project-intent.js +121 -0
  25. package/dist/internal/contracts/projection.js +194 -0
  26. package/dist/internal/contracts/public-reference.js +87 -0
  27. package/dist/internal/contracts/reference-record.js +55 -0
  28. package/dist/internal/contracts/remote-file.js +159 -0
  29. package/dist/internal/contracts/resource-scope.js +38 -0
  30. package/dist/internal/contracts/resource-transfer.js +11 -0
  31. package/dist/internal/contracts/review-packet.js +61 -0
  32. package/dist/internal/contracts/review-plane.js +89 -0
  33. package/dist/internal/contracts/rich-document.js +184 -0
  34. package/dist/internal/contracts/runtime-compatibility.js +55 -0
  35. package/dist/internal/contracts/runtime.js +68 -0
  36. package/dist/internal/contracts/scheduler.js +256 -0
  37. package/dist/internal/contracts/signal.js +77 -0
  38. package/dist/internal/contracts/source-governance.js +194 -0
  39. package/dist/internal/contracts/source.js +32 -0
  40. package/dist/internal/contracts/tenant-budget.js +34 -0
  41. package/dist/internal/contracts/tenant-collection-request.js +58 -0
  42. package/dist/internal/contracts/tenant-collection.js +66 -0
  43. package/dist/internal/contracts/tenant-currentness.js +26 -0
  44. package/dist/internal/contracts/tenant-management.js +27 -0
  45. package/dist/internal/contracts/tenant-material-api.js +46 -0
  46. package/dist/internal/contracts/tenant-material-json-schema.js +16 -0
  47. package/dist/internal/contracts/tenant-pdf-text.js +13 -0
  48. package/dist/internal/contracts/tenant-project-control.js +19 -0
  49. package/dist/internal/contracts/tenant-public-reference.js +26 -0
  50. package/dist/internal/contracts/tenant-resource-api.js +70 -0
  51. package/dist/internal/contracts/tenant-resource.js +118 -0
  52. package/dist/internal/contracts/tenant-rich-document.js +17 -0
  53. package/dist/internal/contracts/tenant-source-binding.js +33 -0
  54. package/dist/internal/contracts/tenant-source-bridge.js +9 -0
  55. package/dist/internal/contracts/tenant-source-cutover.js +26 -0
  56. package/dist/internal/contracts/tenant-source-document.js +38 -0
  57. package/dist/internal/contracts/tenant-source-lease.js +5 -0
  58. package/dist/internal/contracts/tenant-source.js +83 -0
  59. package/dist/internal/contracts/tenant-web-collection.js +118 -0
  60. package/dist/internal/contracts/tenant-web-content-plan.js +39 -0
  61. package/dist/internal/contracts/tenant-web-content-result.js +15 -0
  62. package/dist/internal/contracts/tenant-web-content.js +16 -0
  63. package/dist/internal/contracts/tenant-web-crawl.js +55 -0
  64. package/dist/internal/contracts/tenant-web-dynamic.js +68 -0
  65. package/dist/internal/contracts/tenant-web-results.js +41 -0
  66. package/dist/internal/contracts/tenant-web-workflow.js +29 -0
  67. package/dist/internal/serving-contracts/agent-catalog.js +56 -0
  68. package/dist/internal/serving-contracts/agent-entry.js +57 -0
  69. package/dist/internal/serving-contracts/agent-guidance-shapes.js +12 -0
  70. package/dist/internal/serving-contracts/agent-input-hints.js +104 -0
  71. package/dist/internal/serving-contracts/body-search.js +16 -0
  72. package/dist/internal/serving-contracts/consumer.js +45 -0
  73. package/dist/internal/serving-contracts/core.js +76 -0
  74. package/dist/internal/serving-contracts/http.js +64 -0
  75. package/dist/internal/serving-contracts/index.js +25 -0
  76. package/dist/internal/serving-contracts/management.js +15 -0
  77. package/dist/internal/serving-contracts/material-capabilities.js +14 -0
  78. package/dist/internal/serving-contracts/material-index-status.js +5 -0
  79. package/dist/internal/serving-contracts/mcp.js +137 -0
  80. package/dist/internal/serving-contracts/tenant-governance.js +13 -0
  81. package/dist/internal/serving-contracts/tenant-management.js +89 -0
  82. package/dist/internal/serving-contracts/tenant-project.js +39 -0
  83. package/dist/internal/serving-contracts/tenant-runtime.js +37 -0
  84. package/dist/internal/serving-contracts/tenant-session.js +8 -0
  85. package/dist/internal/serving-contracts/tenant-web-content.js +20 -0
  86. package/dist/internal/serving-contracts/tenant-web-crawl.js +13 -0
  87. package/dist/internal/serving-contracts/tenant-web-dynamic.js +20 -0
  88. package/dist/internal/serving-contracts/tenant-web-workflow.js +36 -0
  89. package/dist/internal/serving-contracts/tenant-web.js +53 -0
  90. package/dist/internal/serving-contracts/tenant-workspace.js +28 -0
  91. package/dist/internal/serving-contracts/workflow.js +37 -0
  92. package/dist/profile-store.js +142 -0
  93. package/package.json +7 -17
  94. package/README.md +0 -28
@@ -0,0 +1,55 @@
1
+ /** 可执行静态网站任务;独立于冻结的 S1 意图,Tenant 是唯一归属。 */
2
+ import { z } from 'zod';
3
+ import { createHash } from 'node:crypto';
4
+ import { tenantStorageKey } from './resource-scope.js';
5
+ import { tenantWebIntentSchema, tenantWebUrlSchema, tenantWebJobRefSchema, tenantWebCanonical } from './tenant-web-collection.js';
6
+ import { tenantSourceSemanticsSchema } from './tenant-source.js';
7
+ import { tenantCollectionCaptureSchema, tenantCollectionCycleSchema } from './tenant-collection.js';
8
+ import { isoDateTimeSchema, sha256Schema } from './common.js';
9
+ export const TENANT_WEB_CRAWL_CAPABILITY = 'TENANT_WEB_CRAWL_V1';
10
+ export const tenantWebPolicySchema = tenantSourceSemanticsSchema.shape.sourcePolicy.refine(v => v.acquisition === 'ALLOW' && v.propagation === 'TENANT_ONLY');
11
+ export const tenantWebCrawlSchema = z.object({ schemaVersion: z.literal(2), intent: tenantWebIntentSchema, policy: tenantWebPolicySchema }).strict();
12
+ export function tenantWebAdmissionKey(tenantId, jobRef) { tenantWebJobRefSchema.parse(jobRef); if (new URL(jobRef).hostname !== tenantStorageKey(tenantId))
13
+ throw new Error('TENANT_WEB_SCOPE_FORBIDDEN'); return createHash('sha256').update(JSON.stringify(['TENANT_WEB_ADMISSION_V1', tenantId, jobRef])).digest('hex'); }
14
+ export const tenantWebTargetSchema = z.object({ ordinal: z.number().int().min(0).max(9999), url: tenantWebUrlSchema, parent: tenantWebUrlSchema.nullable(), depth: z.number().int().min(0).max(10), role: z.enum(['PAGE', 'SITEMAP', 'FEED']) }).strict();
15
+ export const tenantWebOutcomeSchema = z.object({ ordinal: tenantWebTargetSchema.shape.ordinal, batchId: z.string().regex(/^batch_[a-f0-9]{64}$/).nullable(), capture: tenantCollectionCaptureSchema.nullable(), registered: z.boolean(), errorCode: z.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/).nullable(), reused: z.boolean() }).strict().refine(v => !v.registered || v.capture !== null);
16
+ export const tenantWebHeadSchema = z.object({ schemaVersion: z.literal(1), seq: z.number().int().nonnegative(), queued: z.number().int().min(0).max(10000), cursor: z.number().int().min(0).max(10000), stage: z.enum(['ACQUIRE', 'DISCOVER']), after: z.number().int().nonnegative(), attempts: z.number().int().min(0).max(3), currentAcquired: z.boolean(), acquired: z.number().int().min(0).max(10000), registered: z.number().int().min(0).max(10000), chargedBytes: z.number().int().nonnegative(), chargedMs: z.number().int().nonnegative(), reservation: z.object({ bytes: z.number().int().nonnegative(), ms: z.number().int().nonnegative() }).strict().nullable(), gaps: z.array(z.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/)).max(32), state: z.enum(['QUEUED', 'RUNNING', 'COMPLETE', 'PARTIAL']), finishedAt: isoDateTimeSchema.nullable(), notBefore: isoDateTimeSchema.nullable() }).strict().refine(v => v.cursor <= v.queued && v.registered <= v.acquired && v.acquired <= v.queued && (['COMPLETE', 'PARTIAL'].includes(v.state) === (v.finishedAt !== null)) && (!v.reservation || v.state === 'RUNNING' && v.chargedBytes >= v.reservation.bytes && v.chargedMs >= v.reservation.ms) && (!v.notBefore || v.state === 'RUNNING' && !v.reservation) && (v.state !== 'PARTIAL' || v.gaps.length > 0) && (v.state !== 'COMPLETE' || v.queued > 0 && v.cursor === v.queued && v.gaps.length === 0 && v.registered === v.queued && !v.reservation && !v.notBefore));
17
+ export const tenantWebChildOwnerSchema = z.object({ schemaVersion: z.literal(1), jobRef: tenantWebJobRefSchema, ordinal: tenantWebTargetSchema.shape.ordinal, requestId: tenantCollectionCycleSchema, batchId: z.string().regex(/^batch_[a-f0-9]{64}$/) }).strict();
18
+ export const tenantWebCrawlStatusSchema = z.object({ requestId: tenantCollectionCycleSchema, jobRef: tenantWebJobRefSchema, specRevision: sha256Schema, state: tenantWebHeadSchema.shape.state, phase: z.literal('STATIC_ACQUISITION'), requirementsState: z.literal('DECLARED_NOT_EVALUATED'), indexState: z.literal('NOT_EVALUATED'), coverage: z.object({ total: z.number().int().nonnegative().nullable(), discovered: z.number().int().nonnegative(), acquired: z.number().int().nonnegative(), registered: z.number().int().nonnegative(), pending: z.number().int().nonnegative(), gaps: tenantWebHeadSchema.shape.gaps }).strict(), budget: z.object({ chargedBytes: z.number().int().nonnegative(), chargedMs: z.number().int().nonnegative(), conservativeReservation: z.boolean() }).strict(), finishedAt: isoDateTimeSchema.nullable(), nextEligibleAt: isoDateTimeSchema.nullable() }).strict();
19
+ export function tenantWebCrawlStatus(job, head) { return tenantWebCrawlStatusSchema.parse({ requestId: job.intent.requestId, jobRef: job.intent.jobRef, specRevision: job.intent.specRevision, state: head.state, phase: 'STATIC_ACQUISITION', requirementsState: 'DECLARED_NOT_EVALUATED', indexState: 'NOT_EVALUATED', coverage: { total: head.state === 'COMPLETE' ? head.queued : null, discovered: head.queued, acquired: head.acquired, registered: head.registered, pending: head.queued - head.cursor, gaps: head.gaps }, budget: { chargedBytes: head.chargedBytes, chargedMs: head.chargedMs, conservativeReservation: head.reservation !== null }, finishedAt: head.finishedAt, nextEligibleAt: head.notBefore }); }
20
+ export function tenantWebPathMatches(pattern, pathname) { return pattern.endsWith('/**') ? (pathname === pattern.slice(0, -3) || pathname.startsWith(pattern.slice(0, -2))) : pathname === pattern; }
21
+ export function tenantWebInScope(spec, raw, options = {}) {
22
+ const parsed = tenantWebUrlSchema.safeParse(raw);
23
+ if (!parsed.success)
24
+ return false;
25
+ const url = new URL(parsed.data), scope = spec.scope;
26
+ if (!scope.allowedOrigins.includes(url.origin))
27
+ return false;
28
+ if (options.robots)
29
+ return url.pathname === '/robots.txt' && !url.search;
30
+ let pathname;
31
+ try {
32
+ pathname = decodeURIComponent(url.pathname);
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ if (/[\\\x00-\x1f]/.test(pathname) || /%[0-9a-f]{2}/i.test(pathname))
38
+ return false;
39
+ // GitLab and similar public APIs encode a slash inside an identifier. Only an exact
40
+ // caller-listed URL without path filters can use it; section/prefix matching stays closed.
41
+ if (/%2f/i.test(url.pathname) && (!['SINGLE_PAGE', 'URL_LIST'].includes(scope.mode) || scope.includePaths.length > 0 || scope.excludePaths.length > 0))
42
+ return false;
43
+ if (scope.excludePaths.some(p => tenantWebPathMatches(p, pathname)))
44
+ return false;
45
+ if (options.bootstrap && raw === spec.url && ['SITEMAP', 'FEED'].includes(scope.mode))
46
+ return true;
47
+ if (scope.includePaths.length && !scope.includePaths.some(p => tenantWebPathMatches(p, pathname)))
48
+ return false;
49
+ if (scope.mode === 'SINGLE_PAGE')
50
+ return raw === spec.url;
51
+ if (scope.mode === 'URL_LIST')
52
+ return raw === spec.url || scope.urls.includes(raw);
53
+ return true;
54
+ }
55
+ export function sameTenantWebDelegation(a, b) { return a.intent.tenantId === b.intent.tenantId && tenantWebCanonical(a.policy) === tenantWebCanonical(b.policy) && tenantWebCanonical(a.intent.authority) === tenantWebCanonical(b.intent.authority) && tenantWebCanonical(a.intent.actor) === tenantWebCanonical(b.intent.actor); }
@@ -0,0 +1,68 @@
1
+ /** S3 opt-in contracts. Frozen Provider21/22 objects are never silently upgraded. */
2
+ import { createHash } from 'node:crypto';
3
+ import { z } from 'zod';
4
+ import { tenantWebSpecSchema, tenantWebIntentSchema, tenantWebCanonical, tenantWebJobRef, tenantWebUrlSchema } from './tenant-web-collection.js';
5
+ import { tenantWebPolicySchema, tenantWebTargetSchema, tenantWebOutcomeSchema, tenantWebHeadSchema, tenantWebChildOwnerSchema, tenantWebCrawlStatusSchema, tenantWebInScope } from './tenant-web-crawl.js';
6
+ import { parseTenantResourceRef, tenantStorageKey, tenantResourceRefs } from './resource-scope.js';
7
+ import { isoDateTimeSchema, sha256Schema } from './common.js';
8
+ export const TENANT_WEB_DYNAMIC_CAPABILITY = 'TENANT_WEB_DYNAMIC_V1';
9
+ const selector = z.string().trim().min(1).max(512).refine(s => !s.includes('\0'));
10
+ const origin = tenantWebUrlSchema.refine(s => new URL(s).origin === s);
11
+ export const tenantWebRenderSchema = z.object({
12
+ mode: z.enum(['HTTP_ONLY', 'AUTO', 'ALWAYS']).default('HTTP_ONLY'),
13
+ requiredSelector: selector.nullable().default(null), minTextChars: z.number().int().min(0).max(100000).default(80),
14
+ resourceOrigins: z.array(origin).max(16).default([]),
15
+ interaction: z.object({ kind: z.enum(['NONE', 'NEXT_PAGE', 'LOAD_MORE', 'SCROLL']), selector: selector.nullable().default(null), endSelector: selector.nullable().default(null), maxSteps: z.number().int().min(0).max(100).default(0) }).strict().default({ kind: 'NONE', selector: null, endSelector: null, maxSteps: 0 }),
16
+ }).strict().superRefine((v, c) => {
17
+ if (v.interaction.kind !== 'NONE' && (v.mode === 'HTTP_ONLY' || v.interaction.maxSteps === 0))
18
+ c.addIssue({ code: 'custom', message: 'Interactions require a browser and a positive step bound' });
19
+ if (['NEXT_PAGE', 'LOAD_MORE'].includes(v.interaction.kind) && !v.interaction.selector)
20
+ c.addIssue({ code: 'custom', message: 'Button interactions require a selector' });
21
+ if (v.interaction.kind === 'NONE' && v.interaction.maxSteps !== 0)
22
+ c.addIssue({ code: 'custom', message: 'NONE cannot request actions' });
23
+ if (new Set(v.resourceOrigins).size !== v.resourceOrigins.length)
24
+ c.addIssue({ code: 'custom', message: 'Resource origins must be unique' });
25
+ });
26
+ export const tenantWebDynamicSpecSchema = z.object({ ...tenantWebSpecSchema.shape, schemaVersion: z.literal(2), render: tenantWebRenderSchema.default({ mode: 'HTTP_ONLY', requiredSelector: null, minTextChars: 80, resourceOrigins: [], interaction: { kind: 'NONE', selector: null, endSelector: null, maxSteps: 0 } }) }).strict().superRefine((v, c) => {
27
+ const { render: ignored, ...base } = v;
28
+ const old = tenantWebSpecSchema.safeParse({ ...base, schemaVersion: 1 });
29
+ if (!old.success)
30
+ for (const issue of old.error.issues)
31
+ c.addIssue({ ...issue, path: issue.path });
32
+ if (v.render.interaction.maxSteps > v.limits.maxInteractions)
33
+ c.addIssue({ code: 'custom', message: 'Page actions exceed the job interaction budget' });
34
+ if (v.render.interaction.kind === 'NEXT_PAGE' && !v.scope.followPagination)
35
+ c.addIssue({ code: 'custom', message: 'NEXT_PAGE requires pagination scope' });
36
+ });
37
+ export const tenantWebDynamicSpecRevision = (v) => 'sha256:' + createHash('sha256').update(tenantWebCanonical(tenantWebDynamicSpecSchema.parse(v))).digest('hex');
38
+ export const tenantWebDynamicIntentSchema = z.object({ ...tenantWebIntentSchema.shape, schemaVersion: z.literal(2), requestedSpec: tenantWebDynamicSpecSchema, spec: tenantWebDynamicSpecSchema }).strict().superRefine((v, c) => {
39
+ if (v.actor.tenantId !== v.tenantId || v.jobRef !== tenantWebJobRef(v.tenantId, v.requestId) || v.specRevision !== tenantWebDynamicSpecRevision(v.spec) || v.inputRevision !== tenantWebDynamicSpecRevision(v.requestedSpec))
40
+ c.addIssue({ code: 'custom', message: 'Frozen Tenant intent identity mismatch' });
41
+ if (v.spec.limits.maxResourceBytes > Math.min(v.requestedSpec.limits.maxResourceBytes, v.requestedSpec.limits.maxTotalBytes) || tenantWebCanonical({ ...v.spec, limits: { ...v.spec.limits, maxResourceBytes: v.requestedSpec.limits.maxResourceBytes } }) !== tenantWebCanonical(v.requestedSpec))
42
+ c.addIssue({ code: 'custom', message: 'Only resource limit narrowing is permitted' });
43
+ });
44
+ export const tenantWebDynamicJobSchema = z.object({ schemaVersion: z.literal(3), intent: tenantWebDynamicIntentSchema, policy: tenantWebPolicySchema }).strict();
45
+ export const tenantWebDynamicTargetSchema = z.object({ ...tenantWebTargetSchema.shape, ordinal: z.number().int().min(0).max(10999), role: z.enum(['PAGE', 'SITEMAP', 'FEED', 'ATTACHMENT']), attachment: z.enum(['PDF', 'IMAGE', 'AUDIO', 'VIDEO', 'OTHER']).nullable().default(null) }).strict().refine(v => (v.role === 'ATTACHMENT') === (v.attachment !== null));
46
+ export const tenantWebDynamicOutcomeSchema = z.object({ ...tenantWebOutcomeSchema.shape, ordinal: tenantWebDynamicTargetSchema.shape.ordinal }).strict().refine(v => !v.registered || v.capture !== null);
47
+ const count = z.number().int().min(0).max(11000);
48
+ export const tenantWebDynamicHeadSchema = z.object({ ...tenantWebHeadSchema.shape,
49
+ schemaVersion: z.literal(2), queued: count, cursor: count, acquired: count, registered: count,
50
+ phase: z.enum(['RENDER', 'LINKS', 'PDF']).default('RENDER'), renderIndex: z.number().int().min(0).max(101), linkState: z.number().int().min(-1).max(101),
51
+ browserPages: z.number().int().min(0).max(10000), pagesQueued: z.number().int().min(0).max(10000), filesQueued: z.number().int().min(0).max(1000), renderedStates: count, pdfExtracted: z.number().int().min(0).max(1000), interactions: z.number().int().min(0).max(1000),
52
+ reservation: z.object({ bytes: z.number().int().nonnegative(), ms: z.number().int().nonnegative(), interactions: z.number().int().min(0).max(1000) }).strict().nullable(),
53
+ }).strict().refine(v => v.queued === v.pagesQueued + v.filesQueued && v.cursor <= v.queued && v.registered <= v.acquired && v.acquired <= v.queued && (['COMPLETE', 'PARTIAL'].includes(v.state) === (v.finishedAt !== null)) && (!v.reservation || v.state === 'RUNNING' && v.chargedBytes >= v.reservation.bytes && v.chargedMs >= v.reservation.ms && v.interactions >= v.reservation.interactions) && (!v.notBefore || v.state === 'RUNNING' && !v.reservation) && (v.state !== 'PARTIAL' || v.gaps.length > 0) && (v.state !== 'COMPLETE' || v.queued > 0 && v.cursor === v.queued && v.gaps.length === 0 && v.registered === v.queued && !v.reservation && !v.notBefore));
54
+ export const tenantWebDynamicChildOwnerSchema = z.object({ ...tenantWebChildOwnerSchema.shape, schemaVersion: z.literal(2), ordinal: tenantWebDynamicTargetSchema.shape.ordinal }).strict();
55
+ export const tenantWebDynamicStatusSchema = z.object({ ...tenantWebCrawlStatusSchema.shape, phase: z.literal('DYNAMIC_ACQUISITION'), ocr: z.object({ capability: z.literal('UNAVAILABLE'), budgetState: z.literal('NOT_CONFIGURED'), processedPages: z.literal(0) }).strict(), dynamic: z.object({ browserPages: z.number().int().nonnegative(), pagesQueued: z.number().int().nonnegative(), filesQueued: z.number().int().nonnegative(), renderedStates: z.number().int().nonnegative(), pdfExtracted: z.number().int().nonnegative(), interactions: z.number().int().nonnegative() }).strict() }).strict();
56
+ export function tenantWebDynamicStatus(job, h) { return tenantWebDynamicStatusSchema.parse({ requestId: job.intent.requestId, jobRef: job.intent.jobRef, specRevision: job.intent.specRevision, state: h.state, phase: 'DYNAMIC_ACQUISITION', ocr: { capability: 'UNAVAILABLE', budgetState: 'NOT_CONFIGURED', processedPages: 0 }, requirementsState: 'DECLARED_NOT_EVALUATED', indexState: 'NOT_EVALUATED', coverage: { total: h.state === 'COMPLETE' ? h.queued : null, discovered: h.queued, acquired: h.acquired, registered: h.registered, pending: h.queued - h.cursor, gaps: h.gaps }, budget: { chargedBytes: h.chargedBytes, chargedMs: h.chargedMs, conservativeReservation: h.reservation !== null }, dynamic: { browserPages: h.browserPages, pagesQueued: h.pagesQueued, filesQueued: h.filesQueued, renderedStates: h.renderedStates, pdfExtracted: h.pdfExtracted, interactions: h.interactions }, finishedAt: h.finishedAt, nextEligibleAt: h.notBefore }); }
57
+ export function tenantWebAttachmentInScope(spec, url, robots = false) { return tenantWebInScope({ url: spec.url, scope: { ...spec.scope, mode: 'SITE_SECTION' } }, url, { robots }); }
58
+ /** Browser records refer to retained immutable evidence, never inline runtime bodies. */
59
+ export const tenantWebBrowserResponseSchema = z.object({ url: tenantWebUrlSchema, finalUrl: tenantWebUrlSchema, status: z.number().int().min(200).max(299), headers: z.record(z.string(), z.string().max(8192)), ref: z.string().min(1).max(1024), hash: sha256Schema, observedAt: isoDateTimeSchema }).strict();
60
+ export const tenantWebBrowserStateSchema = z.object({ schemaVersion: z.literal(1), ordinal: tenantWebDynamicTargetSchema.shape.ordinal, index: z.number().int().min(0).max(100), url: tenantWebUrlSchema, domRef: z.string().min(1).max(1024), domHash: sha256Schema, visibleRef: z.string().min(1).max(1024).optional(), visibleHash: sha256Schema.optional(), observedAt: isoDateTimeSchema, responseCount: z.number().int().min(0).max(64), navigationCount: z.number().int().min(0).max(64), actions: z.number().int().min(0).max(1000), done: z.boolean(), gap: z.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/).nullable(), interactionRef: z.string().min(1).max(1024) }).strict();
61
+ export function tenantWebPartIdentity(tenantId, parentRef, part) {
62
+ const parent = parseTenantResourceRef(parentRef);
63
+ if (!part || part.length > 256 || !parent || parent.storageKey !== tenantStorageKey(tenantId) || parent.resourceType !== 'evidence')
64
+ throw new Error('TENANT_WEB_PART_IDENTITY');
65
+ const resourceId = 'web_' + createHash('sha256').update(tenantWebCanonical(['TENANT_WEB_PART_V1', tenantId, parentRef, part])).digest('hex');
66
+ return { resourceId, version: 1, ...tenantResourceRefs({ tenantId, resourceType: 'evidence', resourceId, version: 1 }) };
67
+ }
68
+ export const tenantWebBrowserCapabilitySchema = z.object({ executablePath: z.string().min(1).max(4096).refine(s => s.startsWith('/') && !s.includes('\0')), navigationTimeoutMs: z.number().int().min(1000).max(30000).default(15000), settleMs: z.number().int().min(100).max(3000).default(300), maxRequests: z.number().int().min(1).max(64).default(32), captureVisible: z.boolean().optional() }).strict();
@@ -0,0 +1,41 @@
1
+ /** 结果与覆盖独立于意图受理;仅有原件/字段出处才允许声明满足要求。 */
2
+ import { z } from 'zod';
3
+ import { isoDateTimeSchema, sha256Schema } from './common.js';
4
+ import { tenantResourceRefSchema, parseTenantResourceRef } from './resource-scope.js';
5
+ import { tenantWebJobRefSchema, tenantWebUrlSchema } from './tenant-web-collection.js';
6
+ const count = z.number().int().nonnegative().safe();
7
+ export const tenantWebFieldResultSchema = z.object({
8
+ name: z.string().min(1).max(64), state: z.enum(['PRESENT', 'MISSING', 'UNSUPPORTED']), value: z.json().nullable(),
9
+ citations: z.array(z.object({ evidenceRef: tenantResourceRefSchema, contentHash: sha256Schema, locator: z.string().min(1).max(2048), excerpt: z.string().min(1).max(4000) }).strict()).max(16),
10
+ reason: z.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/).nullable(),
11
+ }).strict().superRefine((v, ctx) => {
12
+ if (v.state === 'PRESENT' ? (v.value === null || v.citations.length === 0 || v.reason !== null) : (v.value !== null || v.reason === null))
13
+ ctx.addIssue({ code: 'custom', message: 'Present values require citations; gaps require a reason without an invented value' });
14
+ if (v.citations.some(c => parseTenantResourceRef(c.evidenceRef)?.resourceType !== 'evidence'))
15
+ ctx.addIssue({ code: 'custom', message: 'Citations require actual Evidence identities' });
16
+ });
17
+ export const tenantWebResultSchema = z.object({
18
+ schemaVersion: z.literal(1), jobRef: tenantWebJobRefSchema, evidenceRef: tenantResourceRefSchema,
19
+ sourceUrl: tenantWebUrlSchema, contentHash: sha256Schema, observedAt: isoDateTimeSchema,
20
+ documentRef: tenantResourceRefSchema.nullable(), fields: z.array(tenantWebFieldResultSchema).max(64),
21
+ }).strict().superRefine((v, ctx) => {
22
+ const key = new URL(v.jobRef).hostname, mine = (ref) => parseTenantResourceRef(ref)?.storageKey === key;
23
+ if (!mine(v.evidenceRef) || parseTenantResourceRef(v.evidenceRef)?.resourceType !== 'evidence' || v.documentRef && (!mine(v.documentRef) || parseTenantResourceRef(v.documentRef)?.resourceType !== 'source_document') || v.fields.some(f => f.citations.some(c => !mine(c.evidenceRef))))
24
+ ctx.addIssue({ code: 'custom', message: 'Results and their typed evidence/document must retain the owning Tenant' });
25
+ });
26
+ export const tenantWebCoverageSchema = z.object({
27
+ schemaVersion: z.literal(1), jobRef: tenantWebJobRefSchema, scopeRevision: sha256Schema,
28
+ state: z.enum(['NOT_STARTED', 'PARTIAL', 'COMPLETE', 'UNKNOWN']), total: count.nullable(),
29
+ discovered: count, acquired: count, extracted: count, registered: count, pending: count,
30
+ frontierExhausted: z.boolean(), limitReached: z.boolean(),
31
+ gaps: z.array(z.object({ code: z.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/), url: tenantWebUrlSchema.nullable() }).strict()).max(100),
32
+ }).strict().superRefine((v, ctx) => {
33
+ if (v.state === 'COMPLETE' && (!v.frontierExhausted || v.pending !== 0 || v.limitReached || v.gaps.length > 0))
34
+ ctx.addIssue({ code: 'custom', message: 'A truncated, pending or incomplete scope cannot be complete' });
35
+ if (v.state === 'COMPLETE' && (v.total !== v.discovered || v.acquired !== v.discovered || v.registered !== v.acquired))
36
+ ctx.addIssue({ code: 'custom', message: 'Completion requires a known exhausted target count and all originals registered' });
37
+ if (v.state === 'NOT_STARTED' && (v.discovered !== 0 || v.acquired !== 0 || v.extracted !== 0 || v.registered !== 0 || v.pending !== 0))
38
+ ctx.addIssue({ code: 'custom', message: 'Not-started coverage cannot carry completed work' });
39
+ if (v.acquired > v.discovered || v.extracted > v.acquired || v.registered > v.acquired || v.pending > v.discovered || v.total !== null && v.total < v.discovered)
40
+ ctx.addIssue({ code: 'custom', message: 'Coverage counters must describe actual bounded work' });
41
+ });
@@ -0,0 +1,29 @@
1
+ /** S5 operational state; acquisition/content evidence and business review remain separate. */
2
+ import { z } from 'zod';
3
+ import { tenantWebContentJobSchema } from './tenant-web-content.js';
4
+ import { tenantWebJobRefSchema, tenantWebCanonical } from './tenant-web-collection.js';
5
+ import { tenantCollectionCycleSchema } from './tenant-collection.js';
6
+ import { isoDateTimeSchema } from './common.js';
7
+ export const TENANT_WEB_WORKFLOW_CAPABILITY = 'TENANT_WEB_WORKFLOW_V1';
8
+ export const tenantWebScheduleSchema = z.discriminatedUnion('mode', [z.object({ mode: z.literal('ONCE') }).strict(), z.object({ mode: z.literal('PERIODIC'), intervalSeconds: z.number().int().min(60).max(2592000) }).strict()]);
9
+ export const tenantWebWorkflowJobSchema = z.object({ ...tenantWebContentJobSchema.shape, schemaVersion: z.literal(5), schedule: tenantWebScheduleSchema, cycle: z.object({ kind: z.enum(['INITIAL', 'MANUAL_REFRESH', 'DUE']), rootJobRef: tenantWebJobRefSchema, previousJobRef: tenantWebJobRefSchema.nullable(), ordinal: z.number().int().nonnegative().safe(), requestedEpoch: z.number().int().nonnegative().safe() }).strict() }).strict().superRefine((v, c) => {
10
+ const old = tenantWebContentJobSchema.safeParse({ schemaVersion: 4, intent: v.intent, policy: v.policy, contentPlan: v.contentPlan, contentRevision: v.contentRevision, retainedFrom: v.retainedFrom });
11
+ if (!old.success)
12
+ c.addIssue({ code: 'custom', message: 'Invalid frozen content intent' });
13
+ const wrongTenant = [v.cycle.rootJobRef, v.cycle.previousJobRef].some(ref => ref && new URL(ref).hostname !== new URL(v.intent.jobRef).hostname), wrongCycle = v.cycle.kind === 'INITIAL' ? (v.cycle.rootJobRef !== v.intent.jobRef || v.cycle.previousJobRef !== null || v.cycle.ordinal !== 0 || v.cycle.requestedEpoch !== 0) : (v.cycle.rootJobRef === v.intent.jobRef || v.cycle.previousJobRef === null || v.cycle.ordinal < 1 || v.retainedFrom !== null);
14
+ if (wrongTenant || wrongCycle)
15
+ c.addIssue({ code: 'custom', message: 'Cycle must retain its exact Tenant/root/previous identity' });
16
+ if (v.retainedFrom && v.schedule.mode !== 'ONCE')
17
+ c.addIssue({ code: 'custom', message: 'Retained derivation cannot enable periodic source observations' });
18
+ });
19
+ export function tenantWebExecutableContent(job) { return tenantWebContentJobSchema.parse({ schemaVersion: 4, intent: job.intent, policy: job.policy, contentPlan: job.contentPlan, contentRevision: job.contentRevision, retainedFrom: job.retainedFrom }); }
20
+ export const tenantWebLifecycleSchema = z.object({ schemaVersion: z.literal(1), seq: z.number().int().nonnegative().safe(), epoch: z.number().int().nonnegative().safe(), state: z.enum(['ACTIVE', 'PAUSED', 'CANCELLED']), currentJobRef: tenantWebJobRefSchema, cycleOrdinal: z.number().int().nonnegative().safe(), lastObservedAt: isoDateTimeSchema.nullable(), updatedAt: isoDateTimeSchema }).strict().refine(v => v.epoch <= v.seq && v.cycleOrdinal <= v.seq);
21
+ export const tenantWebControlInputSchema = z.object({ jobRef: tenantWebJobRefSchema, requestId: tenantCollectionCycleSchema, expectedEpoch: z.number().int().nonnegative().safe(), action: z.enum(['PAUSE', 'RESUME', 'CANCEL']) }).strict();
22
+ export const tenantWebControlOperationSchema = z.object({ ...tenantWebControlInputSchema.shape, actor: tenantWebWorkflowJobSchema.shape.intent.shape.actor, authority: tenantWebWorkflowJobSchema.shape.intent.shape.authority }).strict();
23
+ export const tenantWebLifecycleReceiptSchema = z.object({ operation: tenantWebControlOperationSchema, committed: tenantWebLifecycleSchema }).strict();
24
+ export const tenantWebWorkflowStatusSchema = z.object({ jobRef: tenantWebJobRefSchema, requestId: tenantCollectionCycleSchema, rootJobRef: tenantWebJobRefSchema, executionJobRef: tenantWebJobRefSchema, state: z.enum(['QUEUED', 'RUNNING', 'COMPLETE', 'PARTIAL', 'PAUSED', 'CANCELLED']), control: tenantWebLifecycleSchema, cycle: tenantWebWorkflowJobSchema.shape.cycle, schedule: tenantWebScheduleSchema, nextDueAt: isoDateTimeSchema.nullable(), stages: z.object({ intent: z.literal('PERSISTED'), acquisition: z.object({ state: z.enum(['QUEUED', 'RUNNING', 'COMPLETE', 'PARTIAL']), discovered: z.number().int().nonnegative(), acquired: z.number().int().nonnegative(), registered: z.number().int().nonnegative(), pending: z.number().int().nonnegative(), gaps: z.array(z.string()).max(32) }).strict(), content: z.object({ state: z.enum(['PENDING', 'SATISFIED', 'PARTIAL']), processed: z.number().int().nonnegative(), registered: z.number().int().nonnegative(), gaps: z.array(z.string()).max(32) }).strict(), index: z.literal('NOT_PROVEN_QUERY_TENANT_INDEX_STATUS'), readability: z.literal('NOT_PROVEN_UNTIL_ACTUAL_READ'), review: z.literal('UNREVIEWED') }).strict(), budget: z.object({ chargedBytes: z.number().int().nonnegative(), chargedMs: z.number().int().nonnegative(), conservativeReservation: z.boolean() }).strict() }).strict().superRefine((v, c) => { const key = new URL(v.jobRef).hostname; if ([v.rootJobRef, v.executionJobRef, v.control.currentJobRef, v.cycle.rootJobRef].some(ref => new URL(ref).hostname !== key))
25
+ c.addIssue({ code: 'custom', message: 'Exact Tenant workflow required' }); });
26
+ export function tenantWebNextDue(root, head, current) { if (root.schedule.mode !== 'PERIODIC' || head.state !== 'ACTIVE')
27
+ return null; return new Date(Math.max(Date.parse(head.lastObservedAt ?? current.intent.submittedAt), Date.parse(current.intent.submittedAt)) + root.schedule.intervalSeconds * 1000).toISOString(); }
28
+ export const sameTenantWebWorkflow = (a, b) => tenantWebCanonical(a.intent.actor) === tenantWebCanonical(b.intent.actor) && tenantWebCanonical(a.intent.authority) === tenantWebCanonical(b.intent.authority) && tenantWebCanonical(a.policy) === tenantWebCanonical(b.policy) && a.intent.tenantId === b.intent.tenantId && a.intent.specRevision === b.intent.specRevision && a.contentRevision === b.contentRevision && tenantWebCanonical(a.schedule) === tenantWebCanonical(b.schedule);
29
+ export const tenantWebLifecycleEventSchema = z.object({ before: tenantWebLifecycleSchema, after: tenantWebLifecycleSchema, operation: tenantWebControlOperationSchema.nullable() }).strict().refine(v => v.after.seq === v.before.seq + 1 && v.after.epoch === v.before.epoch + (v.operation ? 1 : 0) && (!v.before.lastObservedAt || v.after.lastObservedAt !== null && v.after.lastObservedAt >= v.before.lastObservedAt) && v.after.cycleOrdinal >= v.before.cycleOrdinal && v.after.cycleOrdinal <= v.before.cycleOrdinal + 1 && (v.operation !== null || v.after.state === v.before.state) && (!v.operation || v.operation.expectedEpoch === v.before.epoch && v.after.cycleOrdinal === v.before.cycleOrdinal && v.after.currentJobRef === v.before.currentJobRef && v.after.state === (v.operation.action === 'PAUSE' ? 'PAUSED' : v.operation.action === 'RESUME' ? 'ACTIVE' : 'CANCELLED')) && (v.before.state !== 'CANCELLED' || v.after.state === 'CANCELLED') && (v.after.cycleOrdinal !== v.before.cycleOrdinal ? v.before.state === 'ACTIVE' && v.after.state === 'ACTIVE' && v.after.currentJobRef !== v.before.currentJobRef : v.after.currentJobRef === v.before.currentJobRef));
@@ -0,0 +1,56 @@
1
+ const row = (category, title, description, readOnly, terms) => ({ category, title, description, readOnly, terms });
2
+ export const agentCatalog = {
3
+ knowledge_resource_search_body: row('materials', 'Search document body / 搜索正文', 'Search indexed native UTF-8 text and JSON string passages with hash-bound exact citations; coverage is partial.', true, ['body', 'body search', 'full text', 'passage', 'quote', 'citation', '正文', '搜索正文', '正文检索', '全文', '段落', '引用', '原句']),
4
+ knowledge_resource_read_passage: row('materials', 'Verify exact citation / 核验准确引用', 'Re-read canonical material and verify an exact quote, content hash, JSON pointer and UTF-16 offsets under current authority.', true, ['verify', 'citation', 'quote', '核验', '引用', '出处']),
5
+ knowledge_resource_search: row('materials', 'Search retained material / 搜索资料', 'Search authorized indexed titles and descriptions; consult index status for coverage.', true, ['search', 'find', '查找', '搜索', '检索', '资料', '文档']),
6
+ knowledge_resource_list: row('materials', 'Browse material / 浏览资料', 'Page authorized material without requiring a search phrase.', true, ['list', 'browse', '列出', '浏览', '资料', '文档']),
7
+ knowledge_resource_describe: row('materials', 'Inspect material / 查看资料信息', 'Inspect one exact ref and its access decision before reading.', true, ['describe', 'metadata', '详情', '元数据', '资料']),
8
+ knowledge_resource_read: row('materials', 'Read original / 读取原文', 'Read a bounded original or obtain its authorized download location.', true, ['read', 'original', 'download', '读取', '原文', '下载', '文件']),
9
+ knowledge_resource_capabilities: row('materials', 'Inspect representations / 查看可读表示', 'Inspect available content representations and access boundaries.', true, ['representation', 'capabilities', '表示', '格式', '能力']),
10
+ knowledge_resource_media: row('materials', 'Browse media / 查看图片附件', 'Page images, video and other media of an exact document.', true, ['media', 'image', 'video', '图片', '视频', '媒体', '附件']),
11
+ knowledge_resource_counts: row('materials', 'Count material / 统计资料', 'Read bounded resource counts with a continuation when needed.', true, ['count', 'statistics', '统计', '数量']),
12
+ knowledge_resource_window: row('materials', 'Capture sync window / 获取同步窗口', 'Capture the retained directory change window before synchronization.', true, ['sync', 'window', '同步', '窗口']),
13
+ knowledge_resource_changes: row('materials', 'Read changes / 查看资料变更', 'Page directory changes within the captured synchronization window.', true, ['changes', 'updates', '变更', '同步', '更新']),
14
+ knowledge_resource_index_status: row('runtime', 'Inspect search index / 查看索引状态', 'Inspect actual index coverage; registration does not imply searchable content.', true, ['index', 'searchable', '索引', '搜不到']),
15
+ tenant_material_submit: row('materials', 'Save original / 保存原件', 'Register author-provided bytes and provenance; does not collect websites.', false, ['save', 'upload', 'submit', '保存', '上传', '原件']),
16
+ tenant_artifact_draft: row('governance', 'Create draft / 创建草稿', 'Persist an authored draft with its explicit provenance.', false, ['draft', 'write', '草稿', '撰写']),
17
+ tenant_knowledge_compose: row('governance', 'Compose knowledge / 合成知识', 'Compose from exact retained source refs without asserting review approval.', false, ['compose', 'synthesize', '合成', '研究', '知识']),
18
+ tenant_knowledge_review: row('governance', 'Record review / 记录审核', 'Record an actual independent review and explicit decision.', false, ['review', 'audit', '审核', '审查']),
19
+ tenant_artifact_promote: row('governance', 'Advance lifecycle / 推进审核状态', 'Apply an explicit target status; approval requires its independent scope and review.', false, ['promote', 'approve', '审批', '批准', '状态']),
20
+ tenant_snapshot_declare: row('governance', 'Declare snapshot / 声明快照', 'Declare retained snapshot currentness without pretending to reobserve the source.', false, ['declare', 'freshness', '快照', '声明', '时效']),
21
+ tenant_snapshot_status: row('governance', 'Inspect snapshot / 查看快照状态', 'Read currentness of one exact retained resource.', true, ['snapshot', 'currentness', '快照', '时效', '新鲜度']),
22
+ tenant_workflow_plan: row('governance', 'Plan next step / 规划下一步', 'Use actual persisted facts to identify blockers, missing inputs and allowed next actions.', true, ['plan', 'next', 'blocker', '下一步', '怎么办', '规划', '阻塞', '发布']),
23
+ tenant_workflow_advance: row('governance', 'Execute plan action / 执行计划动作', 'Execute one explicitly chosen lifecycle action; never invent a PASS review.', false, ['advance', 'transition', '推进', '提交审核', '完成审核']),
24
+ tenant_knowledge_read: row('governance', 'Read reviewed knowledge / 读取受治理知识', 'Read reviewed Knowledge under the current consumer policy.', true, ['governed', 'reviewed', 'knowledge', '已审核', '知识', '受治理']),
25
+ tenant_material_share: row('sharing', 'Share material / 共享资料', 'Create an explicit authorized route to an exact resource.', false, ['share', 'route', '共享', '分享']),
26
+ tenant_material_revoke: row('sharing', 'Revoke sharing / 撤销共享', 'Revoke an existing sharing route explicitly.', false, ['revoke', 'unshare', '撤销', '取消共享']),
27
+ tenant_material_routes: row('sharing', 'Inspect sharing / 查看共享', 'Page the current Tenant sharing routes.', true, ['routes', 'sharing', '共享', '路由']),
28
+ tenant_source_define: row('sources', 'Define source / 定义来源', 'Register or revise explicit source semantics.', false, ['define', 'register', 'source', '定义', '登记', '来源']),
29
+ tenant_source_configure: row('sources', 'Configure source / 配置来源', 'Pause, enable or set an explicit refresh policy with the expected generation.', false, ['configure', 'pause', 'refresh', '配置', '暂停', '来源', '刷新']),
30
+ tenant_source_get: row('sources', 'Inspect source / 查看来源', 'Get an exact registered source and its current generation.', true, ['get', 'source', '查看', '来源']),
31
+ tenant_source_list: row('sources', 'List managed sources / 列出管理来源', 'Page source definitions under source management authority.', true, ['list', 'source', '列出', '来源']),
32
+ tenant_source_inventory: row('sources', 'Browse source inventory / 浏览来源清单', 'Page registered source metadata using read authority.', true, ['inventory', 'source', '来源', '清单']),
33
+ tenant_collection_request: row('collection', 'Request source collection / 请求采集来源', 'Persist an explicit manual collection request; Host policy still controls execution.', false, ['collect', 'request', '采集', '抓取', '手动']),
34
+ tenant_collection_status: row('collection', 'Inspect collection request / 查看采集请求', 'Read an exact request and its completion or failure metadata.', true, ['request', 'status', 'progress', '采集', '请求', '进度']),
35
+ tenant_work_queue: row('governance', 'Browse work queue / 查看待办', 'Page authorized research, draft, review and rework metadata.', true, ['queue', 'todo', 'work', '待办', '队列', '返工']),
36
+ tenant_project_list: row('projects', 'Browse projects / 浏览项目', 'Page explicit purpose contexts; Tenant owns the underlying material.', true, ['list', 'project', '项目', '列表']),
37
+ tenant_project_get: row('projects', 'Inspect project / 查看项目', 'Inspect purpose, scope and selection using separate continuations.', true, ['project', 'purpose', '项目', '目的', '范围']),
38
+ tenant_project_materials: row('projects', 'Browse project material / 查看项目资料', 'Page materials inside the explicitly selected project scope.', true, ['project', 'material', '项目', '资料']),
39
+ tenant_project_scope_set: row('projects', 'Set project scope / 修改项目范围', 'Change explicit material selection with an expected revision.', false, ['scope', 'selection', '范围', '选择']),
40
+ tenant_project_lifecycle_set: row('projects', 'Archive or restore project / 归档恢复项目', 'Apply explicit project lifecycle changes without deleting Tenant assets.', false, ['archive', 'restore', '归档', '恢复']),
41
+ tenant_runtime_status: row('runtime', 'Inspect runtime / 查看运行状态', 'Page authorized runtime metadata and actual execution blockers.', true, ['runtime', 'status', 'failure', '运行', '失败', '预算', '执行']),
42
+ tenant_web_collect_preview: row('collection', 'Preview website task / 预览网站采集', 'Validate a website collection intent and extraction plan before submission.', true, ['preview', 'website', 'web', '预览', '网站', '网页', '采集']),
43
+ tenant_web_collect_submit: row('collection', 'Submit website task / 提交网站采集', 'Submit an explicit bounded website task under current Host capabilities.', false, ['submit', 'website', 'crawl', '提交', '网站', '采集', '抓取']),
44
+ tenant_web_collect_status: row('collection', 'Inspect website task / 查看网站任务', 'Read lifecycle, acquisition, registration and requirements separately.', true, ['website', 'status', 'progress', '网站', '采集', '进度', '状态']),
45
+ tenant_web_collect_results: row('collection', 'Browse website results / 浏览网站结果', 'Page sealed result snapshots while preserving the exact job and window.', true, ['results', 'website', 'extraction', '结果', '网站', '提取']),
46
+ tenant_web_collect_cycles: row('collection', 'Browse observation cycles / 浏览观察周期', 'Page retained observation cycles of an exact website task.', true, ['cycles', 'observations', '周期', '观察']),
47
+ tenant_web_collect_control: row('collection', 'Pause resume or cancel task / 暂停恢复取消任务', 'Apply an explicit lifecycle control with the expected epoch.', false, ['pause', 'resume', 'cancel', '暂停', '恢复', '取消', '任务']),
48
+ tenant_web_collect_refresh: row('collection', 'Refresh website task / 刷新网站任务', 'Request one explicit new observation; replay cannot renew an old observation.', false, ['refresh', 'reobserve', '刷新', '重新观察']),
49
+ tenant_web_collect_read: row('collection', 'Read website result / 读取网站原件结果', 'Read an exact retained ORIGINAL, DOM or RESULT with separate read authority.', true, ['read', 'website', 'original', 'result', '读取', '网站', '原文', '结果']),
50
+ tenant_web_collect_index_status: row('runtime', 'Inspect website index / 查看网站索引', 'Read actual indexing status separately from successful acquisition.', true, ['website', 'index', '网站', '索引']),
51
+ };
52
+ export const agentCategoryTitles = { materials: 'Material discovery / 资料检索', sources: 'Source management / 来源管理', collection: 'Collection tasks / 采集任务', governance: 'Knowledge workflow / 知识工作流', sharing: 'Sharing / 共享', projects: 'Project purpose / 项目用途', runtime: 'Runtime diagnostics / 运行诊断' };
53
+ export function recallAgentCapabilities(query, names) {
54
+ const text = query.normalize('NFKC').toLowerCase(), tokens = [...new Set((text.match(/[a-z0-9_]+/g) ?? []).filter(t => t.length >= 3 && !['the', 'and', 'for', 'with', 'from', 'that', 'this', 'want', 'would', 'please'].includes(t)))];
55
+ return names.map(name => { const card = agentCatalog[name], terms = card.terms, score = name === text ? 1000 : terms.reduce((n, term) => n + (text.includes(term) ? 2 : 0), 0) + tokens.reduce((n, t) => n + ((card.description + ' ' + card.title).toLowerCase().includes(t) ? 1 : 0), 0); return { name, score }; }).filter(item => item.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).map(item => item.name);
56
+ }
@@ -0,0 +1,57 @@
1
+ /** Task-first presentation over existing contracts; discovery never grants authority. */
2
+ import { z } from 'zod';
3
+ import { materialTenantIdSchema, resourceToolSchemas, resourceDataSchemas, resourceEnvelopeSchema } from '../contracts/index.js';
4
+ import { tenantWebWorkflowWorkspaceToolSchemas, tenantWebWorkflowDataSchemas, tenantWebWorkflowToolSchemas, tenantWebWorkflowEnvelopeSchema, tenantWebWorkflowErrorSchema } from './tenant-web-workflow.js';
5
+ import { tenantRuntimeWorkspaceDataSchemas, tenantRuntimeEnvelopeSchema } from './tenant-runtime.js';
6
+ import { tenantManagementProviderBaseSchema } from './tenant-management.js';
7
+ import { bodySearchToolSchemas, bodySearchDataSchemas, bodySearchEnvelopeSchema } from './body-search.js';
8
+ import { agentGuidanceActionSchema, agentTaskInputSchema, agentTaskPlanSchema, agentInputHintSchema, agentGuidedFailureSchema } from './agent-guidance-shapes.js';
9
+ const { tenant_workflow_guide: legacyGuide, ...management } = tenantWebWorkflowWorkspaceToolSchemas;
10
+ export const agentCapabilitySchemas = { ...resourceToolSchemas, ...bodySearchToolSchemas, ...management };
11
+ export const agentCapabilityDataSchemas = { ...resourceDataSchemas, ...bodySearchDataSchemas, ...tenantRuntimeWorkspaceDataSchemas, ...tenantWebWorkflowDataSchemas };
12
+ // Keep the registry out of the entry schemas as well as tools/list. The predicate
13
+ // narrows only to registered names; JSON Schema advertises a bounded identifier.
14
+ export const agentCapabilityNameSchema = z.string().min(1).max(128).regex(/^(?:tenant_|knowledge_resource_)[a-z_]+$/).refine(name => Object.hasOwn(agentCapabilitySchemas, name), 'Discover an authorized capability first');
15
+ export const agentCategorySchema = z.enum(['materials', 'sources', 'collection', 'governance', 'sharing', 'projects', 'runtime']);
16
+ const args = z.record(z.string(), z.json()), cursor = z.string().min(1).max(4096);
17
+ export const agentEntryToolSchemas = {
18
+ knowledge_discover: z.object({ query: z.string().trim().min(1).max(500).optional(), category: agentCategorySchema.optional(), tool: agentCapabilityNameSchema.optional(), after: cursor.optional(), limit: z.number().int().min(1).max(8).default(5) }).strict().refine(v => !v.tool || !v.query && !v.category && !v.after),
19
+ knowledge_execute: z.object({ tool: agentCapabilityNameSchema, arguments: args.default({}) }).strict(),
20
+ };
21
+ export const agentNextActionSchema = agentGuidanceActionSchema;
22
+ const card = z.object({ name: agentCapabilityNameSchema, category: agentCategorySchema, title: z.string().max(256), description: z.string().max(1000), readOnly: z.boolean(), scopeAlternatives: z.array(z.array(z.string())).max(4) }).strict();
23
+ export const agentEntryDataSchemas = {
24
+ knowledge_discover: z.object({ mode: z.enum(['HOME', 'SEARCH', 'DETAIL']), categories: z.array(z.object({ id: agentCategorySchema, title: z.string(), count: z.number().int().nonnegative() }).strict()).max(7), items: z.array(card).max(8), detail: z.object({ name: agentCapabilityNameSchema, inputSchema: z.json(), outputSchema: z.json() }).strict().nullable(), nextAfter: cursor.nullable(), nextActions: z.array(agentNextActionSchema).max(16), guidance: z.string().max(1000) }).strict(),
25
+ knowledge_execute: z.object({ capability: agentCapabilityNameSchema, status: z.enum(['COMPLETED', 'NEEDS_INPUT']), result: z.json().nullable(), missingFields: z.array(z.string().max(256)).max(32), nextActions: z.array(agentNextActionSchema).max(16) }).strict(),
26
+ };
27
+ export function agentEntryEnvelopeSchema(tool) { return z.object({ schemaVersion: z.literal(2), tenantId: materialTenantIdSchema, tool: z.literal(tool), data: agentEntryDataSchemas[tool] }).strict(); }
28
+ // Preserve the original opt-in input schema for already installed v1 clients.
29
+ export const agentGuidedV1ToolSchemas = {
30
+ knowledge_discover: agentEntryToolSchemas.knowledge_discover.safeExtend({ arguments: args.optional(), task: agentTaskInputSchema.optional() }).refine(v => (!v.arguments || Boolean(v.tool)) && (!v.task || !v.query && !v.category && !v.tool && !v.after && !v.arguments)),
31
+ knowledge_execute: agentEntryToolSchemas.knowledge_execute,
32
+ };
33
+ export const agentGuidedV1InputJsonSchemas = Object.fromEntries(Object.entries(agentGuidedV1ToolSchemas).map(([n, s]) => [n, z.toJSONSchema(s, { target: 'draft-07' })]));
34
+ export const agentGuidedToolSchemas = {
35
+ knowledge_discover: agentEntryToolSchemas.knowledge_discover.safeExtend({ arguments: args.optional(), resupplyFields: z.array(z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,127}$/)).max(32).optional(), task: agentTaskInputSchema.optional() }).refine(v => (!v.arguments || Boolean(v.tool)) && (!v.resupplyFields || Boolean(v.tool) && v.resupplyFields.every(field => Object.hasOwn(agentCapabilitySchemas[v.tool].shape, field))) && (!v.task || !v.query && !v.category && !v.tool && !v.after && !v.arguments && !v.resupplyFields)),
36
+ knowledge_execute: agentEntryToolSchemas.knowledge_execute,
37
+ };
38
+ export const agentGuidedDataSchemas = {
39
+ knowledge_discover: agentEntryDataSchemas.knowledge_discover.omit({ mode: true }).extend({ mode: z.enum(['HOME', 'SEARCH', 'DETAIL', 'TASK']), inputHints: z.array(agentInputHintSchema).max(32), taskPlan: agentTaskPlanSchema.nullable() }).strict(),
40
+ knowledge_execute: agentEntryDataSchemas.knowledge_execute.extend({ inputHints: z.array(agentInputHintSchema).max(32) }).strict(),
41
+ };
42
+ export function agentGuidedEnvelopeSchema(tool) { return z.object({ schemaVersion: z.literal(2), tenantId: materialTenantIdSchema, tool: z.literal(tool), data: agentGuidedDataSchemas[tool] }).strict(); }
43
+ export const agentGuidedInputJsonSchemas = Object.fromEntries(Object.entries(agentGuidedToolSchemas).map(([n, s]) => [n, z.toJSONSchema(s, { target: 'draft-07' })]));
44
+ export const agentGuidedOutputJsonSchemas = Object.fromEntries(Object.keys(agentGuidedToolSchemas).map(n => [n, { type: 'object', oneOf: [z.toJSONSchema(agentGuidedEnvelopeSchema(n), { target: 'draft-07' }), z.toJSONSchema(agentGuidedFailureSchema, { target: 'draft-07' })] }]));
45
+ export function agentCapabilityEnvelopeSchema(name) {
46
+ if (Object.hasOwn(bodySearchToolSchemas, name))
47
+ return bodySearchEnvelopeSchema(name);
48
+ if (Object.hasOwn(resourceToolSchemas, name))
49
+ return resourceEnvelopeSchema.safeExtend({ data: resourceDataSchemas[name] });
50
+ if (Object.hasOwn(tenantWebWorkflowToolSchemas, name))
51
+ return tenantWebWorkflowEnvelopeSchema(name);
52
+ return tenantRuntimeEnvelopeSchema(name);
53
+ }
54
+ export const agentEntryInputJsonSchemas = Object.fromEntries(Object.entries(agentEntryToolSchemas).map(([n, s]) => [n, z.toJSONSchema(s, { target: 'draft-07' })]));
55
+ export function agentCapabilityDefinition(name) { return { name, inputSchema: z.toJSONSchema(agentCapabilitySchemas[name], { target: 'draft-07' }), outputSchema: z.toJSONSchema(agentCapabilityEnvelopeSchema(name), { target: 'draft-07' }) }; }
56
+ export const agentEntryOutputJsonSchemas = Object.fromEntries(Object.keys(agentEntryToolSchemas).map(n => [n, { type: 'object', oneOf: [z.toJSONSchema(agentEntryEnvelopeSchema(n), { target: 'draft-07' }), z.toJSONSchema(tenantWebWorkflowErrorSchema, { target: 'draft-07' })] }]));
57
+ export const agentEntryProviderSchema = tenantManagementProviderBaseSchema.extend({ schemaVersion: z.literal(26), collectionMode: z.literal('EXPLICIT_HOST_WITH_OPT_IN_DUE'), tools: z.tuple([z.literal('knowledge_discover'), z.literal('knowledge_execute')]), capabilities: z.tuple([z.literal('AGENT_TASK_DISCOVERY_V1')]) }).strict();
@@ -0,0 +1,12 @@
1
+ /** Optional presentation extension. No new execution authority or automatic actions. */
2
+ import { z } from 'zod';
3
+ import { stableUriSchema, projectKeySchema } from '../contracts/index.js';
4
+ import { tenantWebWorkflowErrorSchema } from './tenant-web-workflow.js';
5
+ export const agentGuidanceHeader = 'x-knowledge-agent-guidance', agentGuidanceFeature = 'task-guidance-v1', agentResupplyGuidanceFeature = 'task-guidance-v2';
6
+ export const agentGuidanceActionSchema = z.object({ id: z.string().min(1).max(128), title: z.string().min(1).max(256), tool: z.enum(['knowledge_discover', 'knowledge_execute']), arguments: z.record(z.string(), z.json()), canExecute: z.boolean(), automatic: z.literal(false), missingFields: z.array(z.string().max(256)).max(32), reasonCode: z.string().max(128) }).strict();
7
+ export const agentInputHintSchema = z.object({ path: z.string().min(1).max(256), kind: z.enum(['MISSING', 'INVALID', 'UNSUPPORTED', 'CONFLICT']), expected: z.string().max(256), prompt: z.string().max(600), source: z.enum(['USER_INPUT', 'CURRENT_STATE', 'PRIOR_RESULT', 'INDEPENDENT_REVIEW']), choices: z.array(z.json()).max(16), constraints: z.record(z.string(), z.json()) }).strict();
8
+ export const agentTaskContextSchema = z.object({ query: z.string().min(2).max(200).optional(), ref: stableUriSchema.optional(), jobRef: stableUriSchema.optional(), projectRef: stableUriSchema.optional(), projectKey: projectKeySchema.optional(), sourceId: z.string().min(1).max(128).optional(), requestId: z.string().min(1).max(128).optional(), recipientTenantId: z.string().min(1).max(256).optional(), sourceRefs: z.array(stableUriSchema).max(32).optional(), spec: z.record(z.string(), z.json()).optional(), extraction: z.record(z.string(), z.json()).optional(), citation: z.record(z.string(), z.json()).optional() }).strict();
9
+ export const agentTaskInputSchema = z.object({ goal: z.string().trim().min(1).max(1000), context: agentTaskContextSchema.default({}), inputs: z.record(z.string().regex(/^step-[1-6]$/), z.record(z.string(), z.json())).default({}) }).strict();
10
+ export const agentTaskPlanSchema = z.object({ template: z.enum(['RESEARCH_EXISTING', 'COLLECT_WEBSITE', 'INSPECT_COLLECTION', 'VERIFY_CITATION', 'PROJECT_RESEARCH', 'SHARE_MATERIAL', 'GOVERNANCE', 'UNRECOGNIZED']), status: z.literal('SUGGESTED_NOT_EXECUTED'), steps: z.array(z.object({ id: z.string().max(32), title: z.string().max(256), dependsOn: z.array(z.string().max(32)).max(6), capability: z.string().max(128).nullable(), state: z.enum(['READY', 'NEEDS_INPUT', 'NEEDS_PRIOR_RESULT', 'NOT_AUTHORIZED']), action: agentGuidanceActionSchema.nullable(), inputHints: z.array(agentInputHintSchema).max(32) }).strict()).max(6), guidance: z.string().max(1000) }).strict();
11
+ export const agentRecoverySchema = z.object({ reasonCode: z.enum(['INPUT_REQUIRED', 'CURSOR_RESTART_REQUIRED', 'STATE_CHANGED', 'INSPECT_PERSISTED_RESULT', 'SEMANTIC_PLAN_REQUIRED', 'AUTHORITY_REQUIRED', 'ACCESS_BLOCKED', 'OUTCOME_UNKNOWN', 'INSPECT_INPUT']), summary: z.string().max(1000), inputHints: z.array(agentInputHintSchema).max(32), nextActions: z.array(agentGuidanceActionSchema).max(16) }).strict();
12
+ export const agentGuidedFailureSchema = tenantWebWorkflowErrorSchema.extend({ guidance: agentRecoverySchema }).strict();
@@ -0,0 +1,104 @@
1
+ /** Derive bounded hints from the actual capability schema, never from raw validator messages. */
2
+ import { agentCapabilitySchemas, agentCapabilityDefinition } from './agent-entry.js';
3
+ import { agentInputHintSchema } from './agent-guidance-shapes.js';
4
+ const object = (v) => v && typeof v === 'object' && !Array.isArray(v) ? v : {};
5
+ const source = (path, name) => (/decision|reviewedAt/.test(path) || /review/.test(name) && /summary/.test(path)) ? 'INDEPENDENT_REVIEW' : /expected|generation|epoch|revision/i.test(path) ? 'CURRENT_STATE' : /ref|sourceRefs|jobRef|citation/i.test(path) ? 'PRIOR_RESULT' : 'USER_INPUT';
6
+ function prompt(path, origin) {
7
+ if (origin === 'INDEPENDENT_REVIEW')
8
+ return '由具备独立审核权限的调用者明确提供结论和依据,不预填 PASS。 Supply an explicit independent review.';
9
+ if (origin === 'CURRENT_STATE')
10
+ return '先读取准确目标的当前状态,再提供期望版本;不要猜测或自动重放写入。 Read the current exact target state first.';
11
+ if (origin === 'PRIOR_RESULT')
12
+ return '使用前一步实际返回或用户明确给出的准确引用、游标或结果,不构造占位身份。 Use an exact retained result.';
13
+ if (/requestId/.test(path))
14
+ return '明确提供本次意图的幂等 requestId;检查已有回执后再决定重试或创建新意图。';
15
+ if (/contentBase64/.test(path))
16
+ return '提供真实原始字节的规范 base64,遵守提交大小限制;不在提示中回显内容。';
17
+ return '补充或修正该字段,遵守准确 schema 的类型、枚举和范围。 Supply or correct this field using the exact schema.';
18
+ }
19
+ function variants(node) { const list = node.anyOf ?? node.oneOf; return [node, ...(Array.isArray(list) ? list : []).map(object)]; }
20
+ function child(node, key) {
21
+ for (const v of variants(node)) {
22
+ const next = typeof key === 'number' ? object(v.items) : object(object(v.properties)[key]);
23
+ if (Object.keys(next).length)
24
+ return next;
25
+ }
26
+ return {};
27
+ }
28
+ function locate(root, path) { let node = root, label = ''; for (const key of path) {
29
+ if (typeof key === 'symbol')
30
+ break;
31
+ const next = child(node, key);
32
+ if (!Object.keys(next).length)
33
+ return { node: root, label: 'arguments' };
34
+ label += typeof key === 'number' ? '[]' : (label ? '.' : '') + key;
35
+ node = next;
36
+ } return { node, label: label || 'arguments' }; }
37
+ function valueAt(raw, path) { let value = raw; for (const k of path) {
38
+ if (typeof k === 'symbol' || !value || typeof value !== 'object' || !Object.hasOwn(value, k))
39
+ return undefined;
40
+ value = value[String(k)];
41
+ } return value; }
42
+ export function agentInputHints(name, raw) {
43
+ const parsed = agentCapabilitySchemas[name].safeParse(raw);
44
+ if (parsed.success)
45
+ return [];
46
+ const root = object(agentCapabilityDefinition(name).inputSchema), hints = [], seen = new Set();
47
+ const add = (node, path, missing, depth = 0) => {
48
+ if (hints.length >= 32 || seen.has(path) || path.length > 256)
49
+ return;
50
+ const required = Array.isArray(node.required) ? node.required.filter((k) => typeof k === 'string') : [];
51
+ if (missing && required.length && depth < 5) {
52
+ const before = hints.length;
53
+ for (const key of required) {
54
+ const c = child(node, key);
55
+ if (c.default === undefined)
56
+ add(c, path === 'arguments' ? key : path + '.' + key, true, depth + 1);
57
+ }
58
+ if (hints.length > before)
59
+ return;
60
+ }
61
+ const origin = source(path, name), choices = variants(node).flatMap(v => Array.isArray(v.enum) ? v.enum : Object.hasOwn(v, 'const') ? [v.const] : []).filter(v => v === null || ['string', 'number', 'boolean'].includes(typeof v)).slice(0, 16);
62
+ const constraints = Object.fromEntries(['minimum', 'maximum', 'minLength', 'maxLength', 'minItems', 'maxItems', 'pattern'].filter(k => typeof node[k] === 'string' || typeof node[k] === 'number').map(k => [k, node[k]]));
63
+ seen.add(path);
64
+ hints.push(agentInputHintSchema.parse({ path, kind: missing ? 'MISSING' : 'INVALID', expected: typeof node.type === 'string' ? node.type : Array.isArray(node.type) ? node.type.join('|') : choices.length ? 'enum' : 'exact schema', prompt: prompt(path, origin), source: origin, choices, constraints }));
65
+ };
66
+ for (const issue of parsed.error.issues) {
67
+ const path = name === 'tenant_web_collect_read' && issue.code === 'custom' && !issue.path.length ? ['stateIndex'] : issue.path;
68
+ const located = locate(root, path);
69
+ add(located.node, located.label, valueAt(raw, path) === undefined);
70
+ }
71
+ return hints;
72
+ }
73
+ /** Keep only individually valid, declared, small fields in a completion suggestion. Payload bodies are resupplied explicitly. */
74
+ function hasSensitiveField(value, depth = 0) { if (depth > 12)
75
+ return true; if (!value || typeof value !== 'object')
76
+ return false; return Object.entries(value).some(([key, item]) => /contentBase64|credential|token|secret|password|authorization/i.test(key) || hasSensitiveField(item, depth + 1)); }
77
+ export function agentResumeArguments(name, raw) {
78
+ const shape = agentCapabilitySchemas[name].shape, result = {};
79
+ for (const [key, value] of Object.entries(object(raw))) {
80
+ if (!shape || !Object.hasOwn(shape, key) || /contentBase64|composition|summary|instruction|credential|token|secret/i.test(key))
81
+ continue;
82
+ const parsed = shape[key].safeParse(value);
83
+ if (parsed.success && parsed.data !== undefined && !hasSensitiveField(parsed.data) && JSON.stringify(parsed.data).length <= 8192)
84
+ result[key] = parsed.data;
85
+ }
86
+ return JSON.parse(JSON.stringify(result));
87
+ }
88
+ /** Explain invalid supplied fields and any payload fields intentionally omitted from suggestions. */
89
+ export function agentCompletionHints(name, raw, resupplyFields = []) {
90
+ const retained = agentResumeArguments(name, raw), hints = new Map();
91
+ for (const hint of [...agentInputHints(name, raw), ...agentInputHints(name, retained)])
92
+ if (!hints.has(hint.path))
93
+ hints.set(hint.path, hint);
94
+ const properties = object(object(agentCapabilityDefinition(name).inputSchema).properties);
95
+ for (const key of new Set([...Object.keys(object(raw)), ...resupplyFields])) {
96
+ if (!resupplyFields.includes(key) && object(raw)[key] === undefined || !Object.hasOwn(properties, key) || Object.hasOwn(retained, key) || hints.has(key))
97
+ continue;
98
+ const node = object(properties[key]), origin = source(key, name);
99
+ hints.set(key, agentInputHintSchema.parse({ path: key, kind: 'MISSING', expected: typeof node.type === 'string' ? node.type : 'exact schema', prompt: '该字段未进入续作建议;如仍为原意图,请显式重新提供。 ' + prompt(key, origin), source: origin, choices: [], constraints: {} }));
100
+ }
101
+ return [...hints.values()].slice(0, 32);
102
+ }
103
+ /** Carry only declared field names, never omitted values, across inspection hops. */
104
+ export function agentResupplyFields(name, raw) { const retained = agentResumeArguments(name, raw), properties = object(object(agentCapabilityDefinition(name).inputSchema).properties); return Object.keys(object(raw)).filter(key => object(raw)[key] !== undefined && Object.hasOwn(properties, key) && !Object.hasOwn(retained, key)).slice(0, 32); }
@@ -0,0 +1,16 @@
1
+ /** Explicit raw-material body search; existing descriptive search contracts stay frozen. */
2
+ import { z } from 'zod';
3
+ import { resourceDirectoryItemSchema, resourceEnvelopeSchema, tenantResourceRefSchema, sha256Schema, stableUriSchema } from '../contracts/index.js';
4
+ export const agentBodySearchHeader = 'x-knowledge-agent-capabilities';
5
+ export const agentBodySearchFeature = 'body-search-v1';
6
+ const context = { projectRef: stableUriSchema.optional() };
7
+ export const bodyCitationSchema = z.object({ ref: tenantResourceRefSchema, contentHash: sha256Schema, jsonPointer: z.string().max(2048).nullable(), startUtf16: z.number().int().nonnegative().safe(), endUtf16: z.number().int().positive().safe(), quote: z.string().min(1).max(1200) }).strict().refine(v => v.endUtf16 - v.startUtf16 === v.quote.length);
8
+ export const bodySearchToolSchemas = {
9
+ knowledge_resource_search_body: z.object({ ...context, query: z.string().trim().min(2).max(200), after: z.string().min(1).max(4096).optional(), limit: z.number().int().min(1).max(20).default(5), resourceType: z.string().regex(/^[a-z][a-z0-9._-]{0,63}$/).max(64).optional() }).strict(),
10
+ knowledge_resource_read_passage: z.object({ ...context, citation: bodyCitationSchema }).strict(),
11
+ };
12
+ export const bodySearchDataSchemas = {
13
+ knowledge_resource_search_body: z.object({ items: z.array(z.object({ item: resourceDirectoryItemSchema, citation: bodyCitationSchema }).strict()).max(20), nextAfter: z.string().max(4096).nullable(), coverage: z.literal('INDEXED_NATIVE_TEXT_PASSAGES_ONLY'), completeness: z.literal('NOT_ASSESSED'), semantics: z.literal('RAW_MATERIAL_NOT_GOVERNANCE_APPROVAL') }).strict(),
14
+ knowledge_resource_read_passage: z.object({ citation: bodyCitationSchema, verified: z.literal(true) }).strict(),
15
+ };
16
+ export function bodySearchEnvelopeSchema(name) { return resourceEnvelopeSchema.safeExtend({ data: bodySearchDataSchemas[name] }); }
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ import { idSchema, isoDateTimeSchema, richDocumentSchema, schemaVersionSchema, sha256Schema, stableUriSchema } from '../contracts/index.js';
3
+ import { referenceCorpusSummarySchema, referenceCurrentnessSchema, referenceRecordTypeSchema, referenceSelectorKeySchema, referenceSelectorValueSchema, servingCurrentnessSchema, servingDecisionSchema, servingEvidenceSummarySchema } from './core.js';
4
+ export const consumerProjectRefSchema = stableUriSchema.superRefine((value, ctx) => { try {
5
+ const u = new URL(value);
6
+ if (u.protocol !== 'knowledge-project:' || !/^\/tenant\/[^/]+$/.test(u.pathname))
7
+ ctx.addIssue({ code: 'custom', message: 'projectRef must be a tenant-bound Knowledge project' });
8
+ }
9
+ catch {
10
+ ctx.addIssue({ code: 'custom', message: 'Invalid projectRef' });
11
+ } });
12
+ export const consumerResourceTypeSchema = z.enum(['artifact', 'evidence', 'reference', 'media']);
13
+ export const consumerSearchInputSchema = z.object({ projectRef: consumerProjectRefSchema, query: z.string().trim().min(1).max(2000), limit: z.number().int().min(1).max(50).optional(), cursor: z.string().min(1).max(4096).optional() }).strict();
14
+ // OAuth 消费者按项目关系发现 Evidence;下一页 ref 只决定位置,不携带授权。
15
+ export const consumerEvidencePageInputSchema = z.object({ projectRef: consumerProjectRefSchema, sourceId: idSchema.optional(), after: stableUriSchema.optional(), limit: z.number().int().min(1).max(50).optional() }).strict();
16
+ export const consumerEvidencePageResponseSchema = z.object({ schemaVersion: schemaVersionSchema, projectRef: consumerProjectRefSchema, decision: servingDecisionSchema, items: z.array(z.object({ decision: servingDecisionSchema, fileDecision: servingDecisionSchema, contentDecision: servingDecisionSchema, evidence: servingEvidenceSummarySchema }).strict()).max(50), nextAfter: stableUriSchema.nullable() }).strict();
17
+ export const consumerReadInputSchema = z.object({ projectRef: consumerProjectRefSchema, ref: stableUriSchema }).strict();
18
+ export const consumerCurrentnessInputSchema = consumerReadInputSchema;
19
+ export const referenceLookupSelectorsSchema = z.record(referenceSelectorKeySchema, z.array(referenceSelectorValueSchema).min(1).max(32)).superRefine((value, ctx) => { const keys = Object.keys(value); if (keys.length > 32)
20
+ ctx.addIssue({ code: 'custom', message: 'Reference lookup selectors cannot exceed 32 keys' }); for (const key of keys) {
21
+ const values = value[key] ?? [];
22
+ if (new Set(values).size !== values.length)
23
+ ctx.addIssue({ code: 'custom', path: [key], message: 'Reference lookup selector values must be unique' });
24
+ } });
25
+ const referenceLookupFilterObjectSchema = z.object({ jurisdiction: z.string().trim().min(1).max(128).optional(), corpusKeys: z.array(z.string().trim().min(1).max(128)).min(1).max(32).optional(), recordTypes: z.array(referenceRecordTypeSchema).min(1).max(64).optional(), selectors: referenceLookupSelectorsSchema.optional(), effectiveAt: isoDateTimeSchema.optional(), limit: z.number().int().min(1).max(100).optional() }).strict();
26
+ const requireLookupFilter = (value, ctx) => { if (!value.jurisdiction && !value.corpusKeys?.length && !value.recordTypes?.length && !value.selectors && !value.effectiveAt)
27
+ ctx.addIssue({ code: 'custom', message: 'Reference lookup requires at least one structured filter' }); };
28
+ export const referenceLookupFilterSchema = referenceLookupFilterObjectSchema.superRefine(requireLookupFilter);
29
+ export const consumerReferenceLookupInputSchema = referenceLookupFilterObjectSchema.extend({ projectRef: consumerProjectRefSchema }).superRefine(requireLookupFilter);
30
+ export const consumerSearchItemSchema = z.object({
31
+ ref: stableUriSchema, resourceType: consumerResourceTypeSchema, title: z.string().max(1000).nullable(), summary: z.string().max(4000).nullable(), contentHash: sha256Schema,
32
+ sourceRefs: z.array(stableUriSchema).max(256), observedAt: isoDateTimeSchema.nullable(), validUntil: isoDateTimeSchema.nullable(), decision: servingDecisionSchema,
33
+ }).strict();
34
+ export const consumerSearchResponseSchema = z.object({ schemaVersion: schemaVersionSchema, projectRef: consumerProjectRefSchema, decision: servingDecisionSchema, searchCoverage: z.enum(['AUTHORIZED_METADATA_V1', 'AUTHORIZED_PRESENTATION_V1']), items: z.array(consumerSearchItemSchema).max(50), nextCursor: z.string().max(4096).nullable() }).strict();
35
+ export const consumerContentDescriptorSchema = z.object({ contentHash: sha256Schema, mediaType: z.string().trim().min(1).max(256), byteLength: z.number().int().nonnegative() }).strict();
36
+ export const consumerReadResponseSchema = z.object({
37
+ schemaVersion: schemaVersionSchema, projectRef: consumerProjectRefSchema, decision: servingDecisionSchema,
38
+ item: z.object({ ref: stableUriSchema, resourceType: consumerResourceTypeSchema, descriptor: consumerContentDescriptorSchema.nullable(), content: z.string().nullable(), encoding: z.enum(['utf8', 'base64']).nullable(), truncated: z.boolean(), continuation: z.object({ kind: z.literal('AUTHENTICATED_SERVING_CONTENT'), ref: stableUriSchema }).strict().nullable(), presentation: richDocumentSchema.nullable().optional() }).strict().nullable(),
39
+ }).strict();
40
+ export const consumerCurrentnessResponseSchema = z.object({
41
+ schemaVersion: schemaVersionSchema, projectRef: consumerProjectRefSchema, decision: servingDecisionSchema,
42
+ item: z.object({ ref: stableUriSchema, currentness: z.union([servingCurrentnessSchema, referenceCurrentnessSchema]) }).strict().nullable(),
43
+ }).strict();
44
+ export const consumerReferenceLookupItemSchema = z.object({ decision: servingDecisionSchema, summary: referenceCorpusSummarySchema, payloadDecision: servingDecisionSchema, payload: z.json().nullable() }).strict();
45
+ export const consumerReferenceLookupResponseSchema = z.object({ schemaVersion: schemaVersionSchema, projectRef: consumerProjectRefSchema, decision: servingDecisionSchema, lookupCoverage: z.literal('AUTHORIZED_REFERENCE_SELECTORS_V1'), items: z.array(consumerReferenceLookupItemSchema).max(100) }).strict();