@sema-agent/client-core 0.104.1 → 0.105.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +11 -4
  3. package/dist/adapt/arms.js +14 -6
  4. package/dist/adapter/activeRunSelfHeal.d.ts +1 -0
  5. package/dist/adapter/activeRunSelfHeal.js +10 -5
  6. package/dist/adapter/downstream/eventToSdkMessage.js +4 -4
  7. package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
  8. package/dist/adapter/downstream/wiringManifestView.js +25 -0
  9. package/dist/adapter/runStream.js +5 -1
  10. package/dist/cloudConfigWireCaps.js +3 -0
  11. package/dist/compensations.js +6 -6
  12. package/dist/coreValuePorts.d.ts +3 -1
  13. package/dist/coreValuePorts.js +4 -0
  14. package/dist/displayUntrusted.d.ts +4 -2
  15. package/dist/displayUntrusted.js +45 -22
  16. package/dist/engineGenerationPort.d.ts +11 -2
  17. package/dist/engineGenerationPort.js +63 -9
  18. package/dist/engineHealthFacts.d.ts +5 -0
  19. package/dist/engineHealthFacts.js +11 -0
  20. package/dist/eventLeg.d.ts +1 -1
  21. package/dist/eventLeg.js +23 -3
  22. package/dist/generated/engineFactTables.d.ts +1 -0
  23. package/dist/generated/engineFactTables.js +1 -0
  24. package/dist/generated/sdkFactTables.d.ts +2 -0
  25. package/dist/generated/sdkFactTables.js +9 -0
  26. package/dist/headlessReconnectWire.js +31 -4
  27. package/dist/hitl/askGateWire.js +26 -2
  28. package/dist/hitl/frameRouter.d.ts +4 -0
  29. package/dist/hitl/frameRouter.js +14 -0
  30. package/dist/hitl/gateIdentity.d.ts +1 -0
  31. package/dist/hitl/gateIdentity.js +3 -0
  32. package/dist/hitl/planReviewCard.d.ts +21 -0
  33. package/dist/hitl/planReviewCard.js +229 -0
  34. package/dist/hitl/planReviewWire.d.ts +38 -13
  35. package/dist/hitl/planReviewWire.js +355 -133
  36. package/dist/hostSettingsSource.d.ts +1 -1
  37. package/dist/hostSettingsSource.js +8 -4
  38. package/dist/hostTurnSettings.d.ts +1 -1
  39. package/dist/hostTurnSettings.js +29 -10
  40. package/dist/i18n/messages.en.d.ts +50 -0
  41. package/dist/i18n/messages.en.js +50 -0
  42. package/dist/i18n/messages.zh.js +51 -0
  43. package/dist/index.d.ts +15 -3
  44. package/dist/index.js +9 -1
  45. package/dist/mcpTransportKindsCapability.d.ts +18 -0
  46. package/dist/mcpTransportKindsCapability.js +75 -0
  47. package/dist/mcpWireCaps.d.ts +4 -1
  48. package/dist/mcpWireCaps.js +24 -13
  49. package/dist/notifications.d.ts +9 -0
  50. package/dist/notifications.js +11 -2
  51. package/dist/ownKey.d.ts +4 -0
  52. package/dist/ownKey.js +8 -0
  53. package/dist/permissionModeWriteCapability.d.ts +13 -0
  54. package/dist/permissionModeWriteCapability.js +35 -0
  55. package/dist/permissionsAllowGrantCapability.d.ts +3 -0
  56. package/dist/permissionsAllowGrantCapability.js +26 -3
  57. package/dist/permissionsDeclaration.d.ts +23 -1
  58. package/dist/permissionsDeclaration.js +60 -30
  59. package/dist/planReviewCardFacts.d.ts +116 -0
  60. package/dist/planReviewCardFacts.js +469 -0
  61. package/dist/reattachWindow.d.ts +8 -1
  62. package/dist/reattachWindow.js +44 -12
  63. package/dist/runFollow.d.ts +30 -0
  64. package/dist/runFollow.js +117 -0
  65. package/dist/runLedgerSnapshot.d.ts +20 -0
  66. package/dist/runLedgerSnapshot.js +272 -0
  67. package/dist/runPermissionModeSwitch.d.ts +57 -0
  68. package/dist/runPermissionModeSwitch.js +135 -0
  69. package/dist/runReattachPolicy.d.ts +13 -5
  70. package/dist/runReattachPolicy.js +58 -23
  71. package/dist/seam.d.ts +3 -0
  72. package/dist/seam.js +1 -1
  73. package/dist/seatContract.js +1 -0
  74. package/dist/sessionMemoryErase.d.ts +1 -1
  75. package/dist/sessionMemoryErase.js +5 -0
  76. package/dist/sessionMemoryStatus.d.ts +27 -1
  77. package/dist/sessionMemoryStatus.js +117 -0
  78. package/dist/sseIdleTriage.d.ts +3 -1
  79. package/dist/sseIdleTriage.js +19 -4
  80. package/dist/subagent/engineSubagentTail.js +2 -0
  81. package/dist/subagentContentStore.d.ts +3 -0
  82. package/dist/subagentContentStore.js +10 -1
  83. package/dist/turnFailurePresentation.d.ts +5 -2
  84. package/dist/turnFailurePresentation.js +12 -4
  85. package/docs/INTEGRATION-CLIENTS.md +493 -10
  86. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import type { McpServerSpec } from '@sema-agent/sdk';
