@sema-agent/client-core 0.91.0 → 0.93.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 (195) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/README.md +51 -34
  3. package/dist/adapt/arms.js +6 -12
  4. package/dist/adapt/panelTasks.d.ts +0 -2
  5. package/dist/adapt/panelTasks.js +6 -26
  6. package/dist/adapt/toolCards.js +6 -4
  7. package/dist/adapt.d.ts +1 -1
  8. package/dist/adapt.js +3 -3
  9. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  10. package/dist/adapter/activeRunSelfHeal.js +5 -27
  11. package/dist/adapter/downstream/terminalToSdkResult.js +16 -7
  12. package/dist/adapter/runStream.js +6 -2
  13. package/dist/agentsWireCaps.js +1 -1
  14. package/dist/approvalDecidedByCapability.d.ts +16 -0
  15. package/dist/approvalDecidedByCapability.js +47 -0
  16. package/dist/approvalsStreamLiveCapability.js +3 -3
  17. package/dist/askSurvivesStandingAnswer.d.ts +4 -1
  18. package/dist/askSurvivesStandingAnswer.js +13 -0
  19. package/dist/autoModeUnavailable.js +3 -2
  20. package/dist/centerWiringCapability.js +1 -1
  21. package/dist/cloudConfigWireCaps.js +10 -9
  22. package/dist/compensations.js +7 -7
  23. package/dist/controlRouter.d.ts +23 -23
  24. package/dist/controlRouter.js +114 -49
  25. package/dist/coreValuePorts.d.ts +4 -2
  26. package/dist/coreValuePorts.js +5 -0
  27. package/dist/decideFailureNote.js +4 -2
  28. package/dist/decideReceipt.d.ts +11 -0
  29. package/dist/decideReceipt.js +95 -9
  30. package/dist/detachWire.d.ts +2 -14
  31. package/dist/detachWire.js +1 -47
  32. package/dist/deviceExecutorManagementCapability.d.ts +6 -2
  33. package/dist/deviceExecutorManagementCapability.js +61 -15
  34. package/dist/displayUntrusted.js +2 -1
  35. package/dist/effectiveFacts.d.ts +14 -4
  36. package/dist/effectiveFacts.js +45 -5
  37. package/dist/engineAgentAbsence.js +2 -1
  38. package/dist/engineCapReader.js +16 -10
  39. package/dist/engineCapsCache.d.ts +2 -0
  40. package/dist/engineCapsCache.js +13 -2
  41. package/dist/engineConfigDrift.d.ts +20 -0
  42. package/dist/engineConfigDrift.js +54 -0
  43. package/dist/engineErrorCodes.d.ts +17 -0
  44. package/dist/engineErrorCodes.js +45 -0
  45. package/dist/engineInlineTaskStats.d.ts +1 -0
  46. package/dist/engineNoticeCodes.d.ts +16 -2
  47. package/dist/engineNoticeCodes.js +71 -0
  48. package/dist/engineWireFor.d.ts +22 -0
  49. package/dist/engineWireFor.js +154 -9
  50. package/dist/engineWireSdk.d.ts +0 -1
  51. package/dist/engineWireSdk.js +0 -3
  52. package/dist/executionLaneCapability.js +4 -2
  53. package/dist/fileHistoryCaptureCapability.d.ts +1 -1
  54. package/dist/fileHistoryCaptureCapability.js +1 -6
  55. package/dist/fileHistoryLegNotice.d.ts +1 -1
  56. package/dist/fileHistoryLegNotice.js +1 -6
  57. package/dist/gateOutcome.js +2 -17
  58. package/dist/gateVocabulary.d.ts +2 -8
  59. package/dist/gateVocabulary.js +6 -32
  60. package/dist/generated/engineFactTables.d.ts +17 -1
  61. package/dist/generated/engineFactTables.js +91 -0
  62. package/dist/generated/engineNoticeTables.d.ts +1 -1
  63. package/dist/generated/engineNoticeTables.js +4 -1
  64. package/dist/generated/settingsSchemaTables.d.ts +8 -0
  65. package/dist/generated/settingsSchemaTables.js +40 -0
  66. package/dist/goalStopHook.d.ts +1 -8
  67. package/dist/goalStopHook.js +2 -58
  68. package/dist/headlessPermissionModeWire.d.ts +3 -2
  69. package/dist/headlessPermissionModeWire.js +46 -5
  70. package/dist/hitl/allowRuleShadow.d.ts +21 -0
  71. package/dist/hitl/allowRuleShadow.js +167 -0
  72. package/dist/hitl/askClassPair.d.ts +14 -0
  73. package/dist/hitl/askClassPair.js +42 -0
  74. package/dist/hitl/askGateWire.js +1 -0
  75. package/dist/hitl/askParkRowRouting.js +2 -2
  76. package/dist/hitl/askSurvivesPosture.d.ts +5 -0
  77. package/dist/hitl/askSurvivesPosture.js +17 -0
  78. package/dist/hitl/automaticAnswerVerdict.d.ts +21 -0
  79. package/dist/hitl/automaticAnswerVerdict.js +143 -0
  80. package/dist/hitl/crashConverged.d.ts +1 -0
  81. package/dist/hitl/crashConverged.js +1 -1
  82. package/dist/hitl/frameRouter.d.ts +2 -0
  83. package/dist/hitl/frameRouter.js +73 -16
  84. package/dist/hitl/gateLedger.d.ts +7 -0
  85. package/dist/hitl/gateLedger.js +37 -5
  86. package/dist/hitl/hitlBridge.d.ts +1 -0
  87. package/dist/hitl/hitlBridge.js +6 -1
  88. package/dist/hitl/hitlHostSurface.d.ts +5 -2
  89. package/dist/hitl/hitlHostSurface.js +25 -3
  90. package/dist/hitl/livePendingAsk.js +24 -5
  91. package/dist/hitl/parkResolver.js +24 -4
  92. package/dist/hitl/persistedRulesWire.d.ts +4 -1
  93. package/dist/hitl/persistedRulesWire.js +38 -19
  94. package/dist/hitl/planReviewWire.d.ts +0 -2
  95. package/dist/hitl/planReviewWire.js +3 -20
  96. package/dist/hitl/replayedApproval.d.ts +21 -0
  97. package/dist/hitl/replayedApproval.js +89 -0
  98. package/dist/hitl/sessionPolicyDeliverable.js +20 -2
  99. package/dist/hitl/sessionPolicyWire.d.ts +10 -0
  100. package/dist/hitl/sessionPolicyWire.js +19 -6
  101. package/dist/hitl/toolApprovalWire.d.ts +27 -7
  102. package/dist/hitl/toolApprovalWire.js +153 -94
  103. package/dist/hookMatcherVerdict.js +2 -1
  104. package/dist/hooksWireCaps.d.ts +1 -1
  105. package/dist/hooksWireCaps.js +1 -1
  106. package/dist/humanTurn.js +3 -4
  107. package/dist/index.d.ts +21 -6
  108. package/dist/index.js +17 -4
  109. package/dist/inheritEnvWire.d.ts +1 -4
  110. package/dist/inheritEnvWire.js +3 -22
  111. package/dist/limitsWire.d.ts +0 -6
  112. package/dist/limitsWire.js +0 -38
  113. package/dist/mcpLiveness.js +2 -1
  114. package/dist/mcpProbeCapability.js +1 -1
  115. package/dist/mcpProbeWire.js +10 -12
  116. package/dist/mcpReconnect.d.ts +5 -0
  117. package/dist/mcpReconnect.js +28 -0
  118. package/dist/memoryComplianceCapability.js +2 -2
  119. package/dist/memoryOriginCapability.js +2 -2
  120. package/dist/memoryVerbsWire.d.ts +1 -1
  121. package/dist/memoryVerbsWire.js +17 -13
  122. package/dist/model/catalog.js +2 -1
  123. package/dist/model/providerPresets.d.ts +3 -1
  124. package/dist/model/tierVocabulary.d.ts +2 -1
  125. package/dist/model/tierVocabulary.js +3 -8
  126. package/dist/ownKey.d.ts +2 -0
  127. package/dist/ownKey.js +15 -0
  128. package/dist/peerFrames.js +2 -2
  129. package/dist/peerLaneCapability.js +2 -2
  130. package/dist/permissionModesCapability.d.ts +16 -0
  131. package/dist/permissionModesCapability.js +71 -0
  132. package/dist/permissionRuleIssue.js +10 -7
  133. package/dist/permissionRulesWriteCapability.js +2 -2
  134. package/dist/permissionWireCaps.d.ts +2 -0
  135. package/dist/permissionWireCaps.js +8 -1
  136. package/dist/permissionsAllowGrantCapability.js +7 -2
  137. package/dist/permissionsDeclaration.d.ts +7 -1
  138. package/dist/permissionsDeclaration.js +29 -20
  139. package/dist/pluginHooksWire.js +2 -11
  140. package/dist/postureKnob.js +1 -1
  141. package/dist/promptProfileWireCaps.d.ts +2 -1
  142. package/dist/protocolSegment.d.ts +11 -0
  143. package/dist/protocolSegment.js +31 -0
  144. package/dist/readFacePosture.js +1 -1
  145. package/dist/request/taskRequest.js +1 -15
  146. package/dist/resumeRefusalCopy.d.ts +1 -0
  147. package/dist/resumeRefusalCopy.js +26 -3
  148. package/dist/retryStatus.js +2 -1
  149. package/dist/rewindArchiveCapability.js +2 -2
  150. package/dist/ruleCompileVerdict.d.ts +2 -0
  151. package/dist/ruleCompileVerdict.js +13 -0
  152. package/dist/ruleRemovalConsequence.d.ts +2 -1
  153. package/dist/runCancelContext.d.ts +12 -0
  154. package/dist/runCancelContext.js +83 -1
  155. package/dist/runTerminal.d.ts +16 -0
  156. package/dist/runTerminal.js +32 -3
  157. package/dist/seam.d.ts +1 -0
  158. package/dist/seam.js +1 -1
  159. package/dist/seatContract.d.ts +2 -1
  160. package/dist/sessionMemoryStatus.d.ts +2 -4
  161. package/dist/sessionPolicyCapability.js +1 -1
  162. package/dist/sessionSyncFileHistoryCapability.d.ts +18 -0
  163. package/dist/sessionSyncFileHistoryCapability.js +50 -0
  164. package/dist/skillToolListsCapability.d.ts +18 -0
  165. package/dist/skillToolListsCapability.js +50 -0
  166. package/dist/sqlEngineCapability.js +2 -9
  167. package/dist/steerPriorityCapability.d.ts +18 -0
  168. package/dist/steerPriorityCapability.js +80 -0
  169. package/dist/steerReceipt.d.ts +9 -0
  170. package/dist/steerReceipt.js +25 -0
  171. package/dist/storePostureCapability.js +2 -2
  172. package/dist/subagent/engineSubagentSteer.js +2 -9
  173. package/dist/supportFloor.d.ts +1 -0
  174. package/dist/supportFloor.js +15 -5
  175. package/dist/taskRequestWords.d.ts +4 -2
  176. package/dist/taskRequestWords.js +1 -1
  177. package/dist/toolHistoryBadTurn.js +1 -2
  178. package/dist/toolResult.d.ts +10 -1
  179. package/dist/toolResult.js +80 -6
  180. package/dist/types/engineState.d.ts +2 -12
  181. package/dist/userInvokedSkillsCapability.d.ts +18 -0
  182. package/dist/userInvokedSkillsCapability.js +50 -0
  183. package/dist/webSearchBackendCapability.js +2 -2
  184. package/dist/webSearchWireCaps.d.ts +1 -1
  185. package/dist/webSearchWireCaps.js +40 -2
  186. package/dist/wireFailureShape.d.ts +2 -0
  187. package/dist/wireFailureShape.js +6 -2
  188. package/dist/wireRefusalCopy.d.ts +9 -3
  189. package/dist/wireRefusalCopy.js +41 -4
  190. package/dist/workflowsJournalCapability.d.ts +18 -0
  191. package/dist/workflowsJournalCapability.js +50 -0
  192. package/dist/writeProtectionCapability.d.ts +2 -4
  193. package/dist/writeProtectionCapability.js +1 -1
  194. package/docs/INTEGRATION-CLIENTS.md +1516 -18
  195. package/package.json +2 -2
