@sema-agent/client-core 0.90.0 → 0.91.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 (88) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +24 -18
  3. package/dist/adapt/arms.js +9 -0
  4. package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
  5. package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
  6. package/dist/adapter/downstream/wiringManifestView.js +16 -0
  7. package/dist/adapter/runStream.js +37 -20
  8. package/dist/askSurvivesStandingAnswer.d.ts +10 -0
  9. package/dist/askSurvivesStandingAnswer.js +40 -0
  10. package/dist/attachmentSeatCapability.d.ts +11 -0
  11. package/dist/attachmentSeatCapability.js +27 -0
  12. package/dist/attachmentsWireCaps.d.ts +9 -0
  13. package/dist/attachmentsWireCaps.js +74 -21
  14. package/dist/compensations.js +10 -14
  15. package/dist/controlRouter.d.ts +9 -2
  16. package/dist/controlRouter.js +66 -28
  17. package/dist/decideFailureNote.js +4 -2
  18. package/dist/disableSkillsWire.d.ts +1 -0
  19. package/dist/disableSkillsWire.js +8 -1
  20. package/dist/displayUntrusted.js +153 -4
  21. package/dist/engineCapsCache.d.ts +3 -1
  22. package/dist/engineCapsCache.js +56 -6
  23. package/dist/engineClosedSets.d.ts +2 -0
  24. package/dist/engineClosedSets.js +3 -0
  25. package/dist/engineErrorCodes.d.ts +3 -0
  26. package/dist/engineErrorCodes.js +10 -0
  27. package/dist/generated/engineFactTables.d.ts +12 -0
  28. package/dist/generated/engineFactTables.js +47 -0
  29. package/dist/hitl/askGateWire.d.ts +1 -1
  30. package/dist/hitl/askGateWire.js +1 -1
  31. package/dist/hitl/crashConverged.d.ts +7 -0
  32. package/dist/hitl/crashConverged.js +57 -0
  33. package/dist/hitl/crashConvergedNotice.d.ts +4 -0
  34. package/dist/hitl/crashConvergedNotice.js +57 -0
  35. package/dist/hitl/hitlBridge.d.ts +7 -4
  36. package/dist/hitl/parkResolver.d.ts +7 -2
  37. package/dist/hitl/parkResolver.js +19 -7
  38. package/dist/hitl/planReviewWire.d.ts +1 -1
  39. package/dist/hitl/planReviewWire.js +18 -4
  40. package/dist/hitl/sessionPolicyWire.d.ts +5 -0
  41. package/dist/hitl/sessionPolicyWire.js +63 -0
  42. package/dist/hitl/suspendedReopen.js +2 -2
  43. package/dist/hitl/toolApprovalWire.d.ts +4 -1
  44. package/dist/hitl/toolApprovalWire.js +20 -0
  45. package/dist/hookMatcherVerdict.d.ts +29 -0
  46. package/dist/hookMatcherVerdict.js +265 -0
  47. package/dist/humanTurn.d.ts +91 -0
  48. package/dist/humanTurn.js +253 -0
  49. package/dist/humanTurnRead.d.ts +105 -0
  50. package/dist/humanTurnRead.js +331 -0
  51. package/dist/index.d.ts +25 -8
  52. package/dist/index.js +16 -6
  53. package/dist/inheritEnvWire.js +1 -1
  54. package/dist/liveModelCatalog.d.ts +2 -0
  55. package/dist/liveModelCatalog.js +51 -3
  56. package/dist/permissionRuleIssue.d.ts +1 -1
  57. package/dist/permissionRuleIssue.js +22 -3
  58. package/dist/permissionsAllowGrantCapability.d.ts +2 -1
  59. package/dist/permissionsAllowGrantCapability.js +11 -2
  60. package/dist/permissionsDeclaration.d.ts +23 -0
  61. package/dist/permissionsDeclaration.js +133 -53
  62. package/dist/promptDelivery.d.ts +11 -3
  63. package/dist/promptDelivery.js +164 -29
  64. package/dist/providerRejection.d.ts +1 -0
  65. package/dist/providerRejection.js +20 -0
  66. package/dist/readDenyTiers.d.ts +10 -0
  67. package/dist/readDenyTiers.js +29 -0
  68. package/dist/request/taskRequest.js +2 -2
  69. package/dist/resumeRefusalCopy.d.ts +11 -0
  70. package/dist/resumeRefusalCopy.js +61 -4
  71. package/dist/rewindArchiveCapability.js +4 -4
  72. package/dist/rewindWireCaps.d.ts +2 -1
  73. package/dist/rewindWireCaps.js +19 -2
  74. package/dist/seam.d.ts +2 -0
  75. package/dist/taskRequestWords.d.ts +7 -1
  76. package/dist/taskRequestWords.js +18 -8
  77. package/dist/toolHistoryBadTurn.d.ts +1 -0
  78. package/dist/toolHistoryBadTurn.js +89 -0
  79. package/dist/toolHistoryMismatch.d.ts +2 -1
  80. package/dist/toolHistoryMismatch.js +19 -6
  81. package/dist/userAttachmentEnvelope.d.ts +61 -0
  82. package/dist/userAttachmentEnvelope.js +204 -0
  83. package/dist/wireFailureShape.d.ts +1 -0
  84. package/dist/wireFailureShape.js +4 -0
  85. package/dist/wireRefusalCopy.d.ts +6 -0
  86. package/dist/wireRefusalCopy.js +26 -3
  87. package/docs/INTEGRATION-CLIENTS.md +875 -14
  88. package/package.json +2 -2
