@sema-agent/client-core 0.89.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 (104) hide show
  1. package/CHANGELOG.md +297 -0
  2. package/README.md +35 -24
  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.d.ts +2 -0
  8. package/dist/adapter/runStream.js +180 -38
  9. package/dist/askSurvivesStandingAnswer.d.ts +10 -0
  10. package/dist/askSurvivesStandingAnswer.js +40 -0
  11. package/dist/attachmentSeatCapability.d.ts +11 -0
  12. package/dist/attachmentSeatCapability.js +27 -0
  13. package/dist/attachmentsWireCaps.d.ts +22 -2
  14. package/dist/attachmentsWireCaps.js +95 -22
  15. package/dist/compensations.js +10 -14
  16. package/dist/controlRouter.d.ts +9 -2
  17. package/dist/controlRouter.js +66 -28
  18. package/dist/coreValuePorts.d.ts +1 -0
  19. package/dist/decideFailureNote.js +4 -2
  20. package/dist/disableSkillsWire.d.ts +1 -0
  21. package/dist/disableSkillsWire.js +8 -1
  22. package/dist/displayUntrusted.js +153 -4
  23. package/dist/engineCapReader.js +12 -1
  24. package/dist/engineCapsCache.d.ts +3 -1
  25. package/dist/engineCapsCache.js +56 -6
  26. package/dist/engineClosedSets.d.ts +2 -0
  27. package/dist/engineClosedSets.js +3 -0
  28. package/dist/engineErrorCodes.d.ts +3 -0
  29. package/dist/engineErrorCodes.js +10 -0
  30. package/dist/engineNoticeCodes.d.ts +2 -9
  31. package/dist/engineNoticeCodes.js +19 -27
  32. package/dist/fileHistoryLegNotice.d.ts +20 -0
  33. package/dist/fileHistoryLegNotice.js +122 -0
  34. package/dist/gateOutcome.d.ts +1 -1
  35. package/dist/gateOutcome.js +1 -0
  36. package/dist/generated/engineFactTables.d.ts +13 -0
  37. package/dist/generated/engineFactTables.js +50 -0
  38. package/dist/generated/engineNoticeTables.js +0 -3
  39. package/dist/hitl/askGateWire.d.ts +1 -1
  40. package/dist/hitl/askGateWire.js +1 -1
  41. package/dist/hitl/crashConverged.d.ts +7 -0
  42. package/dist/hitl/crashConverged.js +57 -0
  43. package/dist/hitl/crashConvergedNotice.d.ts +4 -0
  44. package/dist/hitl/crashConvergedNotice.js +57 -0
  45. package/dist/hitl/hitlBridge.d.ts +7 -4
  46. package/dist/hitl/parkResolver.d.ts +7 -2
  47. package/dist/hitl/parkResolver.js +19 -7
  48. package/dist/hitl/planReviewWire.d.ts +1 -1
  49. package/dist/hitl/planReviewWire.js +18 -4
  50. package/dist/hitl/sessionPolicyWire.d.ts +5 -0
  51. package/dist/hitl/sessionPolicyWire.js +63 -0
  52. package/dist/hitl/suspendedReopen.js +2 -2
  53. package/dist/hitl/toolApprovalWire.d.ts +4 -1
  54. package/dist/hitl/toolApprovalWire.js +20 -0
  55. package/dist/hookMatcherVerdict.d.ts +29 -0
  56. package/dist/hookMatcherVerdict.js +265 -0
  57. package/dist/hookToolAliases.d.ts +3 -0
  58. package/dist/hookToolAliases.js +10 -0
  59. package/dist/humanTurn.d.ts +91 -0
  60. package/dist/humanTurn.js +253 -0
  61. package/dist/humanTurnRead.d.ts +105 -0
  62. package/dist/humanTurnRead.js +331 -0
  63. package/dist/index.d.ts +30 -5
  64. package/dist/index.js +19 -4
  65. package/dist/inheritEnvWire.js +1 -1
  66. package/dist/liveModelCatalog.d.ts +2 -0
  67. package/dist/liveModelCatalog.js +51 -3
  68. package/dist/permissionRuleIssue.d.ts +1 -1
  69. package/dist/permissionRuleIssue.js +22 -3
  70. package/dist/permissionsAllowGrantCapability.d.ts +18 -0
  71. package/dist/permissionsAllowGrantCapability.js +84 -0
  72. package/dist/permissionsDeclaration.d.ts +48 -0
  73. package/dist/permissionsDeclaration.js +307 -0
  74. package/dist/promptDelivery.d.ts +16 -5
  75. package/dist/promptDelivery.js +172 -32
  76. package/dist/providerRejection.d.ts +3 -1
  77. package/dist/providerRejection.js +27 -1
  78. package/dist/readDenyTiers.d.ts +10 -0
  79. package/dist/readDenyTiers.js +29 -0
  80. package/dist/request/taskRequest.js +27 -1
  81. package/dist/resumeRefusalCopy.d.ts +11 -0
  82. package/dist/resumeRefusalCopy.js +61 -4
  83. package/dist/rewindArchiveCapability.js +4 -4
  84. package/dist/rewindWireCaps.d.ts +2 -1
  85. package/dist/rewindWireCaps.js +19 -2
  86. package/dist/sdkRegistryTransit.d.ts +1 -0
  87. package/dist/sdkRegistryTransit.js +1 -0
  88. package/dist/seam.d.ts +2 -0
  89. package/dist/supportFloor.d.ts +10 -0
  90. package/dist/supportFloor.js +42 -0
  91. package/dist/taskRequestWords.d.ts +7 -0
  92. package/dist/taskRequestWords.js +24 -0
  93. package/dist/toolHistoryBadTurn.d.ts +1 -0
  94. package/dist/toolHistoryBadTurn.js +89 -0
  95. package/dist/toolHistoryMismatch.d.ts +2 -1
  96. package/dist/toolHistoryMismatch.js +19 -6
  97. package/dist/userAttachmentEnvelope.d.ts +61 -0
  98. package/dist/userAttachmentEnvelope.js +204 -0
  99. package/dist/wireFailureShape.d.ts +1 -0
  100. package/dist/wireFailureShape.js +4 -0
  101. package/dist/wireRefusalCopy.d.ts +6 -0
  102. package/dist/wireRefusalCopy.js +26 -3
  103. package/docs/INTEGRATION-CLIENTS.md +1369 -18
  104. package/package.json +3 -3