@@ -15,9 +15,9 @@ export function classifyAskParkRows(rows, taskId, deps) {
15
15
  kind: 'unborn',
16
16
  reason: rows.length === 0
17
17
  ?
18
- `no pending row at all for task ${taskId} — either an orphan park (a seat held with no decision outstanding) or the next gate's row is not minted yet`
18
+ `no pending row at all for task ${taskId} — either an orphan park (a seat held with no decision outstanding) or the next gate's row is not minted yet; this client cannot tell which`
19
19
  : soleUnowned
20
- ? `the only pending row (task ${String(sole?.taskId)} / session ${String(sole?.sessionId)}) could not be proven to belong to this client (not in its own-run ledger, session id absent or different) — refusing to surface an approval that may be another session's`
20
+ ? `the only pending row (task ${String(sole?.taskId)} / session ${String(sole?.sessionId)}) could not be proven to belong to this client (not in its own-run ledger, session id absent or different; this client cannot tell which) — refusing to surface an approval that may be another session's`
21
21
  : `${String(rows.length)} pending rows but none for task ${taskId} — ambiguous queue, refusing to surface an unrelated row`,
22
22
  };
23
23
  }
@@ -1,3 +1,4 @@
1
+ import { type AutomaticAnswerRoad, type AutomaticAnswerUnknownWhy } from './automaticAnswerVerdict.js';
1
2
  export interface AskPostureFacts {
2
3
  readonly settingsAskRuleMatched?: boolean;
3
4
  }