@@ -1,3 +1,3 @@
1
1
  export declare const PERMISSION_RULE_ISSUE_CODES: readonly string[];
2
2
  export declare const RETIRED_PERMISSION_RULE_ISSUE_CODES: readonly string[];
3
- export declare function permissionRuleIssueDetail(code: unknown, message: unknown): string;
3
+ export declare function permissionRuleIssueDetail(code: unknown, message: unknown, rule?: string): string;
@@ -21,7 +21,7 @@ const ISSUE_MESSAGE_MAX = 200;
21
21
  const ISSUE_SENTENCES = Object.freeze({
22
22
  'invalid.empty_tool': 'this rule is invalid: it names no tool',
23
23
  'invalid.paren': 'this rule is invalid: its parentheses do not parse (escape a literal one as \\( or \\))',
24
- 'invalid.param_split': 'this rule is invalid: the part before the colon is not a parameter name',
24
+ 'invalid.param_split': 'this rule is invalid: its parentheses do not hold a parameter rule written name:pattern',
25
25
  'invalid.allow_param_rule': 'this rule is invalid: an allow rule cannot carry a parameter pattern — only bare tool rules auto-allow',
26
26
  'invalid.primary_field': "this rule is invalid: it patterns the tool's primary field, which matches the whole trimmed string rather than a prefix",
27
27
  'invalid.cap_exceeded': 'this rule set is invalid: it is over one of the deployment caps (rule count, rule length, pattern length, or star count)',
@@ -35,8 +35,27 @@ const ISSUE_SENTENCES = Object.freeze({
35
35
  'unsupported.mcp_paren': 'a parenthesised parameter rule is not supported on an MCP tool name',
36
36
  'unsupported.covering_paren': 'a parenthesised parameter rule is not supported on a covering protocol name — the parameter grammar is keyed by an exact tool name, so no mounted tool could ever bear it',
37
37
  });
38
- export function permissionRuleIssueDetail(code, message) {
39
- const known = typeof code === 'string' && Object.hasOwn(ISSUE_SENTENCES, code) ? ISSUE_SENTENCES[code] : undefined;
38
+ const PARAM_SPLIT_WITH_COLON = 'this rule is invalid: a parameter rule is written name:pattern, and the name before the colon or the pattern after it is empty';
39
+ const PARAM_SPLIT_NO_COLON = 'this rule is invalid: there is no colon inside the parentheses, so this is not a parameter rule (a parameter rule is written name:pattern)';
40
+ function colonInsideParens(rule) {
41
+ if (typeof rule !== 'string')
42
+ return undefined;
43
+ for (let i = 0; i < rule.length; i += 1) {
44
+ const ch = rule[i];
45
+ if (ch === '\\') {
46
+ i += 1;
47
+ continue;
48
+ }
49
+ if (ch === '(')
50
+ return rule.indexOf(':', i + 1) !== -1;
51
+ }
52
+ return undefined;
53
+ }
54
+ export function permissionRuleIssueDetail(code, message, rule) {
55
+ const colon = code === 'invalid.param_split' ? colonInsideParens(rule) : undefined;
56
+ const known = colon !== undefined
57
+ ? colon ? PARAM_SPLIT_WITH_COLON : PARAM_SPLIT_NO_COLON
58
+ : typeof code === 'string' && Object.hasOwn(ISSUE_SENTENCES, code) ? ISSUE_SENTENCES[code] : undefined;
40
59
  const text = typeof message === 'string' && message.length > 0
41
60
  ? capForDisplay(message, ISSUE_MESSAGE_MAX)
42
61
  : '';
@@ -12,6 +12,7 @@ export declare function noteEngineCapsForPermissionsAllowGrant(baseUrl: string,
12
12
  }): void;
13
13
  export declare function observedPermissionsAllowGrant(baseUrl?: string | undefined): PermissionsAllowGrantReading;
14
14
  export declare function permissionsAllowGrantAvailable(reading: PermissionsAllowGrantReading): boolean;
15
- export declare function permissionsAllowGrantDoctorDetail(reading: PermissionsAllowGrantReading): string;
15
+ export declare function allowRulesGate(reading: PermissionsAllowGrantReading, settingsRead: 'clean' | 'partial' | 'unreadable' | undefined): 'open' | 'capability_off' | 'settings_read_not_clean';
16
+ export declare function permissionsAllowGrantDoctorDetail(reading: PermissionsAllowGrantReading, settingsRead?: 'clean' | 'partial' | 'unreadable'): string;
16
17
  export declare function forgetPermissionsAllowGrantReading(baseUrl: string | undefined): void;
17
18
  export declare function __resetPermissionsAllowGrantReadingsForTests(): void;
@@ -44,6 +44,11 @@ export function permissionsAllowGrantAvailable(reading) {
44
44
  return false;
45
45
  }
46
46
  }
47
+ export function allowRulesGate(reading, settingsRead) {
48
+ if (!permissionsAllowGrantAvailable(reading))
49
+ return 'capability_off';
50
+ return settingsRead === 'clean' ? 'open' : 'settings_read_not_clean';
51
+ }
47
52
  function ownReadingField(reading, key) {
48
53
  try {
49
54
  if (reading === null || typeof reading !== 'object' || Array.isArray(reading))
@@ -54,10 +59,14 @@ function ownReadingField(reading, key) {
54
59
  return undefined;
55
60
  }
56
61
  }
57
- export function permissionsAllowGrantDoctorDetail(reading) {
58
- if (permissionsAllowGrantAvailable(reading)) {
62
+ export function permissionsAllowGrantDoctorDetail(reading, settingsRead) {
63
+ const gate = allowRulesGate(reading, settingsRead);
64
+ if (gate === 'open') {
59
65
  return 'allow rules sent to the engine — it reads them as rules, not as an exclusive list: a matching call counts as answered for, and the engine still raises its own prompts where it requires them';
60
66
  }
67
+ if (gate === 'settings_read_not_clean') {
68
+ return 'allow rules from settings files kept on this side — this engine reads allow rules as rules, but the settings read that produced them was not reported clean (a settings file was unreadable or had a problem, or no read health was given), so they are not sent; command-line and session allow rules are still sent, and matching approvals are still answered here';
69
+ }
61
70
  const kind = ownReadingField(reading, 'kind');
62
71
  if (kind === 'present' && ownReadingField(reading, 'grants') === false) {
63
72
  return 'allow rules kept on this side — this engine says it does not read allow rules as rules of that kind, so they are not sent and matching approvals are still answered here';
@@ -12,6 +12,7 @@ export interface PermissionsDeclarationInput {
12
12
  readonly allow: readonly string[];
13
13
  readonly origins?: WireRuleOrigins;
14
14
  readonly cliDeny?: readonly string[];
15
+ readonly settingsRead: 'clean' | 'partial' | 'unreadable';
15
16
  }
16
17
  export interface PermissionsDeclarationBuckets {
17
18
  deny?: string[];
@@ -21,5 +22,27 @@ export interface PermissionsDeclarationBuckets {
21
22
  export interface PermissionsDeclaration {
22
23
  permissions?: PermissionsDeclarationBuckets;
23
24
  }
25
+ 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", "home_relative_path", "compile_refused", "content_form_not_grantable", "judge_failed", "duplicate"];
26
+ export type PermissionsKeptReason = (typeof PERMISSIONS_KEPT_REASONS)[number];
27
+ export interface PermissionsKeptRule {
28
+ rule: string;
29
+ bucket: 'deny' | 'ask' | 'allow';
30
+ reason: PermissionsKeptReason;
31
+ }
32
+ export interface PermissionsRewrittenRule {
33
+ from: string;
34
+ to: string;
35
+ bucket: 'deny' | 'ask' | 'allow';
36
+ }
37
+ export interface PermissionsUncheckedRule {
38
+ rule: string;
39
+ bucket: 'deny' | 'ask';
40
+ }
41
+ export interface PermissionsDeclarationReport extends PermissionsDeclaration {
42
+ kept: PermissionsKeptRule[];
43
+ rewritten: PermissionsRewrittenRule[];
44
+ unchecked: PermissionsUncheckedRule[];
45
+ }
24
46
  export declare function permissionsDeclaration(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading): PermissionsDeclaration;
47
+ export declare function permissionsDeclarationReport(input: PermissionsDeclarationInput, caps: PermissionsAllowGrantReading): PermissionsDeclarationReport;
25
48
  export {};
@@ -1,5 +1,5 @@
1
1
  import { hasCoreValuePort, protocolOf, retiredToolNames, ruleToolGrammarOf, validatePermissionRules } from './coreValuePorts.js';
2
- import { permissionsAllowGrantAvailable } from './permissionsAllowGrantCapability.js';
2
+ import { allowRulesGate } from './permissionsAllowGrantCapability.js';
3
3
  const RULE_TRUST_ORDER = [
4
4
  'policySettings',
5
5
  'cliArg',
@@ -9,6 +9,20 @@ const RULE_TRUST_ORDER = [
9
9
  'localSettings',
10
10
  'projectSettings',
11
11
  ];
12
+ export const PERMISSIONS_KEPT_REASONS = Object.freeze([
13
+ 'allow_capability_off',
14
+ 'settings_read_not_clean',
15
+ 'empty_rule',
16
+ 'paren_in_tool_name',
17
+ 'retired_tool_name',
18
+ 'unprefixed_namespaced_name',
19
+ 'incomplete_protocol_name',
20
+ 'home_relative_path',
21
+ 'compile_refused',
22
+ 'content_form_not_grantable',
23
+ 'judge_failed',
24
+ 'duplicate',
25
+ ]);
12
26
  function normalizeLegacyToolName(name) {
13
27
  switch (name) {
14
28
  case 'Task':
@@ -61,15 +75,15 @@ function requireRulePorts() {
61
75
  throw new Error(`permissionsDeclaration: 规则判据端口缺席(${missing.join(' / ')})—— 先用 installCoreValuePorts 装上 core 的同名实现。` +
62
76
  '缺席不等于全部合格:不判就发会让整份请求被引擎拒收,不判就删会静默丢掉 deny 规则,所以这里拒绝装配。');
63
77
  }
64
- function engineWireSafe(name) {
78
+ function wireIssue(name) {
65
79
  if (name.includes('('))
66
- return false;
80
+ return 'paren_in_tool_name';
67
81
  if (retiredToolNames().has(name))
68
- return false;
82
+ return 'retired_tool_name';
69
83
  const ns = protocolOf(name);
70
84
  if (ns === undefined)
71
- return !name.includes('__');
72
- return ns.parse(name) !== undefined;
85
+ return name.includes('__') ? 'unprefixed_namespaced_name' : undefined;
86
+ return ns.parse(name) !== undefined ? undefined : 'incomplete_protocol_name';
73
87
  }
74
88
  function wholeServerRuleName(name) {
75
89
  const read = name.trim();
@@ -91,12 +105,12 @@ function compileVerdict(rule, behavior) {
91
105
  return 'unknown';
92
106
  }
93
107
  }
94
- function wireSubset(rules, behavior) {
95
- const out = new Set();
108
+ const kept = (reason) => ({ sent: false, reason });
109
+ function judgeList(rules, behavior) {
96
110
  let portsChecked = false;
97
- for (const rule of rules) {
98
- if (typeof rule !== 'string' || rule === '')
99
- continue;
111
+ return rules.map((rule) => {
112
+ if (rule === '')
113
+ return kept('empty_rule');
100
114
  if (!portsChecked) {
101
115
  requireRulePorts();
102
116
  portsChecked = true;
@@ -104,50 +118,78 @@ function wireSubset(rules, behavior) {
104
118
  const parsed = parseRule(rule);
105
119
  if (parsed.ruleContent === undefined) {
106
120
  const name = wholeServerRuleName(parsed.toolName);
107
- if (!engineWireSafe(name))
108
- continue;
109
- if (out.has(name))
110
- continue;
121
+ const issue = wireIssue(name);
122
+ if (issue !== undefined)
123
+ return kept(issue);
111
124
  const verdict = compileVerdict(name, behavior);
112
- if (verdict === 'refused' || (verdict === 'unknown' && behavior === 'allow'))
113
- continue;
114
- out.add(name);
115
- continue;
125
+ if (verdict === 'refused')
126
+ return kept('compile_refused');
127
+ if (verdict === 'unknown' && behavior === 'allow')
128
+ return kept('judge_failed');
129
+ return { sent: true, wire: name, unchecked: verdict === 'unknown' };
116
130
  }
117
- if (!engineWireSafe(parsed.toolName))
118
- continue;
131
+ const issue = wireIssue(parsed.toolName);
132
+ if (issue !== undefined)
133
+ return kept(issue);
119
134
  if (ruleToolGrammarOf(parsed.toolName) === 'path' && parsed.ruleContent.startsWith('~/'))
120
- continue;
135
+ return kept('home_relative_path');
121
136
  const verdict = compileVerdict(rule, behavior);
122
- if (verdict === 'compiles' || (verdict === 'unknown' && behavior !== 'allow'))
123
- out.add(rule);
124
- }
125
- return [...out];
137
+ if (verdict === 'refused')
138
+ return kept(behavior === 'allow' ? 'content_form_not_grantable' : 'compile_refused');
139
+ if (verdict === 'unknown' && behavior === 'allow')
140
+ return kept('judge_failed');
141
+ return { sent: true, wire: rule, unchecked: verdict === 'unknown' };
142
+ });
126
143
  }
127
- function projectBucketByTrust(raw, behavior, originOf, cliRules) {
128
- const present = new Set(raw);
144
+ function outsideSettingsFiles(origin) {
145
+ return origin === 'cliArg' || origin === 'session';
146
+ }
147
+ function judgeBucket(raw, behavior, originOf, cli, settingsGate) {
148
+ const firstSlot = new Map();
149
+ for (const s of raw)
150
+ if (!firstSlot.has(s.rule))
151
+ firstSlot.set(s.rule, s);
129
152
  const groups = RULE_TRUST_ORDER.map(() => []);
130
153
  const rankOf = (origin) => RULE_TRUST_ORDER.indexOf(origin);
131
154
  if (originOf !== undefined) {
132
155
  for (const [r, origin] of originOf) {
133
156
  const rank = rankOf(origin);
134
- if (rank >= 0 && typeof r === 'string' && present.has(r))
157
+ if (rank >= 0 && typeof r === 'string' && firstSlot.has(r))
135
158
  groups[rank].push(r);
136
159
  }
137
160
  }
138
- const unknownOrigin = [...present].filter((r) => originOf === undefined || rankOf(originOf.get(r)) < 0);
139
- const out = new Set();
140
- const append = (list) => {
141
- for (const r of list)
142
- out.add(r);
161
+ const unknownOrigin = [...firstSlot.keys()].filter((r) => originOf === undefined || rankOf(originOf.get(r)) < 0);
162
+ const fateOfRule = new Map();
163
+ const sent = [];
164
+ const winnerOf = new Map();
165
+ const cliFates = cli === undefined ? [] : [...cli.fates];
166
+ const place = (slot, fate) => {
167
+ if (!fate.sent)
168
+ return fate;
169
+ if (winnerOf.has(fate.wire))
170
+ return kept('duplicate');
171
+ winnerOf.set(fate.wire, slot);
172
+ sent.push(fate.wire);
173
+ return fate;
174
+ };
175
+ const appendGroup = (group, origin) => {
176
+ const held = settingsGate !== undefined && !outsideSettingsFiles(origin);
177
+ const fates = held ? group.map(() => kept(settingsGate)) : judgeList(group, behavior);
178
+ group.forEach((r, k) => fateOfRule.set(r, place(firstSlot.get(r), fates[k])));
143
179
  };
144
180
  RULE_TRUST_ORDER.forEach((origin, rank) => {
145
- if (origin === 'cliArg')
146
- append(cliRules);
147
- append(wireSubset(groups[rank], behavior));
181
+ if (origin === 'cliArg' && cli !== undefined)
182
+ cli.slots.forEach((s, k) => (cliFates[k] = place(s, cliFates[k])));
183
+ appendGroup(groups[rank], origin);
148
184
  });
149
- append(wireSubset(unknownOrigin, behavior));
150
- return [...out];
185
+ appendGroup(unknownOrigin, undefined);
186
+ const fates = raw.map((s) => {
187
+ const f = fateOfRule.get(s.rule);
188
+ if (firstSlot.get(s.rule) === s)
189
+ return f;
190
+ return f.sent || f.reason === 'duplicate' ? kept('duplicate') : f;
191
+ });
192
+ return { sent, fates: [...fates, ...cliFates], winnerOf };
151
193
  }
152
194
  const WHO = 'permissionsDeclaration';
153
195
  function ownField(obj, key, where) {
@@ -192,7 +234,8 @@ function originMap(v, key) {
192
234
  }
193
235
  return snap;
194
236
  }
195
- export function permissionsDeclaration(input, caps) {
237
+ const slotsOf = (list, name) => (list ?? []).map((rule, at) => ({ rule, list: name, at }));
238
+ function assemble(input, caps) {
196
239
  if (input === null || typeof input !== 'object' || Array.isArray(input)) {
197
240
  throw new TypeError(`${WHO}: 入参必须是对象 —— 收到 ${input === null ? 'null' : Array.isArray(input) ? 'array' : `typeof=${typeof input}`}。`);
198
241
  }
@@ -211,17 +254,54 @@ export function permissionsDeclaration(input, caps) {
211
254
  const ol = originMap(ownField(originsRaw, 'allow', 'origins.'), 'allow');
212
255
  origins = { ...(od ? { deny: od } : {}), ...(oa ? { ask: oa } : {}), ...(ol ? { allow: ol } : {}) };
213
256
  }
214
- const allowOn = permissionsAllowGrantAvailable(caps);
215
- const cliDenyWire = cliDeny === undefined ? [] : wireSubset(cliDeny, 'deny');
216
- const denyOut = projectBucketByTrust(deny ?? [], 'deny', origins.deny, cliDenyWire);
217
- const askOut = projectBucketByTrust(ask ?? [], 'ask', origins.ask, []);
218
- const allowOut = allowOn && allow !== undefined ? projectBucketByTrust(allow, 'allow', origins.allow, []) : [];
219
- const permissions = {};
220
- if (denyOut.length > 0)
221
- permissions.deny = denyOut;
222
- if (askOut.length > 0)
223
- permissions.ask = askOut;
224
- if (allowOut.length > 0)
225
- permissions.allow = allowOut;
226
- return Object.keys(permissions).length > 0 ? { permissions } : {};
257
+ const gate = allowRulesGate(caps, ownField(input, 'settingsRead', ''));
258
+ const cliSlots = slotsOf(cliDeny, 'cliDeny');
259
+ const cli = cliDeny === undefined ? undefined : { slots: cliSlots, fates: judgeList(cliDeny, 'deny') };
260
+ const denySlots = slotsOf(deny, 'deny');
261
+ const askSlots = slotsOf(ask, 'ask');
262
+ const allowSlots = slotsOf(allow, 'allow');
263
+ const denyJ = judgeBucket(denySlots, 'deny', origins.deny, cli);
264
+ const askJ = judgeBucket(askSlots, 'ask', origins.ask, undefined);
265
+ const allowJ = gate === 'capability_off'
266
+ ? { sent: [], fates: allowSlots.map(() => kept('allow_capability_off')), winnerOf: new Map() }
267
+ : judgeBucket(allowSlots, 'allow', origins.allow, undefined, gate === 'settings_read_not_clean' ? 'settings_read_not_clean' : undefined);
268
+ const buckets = {};
269
+ if (denyJ.sent.length > 0)
270
+ buckets.deny = [...denyJ.sent];
271
+ if (askJ.sent.length > 0)
272
+ buckets.ask = [...askJ.sent];
273
+ if (allowJ.sent.length > 0)
274
+ buckets.allow = [...allowJ.sent];
275
+ const keptRows = [];
276
+ const rewritten = [];
277
+ const unchecked = [];
278
+ const rows = [
279
+ ['deny', [...denySlots, ...cliSlots], denyJ],
280
+ ['ask', askSlots, askJ],
281
+ ['allow', allowSlots, allowJ],
282
+ ];
283
+ for (const [bucket, slots, j] of rows) {
284
+ slots.forEach((s, k) => {
285
+ const f = j.fates[k];
286
+ if (!f.sent)
287
+ keptRows.push({ rule: s.rule, bucket, reason: f.reason });
288
+ });
289
+ for (const wire of j.sent) {
290
+ const winner = j.winnerOf.get(wire);
291
+ if (winner.rule !== wire)
292
+ rewritten.push({ from: winner.rule, to: wire, bucket });
293
+ const f = (winner.list === 'cliDeny' ? j.fates[denySlots.length + winner.at] : j.fates[winner.at]);
294
+ if (f.sent && f.unchecked && bucket !== 'allow')
295
+ unchecked.push({ rule: wire, bucket });
296
+ }
297
+ }
298
+ return { buckets, report: { kept: keptRows, rewritten, unchecked } };
299
+ }
300
+ export function permissionsDeclaration(input, caps) {
301
+ const { buckets } = assemble(input, caps);
302
+ return Object.keys(buckets).length > 0 ? { permissions: buckets } : {};
303
+ }
304
+ export function permissionsDeclarationReport(input, caps) {
305
+ const { buckets, report } = assemble(input, caps);
306
+ return { ...(Object.keys(buckets).length > 0 ? { permissions: buckets } : {}), ...report };
227
307
  }
@@ -1,16 +1,24 @@
1
1
  import type { AgentEvent } from '@sema-agent/sdk';
2
2
  import { type SelfHealOutcome } from './adapter/activeRunSelfHeal.js';
3
+ import type { HumanTurnDelivered } from './humanTurn.js';
3
4
  export interface PromptDeliveryRecord {
4
5
  readonly u: string;
5
6
  readonly s: 'a' | 'd';
7
+ readonly by?: 'frame' | 'steer_receipt';
8
+ readonly share?: 'full' | 'partial';
6
9
  }
7
10
  export interface PromptDeliveryLedger {
8
- attempted(batchUuids: readonly string[]): readonly PromptDeliveryRecord[];
9
- deliveredByFrame(batchUuids: readonly string[], event: AgentEvent): readonly PromptDeliveryRecord[];
10
- deliveredBySteerReceipt(batchUuids: readonly string[], outcome: SelfHealOutcome): readonly PromptDeliveryRecord[];
11
+ attempted(batchUuids: readonly string[], opts?: {
12
+ readonly generation?: number;
13
+ }): readonly PromptDeliveryRecord[];
14
+ deliveredByFrame(batchUuids: readonly string[] | HumanTurnDelivered, event: AgentEvent): readonly PromptDeliveryRecord[];
15
+ deliveredBySteerReceipt(batchUuids: readonly string[] | HumanTurnDelivered, outcome: SelfHealOutcome): readonly PromptDeliveryRecord[];
11
16
  isAttempted(uuid: string): boolean;
12
17
  isDelivered(uuid: string): boolean;
13
18
  knownUndelivered(uuid: string): boolean;
19
+ deliveryShareOf(uuid: string): 'full' | 'partial' | undefined;
20
+ undeliveredInGeneration(generation: number): readonly string[];
21
+ droppedKeyCount(): number;
14
22
  batchOf(uuid: string): readonly string[] | undefined;
15
23
  toRecords(): readonly PromptDeliveryRecord[];
16
24
  fromRecords(records: readonly PromptDeliveryRecord[]): readonly PromptDeliveryRecord[];