@sema-agent/client-core 0.106.0 → 0.108.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 (114) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +35 -26
  3. package/dist/adapter/activeRunSelfHeal.d.ts +10 -0
  4. package/dist/adapter/activeRunSelfHeal.js +68 -23
  5. package/dist/checkpointRowVerdict.d.ts +11 -0
  6. package/dist/checkpointRowVerdict.js +35 -0
  7. package/dist/cloudConfigWireCaps.d.ts +10 -0
  8. package/dist/cloudConfigWireCaps.js +33 -0
  9. package/dist/coreValuePorts.d.ts +17 -1
  10. package/dist/coreValuePorts.js +5 -0
  11. package/dist/decideReceipt.js +2 -1
  12. package/dist/displayUntrusted.d.ts +5 -3
  13. package/dist/displayUntrusted.js +50 -41
  14. package/dist/engineErrorCodes.d.ts +5 -0
  15. package/dist/engineErrorCodes.js +5 -0
  16. package/dist/engineNoticeCodes.d.ts +31 -1
  17. package/dist/engineNoticeCodes.js +182 -9
  18. package/dist/engineTargetSeat.js +1 -0
  19. package/dist/errorSupplementRoles.js +4 -1
  20. package/dist/eventLeg.js +1 -1
  21. package/dist/executionLaneCapability.js +3 -2
  22. package/dist/executionLaneWords.d.ts +1 -0
  23. package/dist/executionLaneWords.js +4 -0
  24. package/dist/gateOutcome.d.ts +10 -1
  25. package/dist/gateOutcome.js +48 -1
  26. package/dist/generated/engineFactTables.d.ts +1 -0
  27. package/dist/generated/engineFactTables.js +5 -0
  28. package/dist/generated/engineNoticeTables.js +12 -3
  29. package/dist/generated/settingsSchemaFns.d.ts +4 -2
  30. package/dist/generated/settingsSchemaFns.js +9 -3
  31. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  32. package/dist/generated/settingsSchemaMcpRequestEntry.js +9 -18
  33. package/dist/generated/settingsSchemaReservedMemberName.d.ts +1 -0
  34. package/dist/generated/settingsSchemaReservedMemberName.js +7 -0
  35. package/dist/generated/settingsSchemaTables.d.ts +2 -0
  36. package/dist/generated/settingsSchemaTables.js +10 -0
  37. package/dist/generated/settingsSchemaWireName.d.ts +1 -0
  38. package/dist/generated/settingsSchemaWireName.js +8 -0
  39. package/dist/headlessReconnectWire.js +12 -7
  40. package/dist/hitl/approvalAskers.d.ts +13 -0
  41. package/dist/hitl/approvalAskers.js +79 -0
  42. package/dist/hitl/approvalResolution.d.ts +8 -1
  43. package/dist/hitl/approvalResolution.js +37 -2
  44. package/dist/hitl/approvalsFeed.js +5 -13
  45. package/dist/hitl/askParkRowRouting.js +17 -1
  46. package/dist/hitl/decisionRefusal.d.ts +1 -0
  47. package/dist/hitl/decisionRefusal.js +5 -2
  48. package/dist/hitl/decisionSend.d.ts +2 -0
  49. package/dist/hitl/decisionSend.js +22 -12
  50. package/dist/hitl/frameRouter.d.ts +3 -2
  51. package/dist/hitl/frameRouter.js +28 -6
  52. package/dist/hitl/gateLedger.d.ts +2 -0
  53. package/dist/hitl/gateLedger.js +3 -1
  54. package/dist/hitl/hitlBridge.d.ts +5 -1
  55. package/dist/hitl/hitlBridge.js +26 -2
  56. package/dist/hitl/livePendingAsk.js +4 -2
  57. package/dist/hitl/parkOwnership.js +4 -5
  58. package/dist/hitl/parkResolver.js +34 -21
  59. package/dist/hitl/parkRowBirthWait.d.ts +9 -0
  60. package/dist/hitl/parkRowBirthWait.js +8 -1
  61. package/dist/hitl/parkRowDelegation.d.ts +6 -0
  62. package/dist/hitl/parkRowDelegation.js +33 -0
  63. package/dist/hitl/persistedRulesWire.d.ts +19 -3
  64. package/dist/hitl/persistedRulesWire.js +118 -20
  65. package/dist/hitl/planReviewWire.js +16 -18
  66. package/dist/hitl/policyDenyCause.d.ts +3 -0
  67. package/dist/hitl/policyDenyCause.js +16 -0
  68. package/dist/hitl/sessionAllowRoute.d.ts +5 -0
  69. package/dist/hitl/sessionAllowRoute.js +42 -0
  70. package/dist/hitl/sessionPolicyDeliverable.js +1 -1
  71. package/dist/hitl/standingRows.js +10 -10
  72. package/dist/hitl/toolApprovalWire.d.ts +8 -2
  73. package/dist/hitl/toolApprovalWire.js +44 -11
  74. package/dist/hooksReaderGenerations.d.ts +1 -0
  75. package/dist/hooksReaderGenerations.js +12 -0
  76. package/dist/hooksWireCaps.js +8 -4
  77. package/dist/i18n/messages.en.d.ts +43 -0
  78. package/dist/i18n/messages.en.js +47 -4
  79. package/dist/i18n/messages.zh.js +53 -4
  80. package/dist/index.d.ts +7 -1
  81. package/dist/index.js +5 -1
  82. package/dist/injectionLaneNotice.js +2 -1
  83. package/dist/liveModelCatalog.d.ts +4 -2
  84. package/dist/liveModelCatalog.js +164 -26
  85. package/dist/model/catalogDefault.d.ts +25 -0
  86. package/dist/model/catalogDefault.js +44 -0
  87. package/dist/noticePresentation.js +2 -0
  88. package/dist/permissionWireCaps.d.ts +1 -0
  89. package/dist/permissionWireCaps.js +11 -0
  90. package/dist/pluginHooksWire.d.ts +5 -3
  91. package/dist/pluginHooksWire.js +116 -7
  92. package/dist/queuedDelivery.d.ts +1 -0
  93. package/dist/queuedDelivery.js +12 -0
  94. package/dist/resultFrameKeys.js +4 -2
  95. package/dist/resumeRefusalCopy.js +15 -8
  96. package/dist/runPermissionModeSwitch.d.ts +2 -1
  97. package/dist/runPermissionModeSwitch.js +16 -6
  98. package/dist/sdkWireTransit.d.ts +1 -1
  99. package/dist/seatContract.d.ts +64 -10
  100. package/dist/seatContract.js +167 -12
  101. package/dist/sessionEngine.d.ts +2 -1
  102. package/dist/sessionEngine.js +5 -0
  103. package/dist/settingsHooksSalvage.d.ts +5 -0
  104. package/dist/settingsHooksSalvage.js +54 -0
  105. package/dist/subagentContentStore.js +1 -1
  106. package/dist/supportFloor.js +2 -0
  107. package/dist/taskNotificationAdmission.d.ts +19 -0
  108. package/dist/taskNotificationAdmission.js +84 -0
  109. package/dist/toolResult.js +2 -1
  110. package/dist/webSearchWireCaps.js +9 -4
  111. package/dist/wireRefusalCopy.d.ts +7 -1
  112. package/dist/wireRefusalCopy.js +7 -1
  113. package/docs/INTEGRATION-CLIENTS.md +1189 -18
  114. package/package.json +5 -5