@@ -0,0 +1,307 @@
1
+ import { hasCoreValuePort, protocolOf, retiredToolNames, ruleToolGrammarOf, validatePermissionRules } from './coreValuePorts.js';
2
+ import { allowRulesGate } from './permissionsAllowGrantCapability.js';
3
+ const RULE_TRUST_ORDER = [
4
+ 'policySettings',
5
+ 'cliArg',
6
+ 'flagSettings',
7
+ 'session',
8
+ 'userSettings',
9
+ 'localSettings',
10
+ 'projectSettings',
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
+ ]);
26
+ function normalizeLegacyToolName(name) {
27
+ switch (name) {
28
+ case 'Task':
29
+ return 'Agent';
30
+ case 'KillShell':
31
+ return 'TaskStop';
32
+ case 'AgentOutputTool':
33
+ case 'BashOutputTool':
34
+ return 'TaskOutput';
35
+ default:
36
+ return name;
37
+ }
38
+ }
39
+ function unescapedIndex(str, char, fromEnd) {
40
+ for (let k = 0; k < str.length; k++) {
41
+ const i = fromEnd ? str.length - 1 - k : k;
42
+ if (str[i] !== char)
43
+ continue;
44
+ let backslashes = 0;
45
+ for (let j = i - 1; j >= 0 && str[j] === '\\'; j--)
46
+ backslashes++;
47
+ if (backslashes % 2 === 0)
48
+ return i;
49
+ }
50
+ return -1;
51
+ }
52
+ function unescapeRuleContent(content) {
53
+ return content.replace(/\\\(/g, '(').replace(/\\\)/g, ')').replace(/\\\\/g, '\\');
54
+ }
55
+ function parseRule(rule) {
56
+ const open = unescapedIndex(rule, '(', false);
57
+ if (open === -1)
58
+ return { toolName: normalizeLegacyToolName(rule) };
59
+ const close = unescapedIndex(rule, ')', true);
60
+ if (close === -1 || close <= open || close !== rule.length - 1)
61
+ return { toolName: normalizeLegacyToolName(rule) };
62
+ const toolName = rule.substring(0, open);
63
+ const rawContent = rule.substring(open + 1, close);
64
+ if (!toolName)
65
+ return { toolName: normalizeLegacyToolName(rule) };
66
+ if (rawContent === '' || rawContent === '*')
67
+ return { toolName: normalizeLegacyToolName(toolName) };
68
+ return { toolName: normalizeLegacyToolName(toolName), ruleContent: unescapeRuleContent(rawContent) };
69
+ }
70
+ function requireRulePorts() {
71
+ const required = ['MCP_NAMESPACE', 'RETIRED_TOOL_NAMES', 'protocolOf', 'ruleToolGrammarOf', 'validatePermissionRules'];
72
+ const missing = required.filter((p) => !hasCoreValuePort(p));
73
+ if (missing.length === 0)
74
+ return;
75
+ throw new Error(`permissionsDeclaration: 规则判据端口缺席(${missing.join(' / ')})—— 先用 installCoreValuePorts 装上 core 的同名实现。` +
76
+ '缺席不等于全部合格:不判就发会让整份请求被引擎拒收,不判就删会静默丢掉 deny 规则,所以这里拒绝装配。');
77
+ }
78
+ function wireIssue(name) {
79
+ if (name.includes('('))
80
+ return 'paren_in_tool_name';
81
+ if (retiredToolNames().has(name))
82
+ return 'retired_tool_name';
83
+ const ns = protocolOf(name);
84
+ if (ns === undefined)
85
+ return name.includes('__') ? 'unprefixed_namespaced_name' : undefined;
86
+ return ns.parse(name) !== undefined ? undefined : 'incomplete_protocol_name';
87
+ }
88
+ function wholeServerRuleName(name) {
89
+ const read = name.trim();
90
+ const ns = protocolOf(read);
91
+ if (ns === undefined || !read.startsWith(ns.prefix))
92
+ return name;
93
+ const peer = read.slice(ns.prefix.length);
94
+ return peer !== '' && !peer.includes('__') && !peer.endsWith('_') ? `${read}__*` : name;
95
+ }
96
+ const RULE_ROOT = '/';
97
+ function compileVerdict(rule, behavior) {
98
+ try {
99
+ const issues = validatePermissionRules([{ rule, behavior }], { pathBases: { root: RULE_ROOT } });
100
+ if (!Array.isArray(issues))
101
+ return 'unknown';
102
+ return issues.length === 0 ? 'compiles' : 'refused';
103
+ }
104
+ catch {
105
+ return 'unknown';
106
+ }
107
+ }
108
+ const kept = (reason) => ({ sent: false, reason });
109
+ function judgeList(rules, behavior) {
110
+ let portsChecked = false;
111
+ return rules.map((rule) => {
112
+ if (rule === '')
113
+ return kept('empty_rule');
114
+ if (!portsChecked) {
115
+ requireRulePorts();
116
+ portsChecked = true;
117
+ }
118
+ const parsed = parseRule(rule);
119
+ if (parsed.ruleContent === undefined) {
120
+ const name = wholeServerRuleName(parsed.toolName);
121
+ const issue = wireIssue(name);
122
+ if (issue !== undefined)
123
+ return kept(issue);
124
+ const verdict = compileVerdict(name, behavior);
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' };
130
+ }
131
+ const issue = wireIssue(parsed.toolName);
132
+ if (issue !== undefined)
133
+ return kept(issue);
134
+ if (ruleToolGrammarOf(parsed.toolName) === 'path' && parsed.ruleContent.startsWith('~/'))
135
+ return kept('home_relative_path');
136
+ const verdict = compileVerdict(rule, behavior);
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
+ });
143
+ }
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);
152
+ const groups = RULE_TRUST_ORDER.map(() => []);
153
+ const rankOf = (origin) => RULE_TRUST_ORDER.indexOf(origin);
154
+ if (originOf !== undefined) {
155
+ for (const [r, origin] of originOf) {
156
+ const rank = rankOf(origin);
157
+ if (rank >= 0 && typeof r === 'string' && firstSlot.has(r))
158
+ groups[rank].push(r);
159
+ }
160
+ }
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])));
179
+ };
180
+ RULE_TRUST_ORDER.forEach((origin, rank) => {
181
+ if (origin === 'cliArg' && cli !== undefined)
182
+ cli.slots.forEach((s, k) => (cliFates[k] = place(s, cliFates[k])));
183
+ appendGroup(groups[rank], origin);
184
+ });
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 };
193
+ }
194
+ const WHO = 'permissionsDeclaration';
195
+ function ownField(obj, key, where) {
196
+ if (Object.hasOwn(obj, key))
197
+ return obj[key];
198
+ if (key in obj)
199
+ throw new TypeError(`${WHO}: ${where}${key} 只在原型链上,不是自有字段 —— 拒绝读取(请交普通对象)。`);
200
+ return undefined;
201
+ }
202
+ function ruleList(v, key) {
203
+ if (v === undefined || v === null)
204
+ return undefined;
205
+ if (!Array.isArray(v))
206
+ throw new TypeError(`${WHO}: ${key} 必须是规则串数组或缺席 —— 收到 typeof=${typeof v}。`);
207
+ const out = [];
208
+ const n = v.length;
209
+ for (let i = 0; i < n; i++) {
210
+ if (!Object.hasOwn(v, i)) {
211
+ if (i in v)
212
+ throw new TypeError(`${WHO}: ${key}[${i}] 是空洞,而原型链上在这个下标有值 —— 拒绝读取(请交不带空洞的普通数组)。`);
213
+ continue;
214
+ }
215
+ const r = v[i];
216
+ if (typeof r === 'string')
217
+ out.push(r);
218
+ }
219
+ return out;
220
+ }
221
+ function originMap(v, key) {
222
+ if (v === undefined || v === null)
223
+ return undefined;
224
+ const ok = typeof v === 'object' &&
225
+ typeof v.get === 'function' &&
226
+ typeof v[Symbol.iterator] === 'function';
227
+ if (!ok)
228
+ throw new TypeError(`${WHO}: origins.${key} 必须是 Map(规则原串 → 来源)或缺席 —— 收到 typeof=${typeof v}。`);
229
+ const snap = new Map();
230
+ for (const entry of v) {
231
+ const [r, origin] = entry;
232
+ if (!snap.has(r))
233
+ snap.set(r, origin);
234
+ }
235
+ return snap;
236
+ }
237
+ const slotsOf = (list, name) => (list ?? []).map((rule, at) => ({ rule, list: name, at }));
238
+ function assemble(input, caps) {
239
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
240
+ throw new TypeError(`${WHO}: 入参必须是对象 —— 收到 ${input === null ? 'null' : Array.isArray(input) ? 'array' : `typeof=${typeof input}`}。`);
241
+ }
242
+ const deny = ruleList(ownField(input, 'deny', ''), 'deny');
243
+ const ask = ruleList(ownField(input, 'ask', ''), 'ask');
244
+ const allow = ruleList(ownField(input, 'allow', ''), 'allow');
245
+ const cliDeny = ruleList(ownField(input, 'cliDeny', ''), 'cliDeny');
246
+ const originsRaw = ownField(input, 'origins', '');
247
+ let origins = {};
248
+ if (originsRaw !== undefined && originsRaw !== null) {
249
+ if (typeof originsRaw !== 'object' || Array.isArray(originsRaw)) {
250
+ throw new TypeError(`${WHO}: origins 必须是 { deny?, ask?, allow? } 对象或缺席 —— 收到 typeof=${typeof originsRaw}。`);
251
+ }
252
+ const od = originMap(ownField(originsRaw, 'deny', 'origins.'), 'deny');
253
+ const oa = originMap(ownField(originsRaw, 'ask', 'origins.'), 'ask');
254
+ const ol = originMap(ownField(originsRaw, 'allow', 'origins.'), 'allow');
255
+ origins = { ...(od ? { deny: od } : {}), ...(oa ? { ask: oa } : {}), ...(ol ? { allow: ol } : {}) };
256
+ }
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 };
307
+ }
@@ -1,20 +1,31 @@
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[];
25
+ knownUndeliveredMessage(message: object | null | undefined): boolean;
26
+ withoutUndelivered<T>(messages: readonly T[]): T[];
17
27
  }