@@ -5,10 +6,14 @@ export type AskPostureSurvivalRoad = 'origin' | 'settings_ask_rule';
5
6
  export type AskPostureVerdict = {
6
7
  readonly kind: 'must_ask';
7
8
  readonly by: AskPostureSurvivalRoad;
9
+ readonly road?: AutomaticAnswerRoad;
10
+ readonly askClass?: string;
8
11
  } | {
9
12
  readonly kind: 'posture_may_answer';
13
+ readonly releaseReason?: string;
10
14
  } | {
11
15
  readonly kind: 'unknown';
16
+ readonly why?: AutomaticAnswerUnknownWhy;
12
17
  };
13
18
  export declare function askSurvivesPosture(req: {
14
19
  readonly origin?: string;
@@ -1,4 +1,5 @@
1
1
  import { readOwnNonEmptyString } from '../ownKey.js';
2
+ import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
2
3
  const ORIGIN_SURVIVES_POSTURE = Object.freeze({
3
4
  content_question: true,
4
5
  ancestor_marked: false,
@@ -17,6 +18,22 @@ const MUST_ASK_BY_SETTINGS = Object.freeze({ kind: 'must_ask', by: 'settings_ask
17
18
  const POSTURE_MAY_ANSWER = Object.freeze({ kind: 'posture_may_answer' });
18
19
  const UNKNOWN = Object.freeze({ kind: 'unknown' });
19
20
  export function askSurvivesPosture(req, facts) {
21
+ const old = legacyPostureVerdict(req, facts);
22
+ const neu = automaticAnswerVerdict(req, 'mode_posture', facts ?? undefined);
23
+ if (old.kind === 'must_ask' || neu.kind === 'must_ask') {
24
+ const by = old.kind === 'must_ask' ? old.by : neu.kind === 'must_ask' && neu.road === 'settings_ask_rule' ? 'settings_ask_rule' : 'origin';
25
+ const road = neu.kind === 'must_ask' ? neu.road : by === 'settings_ask_rule' ? 'settings_ask_rule' : 'origin';
26
+ const askClass = neu.kind === 'must_ask' ? neu.askClass : undefined;
27
+ return Object.freeze({ kind: 'must_ask', by, road, ...(askClass !== undefined ? { askClass } : {}) });
28
+ }
29
+ if (old.kind === 'unknown' || neu.kind === 'unknown') {
30
+ return neu.kind === 'unknown' ? Object.freeze({ kind: 'unknown', why: neu.why }) : UNKNOWN;
31
+ }
32
+ return neu.kind === 'may_answer' && neu.releaseReason !== undefined
33
+ ? Object.freeze({ kind: 'posture_may_answer', releaseReason: neu.releaseReason })
34
+ : POSTURE_MAY_ANSWER;
35
+ }
36
+ function legacyPostureVerdict(req, facts) {
20
37
  const origin = readOwnNonEmptyString(req, 'origin');
21
38
  const rawMatched = typeof facts === 'object' && facts !== null ? facts.settingsAskRuleMatched : undefined;
22
39
  const matched = typeof rawMatched === 'boolean' ? rawMatched : undefined;
@@ -0,0 +1,21 @@
1
+ import { type CoreModeArbitrationRow } from '../generated/engineFactTables.js';
2
+ export type AutomaticAnswerLane = 'mode_posture' | 'standing_rule';
3
+ export interface AutomaticAnswerFacts {
4
+ readonly settingsAskRuleMatched?: boolean;
5
+ }
6
+ export type AutomaticAnswerRoad = 'origin' | 'requires_real_approval' | 'governance' | 'mandate_word' | 'mandated' | 'allow_rule_shadow' | 'ask_class' | 'settings_ask_rule';
7
+ export type AutomaticAnswerUnknownWhy = 'card_unreadable' | 'lane_not_recognised' | 'classes_malformed' | 'classes_absent' | 'class_not_in_table' | 'host_not_reported';
8
+ export type AutomaticAnswerVerdict = {
9
+ readonly kind: 'must_ask';
10
+ readonly road: AutomaticAnswerRoad;
11
+ readonly askClass?: string;
12
+ readonly origin?: string;
13
+ } | {
14
+ readonly kind: 'may_answer';
15
+ readonly releaseReason?: string;
16
+ } | {
17
+ readonly kind: 'unknown';
18
+ readonly why: AutomaticAnswerUnknownWhy;
19
+ };
20
+ export declare function foldModeArbitrationRow(classes: Iterable<string>): CoreModeArbitrationRow;
21
+ export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
@@ -0,0 +1,143 @@
1
+ import { CORE_MODE_ARBITRATION, CORE_MODE_RELEASE_REASONS } from '../generated/engineFactTables.js';
2
+ import { readOwnNonEmptyString } from '../ownKey.js';
3
+ import { readAskClassPairState } from './askClassPair.js';
4
+ import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
5
+ import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
6
+ const TABLE = CORE_MODE_ARBITRATION;
7
+ const ORIGIN_IMPLIES_CLASS = Object.freeze({
8
+ content_question: 'content_question',
9
+ org_unavailable: 'governance_unavailable',
10
+ org_rule: 'org_rule',
11
+ hook: 'hook',
12
+ ask_rule: 'ask_rule',
13
+ denial_limit_fallback: 'denial_limit_fallback',
14
+ });
15
+ const MANDATE_WORD_IMPLIES_CLASS = Object.freeze({
16
+ operator_always: 'shell_floor',
17
+ write_protection_unresolved: 'write_protection_unresolved',
18
+ });
19
+ const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
20
+ export function foldModeArbitrationRow(classes) {
21
+ let any = false;
22
+ let bypassImmune = false;
23
+ let classifierEligible = true;
24
+ let sandboxAdmission = true;
25
+ let ruleMayClear = true;
26
+ let hookAskFloor = false;
27
+ for (const c of classes) {
28
+ if (!Object.hasOwn(TABLE, c))
29
+ continue;
30
+ const row = TABLE[c];
31
+ any = true;
32
+ bypassImmune ||= row.bypassImmune;
33
+ classifierEligible &&= row.classifierEligible;
34
+ sandboxAdmission &&= row.sandboxAdmission;
35
+ ruleMayClear &&= row.ruleMayClear;
36
+ hookAskFloor ||= row.hookAskFloor;
37
+ }
38
+ if (!any)
39
+ return ORDINARY_ROW;
40
+ return Object.freeze({ bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor });
41
+ }
42
+ function laneBlocks(lane, row) {
43
+ return lane === 'mode_posture' ? row.bypassImmune : !row.ruleMayClear;
44
+ }
45
+ function mustAsk(road, extra) {
46
+ return Object.freeze({ kind: 'must_ask', road, ...(extra?.askClass !== undefined ? { askClass: extra.askClass } : {}), ...(extra?.origin !== undefined ? { origin: extra.origin } : {}) });
47
+ }
48
+ const UNKNOWN = Object.freeze({
49
+ card_unreadable: Object.freeze({ kind: 'unknown', why: 'card_unreadable' }),
50
+ lane_not_recognised: Object.freeze({ kind: 'unknown', why: 'lane_not_recognised' }),
51
+ classes_malformed: Object.freeze({ kind: 'unknown', why: 'classes_malformed' }),
52
+ classes_absent: Object.freeze({ kind: 'unknown', why: 'classes_absent' }),
53
+ class_not_in_table: Object.freeze({ kind: 'unknown', why: 'class_not_in_table' }),
54
+ host_not_reported: Object.freeze({ kind: 'unknown', why: 'host_not_reported' }),
55
+ });
56
+ const MAY_ANSWER = Object.freeze({ kind: 'may_answer' });
57
+ function evidenceView(card, ...keys) {
58
+ const out = {};
59
+ for (const k of keys) {
60
+ const v = card[k];
61
+ if (v !== undefined)
62
+ out[k] = v;
63
+ }
64
+ return out;
65
+ }
66
+ function readHostAskRuleMatched(facts) {
67
+ if (typeof facts !== 'object' || facts === null)
68
+ return undefined;
69
+ try {
70
+ const d = Object.getOwnPropertyDescriptor(facts, 'settingsAskRuleMatched');
71
+ return d !== undefined && 'value' in d && typeof d.value === 'boolean' ? d.value : undefined;
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ export function automaticAnswerVerdict(card, lane, facts) {
78
+ if (lane !== 'mode_posture' && lane !== 'standing_rule')
79
+ return UNKNOWN.lane_not_recognised;
80
+ try {
81
+ if (typeof card !== 'object' || card === null)
82
+ return UNKNOWN.card_unreadable;
83
+ const origin = readOwnNonEmptyString(evidenceView(card, 'origin'), 'origin');
84
+ if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
85
+ const cls = ORIGIN_IMPLIES_CLASS[origin];
86
+ if (laneBlocks(lane, TABLE[cls]))
87
+ return mustAsk('origin', { origin, askClass: cls });
88
+ }
89
+ if (lane === 'standing_rule' && evidenceView(card, 'requiresRealApproval').requiresRealApproval === true)
90
+ return mustAsk('requires_real_approval');
91
+ if (evidenceView(card, 'governanceForced').governanceForced === true)
92
+ return mustAsk('governance');
93
+ if (lane === 'mode_posture') {
94
+ const word = readApprovalMandate(evidenceView(card, 'mandate'));
95
+ if (word !== undefined && Object.hasOwn(MANDATE_WORD_IMPLIES_CLASS, word)) {
96
+ const cls = MANDATE_WORD_IMPLIES_CLASS[word];
97
+ if (laneBlocks(lane, TABLE[cls]))
98
+ return mustAsk('mandate_word', { askClass: cls });
99
+ }
100
+ }
101
+ else {
102
+ const mandateView = evidenceView(card, 'mandated', 'ruleOffersAbsence', 'mandate');
103
+ if (approvalIsMandated(mandateView))
104
+ return mustAsk('mandated');
105
+ if (approvalAllowRuleShadowed(card) || readOwnNonEmptyString(mandateView, 'ruleOffersAbsence') === 'shadowed')
106
+ return mustAsk('allow_rule_shadow');
107
+ }
108
+ const pair = readAskClassPairState(card);
109
+ if (pair.state === 'malformed')
110
+ return UNKNOWN.classes_malformed;
111
+ if (pair.state === 'absent')
112
+ return UNKNOWN.classes_absent;
113
+ const folded = foldModeArbitrationRow(pair.classes);
114
+ let anyKnown = false;
115
+ let firstBlocking;
116
+ let anyUnknown = false;
117
+ for (const c of pair.classes) {
118
+ if (!Object.hasOwn(TABLE, c)) {
119
+ anyUnknown = true;
120
+ continue;
121
+ }
122
+ anyKnown = true;
123
+ if (firstBlocking === undefined && laneBlocks(lane, TABLE[c]))
124
+ firstBlocking = c;
125
+ }
126
+ if (anyKnown && laneBlocks(lane, folded))
127
+ return mustAsk('ask_class', firstBlocking !== undefined ? { askClass: firstBlocking } : undefined);
128
+ if (anyUnknown)
129
+ return UNKNOWN.class_not_in_table;
130
+ if (lane === 'standing_rule')
131
+ return MAY_ANSWER;
132
+ const matched = readHostAskRuleMatched(facts);
133
+ if (matched === true)
134
+ return mustAsk('settings_ask_rule');
135
+ if (matched !== false)
136
+ return UNKNOWN.host_not_reported;
137
+ const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => pair.classes.includes(r));
138
+ return releaseReason !== undefined ? Object.freeze({ kind: 'may_answer', releaseReason }) : MAY_ANSWER;
139
+ }
140
+ catch {
141
+ return UNKNOWN.card_unreadable;
142
+ }
143
+ }
@@ -16,6 +16,7 @@ export interface CrashConvergedRow {
16
16
  readonly resumeSafe: boolean;
17
17
  readonly needsHumanReason?: 'approved_then_interrupted' | 'pending_unsafe' | 'unstable_row' | 'contradictory';
18
18
  }
19
+ export declare const isCrashConvergedCause: (v: unknown) => v is CrashConvergedRow["cause"];
19
20
  export interface ApprovalsListEnvelope {
20
21
  pending: PendingCheckpoint[];
21
22
  livePending?: unknown[];
@@ -2,7 +2,7 @@ const CRASH_CONVERGED_CAUSE_WORDS = Object.freeze({
2
2
  crashed_before_park: true,
3
3
  shutdown_before_park: true,
4
4
  });
5
- const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
5
+ export const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
6
6
  const isString = (v) => typeof v === 'string';
7
7
  const isNumber = (v) => typeof v === 'number';
8
8
  const nonEmptyString = (v) => typeof v === 'string' && v.length > 0;
@@ -2,6 +2,7 @@ import type { AgentEvent } from '@sema-agent/sdk';
2
2
  import type { DecideOperationObserver, HitlClientLike } from './hitlBridge.js';
3
3
  import { type RespondToolApprovalFn, type ToolApprovalFrame, type ToolApprovalFrameLaneOpts, type ToolApprovalFrameOutcome } from './toolApprovalWire.js';
4
4
  import type { GateLedger } from './gateLedger.js';
5
+ import { type ReplayedApprovalDisposition } from './replayedApproval.js';
5
6
  export declare const HITL_REJECT_MESSAGE = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
6
7
  export declare const HITL_POLICY_DENY_MESSAGE: string;
7
8
  export declare const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
@@ -23,6 +24,7 @@ export interface AskGateWireDeps {
23
24
  approvalLane?: ToolApprovalFrameLaneOpts;
24
25
  onToolApprovalOutcome?: (frame: ToolApprovalFrame, outcome: ToolApprovalFrameOutcome) => void | Promise<void>;
25
26
  onDecideOperation?: DecideOperationObserver;
27
+ onReplayedApproval?: (frame: ToolApprovalFrame, disposition: ReplayedApprovalDisposition) => void | Promise<void>;
26
28
  }
27
29
  type GateKind = 'ask' | 'fs';
28
30
  export interface GatePark {
@@ -3,8 +3,10 @@ import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolAppro
3
3
  import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
4
4
  import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
5
5
  import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
6
- import { surfaceForCurrentSession } from './hitlHostSurface.js';
7
- import { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
6
+ import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
7
+ import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
8
+ import { replayedApprovalDisposition } from './replayedApproval.js';
9
+ import { settleWithinDeadline } from '../abortableSleep.js';
8
10
  import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
9
11
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
10
12
  import { RUN_CANCELLED_CODE } from '../runCancelContext.js';
@@ -64,6 +66,11 @@ function isGatedToolName(name) {
64
66
  function isFsOrShellToolName(name) {
65
67
  return typeof name === 'string' && (toolNameIsFsWrite(name) || toolNameIsShellExec(name));
66
68
  }
69
+ function parkFamilyEvidence(led, gateToolName, gatedCallId) {
70
+ if (!isFsOrShellToolName(gateToolName))
71
+ return false;
72
+ return gatedCallId === undefined || led.isFsOrShellGatedCall(gatedCallId);
73
+ }
67
74
  const ENGINE_GATE_PARKED_ERROR_CODE = GATE_PARKED_ERROR_CODE;
68
75
  function isEngineAbortToolEnd(ev) {
69
76
  if (ev.errorCode === ENGINE_GATE_PARKED_ERROR_CODE)
@@ -233,22 +240,36 @@ async function routeToolApprovalFrame(ev, ctx) {
233
240
  hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} but no respond wire — engine self-settles (TTL deny)`);
234
241
  return { kind: 'skip' };
235
242
  }
236
- if (isAskToolLoose(ev.toolName)) {
237
- try {
238
- await deps.respondToolApproval(ev.approvalId, 'allow', {
239
- ...(ctx.signal && !ctx.signal.aborted ? { signal: ctx.signal } : {}),
240
- });
241
- hostLog('debug', `liveHitlAskWire: ask-tool approval frame ${ev.approvalId} → allow(问题门放行,候 park 弹卡)`);
242
- }
243
- catch (e) {
244
- hostLog('debug', `liveHitlAskWire: ask-tool approval allow failed for ${ev.approvalId}: ${String(e)} — engine self-settles (TTL)`);
243
+ if (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId)) {
244
+ const disposition = await judgeLedgerApprovalFrame(ev, ctx);
245
+ if (disposition.kind !== 'live') {
246
+ hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
247
+ `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
248
+ ' — not raised as a question, no decision sent');
249
+ if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
250
+ surfaceReplayedApprovalListUnreadable();
251
+ if (deps.onReplayedApproval !== undefined) {
252
+ try {
253
+ const out = deps.onReplayedApproval(ev, disposition);
254
+ if (out && typeof out.then === 'function') {
255
+ void Promise.resolve(out).catch((e) => {
256
+ hostLog('debug', `liveHitlAskWire: onReplayedApproval rejected: ${String(e).slice(0, 160)}`);
257
+ });
258
+ }
259
+ }
260
+ catch (e) {
261
+ hostLog('debug', `liveHitlAskWire: onReplayedApproval threw: ${String(e).slice(0, 160)}`);
262
+ }
263
+ }
264
+ return { kind: 'skip' };
245
265
  }
246
- return { kind: 'skip' };
247
266
  }
248
267
  const fromSubagent = isFromSubagent(ev);
249
268
  hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} tool=${String(ev.toolName)}${fromSubagent ? ` from-subagent=${String(ev.sourceTaskId ?? ev.sourceAgentName ?? 'explicit')}` : ''}`);
250
- const gatedCallId = !fromSubagent ? led.lastPendingFsCall() : undefined;
251
- const outcome = await surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, gatedCallId), ctx.signal, deps.approvalLane);
269
+ const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
270
+ const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
271
+ const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
272
+ const outcome = await surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane);
252
273
  const { decision } = outcome;
253
274
  if (deps.onToolApprovalOutcome !== undefined) {
254
275
  try {
@@ -275,6 +296,30 @@ async function routeToolApprovalFrame(ev, ctx) {
275
296
  }
276
297
  return { kind: 'skip' };
277
298
  }
299
+ const REPLAYED_APPROVAL_LIST_DEADLINE_MS = 3000;
300
+ async function judgeLedgerApprovalFrame(ev, ctx) {
301
+ const outer = ctx.signal;
302
+ const got = await settleWithinDeadline(REPLAYED_APPROVAL_LIST_DEADLINE_MS, (deadline) => {
303
+ const ac = new AbortController();
304
+ const release = () => {
305
+ deadline.removeEventListener('abort', forward);
306
+ outer?.removeEventListener('abort', forward);
307
+ };
308
+ const forward = () => {
309
+ ac.abort();
310
+ release();
311
+ };
312
+ deadline.addEventListener('abort', forward, { once: true });
313
+ outer?.addEventListener('abort', forward, { once: true });
314
+ if (outer?.aborted === true)
315
+ forward();
316
+ return Promise.resolve()
317
+ .then(() => ctx.deps.client.approvals.list({ signal: ac.signal }))
318
+ .finally(release);
319
+ });
320
+ const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
321
+ return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
322
+ }
278
323
  function routeToolStart(ev, callId, led) {
279
324
  led.noteStart(callId, ev);
280
325
  if (!led.markStarted(callId))
@@ -313,7 +358,7 @@ function routeSuspended(ev, ctx) {
313
358
  if (ev.gate &&
314
359
  isToolApprovalGate(ev.gate) &&
315
360
  ctx.taskId.current) {
316
- ctx.led.noteParkGate({ fsOrShellFamily: isFsOrShellToolName(ev.gate.toolName) });
361
+ ctx.led.noteParkGate({ fsOrShellFamily: parkFamilyEvidence(ctx.led, ev.gate.toolName, gatedCallId) });
317
362
  return { kind: 'park', gate: 'fs', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
318
363
  }
319
364
  return { kind: 'yield', events: [ev] };
@@ -324,7 +369,7 @@ function routeDone(ev, ctx) {
324
369
  const park = parkGateOf(ev.result);
325
370
  if (park !== null) {
326
371
  ctx.taskId.current = ctx.taskId.current ?? park.taskId;
327
- led.noteParkGate({ fsOrShellFamily: park.gate === 'fs' && isFsOrShellToolName(park.toolName) });
372
+ led.noteParkGate({ fsOrShellFamily: park.gate === 'fs' && parkFamilyEvidence(led, park.toolName, gatedCallId) });
328
373
  return { kind: 'park', gate: park.gate, pendingDone: ev, ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
329
374
  }
330
375
  const events = [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal })];
@@ -355,5 +400,17 @@ export async function routeFrame(ev, ctx) {
355
400
  if (ev.type === 'failed') {
356
401
  return { kind: 'end', events: [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal }), ev] };
357
402
  }
403
+ if (ev.type === 'approval_request') {
404
+ try {
405
+ if (!hasLedgerPositionKey(ev)) {
406
+ const id = ev.approvalId;
407
+ if (typeof id === 'string' && id !== '')
408
+ led.notePreambleApproval(id);
409
+ }
410
+ }
411
+ catch (e) {
412
+ hostLog('debug', `liveHitlAskWire: approval_request frame unreadable — not counted as preamble evidence: ${String(e).slice(0, 120)}`);
413
+ }
414
+ }
358
415
  return { kind: 'yield', events: [ev] };
359
416
  }
@@ -13,10 +13,16 @@ export interface AskAnsweredOutput {
13
13
  notes?: string;
14
14
  }>;
15
15
  }
16
+ export declare function durableSeqOf(ev: object): string | undefined;
17
+ export declare function hasLedgerPositionKey(ev: object): boolean;
18
+ export declare const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
16
19
  export interface GateLedger {
17
20
  noteSeq(ev: AgentEvent): void;
18
21
  lastSeq(): string | undefined;
19
22
  forgetRunScopedState(): void;
23
+ notePreambleApproval(approvalId: string): void;
24
+ preambleApprovals(): ReadonlySet<string>;
25
+ beginStreamSegment(): void;
20
26
  markStarted(callId: string): boolean;
21
27
  noteGatedStart(callId: string, ev: AgentEvent): void;
22
28
  gatedStartArgs(): ReadonlyMap<string, unknown>;
@@ -40,6 +46,7 @@ export interface GateLedger {
40
46
  lastPendingFsCall(): string | undefined;
41
47
  noteFsOrShellGate(callId: string): void;
42
48
  lastFsOrShellGatedCallId(): string | undefined;
49
+ isFsOrShellGatedCall(callId: string): boolean;
43
50
  noteParkGate(evidence: {
44
51
  fsOrShellFamily: boolean;
45
52
  }): void;
@@ -1,5 +1,18 @@
1
1
  export const SEMA_COLLATERAL_ABORT_KEY = '_sema_collateral_abort';
2
2
  export const SEMA_DENIAL_KIND_KEY = '_sema_denial_kind';
3
+ export function durableSeqOf(ev) {
4
+ const rawSeq = ev.id ?? ev.seq;
5
+ if (typeof rawSeq === 'string' && rawSeq.length > 0)
6
+ return rawSeq;
7
+ if (typeof rawSeq === 'number' && Number.isFinite(rawSeq))
8
+ return String(rawSeq);
9
+ return undefined;
10
+ }
11
+ export function hasLedgerPositionKey(ev) {
12
+ const { id, seq } = ev;
13
+ return (id !== undefined && id !== null) || (seq !== undefined && seq !== null);
14
+ }
15
+ export const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
3
16
  export const START_ARGS_MAX_ENTRIES = 256;
4
17
  function normalizeDenyAttribution(a) {
5
18
  if (a === undefined)
@@ -74,13 +87,12 @@ export function createGateLedger() {
74
87
  let denyStampNextFsEnd = false;
75
88
  let denyStampAttribution;
76
89
  let seq;
90
+ const preambleApprovalIds = new Set();
77
91
  return {
78
92
  noteSeq(ev) {
79
- const rawSeq = ev.id ?? ev.seq;
80
- if (typeof rawSeq === 'string' && rawSeq.length > 0)
81
- seq = rawSeq;
82
- else if (typeof rawSeq === 'number' && Number.isFinite(rawSeq))
83
- seq = String(rawSeq);
93
+ const s = durableSeqOf(ev);
94
+ if (s !== undefined)
95
+ seq = s;
84
96
  },
85
97
  lastSeq() {
86
98
  return seq;
@@ -91,6 +103,23 @@ export function createGateLedger() {
91
103
  gatedStartArgsByCall.clear();
92
104
  startArgsByCall.clear();
93
105
  },
106
+ notePreambleApproval(approvalId) {
107
+ if (typeof approvalId !== 'string' || approvalId === '')
108
+ return;
109
+ preambleApprovalIds.delete(approvalId);
110
+ preambleApprovalIds.add(approvalId);
111
+ if (preambleApprovalIds.size > PREAMBLE_APPROVALS_MAX_ENTRIES) {
112
+ const oldest = preambleApprovalIds.values().next().value;
113
+ if (oldest !== undefined)
114
+ preambleApprovalIds.delete(oldest);
115
+ }
116
+ },
117
+ preambleApprovals() {
118
+ return preambleApprovalIds;
119
+ },
120
+ beginStreamSegment() {
121
+ preambleApprovalIds.clear();
122
+ },
94
123
  markStarted(callId) {
95
124
  if (startedCalls.has(callId))
96
125
  return false;
@@ -206,6 +235,9 @@ export function createGateLedger() {
206
235
  lastFsOrShellGatedCallId() {
207
236
  return lastFsOrShellGatedCall;
208
237
  },
238
+ isFsOrShellGatedCall(callId) {
239
+ return fsOrShellGatedCalls.has(callId);
240
+ },
209
241
  noteParkGate(evidence) {
210
242
  if (batchParkCount > 0 && !heldSinceLastPark)
211
243
  return;
@@ -56,6 +56,7 @@ export interface HitlDecideDenyOutcome {
56
56
  decision: 'deny';
57
57
  reason?: string | undefined;
58
58
  settledBy?: 'policy' | undefined;
59
+ decidedBy?: 'policy' | undefined;
59
60
  }
60
61
  export declare class HitlSafetyError extends Error {
61
62
  readonly code: HitlSafetyCode;
@@ -2,6 +2,7 @@ import { askParkForeignGateKind } from '../adapter/activeRunSelfHeal.js';
2
2
  import { eventSeq } from '../adapter/types.js';
3
3
  import { hostLog } from '../host.js';
4
4
  import { abortableSleep } from '../abortableSleep.js';
5
+ import { readOwnDataValue } from '../ownKey.js';
5
6
  import { suspendedReopenOf } from './suspendedReopen.js';
6
7
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
7
8
  const MAX_DENY_REASON_CHARS = 4096;
@@ -291,7 +292,11 @@ export class HitlBridge {
291
292
  decision: 'deny',
292
293
  ...this.bindingOf(pending),
293
294
  ...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),
294
- ...(outcome.settledBy === 'policy' ? { settledBy: 'policy' } : {}),
295
+ ...(readOwnDataValue(outcome, 'decidedBy') === 'policy'
296
+ ? { decidedBy: 'policy' }
297
+ : readOwnDataValue(outcome, 'settledBy') === 'policy'
298
+ ? { settledBy: 'policy' }
299
+ : {}),
295
300
  };
296
301
  return this.decideRaw(pending.sessionId, decision, opts);
297
302
  }
@@ -28,10 +28,13 @@ export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow w
28
28
  export declare const EDIT_NOT_FORWARDED_WARN_TEXT = "your edited tool input was NOT forwarded \u2014 the tool is running with the ORIGINAL input you saw before editing";
29
29
  export declare function surfaceRememberNotApplied(): void;
30
30
  export declare function surfaceEditNotForwarded(): void;
31
+ export declare const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT: string;
32
+ export declare function surfaceReplayedApprovalListUnreadable(): void;
31
33
  export declare const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT: string;
32
34
  export declare function surfaceEditRefusedOnBlindAsk(): void;
33
35
  export declare const RULE_NOT_SENT_WARN_TEXT = "your \"don't ask again\" choice was NOT sent to this engine \u2014 support for that rule form could not be confirmed. The approval itself went through, but you will be asked again";
34
36
  export declare function surfaceRuleArmNotSent(): void;
35
- export declare const RULE_NOT_SENT_REJECTED_WARN_TEXT = "your \"don't ask again\" choice was NOT sent \u2014 it did not pass the client-side check (the card returned a rule the engine never offered, an index that is not a batch offer, or two conflicting choices at once). The approval itself went through, but you will be asked again";
36
- export declare function surfaceRuleArmRejected(): void;
37
+ export type RuleArmRejectedWhy = 'both_arms' | 'index_not_a_batch_offer' | 'text_not_offered';
38
+ export declare const RULE_NOT_SENT_REJECTED_WARN_TEXT: string;
39
+ export declare function surfaceRuleArmRejected(why?: RuleArmRejectedWhy): void;
37
40
  export declare function observeCancelByDeny(settle: Promise<unknown>, taskId: string): void;
@@ -72,6 +72,13 @@ export function surfaceRememberNotApplied() {
72
72
  export function surfaceEditNotForwarded() {
73
73
  surfaceSelfClearingWarn(EDIT_NOT_FORWARDED_KEY, EDIT_NOT_FORWARDED_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
74
74
  }
75
+ export const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT = "Could not read the pending-approval list, so an approval request read back from this run's event log was not raised as a question, " +
76
+ 'and no decision was sent for it.';
77
+ const REPLAYED_APPROVAL_LIST_UNREADABLE_KEY = 'hitl-replayed-approval-list-unreadable';
78
+ const REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS = 10_000;
79
+ export function surfaceReplayedApprovalListUnreadable() {
80
+ surfaceSelfClearingWarn(REPLAYED_APPROVAL_LIST_UNREADABLE_KEY, REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT, REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS);
81
+ }
75
82
  export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT = 'That approval was NOT sent: this request does not expose its tool arguments here, so an edited approval cannot be applied. ' +
76
83
  'The request is still waiting — approve or deny it as it is.';
77
84
  const EDIT_REFUSED_ON_BLIND_ASK_KEY = 'hitl-edit-refused-blind-ask';
@@ -84,11 +91,26 @@ const RULE_NOT_SENT_TIMEOUT_MS = 10_000;
84
91
  export function surfaceRuleArmNotSent() {
85
92
  surfaceSelfClearingWarn(RULE_NOT_SENT_KEY, RULE_NOT_SENT_WARN_TEXT, RULE_NOT_SENT_TIMEOUT_MS);
86
93
  }
87
- export const RULE_NOT_SENT_REJECTED_WARN_TEXT = 'your "don\'t ask again" choice was NOT sent — it did not pass the client-side check (the card returned a rule the engine never offered, an index that is not a batch offer, or two conflicting choices at once). The approval itself went through, but you will be asked again';
94
+ function ruleNotSentRejectedReason(why) {
95
+ switch (why) {
96
+ case 'both_arms':
97
+ return 'the card returned two conflicting choices at once, a rule text and a batch index';
98
+ case 'index_not_a_batch_offer':
99
+ return 'the card returned an index that is not a batch offer';
100
+ case 'text_not_offered':
101
+ return 'the card returned a rule text that is not one of the single rules the engine offered';
102
+ default:
103
+ return 'the card returned a rule text that is not one of the single rules the engine offered, an index that is not a batch offer, or two conflicting choices at once; this client cannot tell which';
104
+ }
105
+ }
106
+ function ruleNotSentRejectedText(why) {
107
+ return `your "don't ask again" choice was NOT sent — it did not pass the client-side check (${ruleNotSentRejectedReason(why)}). The approval itself went through, but you will be asked again`;
108
+ }
109
+ export const RULE_NOT_SENT_REJECTED_WARN_TEXT = ruleNotSentRejectedText();
88
110
  const RULE_NOT_SENT_REJECTED_KEY = 'hitl-rule-arm-rejected';
89
111
  const RULE_NOT_SENT_REJECTED_TIMEOUT_MS = 10_000;
90
- export function surfaceRuleArmRejected() {
91
- surfaceSelfClearingWarn(RULE_NOT_SENT_REJECTED_KEY, RULE_NOT_SENT_REJECTED_WARN_TEXT, RULE_NOT_SENT_REJECTED_TIMEOUT_MS);
112
+ export function surfaceRuleArmRejected(why) {
113
+ surfaceSelfClearingWarn(RULE_NOT_SENT_REJECTED_KEY, ruleNotSentRejectedText(why), RULE_NOT_SENT_REJECTED_TIMEOUT_MS);
92
114
  }
93
115
  function isNoPendingError(e) {
94
116
  if (e instanceof HitlSafetyError)
@@ -5,20 +5,39 @@ function nonEmptyString(v) {
5
5
  function nonNegativeFinite(v) {
6
6
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
7
7
  }
8
+ function snapshotMembers(raw) {
9
+ const length = raw.length;
10
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
11
+ return undefined;
12
+ const out = [];
13
+ for (let i = 0; i < length; i++) {
14
+ const d = Object.getOwnPropertyDescriptor(raw, i);
15
+ if (d === undefined || !('value' in d))
16
+ return undefined;
17
+ out.push(d.value);
18
+ }
19
+ return raw.length === length ? out : undefined;
20
+ }
8
21
  export function readLivePendingRows(body) {
9
22
  if (body === null || typeof body !== 'object' || Array.isArray(body))
10
23
  return { kind: 'malformed' };
11
- if (!('livePending' in body))
12
- return { kind: 'not_reported' };
13
- const raw = body.livePending;
24
+ const field = Object.getOwnPropertyDescriptor(body, 'livePending');
25
+ if (field === undefined)
26
+ return 'livePending' in body ? { kind: 'malformed' } : { kind: 'not_reported' };
27
+ if (!('value' in field))
28
+ return { kind: 'malformed' };
29
+ const raw = field.value;
14
30
  if (raw === undefined)
15
31
  return { kind: 'not_reported' };
16
32
  if (!Array.isArray(raw))
17
33
  return { kind: 'malformed' };
34
+ const members = snapshotMembers(raw);
35
+ if (members === undefined)
36
+ return { kind: 'malformed' };
18
37
  const rows = [];
19
38
  const seen = new Set();
20
39
  let dropped = 0;
21
- for (const item of raw) {
40
+ for (const item of members) {
22
41
  if (item === null || typeof item !== 'object' || Array.isArray(item)) {
23
42
  dropped++;
24
43
  continue;
@@ -46,7 +65,7 @@ export function readLivePendingRows(body) {
46
65
  ...(isToolApprovalFrame(o.frame) && o.frame.type === 'tool_approval' && o.frame.approvalId === o.approvalId ? { frame: o.frame } : {}),
47
66
  });
48
67
  }
49
- if (raw.length > 0 && rows.length === 0)
68
+ if (members.length > 0 && rows.length === 0)
50
69
  return { kind: 'malformed' };
51
70
  return { kind: 'present', rows, dropped };
52
71
  }