@@ -1,11 +1,11 @@
1
1
  import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
2
2
  import { engineCapTrue } from '../engineCapsCache.js';
3
3
  import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
4
- import { wireFailureShapeOf } from '../wireFailureShape.js';
4
+ import { slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
5
5
  import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { msg, renderMessage } from '../i18n/translate.js';
8
- import { guardedRead, readOwnDataValue } from '../ownKey.js';
8
+ import { appendOwnElement, guardedRead, readOwnDataValue } from '../ownKey.js';
9
9
  import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
10
10
  import { isToolApprovalRespondReplayed, readToolApprovalRespondAck } from './toolApprovalWire.js';
11
11
  export function persistedRulesLaneAvailable(baseUrl) {
@@ -17,27 +17,125 @@ export function persistedRulesGovernanceAvailable(baseUrl) {
17
17
  export function persistedRulesWriteAvailable(baseUrl) {
18
18
  return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
19
19
  }
20
- export function readCcImportRedeemCounts(result) {
21
- const o = (result ?? {});
22
- if (Array.isArray(o.members)) {
23
- let persisted = 0;
24
- let deduped = 0;
25
- for (const m of o.members) {
26
- const status = m?.status;
27
- if (status === 'persisted')
28
- persisted += 1;
29
- else if (status === 'deduped')
30
- deduped += 1;
20
+ const NO_INDETERMINATE = Object.freeze([]);
21
+ const INDETERMINATE_DETAIL_SHOWN_MAX = 400;
22
+ function ownArrayItems(v) {
23
+ try {
24
+ if (typeof v !== 'object' || v === null || !Array.isArray(v))
25
+ return undefined;
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ const len = slotReadOf(v, 'length');
31
+ if (len.presence !== 'present' || typeof len.value !== 'number' || !Number.isSafeInteger(len.value) || len.value < 0)
32
+ return undefined;
33
+ const out = [];
34
+ for (let i = 0; i < len.value; i++) {
35
+ const item = slotReadOf(v, String(i));
36
+ if (item.presence !== 'present')
37
+ return undefined;
38
+ appendOwnElement(out, item.value);
39
+ }
40
+ return out;
41
+ }
42
+ function ownNonEmpty(o, key) {
43
+ const r = slotReadOf(o, key);
44
+ return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
45
+ }
46
+ function ruleKeyOfRow(row) {
47
+ const k = slotReadOf(row, 'key');
48
+ const keyObj = k.presence === 'present' && typeof k.value === 'object' && k.value !== null ? k.value : undefined;
49
+ const behavior = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'behavior');
50
+ const rule = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'rule');
51
+ const scope = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'scope');
52
+ return behavior !== undefined && rule !== undefined && scope !== undefined ? Object.freeze({ behavior, rule, scope }) : undefined;
53
+ }
54
+ function indeterminateRowOf(row) {
55
+ const key = ruleKeyOfRow(row);
56
+ const reason = ownNonEmpty(row, 'reason');
57
+ const d = slotReadOf(row, 'detail');
58
+ const shown = d.presence === 'present' && typeof d.value === 'string' ? displayUntrusted(d.value, { max: INDETERMINATE_DETAIL_SHOWN_MAX }) : undefined;
59
+ return Object.freeze({
60
+ ...(key !== undefined ? { key } : {}),
61
+ ...(reason !== undefined ? { reason } : {}),
62
+ ...(shown !== undefined && shown.trim() !== '' ? { detail: shown } : {}),
63
+ });
64
+ }
65
+ function refusedRowOf(row) {
66
+ const key = ruleKeyOfRow(row);
67
+ return Object.freeze(key !== undefined ? { key } : {});
68
+ }
69
+ const RECEIPT_ROW_ARMS = {
70
+ persisted: (acc) => {
71
+ acc.persisted += 1;
72
+ },
73
+ 'no-op': (acc) => {
74
+ acc.deduped += 1;
75
+ },
76
+ refused: (acc, row) => {
77
+ appendOwnElement(acc.refused, refusedRowOf(row));
78
+ },
79
+ indeterminate: (acc, row) => {
80
+ appendOwnElement(acc.indeterminate, indeterminateRowOf(row));
81
+ },
82
+ };
83
+ function receiptCountsOf(rows) {
84
+ const acc = { persisted: 0, deduped: 0, indeterminate: [], refused: [] };
85
+ for (const row of rows) {
86
+ if (typeof row !== 'object' || row === null)
87
+ return null;
88
+ const o = slotReadOf(row, 'outcome');
89
+ const word = o.presence === 'present' ? o.value : undefined;
90
+ if (typeof word !== 'string' || !Object.hasOwn(RECEIPT_ROW_ARMS, word))
91
+ return null;
92
+ RECEIPT_ROW_ARMS[word](acc, row);
93
+ }
94
+ return {
95
+ persisted: acc.persisted,
96
+ deduped: acc.deduped,
97
+ indeterminate: acc.indeterminate.length === 0 ? NO_INDETERMINATE : Object.freeze(acc.indeterminate),
98
+ ...(acc.refused.length === 0 ? {} : { refused: Object.freeze(acc.refused) }),
99
+ };
100
+ }
101
+ function legacyEnvelopeCountsOf(result) {
102
+ if (typeof result !== 'object' || result === null)
103
+ return null;
104
+ const m = slotReadOf(result, 'members');
105
+ const rows = m.presence === 'present' ? ownArrayItems(m.value) : undefined;
106
+ if (rows === undefined)
107
+ return null;
108
+ let persisted = 0;
109
+ let deduped = 0;
110
+ for (const row of rows) {
111
+ if (typeof row !== 'object' || row === null)
112
+ continue;
113
+ const st = slotReadOf(row, 'status');
114
+ const status = st.presence === 'present' ? st.value : undefined;
115
+ if (status === 'persisted')
116
+ persisted += 1;
117
+ else if (status === 'deduped')
118
+ deduped += 1;
119
+ }
120
+ return { persisted, deduped, indeterminate: NO_INDETERMINATE };
121
+ }
122
+ export function readCcImportRedeemCounts(body) {
123
+ try {
124
+ if (typeof body !== 'object' || body === null)
125
+ return null;
126
+ const top = slotReadOf(body, 'members');
127
+ if (top.presence !== 'absent') {
128
+ if (top.presence !== 'present')
129
+ return null;
130
+ const rows = ownArrayItems(top.value);
131
+ return rows === undefined ? null : receiptCountsOf(rows);
31
132
  }
32
- return { persisted, deduped };
133
+ const legacy = slotReadOf(body, 'result');
134
+ return legacy.presence === 'present' ? legacyEnvelopeCountsOf(legacy.value) : null;
33
135
  }
34
- if (Array.isArray(o.persisted) || Array.isArray(o.deduped)) {
35
- return {
36
- persisted: Array.isArray(o.persisted) ? o.persisted.length : 0,
37
- deduped: Array.isArray(o.deduped) ? o.deduped.length : 0,
38
- };
136
+ catch {
137
+ return null;
39
138
  }
40
- return null;
41
139
  }
42
140
  export function classifyRulesFailure(e, context) {
43
141
  const { status, errorCode, message, retryAfterSec, committed } = wireFailureShapeOf(e);
@@ -1,5 +1,6 @@
1
1
  import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
2
2
  import { hostLog } from '../host.js';
3
+ import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
3
4
  import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
4
5
  import { clientHasVerbs, engineWireFor, noAnswerKindOf, sendOnce } from '../engineWireFor.js';
5
6
  import { engineWireTarget } from '../engineWireTarget.js';
@@ -677,6 +678,8 @@ function planReviewSettledVerdict(r) {
677
678
  return status !== 'completed' && !gateMoved ? 'still_parked' : 'unconfirmed';
678
679
  if (status === 'completed')
679
680
  return 'took_effect';
681
+ if (status === 'running' && r.decision === 'reject')
682
+ return 'took_effect';
680
683
  const newGateHasCard = () => {
681
684
  const leg = durableDecideLegOf({ gateKind: r.gateKindAfter ?? null });
682
685
  return leg === 'approval' || leg === 'plan_review';
@@ -874,7 +877,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
874
877
  const bodyStatus = bodyWordOf(body, 'status');
875
878
  const effective = postStatus ?? bodyStatus;
876
879
  statusSeen = effective;
877
- effect = planReviewSettledVerdict({ postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
880
+ effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
878
881
  const code = bodyWordOf(body, 'errorCode');
879
882
  if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
880
883
  outcome =
@@ -892,7 +895,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
892
895
  }
893
896
  else if (effect === 'took_effect') {
894
897
  outcome =
895
- `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} ` +
898
+ `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — ${effective === 'completed' ? 'final status' : 'status'}: ${effective} ` +
896
899
  `(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
897
900
  }
898
901
  else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
@@ -1026,22 +1029,17 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
1026
1029
  hostLog('debug', `planReviewWire: outcome enqueue threw: ${String(e)}`);
1027
1030
  }
1028
1031
  if (onOutcome !== undefined) {
1029
- try {
1030
- onOutcome({
1031
- taskId,
1032
- dispatchNo,
1033
- decision: semanticDecision,
1034
- effect,
1035
- ...(effect === 'still_parked' && stillParkedCause !== undefined ? { cause: stillParkedCause } : {}),
1036
- detail: outcome,
1037
- tail,
1038
- prompt,
1039
- ...(statusSeen !== undefined ? { status: statusSeen } : {}),
1040
- });
1041
- }
1042
- catch (e) {
1043
- hostLog('debug', `planReviewWire: onOutcome callback threw for task ${taskId} (ignored): ${String(e)}`);
1044
- }
1032
+ invokeHostCallbackIsolated(`planReviewWire: onOutcome callback (task ${taskId})`, () => onOutcome({
1033
+ taskId,
1034
+ dispatchNo,
1035
+ decision: semanticDecision,
1036
+ effect,
1037
+ ...(effect === 'still_parked' && stillParkedCause !== undefined ? { cause: stillParkedCause } : {}),
1038
+ detail: outcome,
1039
+ tail,
1040
+ prompt,
1041
+ ...(statusSeen !== undefined ? { status: statusSeen } : {}),
1042
+ }));
1045
1043
  }
1046
1044
  }
1047
1045
  function reopenIdSuffix() {
@@ -0,0 +1,3 @@
1
+ export declare const POLICY_DENY_CAUSES: readonly ["no_approval_ui", "dont_ask", "deny_rule", "engine_floor"];
2
+ export type PolicyDenyCause = (typeof POLICY_DENY_CAUSES)[number];
3
+ export declare function readPolicyDenyCause(decision: object | null | undefined): PolicyDenyCause | undefined;
@@ -0,0 +1,16 @@
1
+ export const POLICY_DENY_CAUSES = Object.freeze(['no_approval_ui', 'dont_ask', 'deny_rule', 'engine_floor']);
2
+ export function readPolicyDenyCause(decision) {
3
+ if (decision === null || typeof decision !== 'object')
4
+ return undefined;
5
+ let d;
6
+ try {
7
+ d = Object.getOwnPropertyDescriptor(decision, 'policyCause');
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ if (d === undefined || !Object.hasOwn(d, 'value'))
13
+ return undefined;
14
+ const v = d.value;
15
+ return typeof v === 'string' && POLICY_DENY_CAUSES.includes(v) ? v : undefined;
16
+ }
@@ -1,2 +1,7 @@
1
1
  export type SessionAllowLeg = 'frame' | 'v1' | 'generic';
2
2
  export declare function sessionAllowCarried(leg: SessionAllowLeg, approvalId: unknown): boolean;
3
+ export type SessionAllowWhy = 'given' | 'route_cannot_carry' | 'evidence_unreadable' | 'card_unreadable';
4
+ export type CardUnreadableEvidence = readonly string[] | 'absent' | 'unreadable';
5
+ export declare function readCardUnreadableEvidence(card: object): CardUnreadableEvidence;
6
+ export declare function sessionAllowCardReadable(card: object | null | undefined): Exclude<SessionAllowWhy, 'route_cannot_carry'>;
7
+ export declare function sessionAllowEligible(card: object | null | undefined): SessionAllowWhy;
@@ -5,3 +5,45 @@ export function sessionAllowCarried(leg, approvalId) {
5
5
  return typeof approvalId === 'string' && approvalId !== '';
6
6
  return false;
7
7
  }
8
+ export function readCardUnreadableEvidence(card) {
9
+ try {
10
+ if (!('unreadableEvidence' in card))
11
+ return 'absent';
12
+ const d = Object.getOwnPropertyDescriptor(card, 'unreadableEvidence');
13
+ const list = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
14
+ return Array.isArray(list) && list.every((k) => typeof k === 'string') ? list : 'unreadable';
15
+ }
16
+ catch {
17
+ return 'unreadable';
18
+ }
19
+ }
20
+ export function sessionAllowCardReadable(card) {
21
+ if (typeof card !== 'object' || card === null)
22
+ return 'card_unreadable';
23
+ try {
24
+ if ('cardPayloadUnreadable' in card)
25
+ return 'card_unreadable';
26
+ }
27
+ catch {
28
+ return 'card_unreadable';
29
+ }
30
+ const evidence = readCardUnreadableEvidence(card);
31
+ if (evidence === 'unreadable')
32
+ return 'card_unreadable';
33
+ return evidence !== 'absent' && evidence.length > 0 ? 'evidence_unreadable' : 'given';
34
+ }
35
+ export function sessionAllowEligible(card) {
36
+ if (typeof card !== 'object' || card === null)
37
+ return 'card_unreadable';
38
+ let carried;
39
+ try {
40
+ const d = Object.getOwnPropertyDescriptor(card, 'allowSessionCarried');
41
+ carried = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
42
+ }
43
+ catch {
44
+ return 'card_unreadable';
45
+ }
46
+ if (carried !== true)
47
+ return 'route_cannot_carry';
48
+ return sessionAllowCardReadable(card);
49
+ }
@@ -12,7 +12,7 @@ const WITHHELD_NOTICE = {
12
12
  qualified_deny: 'Not written to this session’s rule record: that record holds whole tool names and command names only, and this rule has a part in parentheses it cannot carry — recording just the name could block more than this rule does.',
13
13
  peer_wide: 'Not written to this session’s rule record: this rule names a server or agent peer without naming one of its tools, and an engine that matches that record by exact tool name would block none of that peer’s tools.',
14
14
  wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
15
- not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, has spaces around the name, is not a single tool name in rule syntax (for example it contains a backslash or a parenthesis, or is longer than a rule can be), or spells its server or agent peer part in a way the engine never uses when naming tools (for example with a dot or a space), so no tool would ever match it.',
15
+ not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, has spaces around the name, is not a single tool name in rule syntax (for example it contains a backslash or a parenthesis, or is longer than a rule can be), or spells its server or agent peer part in a form this record does not accept (for example with a dot or a space) · This record does not take entries of that form; record the exact name of a single tool instead.',
16
16
  legacy_tool_name: `Not written to this session’s rule record: unless a tool by exactly this name is available, the engine refuses it — it is a retired tool name, or it contains "__" but does not start with ${PREFIXES_IN_PROSE} — and with it in that record every later run of this session would fail at startup until that entry is removed from the record. ${REFUSED_NAME_FIX}`,
17
17
  not_in_roster: 'Not written to this session’s rule record: the tool roster supplied for this check has no tool this rule would block — no tool by exactly this name, as a name or an alias, with letter case counted, and, where the engine also reads the name as naming every tool of a server (a server whose own name contains "__"), no tool of that server either — it may be misspelled, or name a tool this deployment does not provide — so recorded as it is it would block nothing here.',
18
18
  namespace_covering_unconfirmed: msg('session.policy.withheld.namespaceCoveringUnconfirmed'),
@@ -4,6 +4,7 @@ import { readApprovalCardWord, readRuleOffers } from './toolApprovalWire.js';
4
4
  import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
5
5
  import { toolPermissionRequestIdDomain } from '../seatContract.js';
6
6
  import { permissionsDeclarationReport } from '../permissionsDeclaration.js';
7
+ import { readCardUnreadableEvidence, sessionAllowCardReadable, sessionAllowEligible } from './sessionAllowRoute.js';
7
8
  const UNREADABLE = Symbol('unreadable');
8
9
  function ownData(v, key) {
9
10
  try {
@@ -90,18 +91,17 @@ function enginePremiseOf(card) {
90
91
  return 'card_unreadable';
91
92
  if (refused.inCard)
92
93
  return refused.value === true ? 'remember_refused' : 'remember_unreadable';
93
- const evidence = present(card, 'unreadableEvidence');
94
- if (evidence === UNREADABLE)
94
+ const list = readCardUnreadableEvidence(card);
95
+ if (list === 'unreadable')
95
96
  return 'card_unreadable';
96
- if (!evidence.inCard)
97
+ if (list === 'absent')
97
98
  return { evidenceUnreadable: false };
98
- const list = ownData(card, 'unreadableEvidence');
99
- if (!Array.isArray(list) || !list.every((k) => typeof k === 'string'))
100
- return 'card_unreadable';
101
99
  if (list.includes('rememberRefused'))
102
100
  return 'remember_unreadable';
103
101
  return { evidenceUnreadable: list.length > 0 };
104
102
  }
103
+ const _sessionWhyPin = true;
104
+ void _sessionWhyPin;
105
105
  export function standingRowsOf(card, host) {
106
106
  if (typeof card !== 'object' || card === null)
107
107
  return allClosed('card_unreadable');
@@ -110,11 +110,11 @@ export function standingRowsOf(card, host) {
110
110
  if (typeof premise === 'string')
111
111
  return allClosed(premise);
112
112
  const evidenceUnreadable = premise.evidenceUnreadable;
113
- const sessionRow = ownData(card, 'allowSessionCarried') === true;
114
- const sessionWhy = sessionRow ? 'given' : 'route_cannot_carry';
113
+ const sessionWhy = sessionAllowEligible(card);
114
+ const sessionRow = sessionWhy === 'given';
115
115
  let deploymentList;
116
116
  if (!sessionRow) {
117
- deploymentList = 'route_cannot_carry';
117
+ deploymentList = sessionWhy;
118
118
  }
119
119
  else {
120
120
  const pair = readAskClassPairState(card);
@@ -143,7 +143,7 @@ export function seatSessionRowOf(card, requestId) {
143
143
  const domain = toolPermissionRequestIdDomain(requestId);
144
144
  if (domain !== 'live:' && domain !== 'durable:')
145
145
  return false;
146
- return typeof enginePremiseOf(card) !== 'string';
146
+ return typeof enginePremiseOf(card) !== 'string' && sessionAllowCardReadable(card) === 'given';
147
147
  }
148
148
  catch {
149
149
  return false;
@@ -1,4 +1,6 @@
1
1
  import { type HitlClientLike, type DecideOperationObserver } from './hitlBridge.js';
2
+ import { type ApprovalAskReason } from './approvalAskers.js';
3
+ export type { ApprovalAskReason, ApprovalAskReasonByHook, ApprovalAskReasonByPolicy } from './approvalAskers.js';
2
4
  import { readApprovalCardWord, type ApprovalCardWord } from './approvalCardReading.js';
3
5
  import { type ApprovalAllowRuleShadow } from './allowRuleShadow.js';
4
6
  import { CORE_PERSISTED_RULE_MATCHES } from '../generated/engineFactTables.js';
@@ -9,6 +11,7 @@ import { type ApprovalResolution } from './approvalResolution.js';
9
11
  import { type ApprovalMandateWord } from '../gateVocabulary.js';
10
12
  import type { ToolPermissionRequest } from '../seatContract.js';
11
13
  import { type ApprovalDecidedByReading } from '../approvalDecidedByCapability.js';
14
+ import { type PolicyDenyCause } from './policyDenyCause.js';
12
15
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS } from '@sema-agent/sdk';
13
16
  import type { ApprovalDecidedBy, ApprovalCardDelegation, ToolApprovalDecision as SdkToolApprovalDecision, ToolApprovalRespondAck, ToolApprovalRespondReplayed, PersistedRuleAnchor, RuleOfferMatch, RuleOfferBatchMember, RuleOfferUncoveredDetail, RuleOfferUncoveredReason, AskOrigin, RuleOffersAbsence, DenialLimitKind, DenialLimitFallback, ToolApprovalFrame, ApprovalRequestFrame, RuleOffer as SdkRuleOffer } from '@sema-agent/sdk';
14
17
  export declare function toolNameIsFsWrite(name: string): boolean;
@@ -71,6 +74,7 @@ export interface ApprovalCardDenyDecision {
71
74
  kind: 'deny';
72
75
  reason?: string;
73
76
  settledBy?: ApprovalDenySettledBy;
77
+ policyCause?: PolicyDenyCause;
74
78
  }
75
79
  export declare const APPROVAL_DENY_SETTLED_BY_WORDS: readonly ["human", "policy"];
76
80
  export type ApprovalDenySettledBy = (typeof APPROVAL_DENY_SETTLED_BY_WORDS)[number];
@@ -125,6 +129,8 @@ export interface ApprovalCardRequest {
125
129
  approvalCard?: ApprovalCardWord;
126
130
  askClass?: string;
127
131
  classes?: readonly string[];
132
+ agentId?: string;
133
+ askReasons?: readonly ApprovalAskReason[];
128
134
  boundGrants?: BoundSkillGrantsWire;
129
135
  grantBinding?: ApprovalGrantBinding;
130
136
  unreadableEvidence?: readonly string[];
@@ -163,7 +169,7 @@ export declare function approvalCardPortFor(sessionKey: string): ApprovalCardPor
163
169
  export declare function __resetSettledApprovalsForTests(): void;
164
170
  export declare function _resetApprovalCardPortForTest(): void;
165
171
  export declare function surfaceApprovalCard(req: ApprovalCardRequest): Promise<ApprovalCardDecision>;
166
- declare const REMEMBER_REFUSAL_CODES: readonly ["remember_not_in_proof", "capability.store_required", "decide.workflow_remember_unsupported", "decide.parked_remember_unsupported"];
172
+ declare const REMEMBER_REFUSAL_CODES: readonly ["remember_not_in_proof", "capability.store_required", "decide.workflow_remember_unsupported", "decide.parked_remember_unsupported", "remember_requires_binding"];
167
173
  export type RememberRefusalCode = (typeof REMEMBER_REFUSAL_CODES)[number];
168
174
  export declare function rememberRefusalOf(error: unknown): RememberRefusalCode | undefined;
169
175
  export declare function surfaceFsApprovalAndDecide(deps: FsApprovalWireDeps, taskId: string, argsByCall: Map<string, unknown>, signal?: AbortSignal, parkGatedCallId?: string, parkToolName?: string): Promise<ApprovalResolution>;
@@ -276,7 +282,7 @@ export declare function approvalSupersedesDetail(card: Pick<ApprovalCardRequest,
276
282
  export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback | undefined;
277
283
  export declare function readRuleOffers(v: unknown): RuleOfferReading[] | undefined;
278
284
  export declare function readRuleOfferSupply(offers: unknown): RuleOfferReading[] | undefined;
279
- export type ApprovalCardV1Facts = Pick<ApprovalCardRequest, 'toolName' | 'args'> & Pick<ApprovalCardRequest, 'toolCallId' | 'workerBadge' | 'wireNote' | 'argsUnavailable' | 'governanceForced' | 'delegation' | 'ruleOffersReadOnly' | 'inputHasBidi' | 'allowRuleShadow' | 'probeCause' | 'rememberRefused' | 'ruleEvidence' | 'requiresRealApproval' | 'ruleOffersAbsence' | 'denialLimitFallback' | 'askClass' | 'classes' | 'grantBinding' | 'unreadableEvidence' | 'message' | 'approvalCard'>;
285
+ export type ApprovalCardV1Facts = Pick<ApprovalCardRequest, 'toolName' | 'args'> & Pick<ApprovalCardRequest, 'toolCallId' | 'workerBadge' | 'wireNote' | 'argsUnavailable' | 'governanceForced' | 'delegation' | 'ruleOffersReadOnly' | 'inputHasBidi' | 'allowRuleShadow' | 'probeCause' | 'rememberRefused' | 'ruleEvidence' | 'requiresRealApproval' | 'ruleOffersAbsence' | 'denialLimitFallback' | 'askClass' | 'classes' | 'agentId' | 'askReasons' | 'grantBinding' | 'unreadableEvidence' | 'message' | 'approvalCard'>;
280
286
  export type ApprovalCardFacts = ApprovalCardV1Facts & Pick<ApprovalCardRequest, 'argsRewritten' | 'ruleOffers' | 'probeReason' | 'mandated' | 'mandate' | 'readRootCandidate' | 'origin' | 'boundGrants' | 'classifierNoRuling' | 'allowSessionCarried' | 'cardPayloadUnreadable'>;
281
287
  export interface ApprovalCardFrameFactsOptions {
282
288
  readonly streamArgs?: object;
@@ -7,6 +7,7 @@ import { trimEditedRuleText } from './editedRuleTextPrecheck.js';
7
7
  import { appendOwnElement, guardedRead, readAskOrigin, readOwnDataNonEmptyString, readOwnDataValue, readOwnNonEmptyString } from '../ownKey.js';
8
8
  import { ownReadingSlot } from '../engineWireFor.js';
9
9
  import { DEPLOYMENT_LIST_ASK_CLASSES, readAskClassPair } from './askClassPair.js';
10
+ import { approvalAgentIdOf, approvalAskReasonsOf } from './approvalAskers.js';
10
11
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
11
12
  import { approvalWindowKeysOf, readApprovalCardWord, readRequiresRealApproval } from './approvalCardReading.js';
12
13
  import { readAllowRuleShadowFromWire, retiredAllowRuleShadowKeyPresent, RETIRED_SHADOW_EVIDENCE_LABEL } from './allowRuleShadow.js';
@@ -15,7 +16,7 @@ import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
15
16
  import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
16
17
  import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_PERSISTED_RULE_MATCHES } from '../generated/engineFactTables.js';
17
18
  import { msg, renderMessage } from '../i18n/translate.js';
18
- import { sessionAllowCarried } from './sessionAllowRoute.js';
19
+ import { sessionAllowCarried, sessionAllowEligible } from './sessionAllowRoute.js';
19
20
  import { INTERRUPT_DENY_REASON, observeCancelByDeny, surfaceApprovalParking, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
20
21
  import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
21
22
  import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
@@ -28,6 +29,7 @@ import { parkRowDeadlineState } from './parkExpiryJudgements.js';
28
29
  import { isApprovalMandateWord } from '../gateVocabulary.js';
29
30
  import { approvalDecidedByAdmitted } from '../approvalDecidedByCapability.js';
30
31
  import { decideFailureNote } from '../decideFailureNote.js';
32
+ import { readPolicyDenyCause } from './policyDenyCause.js';
31
33
  import { slotPresenceOf, wireFailureTextsOf } from '../wireFailureShape.js';
32
34
  import { APPROVAL_LIVE_FACE_KEYS, approvalLiveFaceOf, hasOwnStrictTrue } from './approvalLiveFace.js';
33
35
  import { subagentSourceSessionOf } from './subagentSource.js';
@@ -157,11 +159,13 @@ function cardPortMissReason() {
157
159
  return APPROVAL_CARD_PORT_KEY_MISMATCH_REASON;
158
160
  return NO_APPROVAL_CARD_PORT_REASON;
159
161
  }
162
+ const LEGACY_REMEMBER_REFUSAL_CODE = 'remember_requires_binding';
160
163
  const REMEMBER_REFUSAL_CODES = Object.freeze([
161
164
  'remember_not_in_proof',
162
165
  'capability.store_required',
163
166
  DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED,
164
167
  'decide.parked_remember_unsupported',
168
+ LEGACY_REMEMBER_REFUSAL_CODE,
165
169
  ]);
166
170
  export function rememberRefusalOf(error) {
167
171
  return rememberRefusalIn(readWireErrorCodeReading(error));
@@ -449,7 +453,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
449
453
  closeDurableRead(readMark);
450
454
  }
451
455
  if (!found.ok)
452
- return { kind: 'not_sent', lane: 'decide', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
456
+ return { kind: 'not_sent', lane: 'decide', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
453
457
  if (held !== undefined)
454
458
  return joinDurableDecision(held, deps, signal);
455
459
  const rowIdentity = parkRowHolderIdentity(found.pending, found.gatedCallId);
@@ -500,7 +504,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
500
504
  const durableRuleOffersAbsence = pending.ruleOffersAbsence;
501
505
  hold.presented = true;
502
506
  deps.onPresented?.();
503
- const card = await surfaceCallTrackedCard(gatedCallId, undefined, ledgerSlot, withUnreadableEvidence(pending, 'row', {
507
+ const cardRequest = withUnreadableEvidence(pending, 'row', {
504
508
  toolName,
505
509
  args,
506
510
  callKey,
@@ -517,6 +521,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
517
521
  ...approvalLiveFaceOf(pending).facts,
518
522
  ...((w) => (w !== undefined ? { approvalCard: w } : {}))(readApprovalCardWord(pending)),
519
523
  ...(readAskClassPair(pending) ?? {}),
524
+ ...((r) => (r !== undefined ? { askReasons: r } : {}))(approvalAskReasonsOf(pending)),
520
525
  ...(pending.gateKind === 'irreversible_ask' ? { irreversibleParkGate: true } : {}),
521
526
  ...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
522
527
  ...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
@@ -533,7 +538,8 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
533
538
  allowSessionCarried: true,
534
539
  grantBinding: readApprovalGrantBinding(pending),
535
540
  parkRow: parkRowOf(taskId, pending, gatedCallId),
536
- }));
541
+ });
542
+ const card = await surfaceCallTrackedCard(gatedCallId, undefined, ledgerSlot, cardRequest);
537
543
  hold.phase = 'respond';
538
544
  if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
539
545
  hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
@@ -560,10 +566,15 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
560
566
  let rememberStop;
561
567
  try {
562
568
  let rememberRefused = false;
563
- const approveBody = answerPayloadFor(card, 'decide');
569
+ const sessionAllowWhy = sessionAllowEligible(cardRequest);
570
+ const sessionDowngraded = card.allowSession === true && sessionAllowWhy !== 'given';
571
+ if (sessionDowngraded)
572
+ hostLog('debug', `liveToolApprovalWire: ${gatedCallId ?? `task ${taskId}`} session-wide allow taken as allow-once — the card is not eligible (${sessionAllowWhy})`);
573
+ const answered = sessionDowngraded ? { ...card, allowSession: false } : card;
574
+ const approveBody = answerPayloadFor(answered, 'decide');
564
575
  const { remember: _remember, ...plainApproveBody } = approveBody;
565
576
  void _remember;
566
- emitAnswerCommitted(callKey, 'decide', card, approveBody);
577
+ emitAnswerCommitted(callKey, 'decide', answered, approveBody);
567
578
  if (approveBody.remember === 'session') {
568
579
  let sessionReceipt;
569
580
  let sessionDecided = false;
@@ -591,7 +602,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
591
602
  }
592
603
  const raw = await bridge.decideTool(plainApproveBody, gatedCallId, decideOpts, pending);
593
604
  const receipt = readDecideReceipt(raw);
594
- if (rememberRefused)
605
+ if (rememberRefused || sessionDowngraded)
595
606
  surfaceRememberNotApplied();
596
607
  return { kind: 'decided', ...at, decision: 'allow', ...(receipt !== undefined ? { receipt } : {}) };
597
608
  }
@@ -603,6 +614,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
603
614
  }
604
615
  case 'deny': {
605
616
  const denySettledByForWire = readApprovalDenySettledBy(card);
617
+ const denyPolicyCauseForWire = readPolicyDenyCause(card);
606
618
  const attributionKeys = decisionAttributionKeyOf(decidedByReading, denySettledByForWire);
607
619
  const denyReasonSnapshot = ((r) => (typeof r === 'string' ? r : undefined))(card.reason);
608
620
  let lastSendCarriedAttribution = false;
@@ -636,7 +648,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
636
648
  const attributionDropped = ladder.dropped;
637
649
  const attributionRenamed = ladder.renamed;
638
650
  const receipt = readDecideReceipt(raw);
639
- const deny = denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed);
651
+ const deny = denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed, denyPolicyCauseForWire);
640
652
  return {
641
653
  kind: 'decided',
642
654
  ...at,
@@ -1008,14 +1020,16 @@ function readDecidedByReadingOf(holder) {
1008
1020
  function decidedByReadingSnapshotOf(holder) {
1009
1021
  return approvalDecidedByAdmitted(readDecidedByReadingOf(holder)) ? Object.freeze({ kind: 'present', value: true }) : undefined;
1010
1022
  }
1011
- function denyFactsOf(settledBy, reasonSnapshot, dropped, renamed) {
1023
+ function denyFactsOf(settledBy, reasonSnapshot, dropped, renamed, policyCauseSnapshot) {
1012
1024
  const reason = reasonSnapshot !== undefined && reasonSnapshot.trim() !== '' ? reasonSnapshot : undefined;
1013
1025
  const attributionKey = dropped ? 'dropped' : renamed ? 'renamed' : undefined;
1026
+ const policyCause = settledBy === 'policy' ? policyCauseSnapshot : undefined;
1014
1027
  if (settledBy === undefined && reason === undefined && attributionKey === undefined)
1015
1028
  return undefined;
1016
1029
  return {
1017
1030
  ...(settledBy !== undefined ? { settledBy } : {}),
1018
1031
  ...(reason !== undefined ? { reason } : {}),
1032
+ ...(policyCause !== undefined ? { policyCause } : {}),
1019
1033
  ...(attributionKey !== undefined ? { attributionKey } : {}),
1020
1034
  };
1021
1035
  }
@@ -1816,6 +1830,16 @@ function cardFactsOf(carrier, leg, opts, liveFacts, approvalId) {
1816
1830
  if (w !== undefined)
1817
1831
  out.approvalCard = w;
1818
1832
  });
1833
+ v1Bit(() => {
1834
+ const id = approvalAgentIdOf(carrier);
1835
+ if (id !== undefined)
1836
+ out.agentId = id;
1837
+ });
1838
+ v1Bit(() => {
1839
+ const reasons = approvalAskReasonsOf(carrier);
1840
+ if (reasons !== undefined)
1841
+ out.askReasons = reasons;
1842
+ });
1819
1843
  }
1820
1844
  v1Bit(() => {
1821
1845
  const pair = readAskClassPair(carrier);
@@ -2038,15 +2062,22 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2038
2062
  enterLiveRespondPhase(hold);
2039
2063
  const noteCapable = lane?.approvalDecisionNoteCapable === true;
2040
2064
  const denyReasonRead = card.kind === 'deny' ? ((r) => (typeof r === 'string' ? r : undefined))(card.reason) : undefined;
2065
+ const sessionAllowWhy = sessionAllowEligible(facts);
2066
+ const sessionDowngraded = card.kind === 'allow' && card.allowSession === true && sessionAllowWhy !== 'given';
2067
+ if (sessionDowngraded)
2068
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} session-wide allow taken as allow-once — the card is not eligible (${sessionAllowWhy})`);
2041
2069
  const answerSnapshot = card.kind === 'deny'
2042
2070
  ? ((by) => ({ kind: 'deny', ...(denyReasonRead !== undefined ? { reason: denyReasonRead } : {}), ...(by !== undefined ? { settledBy: by } : {}) }))(readApprovalDenySettledBy(card))
2043
- : card;
2071
+ : sessionDowngraded && card.kind === 'allow'
2072
+ ? { ...card, allowSession: false }
2073
+ : card;
2044
2074
  const basePayload = answerPayloadFor(answerSnapshot, 'respond', { approvalDecisionNoteCapable: noteCapable });
2045
2075
  const decision = basePayload?.decision ?? 'deny';
2046
2076
  if (card.kind === 'allow' && typeof card.note === 'string' && card.note.trim() !== '' && answerCarriage({ callKey: liveFrameCallKey(approvalId) }).allowNote === 'none') {
2047
2077
  hostLog('debug', `liveToolApprovalWire: ${approvalId} card carried a note on Yes (len=${card.note.length}) — this engine has no field that brings it to the model, so it was not sent`);
2048
2078
  }
2049
2079
  const denySettledByForWire = card.kind === 'deny' ? readApprovalDenySettledBy(card) : undefined;
2080
+ const denyPolicyCauseForWire = card.kind === 'deny' ? readPolicyDenyCause(card) : undefined;
2050
2081
  const attributionKeys = decision === 'deny' ? decisionAttributionKeyOf(decidedByReading, denySettledByForWire) : {};
2051
2082
  const sentAttributionKey = carriesAttributionKey(attributionKeys);
2052
2083
  let lastSendCarriedAttribution = sentAttributionKey;
@@ -2273,6 +2304,8 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2273
2304
  logAfterDecided(`liveToolApprovalWire: ${approvalId} allow_session ack rememberApplied=false — grant not stored, surfacing honest notice`);
2274
2305
  surfaceRememberNotApplied();
2275
2306
  }
2307
+ if (replayed === undefined && sessionDowngraded)
2308
+ surfaceRememberNotApplied();
2276
2309
  if (respondOpts.updatedInput !== undefined && applied?.updatedInputForwarded === false) {
2277
2310
  logAfterDecided(`liveToolApprovalWire: ${approvalId} edited args were NOT forwarded (ack.updatedInputForwarded=false) — the tool runs on the ORIGINAL input`);
2278
2311
  surfaceEditNotForwarded();
@@ -2286,7 +2319,7 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2286
2319
  'this answer was not delivered, reporting the earlier decision');
2287
2320
  return { kind: 'decided', lane: 'respond', decision: replayed.decision, ack: replayed };
2288
2321
  }
2289
- const deny = decision === 'deny' ? denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed) : undefined;
2322
+ const deny = decision === 'deny' ? denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed, denyPolicyCauseForWire) : undefined;
2290
2323
  return { kind: 'decided', lane: 'respond', decision, ...(ack !== undefined ? { ack } : {}), ...(deny !== undefined ? { deny } : {}) };
2291
2324
  }
2292
2325
  catch (e) {
@@ -0,0 +1 @@
1
+ export declare function hooksReaderNotReadKeys(serverVersion: string | undefined): readonly string[];
@@ -0,0 +1,12 @@
1
+ import { engineVersionTier } from './supportFloor.js';
2
+ const HOOKS_READER_GENERATIONS = Object.freeze([
3
+ Object.freeze({ from: '7.139.0', below: '7.147.0', notRead: Object.freeze(['onFailure']) }),
4
+ ]);
5
+ const HOOKS_READER_FAIL_CLOSED = Object.freeze([...new Set(HOOKS_READER_GENERATIONS.flatMap((g) => g.notRead))]);
6
+ export function hooksReaderNotReadKeys(serverVersion) {
7
+ for (const g of HOOKS_READER_GENERATIONS) {
8
+ if (engineVersionTier(serverVersion, g.from) === 'engine' && engineVersionTier(serverVersion, g.below) === 'legacy')
9
+ return g.notRead;
10
+ }
11
+ return HOOKS_READER_FAIL_CLOSED;
12
+ }