28
+ export declare function frameProvesDelivery(event: object | null | undefined): boolean;
18
29
  export declare function createPromptDeliveryLedger(): PromptDeliveryLedger;
19
- export declare function promptKnownUndelivered(ledger: PromptDeliveryLedger, message: object | null | undefined): boolean;
20
- export declare function withoutUndeliveredPrompts<T>(ledger: PromptDeliveryLedger, messages: readonly T[]): T[];
30
+ export declare function promptKnownUndelivered(ledger: Pick<PromptDeliveryLedger, 'knownUndelivered'>, message: object | null | undefined): boolean;
31
+ export declare function withoutUndeliveredPrompts<T>(ledger: Pick<PromptDeliveryLedger, 'knownUndelivered'>, messages: readonly T[]): T[];
@@ -1,8 +1,43 @@
1
1
  import { activeRunBusySignal } from './adapter/runStream.js';
2
2
  import { selfHealSubmissionDisposition } from './adapter/activeRunSelfHeal.js';
3
3
  const uuidOk = (u) => typeof u === 'string' && u.length > 0;
4
+ function isArraySafe(v) {
5
+ try {
6
+ return Array.isArray(v);
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ const generationOk = (g) => typeof g === 'number' && Number.isSafeInteger(g) && g >= 0;
13
+ function generationOpt(opts) {
14
+ if (typeof opts !== 'object' || opts === null)
15
+ return undefined;
16
+ try {
17
+ const g = opts.generation;
18
+ return generationOk(g) ? g : undefined;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ const levelOf = (rec) => !Object.hasOwn(rec, 'by') ? 1 : Object.hasOwn(rec, 'share') && rec.share === 'partial' ? 2 : 3;
25
+ function deliveredByWord(v) {
26
+ const words = { frame: true, steer_receipt: true };
27
+ return typeof v === 'string' && Object.hasOwn(words, v) ? v : undefined;
28
+ }
29
+ function deliveredShareWord(v) {
30
+ const words = { full: true, partial: true };
31
+ return typeof v === 'string' && Object.hasOwn(words, v) ? v : undefined;
32
+ }
33
+ function ownExtension(rec, key) {
34
+ const desc = Object.getOwnPropertyDescriptor(rec, key);
35
+ if (desc === undefined)
36
+ return key in rec ? { kind: 'unreadable' } : { kind: 'absent' };
37
+ return 'value' in desc ? { kind: 'value', value: desc.value } : { kind: 'unreadable' };
38
+ }
4
39
  function cleanBatch(batch) {
5
- if (!Array.isArray(batch))
40
+ if (!isArraySafe(batch))
6
41
  return [];
7
42
  const out = [];
8
43
  const seen = new Set();
@@ -19,23 +54,81 @@ function cleanBatch(batch) {
19
54
  }
20
55
  return out;
21
56
  }
22
- function readRecord(rec) {
23
- if (typeof rec !== 'object' || rec === null || Array.isArray(rec))
57
+ function readRecord(raw) {
58
+ if (typeof raw !== 'object' || raw === null)
24
59
  return undefined;
60
+ let u;
61
+ let s;
25
62
  try {
26
- const u = rec.u;
27
- const s = rec.s;
28
- return uuidOk(u) && (s === 'a' || s === 'd') ? { u, s } : undefined;
63
+ if (Array.isArray(raw))
64
+ return undefined;
65
+ u = raw.u;
66
+ s = raw.s;
29
67
  }
30
68
  catch {
31
69
  return undefined;
32
70
  }
71
+ if (!uuidOk(u) || (s !== 'a' && s !== 'd'))
72
+ return undefined;
73
+ const legacy = { u, s };
74
+ try {
75
+ let rec = legacy;
76
+ if (s === 'd') {
77
+ const byExt = ownExtension(raw, 'by');
78
+ const shareExt = ownExtension(raw, 'share');
79
+ const by = byExt.kind === 'value' ? deliveredByWord(byExt.value) : undefined;
80
+ const share = shareExt.kind === 'value' ? deliveredShareWord(shareExt.value) : undefined;
81
+ if (by !== undefined && (shareExt.kind === 'absent' || share !== undefined))
82
+ rec = share === undefined ? { u, s, by } : { u, s, by, share };
83
+ }
84
+ const dropped = Object.keys(raw).filter((k) => !Object.hasOwn(rec, k)).length;
85
+ return { rec, dropped };
86
+ }
87
+ catch {
88
+ return { rec: legacy, dropped: 0 };
89
+ }
90
+ }
91
+ function readTierStrict(holder, key) {
92
+ try {
93
+ if (!Object.hasOwn(holder, key))
94
+ return [];
95
+ const raw = holder[key];
96
+ if (!Array.isArray(raw))
97
+ return null;
98
+ const out = [];
99
+ const seen = new Set();
100
+ for (const u of raw) {
101
+ if (!uuidOk(u) || seen.has(u))
102
+ continue;
103
+ seen.add(u);
104
+ out.push(u);
105
+ }
106
+ return out;
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ function batchTiers(batch) {
113
+ const none = { full: [], partial: [] };
114
+ if (isArraySafe(batch))
115
+ return { full: cleanBatch(batch), partial: [] };
116
+ if (typeof batch !== 'object' || batch === null)
117
+ return none;
118
+ const full = readTierStrict(batch, 'full');
119
+ const partial = readTierStrict(batch, 'partial');
120
+ if (full === null || partial === null)
121
+ return none;
122
+ const inFull = new Set(full);
123
+ return { full, partial: partial.filter((u) => !inFull.has(u)) };
33
124
  }
34
125
  const PRE_RUN_FRAME_TYPES = new Set(['meta', 'approval_request', 'approval_revoke']);
35
- function frameProvesDelivery(event) {
36
- if (typeof event !== 'object' || event === null || Array.isArray(event))
126
+ export function frameProvesDelivery(event) {
127
+ if (typeof event !== 'object' || event === null)
37
128
  return false;
38
129
  try {
130
+ if (Array.isArray(event))
131
+ return false;
39
132
  const type = event.type;
40
133
  if (typeof type !== 'string' || type.length === 0 || PRE_RUN_FRAME_TYPES.has(type))
41
134
  return false;
@@ -54,9 +147,11 @@ function frameProvesDelivery(event) {
54
147
  }
55
148
  }
56
149
  function outcomeProvesDelivery(outcome) {
57
- if (typeof outcome !== 'object' || outcome === null || Array.isArray(outcome))
150
+ if (typeof outcome !== 'object' || outcome === null)
58
151
  return false;
59
152
  try {
153
+ if (Array.isArray(outcome))
154
+ return false;
60
155
  return selfHealSubmissionDisposition(outcome) === 'handed-off';
61
156
  }
62
157
  catch {
@@ -65,48 +160,88 @@ function outcomeProvesDelivery(outcome) {
65
160
  }
66
161
  export function createPromptDeliveryLedger() {
67
162
  const attemptedSet = new Set();
68
- const deliveredSet = new Set();
163
+ const deliveredLevel = new Map();
69
164
  const batchOfMap = new Map();
165
+ const generationOf = new Map();
70
166
  const log = [];
71
- const add = (u, s, into) => {
72
- const set = s === 'a' ? attemptedSet : deliveredSet;
73
- if (set.has(u))
167
+ let droppedKeys = 0;
168
+ const keep = (rec, into) => {
169
+ const frozen = Object.freeze(rec);
170
+ log.push(frozen);
171
+ into.push(frozen);
172
+ };
173
+ const add = (rec, into) => {
174
+ if (rec.s === 'a') {
175
+ if (attemptedSet.has(rec.u))
176
+ return;
177
+ attemptedSet.add(rec.u);
178
+ keep(rec, into);
74
179
  return;
75
- set.add(u);
76
- const rec = Object.freeze({ u, s });
77
- log.push(rec);
78
- into.push(rec);
180
+ }
181
+ const level = levelOf(rec);
182
+ if ((deliveredLevel.get(rec.u) ?? 0) >= level)
183
+ return;
184
+ deliveredLevel.set(rec.u, level);
185
+ keep(rec, into);
79
186
  };
80
- const markDelivered = (batchUuids) => {
187
+ const markDelivered = (batch, by) => {
81
188
  const added = [];
82
- for (const u of cleanBatch(batchUuids))
83
- add(u, 'd', added);
189
+ const { full, partial } = batchTiers(batch);
190
+ for (const u of full)
191
+ add({ u, s: 'd', by }, added);
192
+ for (const u of partial)
193
+ add({ u, s: 'd', by, share: 'partial' }, added);
84
194
  return Object.freeze(added);
85
195
  };
86
- return Object.freeze({
87
- attempted(batchUuids) {
196
+ const levelNow = (uuid) => (uuidOk(uuid) ? (deliveredLevel.get(uuid) ?? 0) : 0);
197
+ const knownUndeliveredUuid = (uuid) => uuidOk(uuid) && attemptedSet.has(uuid) && !deliveredLevel.has(uuid);
198
+ const ledger = Object.freeze({
199
+ attempted(batchUuids, opts) {
200
+ const generation = generationOpt(opts);
88
201
  const batch = Object.freeze(cleanBatch(batchUuids));
89
202
  const added = [];
90
203
  for (const u of batch) {
91
204
  batchOfMap.set(u, batch);
92
- add(u, 'a', added);
205
+ if (generation !== undefined) {
206
+ generationOf.delete(u);
207
+ generationOf.set(u, generation);
208
+ }
209
+ add({ u, s: 'a' }, added);
93
210
  }
94
211
  return Object.freeze(added);
95
212
  },
96
213
  deliveredByFrame(batchUuids, event) {
97
- return frameProvesDelivery(event) ? markDelivered(batchUuids) : Object.freeze([]);
214
+ return frameProvesDelivery(event) ? markDelivered(batchUuids, 'frame') : Object.freeze([]);
98
215
  },
99
216
  deliveredBySteerReceipt(batchUuids, outcome) {
100
- return outcomeProvesDelivery(outcome) ? markDelivered(batchUuids) : Object.freeze([]);
217
+ return outcomeProvesDelivery(outcome) ? markDelivered(batchUuids, 'steer_receipt') : Object.freeze([]);
101
218
  },
102
219
  isAttempted: (uuid) => uuidOk(uuid) && attemptedSet.has(uuid),
103
- isDelivered: (uuid) => uuidOk(uuid) && deliveredSet.has(uuid),
104
- knownUndelivered: (uuid) => uuidOk(uuid) && attemptedSet.has(uuid) && !deliveredSet.has(uuid),
220
+ isDelivered: (uuid) => levelNow(uuid) >= 2,
221
+ knownUndelivered: (uuid) => knownUndeliveredUuid(uuid),
222
+ deliveryShareOf: (uuid) => {
223
+ const level = levelNow(uuid);
224
+ return level === 3 ? 'full' : level === 2 ? 'partial' : undefined;
225
+ },
226
+ undeliveredInGeneration(generation) {
227
+ const out = [];
228
+ if (!generationOk(generation))
229
+ return Object.freeze(out);
230
+ for (const [u, g] of generationOf) {
231
+ if (g !== generation)
232
+ continue;
233
+ generationOf.delete(u);
234
+ if (knownUndeliveredUuid(u))
235
+ out.push(u);
236
+ }
237
+ return Object.freeze(out);
238
+ },
239
+ droppedKeyCount: () => droppedKeys,
105
240
  batchOf: (uuid) => (uuidOk(uuid) ? batchOfMap.get(uuid) : undefined),
106
241
  toRecords: () => Object.freeze([...log]),
107
242
  fromRecords(records) {
108
243
  const added = [];
109
- if (!Array.isArray(records))
244
+ if (!isArraySafe(records))
110
245
  return Object.freeze(added);
111
246
  let list;
112
247
  try {
@@ -116,13 +251,18 @@ export function createPromptDeliveryLedger() {
116
251
  return Object.freeze(added);
117
252
  }
118
253
  for (const raw of list) {
119
- const rec = readRecord(raw);
120
- if (rec !== undefined)
121
- add(rec.u, rec.s, added);
254
+ const read = readRecord(raw);
255
+ if (read === undefined)
256
+ continue;
257
+ droppedKeys += read.dropped;
258
+ add(read.rec, added);
122
259
  }
123
260
  return Object.freeze(added);
124
261
  },
262
+ knownUndeliveredMessage: (message) => promptKnownUndelivered(ledger, message),
263
+ withoutUndelivered: (messages) => withoutUndeliveredPrompts(ledger, messages),
125
264
  });
265
+ return ledger;
126
266
  }
127
267
  function uuidOfMessage(m) {
128
268
  if (typeof m !== 'object' || m === null)
@@ -147,7 +287,7 @@ export function promptKnownUndelivered(ledger, message) {
147
287
  }
148
288
  export function withoutUndeliveredPrompts(ledger, messages) {
149
289
  assertLedger(ledger, 'withoutUndeliveredPrompts');
150
- if (!Array.isArray(messages))
290
+ if (!isArraySafe(messages))
151
291
  return [];
152
292
  return messages.filter((m) => !promptKnownUndelivered(ledger, m));
153
293
  }
@@ -1,6 +1,8 @@
1
1
  import { type RunTerminalFailureDetail } from './runTerminal.js';
2
+ import { type ToolHistoryErrorCarrier } from './toolHistoryMismatch.js';
2
3
  export interface ProviderRejectionResend {
3
4
  resend: 'edit_and_resend' | 'not_by_editing';
4
5
  text: string;
5
6
  }
6
- export declare function providerRejectionResend(detail: RunTerminalFailureDetail | null | undefined): ProviderRejectionResend | undefined;
7
+ export declare function readProviderRejectionResendKey(v: object | null | undefined): ProviderRejectionResend | undefined;
8
+ export declare function providerRejectionResend(detail: RunTerminalFailureDetail | null | undefined, carrier?: ToolHistoryErrorCarrier | null): ProviderRejectionResend | undefined;