2
2
  import type { McpEntryKeyIssue } from './generated/settingsSchemaTypes.js';
3
+ import { type McpTransportKindsReading } from './mcpTransportKindsCapability.js';
3
4
  import type { UiLanguage } from './env/uiLanguage.js';
4
5
  export type { McpServerSpec };
5
6
  export interface McpConfigLike {
@@ -16,6 +17,7 @@ export interface McpConfigLike {
16
17
  toolFaces?: unknown;
17
18
  toolAxes?: unknown;
18
19
  startupTimeoutMs?: number;
20
+ sessionMetaKey?: string;
19
21
  }
20
22
  export type McpTransportEntryLike = Pick<McpConfigLike, 'type' | 'command' | 'args' | 'env' | 'url' | 'headers' | 'principalHeader' | 'assertionHeader'>;
21
23
  export type McpTransportVerdict = {
@@ -72,7 +74,7 @@ export type McpAllowToolsVerdict = {
72
74
  readonly problem: string;
73
75
  };
74
76
  export declare function mcpAllowToolsVerdict(read: () => {} | null | undefined, keyPrefix?: string): McpAllowToolsVerdict;
75
- export type McpConfigDropReason = 'name_blank' | 'name_edge_whitespace' | 'name_collides_after_trim' | 'name_too_long' | 'name_reserved' | 'transport_invalid' | 'identity_headers_both_set' | 'tool_axes_invalid' | 'tool_faces_invalid' | 'elicitation_invalid' | 'allow_tools_invalid' | 'entry_not_object' | 'entry_unreadable';
77
+ export type McpConfigDropReason = 'name_blank' | 'name_edge_whitespace' | 'name_collides_after_trim' | 'name_too_long' | 'name_reserved' | 'transport_invalid' | 'identity_headers_both_set' | 'tool_axes_invalid' | 'tool_faces_invalid' | 'elicitation_invalid' | 'allow_tools_invalid' | 'entry_not_object' | 'entry_unreadable' | 'transport_not_advertised';
76
78
  export type McpServerNameUnusableReason = 'name_blank' | 'name_edge_whitespace' | 'name_collides_after_trim' | 'name_too_long' | 'name_reserved';
77
79
  export type McpServerNameVerdict = {
78
80
  readonly usable: true;
@@ -100,6 +102,7 @@ export interface McpConfigProjection {
100
102
  }
101
103
  export interface ProjectMcpConfigsOptions {
102
104
  readonly sourceLabel?: (serverName: string) => string | undefined;
105
+ readonly transportKinds?: McpTransportKindsReading;
103
106
  readonly lang?: UiLanguage;
104
107
  }
105
108
  export declare function projectMcpConfigs(servers: Record<string, McpConfigLike> | undefined, opts?: ProjectMcpConfigsOptions): McpConfigProjection;
@@ -4,14 +4,16 @@ import { appendOwnElement, guardedRead } from './ownKey.js';
4
4
  import { readMcpToolAxes, readToolFaces } from './coreValuePorts.js';
5
5
  import { capForDisplay } from './fleetTaskDesc.js';
6
6
  import { msg, renderMessage } from './i18n/translate.js';
7
+ import { mcpTransportNotAdvertised } from './mcpTransportKindsCapability.js';
7
8
  const _mcpServerSpecPin = true;
8
9
  void _mcpServerSpecPin;
9
- function transparentKeys(startupTimeoutMs, axes, faces, elicitation) {
10
+ function transparentKeys(startupTimeoutMs, axes, faces, elicitation, sessionMetaKey) {
10
11
  return {
11
12
  ...(faces !== undefined ? { toolFaces: faces } : {}),
12
13
  ...(axes !== undefined ? { toolAxes: axes } : {}),
13
14
  ...(elicitation !== undefined ? { elicitation } : {}),
14
15
  ...(startupTimeoutMs !== undefined ? { startupTimeoutMs } : {}),
16
+ ...(sessionMetaKey !== undefined ? { sessionMetaKey } : {}),
15
17
  };
16
18
  }
17
19
  export function readMcpHttpIdentityHeaders(transport) {
@@ -36,7 +38,7 @@ function entryShapeOf(c) {
36
38
  function readEntryOwnKeys(c) {
37
39
  try {
38
40
  const allowTools = c.allowTools;
39
- return { toolAxes: c.toolAxes, elicitation: c.elicitation, allowTools, startupTimeoutMs: c.startupTimeoutMs };
41
+ return { toolAxes: c.toolAxes, elicitation: c.elicitation, allowTools, startupTimeoutMs: c.startupTimeoutMs, sessionMetaKey: c.sessionMetaKey };
40
42
  }
41
43
  catch {
42
44
  return undefined;
@@ -153,7 +155,7 @@ function specOf(name, c, own, axes, faces, elicitation, allowTools) {
153
155
  name,
154
156
  transport: t.transport,
155
157
  ...(allowTools !== undefined ? { allowTools } : {}),
156
- ...transparentKeys(own.startupTimeoutMs, axes, faces, elicitation),
158
+ ...transparentKeys(own.startupTimeoutMs, axes, faces, elicitation, own.sessionMetaKey),
157
159
  };
158
160
  return { kind: 'ok', spec, lost: t.lost };
159
161
  }
@@ -305,7 +307,7 @@ const DROP_LABEL_SHOWN = 140;
305
307
  const DROP_PROBLEM_SHOWN = 300;
306
308
  const DROP_BAD_NAME_SHOWN = 160;
307
309
  const DEGRADED_LOST_SHOWN = 400;
308
- function dropNotice(name, reason, problem, opts, path) {
310
+ function dropNotice(name, reason, problem, opts, path, transportKind) {
309
311
  let label;
310
312
  try {
311
313
  label = opts?.sourceLabel?.(name);
@@ -332,15 +334,17 @@ function dropNotice(name, reason, problem, opts, path) {
332
334
  ? msg('config.mcp.identityHeadersBothSet', { name: quoted, where })
333
335
  : reason === 'transport_invalid'
334
336
  ? msg('config.mcp.transportInvalid', { name: quoted, where, path: capForDisplay(path ?? 'transport', DROP_NAME_SHOWN), problem: capForDisplay(problem ?? '', DROP_PROBLEM_SHOWN) })
335
- : reason === 'name_blank'
336
- ? msg('config.mcp.nameBlank', { name: badName(), where })
337
- : reason === 'name_collides_after_trim'
338
- ? msg('config.mcp.nameCollides', { name: badName(), where })
339
- : reason === 'name_too_long'
340
- ? msg('config.mcp.nameTooLong', { name: badName(), where })
341
- : reason === 'name_reserved'
342
- ? msg('config.mcp.nameReserved', { name: badName(), where })
343
- : msg('config.mcp.nameEdgeWhitespace', { name: badName(), where });
337
+ : reason === 'transport_not_advertised'
338
+ ? msg('config.mcp.transportNotAdvertised', { name: quoted, where, kind: capForDisplay(transportKind ?? '', DROP_NAME_SHOWN), supported: problem !== undefined && problem.length > 0 ? problem : renderMessage(msg('config.mcp.transportsNone'), opts?.lang) })
339
+ : reason === 'name_blank'
340
+ ? msg('config.mcp.nameBlank', { name: badName(), where })
341
+ : reason === 'name_collides_after_trim'
342
+ ? msg('config.mcp.nameCollides', { name: badName(), where })
343
+ : reason === 'name_too_long'
344
+ ? msg('config.mcp.nameTooLong', { name: badName(), where })
345
+ : reason === 'name_reserved'
346
+ ? msg('config.mcp.nameReserved', { name: badName(), where })
347
+ : msg('config.mcp.nameEdgeWhitespace', { name: badName(), where });
344
348
  return renderMessage(ref, opts?.lang);
345
349
  }
346
350
  function degradedNotice(name, lost, opts) {
@@ -381,6 +385,13 @@ export function projectMcpConfigs(servers, opts) {
381
385
  }
382
386
  const out = slotUnreadable ? { spec: null, entryUnreadable: true } : mcpConfigEntry(name, c);
383
387
  if (out.spec !== null) {
388
+ const kind = out.spec.transport.kind;
389
+ const supported = opts?.transportKinds === undefined ? undefined : mcpTransportNotAdvertised(opts.transportKinds, kind);
390
+ if (supported !== undefined) {
391
+ const listed = supported.join(', ');
392
+ dropped.push({ name, reason: 'transport_not_advertised', path: 'transport.kind', problem: listed, notice: dropNotice(name, 'transport_not_advertised', listed, opts, 'transport.kind', kind) });
393
+ continue;
394
+ }
384
395
  specs.push(out.spec);
385
396
  if (out.transportLost !== undefined)
386
397
  degraded.push({ name, lost: Object.freeze([...out.transportLost]), notice: degradedNotice(name, out.transportLost, opts) });
@@ -42,6 +42,14 @@ export declare function taskNotificationDedupKey(n: {
42
42
  seq?: number | string;
43
43
  taskType?: string;
44
44
  }): string;
45
+ export interface TaskNotificationRenderKeyInput {
46
+ taskId: string;
47
+ status: string;
48
+ seq?: number | string;
49
+ taskType?: string;
50
+ completionId?: string;
51
+ }
52
+ export declare function taskNotificationRenderKeys(n: TaskNotificationRenderKeyInput): readonly [selfKey: string, ...completionKey: string[]];
45
53
  export declare function taskNotificationDedupKeyFromWire(raw: Record<string, unknown>): string;
46
54
  export interface TranscriptNotificationSeeds {
47
55
  renderedKeys: string[];
@@ -120,6 +128,7 @@ export interface EngineWorkflowCompletion {
120
128
  runId: string;
121
129
  status: 'completed' | 'failed';
122
130
  summary: string;
131
+ completionId?: string;
123
132
  }
124
133
  export declare function enqueueBgChildNotification(raw: {
125
134
  taskId: string;
@@ -5,7 +5,7 @@ import { panelEndFact } from './panelReduce.js';
5
5
  import { isCoreTaskNotificationTerminal, isCoreTaskNotificationTerminalNotSuccess } from './terminalStatuses.js';
6
6
  import { cycleSeqValue } from './cycleOrder.js';
7
7
  import { CORE_ENGINE_AUTHORITY_ENVELOPE_TAGS } from './generated/engineFactTables.js';
8
- import { guardedRead, ownSnapshot, ownSnapshotRecord, ownSnapshotTyped, readOwnDataValue } from './ownKey.js';
8
+ import { guardedRead, ownSnapshot, ownSnapshotRecord, ownSnapshotTyped, readOwnDataValue, workflowCompletionExtrasOf } from './ownKey.js';
9
9
  function escapeXml(v) {
10
10
  return v.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
11
11
  }
@@ -244,6 +244,14 @@ export function taskNotificationDedupKey(n) {
244
244
  const lane = n.taskType === 'external' ? 'external:' : '';
245
245
  return `${lane}${n.taskId}:${n.status}${seq ? `:${seq}` : ''}`;
246
246
  }
247
+ const COMPLETION_KEY_PREFIX = 'completion:';
248
+ export function taskNotificationRenderKeys(n) {
249
+ const own = taskNotificationDedupKey(n);
250
+ const id = n.completionId;
251
+ if (n.taskType === 'external' || !isCoreTaskNotificationTerminal(n.status) || typeof id !== 'string' || id.length === 0)
252
+ return [own];
253
+ return [own, `${COMPLETION_KEY_PREFIX}${id}`];
254
+ }
247
255
  export function taskNotificationDedupKeyFromWire(raw) {
248
256
  const n = ownSnapshotRecord(raw) ?? {};
249
257
  return taskNotificationDedupKey({
@@ -830,6 +838,7 @@ function enqueueBgChild(n, identity) {
830
838
  ...identity,
831
839
  }));
832
840
  }
841
+ const completionIdOnly = (x) => (x.completionId !== undefined ? { completionId: x.completionId } : {});
833
842
  export function enqueueEngineWorkflowNotification(raw) {
834
843
  const c = ownSnapshotRecord(raw);
835
844
  if (c === undefined) {
@@ -843,7 +852,7 @@ export function enqueueEngineWorkflowNotification(raw) {
843
852
  }
844
853
  markRunNotified(c.runId);
845
854
  cardEnqueuedRunIds.add(c.runId);
846
- port()?.enqueuePendingNotification(taskNotificationQueueCommand({ taskId: c.runId, taskType: 'workflow', status: c.status, summary: c.summary }));
855
+ port()?.enqueuePendingNotification(taskNotificationQueueCommand({ taskId: c.runId, taskType: 'workflow', status: c.status, summary: c.summary, ...completionIdOnly(workflowCompletionExtrasOf(c)) }));
847
856
  }
848
857
  export function _resetEngineTaskNotificationForTest() {
849
858
  notifiedRunIds.clear();
package/dist/ownKey.d.ts CHANGED
@@ -71,3 +71,7 @@ export interface SampledRecord {
71
71
  export declare function ownSnapshotWhenTyped<V>(value: V, word: string): SampledJson;
72
72
  export declare function ownSnapshotTyped<T extends object>(value: T): T | undefined;
73
73
  export declare function ownSnapshotRecord<V>(value: V): SampledRecord | undefined;
74
+ export declare function workflowCompletionExtrasOf(c: object): {
75
+ completionId?: string;
76
+ diagnostics?: string;
77
+ };
package/dist/ownKey.js CHANGED
@@ -440,3 +440,11 @@ function sampleObject(src, depth, run) {
440
440
  run.path.delete(src);
441
441
  }
442
442
  }
443
+ export function workflowCompletionExtrasOf(c) {
444
+ const completionId = guardedRead(() => readOwnDataNonEmptyString(c, 'completionId'));
445
+ const diagnostics = guardedRead(() => readOwnDataNonEmptyString(c, 'diagnostics'));
446
+ return {
447
+ ...(completionId !== undefined ? { completionId } : {}),
448
+ ...(diagnostics !== undefined ? { diagnostics } : {}),
449
+ };
450
+ }
@@ -0,0 +1,13 @@
1
+ export type PermissionModeWriteReading = {
2
+ kind: 'unobserved';
3
+ } | {
4
+ kind: 'unreadable';
5
+ } | {
6
+ kind: 'not_reported';
7
+ } | {
8
+ kind: 'present';
9
+ value: boolean;
10
+ };
11
+ export declare function projectPermissionModeWriteCapability(caps: unknown): PermissionModeWriteReading | undefined;
12
+ export declare function observedPermissionModeWrite(baseUrl?: string | undefined): PermissionModeWriteReading;
13
+ export declare function permissionModeWriteAvailable(reading: PermissionModeWriteReading | undefined): boolean;
@@ -0,0 +1,35 @@
1
+ import { engineWireTargetBaseUrl } from './engineWireTarget.js';
2
+ import { createEngineCapReader } from './engineCapReader.js';
3
+ import { ownDataField, readOwnDataValue } from './ownKey.js';
4
+ export function projectPermissionModeWriteCapability(caps) {
5
+ try {
6
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
7
+ return undefined;
8
+ const v = ownDataField(caps, 'permissionModeWrite');
9
+ if (v === undefined)
10
+ return { kind: 'not_reported' };
11
+ return typeof v === 'boolean' ? { kind: 'present', value: v } : undefined;
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ }
17
+ const reader = createEngineCapReader({
18
+ name: 'permissionModeWrite',
19
+ project: projectPermissionModeWriteCapability,
20
+ });
21
+ export function observedPermissionModeWrite(baseUrl = engineWireTargetBaseUrl()) {
22
+ return reader.observed(baseUrl);
23
+ }
24
+ export function permissionModeWriteAvailable(reading) {
25
+ try {
26
+ if (reading === null || reading === undefined || typeof reading !== 'object' || Array.isArray(reading))
27
+ return false;
28
+ if (readOwnDataValue(reading, 'kind') !== 'present')
29
+ return false;
30
+ return readOwnDataValue(reading, 'value') === true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
@@ -1,3 +1,4 @@
1
+ export declare const RULE_LAYERS_CAP_LEAF: "layers";
1
2
  export type PermissionsAllowGrantReading = {
2
3
  kind: 'unobserved';
3
4
  } | {
@@ -8,10 +9,12 @@ export type PermissionsAllowGrantReading = {
8
9
  kind: 'present';
9
10
  grants: boolean;
10
11
  layer?: boolean;
12
+ layers?: boolean;
11
13
  };
12
14
  export declare function projectPermissionsAllowGrantCapability(caps: unknown): PermissionsAllowGrantReading | undefined;
13
15
  export declare function observedPermissionsAllowGrant(baseUrl?: string | undefined): PermissionsAllowGrantReading;
14
16
  export declare function permissionsAllowGrantAvailable(reading: PermissionsAllowGrantReading): boolean;
15
17
  export declare function allowWireForm(reading: PermissionsAllowGrantReading): 'layer' | 'grant' | 'off';
16
18
  export declare function allowRulesGate(reading: PermissionsAllowGrantReading, settingsRead: 'clean' | 'partial' | 'unreadable' | undefined): 'open' | 'capability_off' | 'settings_read_not_clean';
19
+ export declare function permissionRuleLayersOn(reading: PermissionsAllowGrantReading | undefined): boolean;
17
20
  export declare function permissionsAllowGrantDoctorDetail(reading: PermissionsAllowGrantReading, settingsRead?: 'clean' | 'partial' | 'unreadable'): string;
@@ -4,6 +4,7 @@ import { ownDataField } from './ownKey.js';
4
4
  import { hasCoreValuePort } from './coreValuePorts.js';
5
5
  const ALLOW_GRANT_CAP_PATH = ['settings', 'permissions', 'allowGrant'];
6
6
  const ALLOW_LAYER_CAP_LEAF = 'allowLayer';
7
+ export const RULE_LAYERS_CAP_LEAF = 'layers';
7
8
  export function projectPermissionsAllowGrantCapability(caps) {
8
9
  try {
9
10
  if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
@@ -18,9 +19,21 @@ export function projectPermissionsAllowGrantCapability(caps) {
18
19
  if (typeof v !== 'boolean')
19
20
  return undefined;
20
21
  const layer = ownDataField(node, ALLOW_LAYER_CAP_LEAF);
21
- if (layer === undefined)
22
- return { kind: 'present', grants: v };
23
- return typeof layer === 'boolean' ? { kind: 'present', grants: v, layer } : undefined;
22
+ if (layer !== undefined && typeof layer !== 'boolean')
23
+ return undefined;
24
+ let layers;
25
+ try {
26
+ layers = ownDataField(node, RULE_LAYERS_CAP_LEAF);
27
+ }
28
+ catch {
29
+ layers = undefined;
30
+ }
31
+ return {
32
+ kind: 'present',
33
+ grants: v,
34
+ ...(layer !== undefined ? { layer } : {}),
35
+ ...(typeof layers === 'boolean' ? { layers } : {}),
36
+ };
24
37
  }
25
38
  if (v === null || typeof v !== 'object' || Array.isArray(v))
26
39
  return undefined;
@@ -61,6 +74,16 @@ export function allowRulesGate(reading, settingsRead) {
61
74
  return 'capability_off';
62
75
  return settingsRead === 'clean' ? 'open' : 'settings_read_not_clean';
63
76
  }
77
+ export function permissionRuleLayersOn(reading) {
78
+ try {
79
+ if (reading === null || typeof reading !== 'object' || Array.isArray(reading))
80
+ return false;
81
+ return ownDataField(reading, 'kind') === 'present' && ownDataField(reading, RULE_LAYERS_CAP_LEAF) === true;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
64
87
  function ownReadingField(reading, key) {
65
88
  try {
66
89
  if (reading === null || typeof reading !== 'object' || Array.isArray(reading))
@@ -39,6 +39,24 @@ export interface PermissionsDeclarationBuckets {
39
39
  export interface PermissionsDeclaration {
40
40
  permissions?: PermissionsDeclarationBuckets;
41
41
  }
42
+ export type SettingsRuleLayerOrigin = keyof typeof SETTINGS_SOURCE_TIERS;
43
+ export interface TightenRuleContribution {
44
+ readonly rule: string;
45
+ readonly origin: WireRuleOrigin;
46
+ }
47
+ export interface LayeredTightenEntry {
48
+ readonly rule: string;
49
+ readonly layer: SettingsRuleLayerOrigin;
50
+ }
51
+ export type TightenWireEntry = string | LayeredTightenEntry;
52
+ export interface HostPermissionsBuckets {
53
+ deny?: TightenWireEntry[];
54
+ ask?: TightenWireEntry[];
55
+ allow?: string[];
56
+ }
57
+ export interface HostPermissionsDeclaration {
58
+ permissions?: HostPermissionsBuckets;
59
+ }
42
60
  export declare const PERMISSIONS_KEPT_REASONS: readonly ["allow_capability_off", "settings_read_not_clean", "empty_rule", "paren_in_tool_name", "retired_tool_name", "unprefixed_namespaced_name", "incomplete_protocol_name", "unminted_protocol_segment", "home_relative_path", "compile_refused", "content_form_not_grantable", "content_form_never_matches", "judge_failed", "duplicate", "voids_allow_list"];
43
61
  export type PermissionsKeptReason = (typeof PERMISSIONS_KEPT_REASONS)[number];
44
62
  export interface PermissionsKeptRule {
@@ -86,8 +104,12 @@ export interface AssembledRuleRow {
86
104
  readonly origin?: WireRuleOrigin;
87
105
  readonly fate: AssembledRuleFate;
88
106
  }
107
+ export interface LayeredTightenSources {
108
+ readonly deny: readonly TightenRuleContribution[];
109
+ readonly ask: readonly TightenRuleContribution[];
110
+ }
89
111
  export declare function permissionsDeclaration(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading): PermissionsDeclaration;
90
- export declare function projectPermissionsDeclaration(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading, projection: RulePortsProjection): PermissionsDeclaration;
112
+ export declare function projectPermissionsDeclaration(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading, projection: RulePortsProjection, layered?: LayeredTightenSources): HostPermissionsDeclaration;
91
113
  export declare function permissionsDeclarationReport(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading): PermissionsDeclarationReport;
92
114
  export declare function assembledRuleRows(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading): readonly AssembledRuleRow[];
93
115
  export interface PermissionsAllowKeptFacts {
@@ -244,45 +244,60 @@ function allowListVoiders(sent) {
244
244
  return 'unknown';
245
245
  }
246
246
  }
247
- function judgeBucket(raw, behavior, originOf, cli, settingsGate, allowForm = 'grant', dropWire, owned = [], serverVersion) {
247
+ function judgeBucket(raw, behavior, originOf, cli, settingsGate, allowForm = 'grant', dropWire, owned = [], serverVersion, layeredFrom) {
248
248
  const firstSlot = new Map();
249
249
  for (const s of raw)
250
250
  if (!firstSlot.has(s.rule))
251
251
  firstSlot.set(s.rule, s);
252
252
  const groups = WIRE_RULE_TRUST_ORDER.map(() => []);
253
253
  const rankOf = (origin) => WIRE_RULE_TRUST_ORDER.indexOf(origin);
254
- if (originOf !== undefined) {
255
- for (const [r, origin] of originOf) {
254
+ const pairs = layeredFrom !== undefined ? layeredFrom.map((c) => [c.rule, c.origin]) : originOf;
255
+ const known = new Set();
256
+ if (pairs !== undefined) {
257
+ for (const [r, origin] of pairs) {
256
258
  const rank = rankOf(origin);
257
- if (rank >= 0 && typeof r === 'string' && firstSlot.has(r))
259
+ if (rank >= 0 && typeof r === 'string' && firstSlot.has(r)) {
258
260
  appendOwnElement(groups[rank], r);
261
+ known.add(r);
262
+ }
259
263
  }
260
264
  }
261
- const unknownOrigin = [...firstSlot.keys()].filter((r) => originOf === undefined || rankOf(originOf.get(r)) < 0);
265
+ const unknownOrigin = [...firstSlot.keys()].filter((r) => !known.has(r));
262
266
  const fateOfRule = new Map();
263
267
  const sent = [];
268
+ const sentLayers = [];
269
+ const sentIds = [];
264
270
  const winnerOf = new Map();
265
271
  const cliFates = cli === undefined ? [] : [...cli.fates];
266
- const place = (slot, fate) => {
272
+ const layerOf = (origin) => layeredFrom !== undefined && origin !== undefined && Object.hasOwn(SETTINGS_SOURCE_TIERS, origin) ? origin : undefined;
273
+ const place = (slot, fate, origin) => {
267
274
  if (!fate.sent)
268
275
  return fate;
269
276
  const dropped = dropWire?.(fate.wire);
270
277
  if (dropped !== undefined)
271
278
  return kept(dropped);
272
- if (winnerOf.has(fate.wire))
279
+ const layer = layerOf(origin);
280
+ const id = layeredFrom === undefined ? fate.wire : JSON.stringify([layer ?? null, fate.wire]);
281
+ if (winnerOf.has(id))
273
282
  return kept('duplicate');
274
- winnerOf.set(fate.wire, slot);
283
+ winnerOf.set(id, slot);
275
284
  appendOwnElement(sent, fate.wire);
285
+ appendOwnElement(sentLayers, layer);
286
+ appendOwnElement(sentIds, id);
276
287
  return fate;
277
288
  };
278
289
  const appendGroup = (group, origin) => {
279
290
  const held = settingsGate !== undefined && !outsideSettingsFiles(origin);
280
291
  const fates = held ? group.map(() => kept(settingsGate)) : judgeList(group, behavior, allowForm, serverVersion);
281
- group.forEach((r, k) => fateOfRule.set(r, place(firstSlot.get(r), fates[k])));
292
+ group.forEach((r, k) => {
293
+ const f = place(firstSlot.get(r), fates[k], origin);
294
+ if (!fateOfRule.has(r))
295
+ fateOfRule.set(r, f);
296
+ });
282
297
  };
283
298
  WIRE_RULE_TRUST_ORDER.forEach((origin, rank) => {
284
299
  if (origin === 'cliArg' && cli !== undefined)
285
- cli.slots.forEach((s, k) => (cliFates[k] = place(s, cliFates[k])));
300
+ cli.slots.forEach((s, k) => (cliFates[k] = place(s, cliFates[k], 'cliArg')));
286
301
  appendGroup(groups[rank], origin);
287
302
  });
288
303
  const ownedFates = owned.map((fate, k) => place({ rule: fate.sent ? fate.wire : '', list: 'allow', at: -1 - k }, fate));
@@ -293,7 +308,7 @@ function judgeBucket(raw, behavior, originOf, cli, settingsGate, allowForm = 'gr
293
308
  return f;
294
309
  return f.sent || f.reason === 'duplicate' ? kept('duplicate') : f;
295
310
  });
296
- return { sent, fates: [...fates, ...cliFates], winnerOf, ownedFates };
311
+ return { sent, sentLayers, sentIds, fates: [...fates, ...cliFates], winnerOf, ownedFates };
297
312
  }
298
313
  function clientOwnedFate(server) {
299
314
  if (server.length === 0)
@@ -312,7 +327,7 @@ function ownField(obj, key, where) {
312
327
  throw malformed(`${WHO}: ${where}${key} 只在原型链上,不是自有字段 —— 拒绝读取(请交普通对象)。`);
313
328
  return undefined;
314
329
  }
315
- function ruleList(v, key) {
330
+ function ruleList(v, key, tighten = false) {
316
331
  if (v === undefined || v === null)
317
332
  return undefined;
318
333
  if (!Array.isArray(v))
@@ -326,6 +341,9 @@ function ruleList(v, key) {
326
341
  continue;
327
342
  }
328
343
  const r = v[i];
344
+ if (tighten && r !== null && typeof r === 'object') {
345
+ throw malformed(`${WHO}: ${key}[${i}] 是对象(带出处层的条目形)—— 本口只收规则串:收紧方向的条目不静默丢,拒绝装配(带层条目由宿主设置入口在能力位恰真时铸)。`);
346
+ }
329
347
  if (typeof r === 'string')
330
348
  appendOwnElement(out, r);
331
349
  }
@@ -348,17 +366,17 @@ function originMap(v, key) {
348
366
  return snap;
349
367
  }
350
368
  const slotsOf = (list, name) => (list ?? []).map((rule, at) => ({ rule, list: name, at }));
351
- function assemble(input, caps, projection) {
369
+ function assemble(input, caps, projection, layered) {
352
370
  const outer = activeRulePortsProjection;
353
371
  activeRulePortsProjection = projection;
354
372
  try {
355
- return assembleNow(input, caps);
373
+ return assembleNow(input, caps, layered);
356
374
  }
357
375
  finally {
358
376
  activeRulePortsProjection = outer;
359
377
  }
360
378
  }
361
- function assembleNow(input, caps) {
379
+ function assembleNow(input, caps, layered) {
362
380
  let read;
363
381
  try {
364
382
  read = readAssemblyInput(input, caps);
@@ -367,7 +385,7 @@ function assembleNow(input, caps) {
367
385
  throw asAssemblyRefusal(e, 'input_malformed', WHO);
368
386
  }
369
387
  try {
370
- return judgeAssembly(read, caps);
388
+ return judgeAssembly(read, caps, layered);
371
389
  }
372
390
  catch (e) {
373
391
  throw asAssemblyRefusal(e, 'judge_unanswerable', WHO);
@@ -377,10 +395,10 @@ function readAssemblyInput(input, caps) {
377
395
  if (input === null || typeof input !== 'object' || Array.isArray(input)) {
378
396
  throw malformed(`${WHO}: 入参必须是对象 —— 收到 ${input === null ? 'null' : Array.isArray(input) ? 'array' : `typeof=${typeof input}`}。`);
379
397
  }
380
- const deny = ruleList(ownField(input, 'deny', ''), 'deny');
381
- const ask = ruleList(ownField(input, 'ask', ''), 'ask');
398
+ const deny = ruleList(ownField(input, 'deny', ''), 'deny', true);
399
+ const ask = ruleList(ownField(input, 'ask', ''), 'ask', true);
382
400
  const allow = ruleList(ownField(input, 'allow', ''), 'allow');
383
- const cliDeny = ruleList(ownField(input, 'cliDeny', ''), 'cliDeny');
401
+ const cliDeny = ruleList(ownField(input, 'cliDeny', ''), 'cliDeny', true);
384
402
  const ownedServers = [...new Set(ruleList(ownField(input, 'clientOwnedServers', ''), 'clientOwnedServers') ?? [])];
385
403
  const originsRaw = ownField(input, 'origins', '');
386
404
  let origins = {};
@@ -398,19 +416,19 @@ function readAssemblyInput(input, caps) {
398
416
  const serverVersion = typeof versionRaw === 'string' ? versionRaw : undefined;
399
417
  return { deny, ask, allow, cliDeny, ownedServers, origins, gate, serverVersion };
400
418
  }
401
- function judgeAssembly(read, caps) {
419
+ function judgeAssembly(read, caps, layered) {
402
420
  const { deny, ask, allow, cliDeny, ownedServers, origins, gate, serverVersion } = read;
403
421
  const cliSlots = slotsOf(cliDeny, 'cliDeny');
404
422
  const cli = cliDeny === undefined ? undefined : { slots: cliSlots, fates: judgeList(cliDeny, 'deny', 'grant', serverVersion) };
405
423
  const denySlots = slotsOf(deny, 'deny');
406
424
  const askSlots = slotsOf(ask, 'ask');
407
425
  const allowSlots = slotsOf(allow, 'allow');
408
- const denyJ = judgeBucket(denySlots, 'deny', origins.deny, cli, undefined, 'grant', undefined, [], serverVersion);
409
- const askJ = judgeBucket(askSlots, 'ask', origins.ask, undefined, undefined, 'grant', undefined, [], serverVersion);
426
+ const denyJ = judgeBucket(denySlots, 'deny', origins.deny, cli, undefined, 'grant', undefined, [], serverVersion, layered?.deny);
427
+ const askJ = judgeBucket(askSlots, 'ask', origins.ask, undefined, undefined, 'grant', undefined, [], serverVersion, layered?.ask);
410
428
  const allowForm = allowWireForm(caps) === 'layer' ? 'layer' : 'grant';
411
429
  const settingsGate = gate === 'settings_read_not_clean' ? 'settings_read_not_clean' : undefined;
412
430
  let allowJ = gate === 'capability_off'
413
- ? { sent: [], fates: allowSlots.map(() => kept('allow_capability_off')), winnerOf: new Map(), ownedFates: ownedServers.map(() => kept('allow_capability_off')) }
431
+ ? { sent: [], sentLayers: [], sentIds: [], fates: allowSlots.map(() => kept('allow_capability_off')), winnerOf: new Map(), ownedFates: ownedServers.map(() => kept('allow_capability_off')) }
414
432
  : judgeBucket(allowSlots, 'allow', origins.allow, undefined, settingsGate, allowForm, undefined, ownedServers.map(clientOwnedFate));
415
433
  if (gate !== 'capability_off' && allowForm === 'layer' && allowJ.sent.length > 0) {
416
434
  const voiders = allowListVoiders(allowJ.sent);
@@ -426,6 +444,17 @@ function judgeAssembly(read, caps) {
426
444
  buckets.ask = [...askJ.sent];
427
445
  if (allowJ.sent.length > 0)
428
446
  buckets.allow = [...allowJ.sent];
447
+ const entriesOf = (j) => j.sent.map((wire, i) => {
448
+ const layer = j.sentLayers[i];
449
+ return layer === undefined ? wire : Object.freeze({ rule: wire, layer });
450
+ });
451
+ const wireBuckets = {};
452
+ if (denyJ.sent.length > 0)
453
+ wireBuckets.deny = entriesOf(denyJ);
454
+ if (askJ.sent.length > 0)
455
+ wireBuckets.ask = entriesOf(askJ);
456
+ if (allowJ.sent.length > 0)
457
+ wireBuckets.allow = [...allowJ.sent];
429
458
  const keptRows = [];
430
459
  const rewritten = [];
431
460
  const ruleRows = [];
@@ -458,21 +487,22 @@ function judgeAssembly(read, caps) {
458
487
  });
459
488
  }
460
489
  });
461
- for (const wire of j.sent) {
462
- const winner = j.winnerOf.get(wire);
490
+ j.sent.forEach((wire, i) => {
491
+ const winner = j.winnerOf.get(j.sentIds[i]);
463
492
  if (winner.at < 0)
464
- continue;
493
+ return;
465
494
  if (winner.rule !== wire)
466
495
  appendOwnElement(rewritten, { from: winner.rule, to: wire, bucket });
467
- }
496
+ });
468
497
  }
469
- return { buckets, report: { kept: keptRows, rewritten, ...(clientOwned.length > 0 ? { clientOwned } : {}) }, rows: ruleRows };
498
+ return { buckets, wireBuckets, report: { kept: keptRows, rewritten, ...(clientOwned.length > 0 ? { clientOwned } : {}) }, rows: ruleRows };
470
499
  }
471
500
  export function permissionsDeclaration(input, caps) {
472
501
  return declarationOf(assemble(input, caps));
473
502
  }
474
- export function projectPermissionsDeclaration(input, caps, projection) {
475
- return declarationOf(assemble(input, caps, projection));
503
+ export function projectPermissionsDeclaration(input, caps, projection, layered) {
504
+ const { wireBuckets } = assemble(input, caps, projection, layered);
505
+ return Object.keys(wireBuckets).length > 0 ? { permissions: wireBuckets } : {};
476
506
  }
477
507
  function declarationOf({ buckets }) {
478
508
  return Object.keys(buckets).length > 0 ? { permissions: buckets } : {};