@sema-agent/client-core 0.95.0 → 0.96.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 (181) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +49 -25
  3. package/dist/adapt/arms.js +7 -1
  4. package/dist/adapt/toolCards.d.ts +1 -0
  5. package/dist/adapt/toolCards.js +16 -3
  6. package/dist/adapt/wireShapes.js +2 -16
  7. package/dist/adapter/activeRunSelfHeal.d.ts +4 -0
  8. package/dist/adapter/activeRunSelfHeal.js +30 -4
  9. package/dist/adapter/downstream/eventToSdkMessage.js +11 -3
  10. package/dist/adapter/downstream/terminalToSdkResult.js +8 -9
  11. package/dist/adapter/runStream.js +30 -47
  12. package/dist/agentSession/engineAgentRegistryRead.js +5 -4
  13. package/dist/agentSession/engineSessionBackgroundStop.js +22 -15
  14. package/dist/agentsWireCaps.js +13 -2
  15. package/dist/callerCwdCapability.d.ts +0 -4
  16. package/dist/callerCwdCapability.js +0 -6
  17. package/dist/centerWiringCapability.d.ts +0 -4
  18. package/dist/centerWiringCapability.js +0 -6
  19. package/dist/classifierVerdictWire.js +4 -16
  20. package/dist/clientSlice.js +1 -1
  21. package/dist/cloudConfigWireCaps.d.ts +3 -3
  22. package/dist/cloudConfigWireCaps.js +65 -45
  23. package/dist/coreValuePorts.d.ts +11 -7
  24. package/dist/coreValuePorts.js +5 -8
  25. package/dist/credentialProvenance.d.ts +28 -0
  26. package/dist/credentialProvenance.js +76 -0
  27. package/dist/decideFailureNote.js +3 -1
  28. package/dist/decideReceipt.js +7 -10
  29. package/dist/declared/supportFloorDeclared.js +3 -3
  30. package/dist/displayUntrusted.d.ts +7 -3
  31. package/dist/displayUntrusted.js +356 -60
  32. package/dist/engineCapsCache.d.ts +6 -0
  33. package/dist/engineCapsCache.js +226 -32
  34. package/dist/engineDirectory.d.ts +79 -0
  35. package/dist/engineDirectory.js +186 -0
  36. package/dist/engineErrorCodes.d.ts +14 -1
  37. package/dist/engineErrorCodes.js +37 -15
  38. package/dist/engineNoticeCodes.d.ts +17 -0
  39. package/dist/engineNoticeCodes.js +57 -3
  40. package/dist/engineWordRenames.d.ts +8 -0
  41. package/dist/engineWordRenames.js +13 -0
  42. package/dist/fileHistoryCaptureCapability.d.ts +14 -0
  43. package/dist/fileHistoryCaptureCapability.js +70 -0
  44. package/dist/fleet/bgSubagentRow.d.ts +15 -0
  45. package/dist/fleet/bgSubagentRow.js +67 -0
  46. package/dist/fleet/fleetLedger.d.ts +1 -0
  47. package/dist/fleet/fleetLedger.js +103 -89
  48. package/dist/fleet/fleetProjection.d.ts +1 -0
  49. package/dist/fleet/fleetProjection.js +10 -3
  50. package/dist/fleetTaskDesc.js +4 -17
  51. package/dist/gateOutcome.d.ts +12 -1
  52. package/dist/gateOutcome.js +58 -1
  53. package/dist/gateVocabulary.d.ts +7 -2
  54. package/dist/gateVocabulary.js +5 -1
  55. package/dist/generated/engineAskFaceTables.js +1 -1
  56. package/dist/generated/engineFactTables.d.ts +5 -3
  57. package/dist/generated/engineFactTables.js +17 -4
  58. package/dist/generated/engineNoticeTables.js +12 -3
  59. package/dist/generated/settingsSchemaFns.d.ts +3 -0
  60. package/dist/generated/settingsSchemaFns.js +53 -0
  61. package/dist/generated/toolNameTables.d.ts +1 -0
  62. package/dist/generated/toolNameTables.js +1 -0
  63. package/dist/hitl/approvalEndingCause.d.ts +2 -0
  64. package/dist/hitl/approvalEndingCause.js +31 -21
  65. package/dist/hitl/askParkRowRouting.js +1 -1
  66. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  67. package/dist/hitl/automaticAnswerVerdict.js +13 -8
  68. package/dist/hitl/decisionConflict.js +0 -2
  69. package/dist/hitl/decisionRefusal.d.ts +8 -0
  70. package/dist/hitl/decisionRefusal.js +48 -14
  71. package/dist/hitl/frameRouter.d.ts +0 -1
  72. package/dist/hitl/frameRouter.js +6 -14
  73. package/dist/hitl/hitlBridge.d.ts +6 -2
  74. package/dist/hitl/hitlBridge.js +28 -7
  75. package/dist/hitl/localAllowRule.d.ts +1 -0
  76. package/dist/hitl/localAllowRule.js +2 -2
  77. package/dist/hitl/noAllowCause.d.ts +2 -1
  78. package/dist/hitl/noAllowCause.js +3 -2
  79. package/dist/hitl/parkResolver.d.ts +1 -0
  80. package/dist/hitl/parkResolver.js +8 -54
  81. package/dist/hitl/parkRowBirthWait.d.ts +1 -0
  82. package/dist/hitl/parkRowBirthWait.js +2 -2
  83. package/dist/hitl/planReviewWire.d.ts +1 -0
  84. package/dist/hitl/planReviewWire.js +98 -20
  85. package/dist/hitl/replayedApproval.d.ts +1 -0
  86. package/dist/hitl/replayedApproval.js +14 -0
  87. package/dist/hitl/sessionPolicyDeliverable.js +8 -6
  88. package/dist/hitl/standingRows.d.ts +18 -0
  89. package/dist/hitl/standingRows.js +127 -0
  90. package/dist/hitl/toolApprovalWire.d.ts +15 -3
  91. package/dist/hitl/toolApprovalWire.js +238 -25
  92. package/dist/hookMatcherVerdict.d.ts +1 -9
  93. package/dist/hookMatcherVerdict.js +21 -61
  94. package/dist/hooksWireCaps.js +2 -2
  95. package/dist/hostCallbackGuard.d.ts +1 -0
  96. package/dist/hostCallbackGuard.js +6 -0
  97. package/dist/index.d.ts +21 -9
  98. package/dist/index.js +17 -8
  99. package/dist/mcpProbeCapability.d.ts +0 -4
  100. package/dist/mcpProbeCapability.js +0 -6
  101. package/dist/memoryComplianceCapability.d.ts +0 -4
  102. package/dist/memoryComplianceCapability.js +0 -6
  103. package/dist/memoryOriginCapability.d.ts +0 -4
  104. package/dist/memoryOriginCapability.js +0 -6
  105. package/dist/memorySpecWire.js +2 -2
  106. package/dist/modelIdentityResolvability.d.ts +2 -2
  107. package/dist/modelIdentityResolvability.js +12 -3
  108. package/dist/noticePresentation.d.ts +68 -0
  109. package/dist/noticePresentation.js +247 -0
  110. package/dist/outputStyleSeatCapability.d.ts +0 -1
  111. package/dist/outputStyleSeatCapability.js +0 -3
  112. package/dist/ownKey.d.ts +2 -0
  113. package/dist/ownKey.js +18 -0
  114. package/dist/peerFrames.js +13 -9
  115. package/dist/permissionModesCapability.d.ts +0 -4
  116. package/dist/permissionModesCapability.js +0 -6
  117. package/dist/permissionRuleIssue.js +0 -3
  118. package/dist/permissionsDeclaration.d.ts +2 -0
  119. package/dist/permissionsDeclaration.js +56 -41
  120. package/dist/printPermissionOutcome.d.ts +12 -0
  121. package/dist/printPermissionOutcome.js +50 -0
  122. package/dist/printToolResultFrame.d.ts +1 -0
  123. package/dist/printToolResultFrame.js +5 -9
  124. package/dist/promptBlockedCopy.d.ts +5 -0
  125. package/dist/promptBlockedCopy.js +35 -0
  126. package/dist/protocolSegment.d.ts +9 -6
  127. package/dist/protocolSegment.js +31 -21
  128. package/dist/providerRejection.d.ts +4 -0
  129. package/dist/providerRejection.js +75 -14
  130. package/dist/replayBody.d.ts +12 -0
  131. package/dist/replayBody.js +25 -0
  132. package/dist/resumeRefusalCopy.js +5 -21
  133. package/dist/retryStatus.d.ts +9 -1
  134. package/dist/retryStatus.js +9 -2
  135. package/dist/rewindArchiveCapability.d.ts +3 -13
  136. package/dist/rewindArchiveCapability.js +7 -58
  137. package/dist/rewindArchiveReading.d.ts +15 -0
  138. package/dist/rewindArchiveReading.js +56 -0
  139. package/dist/ruleCompileVerdict.d.ts +9 -1
  140. package/dist/ruleCompileVerdict.js +29 -4
  141. package/dist/runTerminal.d.ts +15 -1
  142. package/dist/runTerminal.js +56 -8
  143. package/dist/sandboxWire.js +13 -0
  144. package/dist/sealedKeyCapability.d.ts +0 -4
  145. package/dist/sealedKeyCapability.js +0 -6
  146. package/dist/seam.js +4 -2
  147. package/dist/seatContract.d.ts +73 -11
  148. package/dist/seatContract.js +195 -14
  149. package/dist/sessionReadRedactionCapability.d.ts +0 -4
  150. package/dist/sessionReadRedactionCapability.js +0 -6
  151. package/dist/sessionSyncFileHistory.d.ts +4 -0
  152. package/dist/sessionSyncFileHistory.js +66 -0
  153. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -1
  154. package/dist/sessionSyncFileHistoryCapability.js +0 -3
  155. package/dist/skillToolListsCapability.d.ts +0 -1
  156. package/dist/skillToolListsCapability.js +0 -3
  157. package/dist/steerPriorityCapability.d.ts +0 -1
  158. package/dist/steerPriorityCapability.js +0 -3
  159. package/dist/storePostureCapability.d.ts +0 -4
  160. package/dist/storePostureCapability.js +0 -6
  161. package/dist/subagent/engineSubagentSteer.js +7 -5
  162. package/dist/subagentContentStore.js +4 -15
  163. package/dist/supportFloor.d.ts +1 -0
  164. package/dist/supportFloor.js +13 -9
  165. package/dist/systemReminderTag.d.ts +1 -0
  166. package/dist/systemReminderTag.js +8 -0
  167. package/dist/toolHistoryMismatch.d.ts +0 -3
  168. package/dist/toolHistoryMismatch.js +3 -7
  169. package/dist/toolResult.d.ts +1 -1
  170. package/dist/toolResult.js +2 -1
  171. package/dist/toolResultContent.d.ts +2 -0
  172. package/dist/toolResultContent.js +63 -0
  173. package/dist/usageAnalytics.js +40 -16
  174. package/dist/userInvokedSkillsCapability.d.ts +0 -1
  175. package/dist/userInvokedSkillsCapability.js +0 -3
  176. package/dist/wireErrorTriage.d.ts +2 -0
  177. package/dist/wireErrorTriage.js +34 -5
  178. package/dist/wireFailureShape.d.ts +19 -2
  179. package/dist/wireFailureShape.js +55 -50
  180. package/docs/INTEGRATION-CLIENTS.md +1262 -17
  181. package/package.json +5 -5
@@ -14,7 +14,7 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
14
14
  "config.artifact_host_invalid",
15
15
  "config.task_root_not_canonical",
16
16
  "config.write_protection_target_view_absent",
17
- "config.write_protection_unresolved",
17
+ "config.path_view_unresolved",
18
18
  "config.execution_env_capability_invalid",
19
19
  "config.tool_card_undeclared",
20
20
  "config.tool_face_undeclared",
@@ -66,6 +66,9 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
66
66
  "route.base_url_changed_key_unchanged",
67
67
  "route.response_model_mismatch",
68
68
  "route.tool_call_id_folded",
69
+ "route.finish_reason_unknown",
70
+ "route.stream_end_ignored",
71
+ "route.extra_body_reserved_dropped",
69
72
  "context.tool_turn_repaired",
70
73
  "task.user_steer_undrained",
71
74
  "task.user_followup_undrained",
@@ -103,7 +106,7 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
103
106
  "config.artifact_host_invalid": "operator",
104
107
  "config.task_root_not_canonical": "operator",
105
108
  "config.write_protection_target_view_absent": "operator",
106
- "config.write_protection_unresolved": "operator",
109
+ "config.path_view_unresolved": "operator",
107
110
  "config.execution_env_capability_invalid": "operator",
108
111
  "config.tool_card_undeclared": "operator",
109
112
  "config.tool_face_undeclared": "operator",
@@ -155,6 +158,9 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
155
158
  "route.base_url_changed_key_unchanged": "operator",
156
159
  "route.response_model_mismatch": "operator",
157
160
  "route.tool_call_id_folded": "operator",
161
+ "route.finish_reason_unknown": "user",
162
+ "route.stream_end_ignored": "user",
163
+ "route.extra_body_reserved_dropped": "operator",
158
164
  "context.tool_turn_repaired": "operator",
159
165
  "task.user_steer_undrained": "user",
160
166
  "task.user_followup_undrained": "user",
@@ -198,7 +204,7 @@ export const CORE_NOTICE_MINTER = Object.freeze({
198
204
  "config.artifact_host_invalid": "engine",
199
205
  "config.task_root_not_canonical": "engine",
200
206
  "config.write_protection_target_view_absent": "engine",
201
- "config.write_protection_unresolved": "engine",
207
+ "config.path_view_unresolved": "engine",
202
208
  "config.execution_env_capability_invalid": "engine",
203
209
  "config.tool_card_undeclared": "engine",
204
210
  "config.tool_face_undeclared": "engine",
@@ -250,6 +256,9 @@ export const CORE_NOTICE_MINTER = Object.freeze({
250
256
  "route.base_url_changed_key_unchanged": "engine",
251
257
  "route.response_model_mismatch": "engine",
252
258
  "route.tool_call_id_folded": "engine",
259
+ "route.finish_reason_unknown": "engine",
260
+ "route.stream_end_ignored": "engine",
261
+ "route.extra_body_reserved_dropped": "engine",
253
262
  "context.tool_turn_repaired": "engine",
254
263
  "task.user_steer_undrained": "engine",
255
264
  "task.user_followup_undrained": "engine",
@@ -5,6 +5,9 @@ export type SettingsAtMention = {
5
5
  readonly mode: 'only';
6
6
  readonly models: readonly string[];
7
7
  };
8
+ export type SettingsHookMatcherForm = "literal" | "regex" | "invalid";
8
9
  export declare const SETTINGS_IS_LOOPBACK_AUTH_HOST: (host: string) => boolean;
9
10
  export declare const SETTINGS_IS_HIERARCHICAL_URL: (raw: string) => boolean;
10
11
  export declare const SETTINGS_NARROW_AT_MENTION: (atMention: SettingsAtMention, available: ReadonlySet<string>) => SettingsAtMention;
12
+ export declare const SETTINGS_HOOK_MATCHER_SELECTS: (matcher: string | undefined, name: string) => boolean | 'invalid';
13
+ export declare const SETTINGS_HOOK_MATCHER_FORM: (matcher: string | undefined) => SettingsHookMatcherForm;
@@ -20,3 +20,56 @@ export const SETTINGS_NARROW_AT_MENTION = function narrowAtMention(atMention, av
20
20
  return atMention;
21
21
  return kept.length > 0 ? { mode: "only", models: kept } : { mode: "none" };
22
22
  };
23
+ const HOOK_MATCHER_NAME_LIST = /^[a-zA-Z0-9_|, -]+$/;
24
+ const readHookMatcher = function readHookMatcher(matcher) {
25
+ if (matcher === undefined || matcher === "" || matcher === "*")
26
+ return { form: "all" };
27
+ if (HOOK_MATCHER_NAME_LIST.test(matcher)) {
28
+ return {
29
+ form: "names",
30
+ names: matcher
31
+ .split(/[|,]/)
32
+ .map((part) => part.trim())
33
+ .filter((part) => part !== ""),
34
+ };
35
+ }
36
+ try {
37
+ return { form: "regex", pattern: new RegExp(matcher) };
38
+ }
39
+ catch {
40
+ return { form: "invalid" };
41
+ }
42
+ };
43
+ export const SETTINGS_HOOK_MATCHER_SELECTS = function hookMatcherSelects(matcher, name) {
44
+ const r = readHookMatcher(matcher);
45
+ switch (r.form) {
46
+ case "all":
47
+ return true;
48
+ case "names":
49
+ return r.names.includes(name);
50
+ case "regex":
51
+ return r.pattern.test(name);
52
+ case "invalid":
53
+ return "invalid";
54
+ default: {
55
+ const unhandled = r;
56
+ throw new Error(`hookMatcherSelects: unhandled matcher reading ${JSON.stringify(unhandled)}`);
57
+ }
58
+ }
59
+ };
60
+ export const SETTINGS_HOOK_MATCHER_FORM = function hookMatcherForm(matcher) {
61
+ const r = readHookMatcher(matcher);
62
+ switch (r.form) {
63
+ case "all":
64
+ case "names":
65
+ return "literal";
66
+ case "regex":
67
+ return "regex";
68
+ case "invalid":
69
+ return "invalid";
70
+ default: {
71
+ const unhandled = r;
72
+ throw new Error(`hookMatcherForm: unhandled matcher reading ${JSON.stringify(unhandled)}`);
73
+ }
74
+ }
75
+ };
@@ -1,2 +1,3 @@
1
1
  export declare const CORE_PROTOCOL_PREFIXES: readonly ["mcp__", "a2a__"];
2
2
  export declare const CORE_RETIRED_TOOL_CURRENT_NAMES: ReadonlyMap<string, string | null>;
3
+ export declare const CORE_SKILL_TOOL_NAME: "Skill";
@@ -41,3 +41,4 @@ export const CORE_RETIRED_TOOL_CURRENT_NAMES = new Map([
41
41
  ["Recall", null],
42
42
  ["recall", null],
43
43
  ]);
44
+ export const CORE_SKILL_TOOL_NAME = "Skill";
@@ -2,4 +2,6 @@ import type { ApprovalSettleCause, AskRowEndingCause } from '@sema-agent/sdk';
2
2
  export declare const ASK_ROW_ENDING_CAUSE_WORDS: readonly ["routing_failure_fail_closed", "run_cancelled", "run_exited", "batch_aborted", "batch_sibling_withdrawn", "batch_bound_elsewhere", "adhoc_leg_no_durable_domain", "orphan_ttl_exceeded", "crashed_before_park", "shutdown_before_park"];
3
3
  export declare function askRowEndingCauseFor(state: 'VOID' | 'DENIED' | undefined, word: unknown): AskRowEndingCause | undefined;
4
4
  export declare function approvalCompleteCauseOf(frame: unknown): ApprovalSettleCause | undefined;
5
+ export declare function notPendingCauseReading(err: object): 'ended' | 'other_replica' | 'none';
6
+ export declare function refusalCauseWordOf(err: object): string | undefined;
5
7
  export declare function notPendingCauseSaysEnded(err: object): boolean;
@@ -1,5 +1,6 @@
1
1
  import { ASK_GONE_DENIED_WORD } from '../engineErrorCodes.js';
2
2
  import { SDK_ASK_ROW_ENDING_CAUSES } from '../generated/sdkFactTables.js';
3
+ import { readThrownExtra } from '../wireFailureShape.js';
3
4
  export const ASK_ROW_ENDING_CAUSE_WORDS = SDK_ASK_ROW_ENDING_CAUSES;
4
5
  const _askRowCausePin = true;
5
6
  void _askRowCausePin;
@@ -25,37 +26,46 @@ export function askRowEndingCauseFor(state, word) {
25
26
  return word !== DENIED_GROUP_WORD ? word : undefined;
26
27
  return undefined;
27
28
  }
29
+ function ownDataSlot(o, key) {
30
+ try {
31
+ const slot = Object.getOwnPropertyDescriptor(o, key);
32
+ return slot !== undefined && Object.hasOwn(slot, 'value') ? slot.value : undefined;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
28
38
  export function approvalCompleteCauseOf(frame) {
29
39
  try {
30
40
  if (typeof frame !== 'object' || frame === null)
31
41
  return undefined;
32
- const typeSlot = Object.getOwnPropertyDescriptor(frame, 'type');
33
- if (typeSlot === undefined || !Object.hasOwn(typeSlot, 'value') || typeSlot.value !== 'tool_approval_complete')
42
+ if (ownDataSlot(frame, 'type') !== 'tool_approval_complete')
34
43
  return undefined;
35
- const causeSlot = Object.getOwnPropertyDescriptor(frame, 'cause');
36
- if (causeSlot === undefined || !Object.hasOwn(causeSlot, 'value'))
37
- return undefined;
38
- const word = causeSlot.value;
44
+ const word = ownDataSlot(frame, 'cause');
39
45
  return isApprovalSettleCause(word) ? word : undefined;
40
46
  }
41
47
  catch {
42
48
  return undefined;
43
49
  }
44
50
  }
45
- const LEGACY_NOT_PENDING_ENDED_WORDS = new Set(['voided', 'denied']);
51
+ const NOT_PENDING_OTHER_REPLICA_WORDS = Object.freeze({
52
+ unknown_or_other_replica: true,
53
+ });
54
+ export function notPendingCauseReading(err) {
55
+ const word = refusalCauseWordOf(err);
56
+ if (word === undefined)
57
+ return 'none';
58
+ if (isAskRowEndingCause(word))
59
+ return 'ended';
60
+ return Object.hasOwn(NOT_PENDING_OTHER_REPLICA_WORDS, word) ? 'other_replica' : 'none';
61
+ }
62
+ export function refusalCauseWordOf(err) {
63
+ const reading = readThrownExtra(err);
64
+ if (reading.state !== 'object')
65
+ return undefined;
66
+ const word = ownDataSlot(reading.value, 'cause');
67
+ return typeof word === 'string' && word !== '' ? word : undefined;
68
+ }
46
69
  export function notPendingCauseSaysEnded(err) {
47
- try {
48
- const extraSlot = Object.getOwnPropertyDescriptor(err, 'extra');
49
- const extra = extraSlot !== undefined && Object.hasOwn(extraSlot, 'value') ? extraSlot.value : undefined;
50
- if (typeof extra !== 'object' || extra === null || Array.isArray(extra))
51
- return false;
52
- const causeSlot = Object.getOwnPropertyDescriptor(extra, 'cause');
53
- if (causeSlot === undefined || !Object.hasOwn(causeSlot, 'value'))
54
- return false;
55
- const word = causeSlot.value;
56
- return isAskRowEndingCause(word) || (typeof word === 'string' && LEGACY_NOT_PENDING_ENDED_WORDS.has(word));
57
- }
58
- catch {
59
- return false;
60
- }
70
+ return notPendingCauseReading(err) === 'ended';
61
71
  }
@@ -50,7 +50,7 @@ export function askParkRowArm(row) {
50
50
  const rowHasToolName = typeof row.toolName === 'string' && row.toolName.trim().length > 0;
51
51
  return askQuestionsFromPending(row) !== null || !rowHasToolName ? 'question' : 'tool-gate';
52
52
  }
53
- const GENERIC_DECISION_GATE_KINDS = ['human', 'policy_ask'];
53
+ const GENERIC_DECISION_GATE_KINDS = ['human'];
54
54
  export function askParkRowStillPending(rows, identity) {
55
55
  return rows.some(r => identity.gatedCallId !== undefined
56
56
  ? (r.toolCallId ?? r.boundCallId ?? undefined) === identity.gatedCallId
@@ -19,7 +19,7 @@ export type AutomaticAnswerVerdict = {
19
19
  readonly kind: 'unknown';
20
20
  readonly why: AutomaticAnswerUnknownWhy;
21
21
  };
22
- export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "write_protection_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", CoreModeArbitrationRow>>;
22
+ export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "target_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "unverifiable_shell_text" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", CoreModeArbitrationRow>>;
23
23
  export declare function foldModeArbitrationRow(classes: Iterable<string>, askFace?: AutomaticAnswerAskFace): CoreModeArbitrationRow;
24
24
  export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
25
25
  export declare function mandatedApprovalDetailOf(card: object | null | undefined): string;
@@ -3,6 +3,7 @@ import { CORE_RELEASABLE_ASK_CLASSES_BY_FACE, CORE_SHELL_FACE_TOOL_NAMES } from
3
3
  import { mandatedApprovalDetail } from '../gateVocabulary.js';
4
4
  import { readAskOrigin, readOwnNonEmptyString } from '../ownKey.js';
5
5
  import { readAskClassPairState } from './askClassPair.js';
6
+ import { currentEngineWord } from '../engineWordRenames.js';
6
7
  import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
7
8
  import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
8
9
  export const MODE_ARBITRATION = CORE_MODE_ARBITRATION;
@@ -15,9 +16,10 @@ const ORIGIN_IMPLIES_CLASS = Object.freeze({
15
16
  ask_rule: 'ask_rule',
16
17
  denial_limit_fallback: 'denial_limit_fallback',
17
18
  });
18
- function mandateWordClass(word) {
19
- if (word === 'operator_always')
19
+ function mandateWordClass(raw) {
20
+ if (raw === 'operator_always')
20
21
  return 'shell_floor';
22
+ const word = currentEngineWord(raw);
21
23
  return Object.hasOwn(TABLE, word) ? word : undefined;
22
24
  }
23
25
  const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
@@ -41,7 +43,8 @@ export function foldModeArbitrationRow(classes, askFace) {
41
43
  let sandboxAdmission = true;
42
44
  let ruleMayClear = true;
43
45
  let hookAskFloor = false;
44
- for (const c of classes) {
46
+ for (const raw of classes) {
47
+ const c = typeof raw === 'string' ? currentEngineWord(raw) : raw;
45
48
  if (!Object.hasOwn(TABLE, c))
46
49
  continue;
47
50
  const row = TABLE[c];
@@ -130,11 +133,12 @@ export function automaticAnswerVerdict(card, lane, facts) {
130
133
  return UNKNOWN.classes_malformed;
131
134
  if (pair.state === 'absent')
132
135
  return UNKNOWN.classes_absent;
133
- const folded = foldModeArbitrationRow(pair.classes, face);
136
+ const classes = pair.classes.map(currentEngineWord);
137
+ const folded = foldModeArbitrationRow(classes, face);
134
138
  let anyKnown = false;
135
139
  let firstBlocking;
136
140
  let anyUnknown = false;
137
- for (const c of pair.classes) {
141
+ for (const c of classes) {
138
142
  if (!Object.hasOwn(TABLE, c)) {
139
143
  anyUnknown = true;
140
144
  continue;
@@ -154,7 +158,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
154
158
  return mustAsk('settings_ask_rule');
155
159
  if (matched !== false)
156
160
  return UNKNOWN.host_not_reported;
157
- const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => pair.classes.includes(r));
161
+ const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => classes.includes(r));
158
162
  return releaseReason !== undefined ? Object.freeze({ kind: 'may_answer', releaseReason }) : MAY_ANSWER;
159
163
  }
160
164
  catch {
@@ -168,9 +172,10 @@ export function mandatedApprovalDetailOf(card) {
168
172
  const pair = readAskClassPairState(card);
169
173
  if (pair.state !== 'pair')
170
174
  return mandatedApprovalDetail();
171
- if (pair.classes.some((c) => !Object.hasOwn(TABLE, c)))
175
+ const classes = pair.classes.map(currentEngineWord);
176
+ if (classes.some((c) => !Object.hasOwn(TABLE, c)))
172
177
  return mandatedApprovalDetail();
173
- const row = foldModeArbitrationRow(pair.classes);
178
+ const row = foldModeArbitrationRow(classes);
174
179
  if (row.classifierEligible || row.sandboxAdmission)
175
180
  return mandatedApprovalDetail();
176
181
  return MANDATED_NO_AUTOMATIC_CHECK_DETAIL;
@@ -34,8 +34,6 @@ export function decisionConflictOf(err) {
34
34
  }
35
35
  if (code === RESUME_OUTCOME_INVALID)
36
36
  return field === ANSWER_FIELD ? 'answer_invalid' : undefined;
37
- if (field === ANSWER_FIELD)
38
- return 'answer_invalid';
39
37
  if (field === undefined)
40
38
  return 'row_integrity';
41
39
  return BINDING_SEAT_FIELDS.has(field) ? 'binding_mismatch' : undefined;
@@ -2,6 +2,14 @@ import { type DecisionRefusalConflict } from './decisionConflict.js';
2
2
  export type DecisionRefusalStanding = 'settled-elsewhere' | 'ambiguous' | 'refused-pending';
3
3
  export type { DecisionRefusalConflict } from './decisionConflict.js';
4
4
  export declare function decisionRefusalSaysAskGone(err: unknown): boolean;
5
+ export type DecisionRefusalAskReading = 'settled' | 'not_settled' | 'unreadable';
6
+ interface DecisionRefusalAskFacts {
7
+ readonly reading: DecisionRefusalAskReading;
8
+ readonly gateMoved: boolean;
9
+ readonly wordingHint: boolean;
10
+ }
11
+ export declare function readDecisionRefusalAsk(failure: unknown): DecisionRefusalAskFacts;
12
+ export declare function decisionRefusalAskReading(failure: unknown): DecisionRefusalAskReading;
5
13
  export interface DecisionRefusalClassified {
6
14
  readonly standing: DecisionRefusalStanding;
7
15
  readonly status: number;
@@ -1,5 +1,8 @@
1
- import { refusalSaysStillPending } from '../engineErrorCodes.js';
2
- import { notPendingCauseSaysEnded } from './approvalEndingCause.js';
1
+ import { ApprovalStaleError, AskDecidedError, ParkedResumeStartupError } from '@sema-agent/sdk';
2
+ import { ASK_GONE, ASK_GONE_PARKED, isDecideAlreadyResolvedCode, isDecideParkMovedCode, refusalRetriableReading, refusalSaysStillPending } from '../engineErrorCodes.js';
3
+ import { notPendingCauseReading } from './approvalEndingCause.js';
4
+ import { GATE_FAILURE_CODES } from './hitlBridge.js';
5
+ import { isAlreadyResolvedGateReason, isCodeClassifiedGateFailure } from './parkResolver.js';
3
6
  import { decisionConflictOf } from './decisionConflict.js';
4
7
  import { readLivePendingRows } from './livePendingAsk.js';
5
8
  import { readToolApprovalRespondRefusal } from './toolApprovalWire.js';
@@ -7,20 +10,50 @@ const ASK_DECIDED_CODE = 'conflict.ask_decided';
7
10
  const TRANSIENT_STATUSES = new Set([408, 425, 429, 502, 503, 504]);
8
11
  const DISPLAYABLE_CODE = /^[A-Za-z0-9._:-]{1,64}$/;
9
12
  export function decisionRefusalSaysAskGone(err) {
13
+ return readDecisionRefusalAsk(err).reading === 'settled';
14
+ }
15
+ const facts = (reading, gateMoved = false, wordingHint = false) => ({ reading, gateMoved, wordingHint });
16
+ export function readDecisionRefusalAsk(failure) {
17
+ if (typeof failure !== 'object' || failure === null)
18
+ return facts('not_settled');
19
+ let flattened, localCode, name, errorCode, status, retriable, text;
10
20
  try {
11
- if (typeof err === 'object' && err !== null && 'code' in err) {
12
- const code = err.code;
13
- if (code === 'no_pending')
14
- return true;
15
- }
16
- const c = classifyDecisionRefusal(err);
17
- if (c?.standing !== 'settled-elsewhere')
18
- return false;
19
- return c.status !== 404 || (typeof err === 'object' && err !== null && notPendingCauseSaysEnded(err));
21
+ const o = failure;
22
+ if (Object.hasOwn(failure, 'unreadable') && failure.unreadable === true)
23
+ return facts('unreadable');
24
+ flattened = Object.hasOwn(failure, 'kind') && o.kind === 'failed';
25
+ const closed = (v) => (typeof v === 'string' && GATE_FAILURE_CODES.includes(v) ? v : undefined);
26
+ localCode = flattened ? (closed(o.code) ?? closed(o.safetyCode)) : closed(o.code);
27
+ name = flattened ? undefined : o.name;
28
+ errorCode = o.errorCode;
29
+ status = o.status;
30
+ retriable = refusalRetriableReading(failure);
31
+ text = flattened ? o.reason : String(failure);
20
32
  }
21
33
  catch {
22
- return false;
34
+ return facts('unreadable');
23
35
  }
36
+ if (retriable === undefined)
37
+ return facts('unreadable');
38
+ if (localCode !== undefined)
39
+ return facts(localCode === 'no_pending' ? 'settled' : 'not_settled');
40
+ if (name === 'HitlSafetyError')
41
+ return facts('not_settled');
42
+ const code = typeof errorCode === 'string' && errorCode.length > 0 ? errorCode : undefined;
43
+ const st = typeof status === 'number' && Number.isInteger(status) && status >= 400 && status <= 599 ? status : undefined;
44
+ if (code === ASK_DECIDED_CODE || code === ASK_GONE || (st === 410 && code !== ASK_GONE_PARKED))
45
+ return facts('settled');
46
+ if (st === 404 && notPendingCauseReading(failure) === 'ended')
47
+ return facts('settled');
48
+ if (code === undefined && name === AskDecidedError.name)
49
+ return facts('settled');
50
+ const gateMoved = isDecideAlreadyResolvedCode(code) || isDecideParkMovedCode(code) || code === ASK_GONE_PARKED || (code === undefined && name === ApprovalStaleError.name);
51
+ if (gateMoved || st !== undefined || name === ParkedResumeStartupError.name || isCodeClassifiedGateFailure(code, retriable))
52
+ return facts('not_settled', gateMoved);
53
+ return facts('not_settled', false, isAlreadyResolvedGateReason(text));
54
+ }
55
+ export function decisionRefusalAskReading(failure) {
56
+ return readDecisionRefusalAsk(failure).reading;
24
57
  }
25
58
  export function classifyDecisionRefusal(err) {
26
59
  const r = readToolApprovalRespondRefusal(err);
@@ -29,12 +62,13 @@ export function classifyDecisionRefusal(err) {
29
62
  return undefined;
30
63
  const code = r?.errorCode;
31
64
  const errorCode = code !== undefined && DISPLAYABLE_CODE.test(code) ? code : undefined;
32
- const standing = status === 404 || status === 410 || (status === 409 && code === ASK_DECIDED_CODE)
65
+ const standing = readDecisionRefusalAsk(err).reading === 'settled'
33
66
  ? 'settled-elsewhere'
34
67
  : status >= 500 && status !== 501
35
68
  ? 'ambiguous'
36
69
  : 'refused-pending';
37
- const transient = TRANSIENT_STATUSES.has(status) || (typeof err === 'object' && err !== null && refusalSaysStillPending(err));
70
+ const transient = TRANSIENT_STATUSES.has(status) ||
71
+ (typeof err === 'object' && err !== null && (refusalSaysStillPending(err) || (status === 404 && notPendingCauseReading(err) === 'other_replica')));
38
72
  const conflict = status === 409 ? decisionConflictOf(err) : undefined;
39
73
  return Object.freeze({
40
74
  standing,
@@ -17,7 +17,6 @@ export interface PolicyDenyReasonFacts {
17
17
  export declare function policyDenyReason(facts: PolicyDenyReasonFacts): string;
18
18
  export declare const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
19
19
  export declare const ENGINE_ABORT_TOOL_RESULT = "Operation aborted";
20
- export declare function toolEndOutputText(output: unknown): string | undefined;
21
20
  export interface ParkReattachNotice {
22
21
  attempt: number;
23
22
  max: number;
@@ -2,6 +2,7 @@ import { hostLog } from '../host.js';
2
2
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
3
3
  import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolApprovalFrame, isFromSubagent, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
4
4
  import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
5
+ import { toolEndOutputText } from '../toolResultContent.js';
5
6
  import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
6
7
  import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
7
8
  import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
@@ -23,7 +24,7 @@ export function policyDenyReason(facts) {
23
24
  const one = typeof facts.toolName === 'string' && SENTENCE_TOOL_NAME.test(facts.toolName) ? `this one "${facts.toolName}" call` : 'this one call';
24
25
  const flag = typeof facts.allowRuleFlag === 'string' && SENTENCE_TOOL_NAME.test(facts.allowRuleFlag.replace(/^--?/, '')) ? ` (or ${facts.allowRuleFlag})` : '';
25
26
  const budgetKnob = typeof facts.budgetSetting === 'string' && SENTENCE_TOOL_NAME.test(facts.budgetSetting) ? facts.budgetSetting : undefined;
26
- const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
27
+ const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'engine_ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
27
28
  if (facts.lane === 'dont_ask') {
28
29
  const mode = 'denied by the dontAsk permission mode';
29
30
  switch (cause) {
@@ -31,6 +32,8 @@ export function policyDenyReason(facts) {
31
32
  return `${mode}: ${one} needed approval, no permission rule allowed it, and this mode refuses anything that would ask — calls that need no approval still run; an allow rule in settings${flag} can approve calls like this ahead of time`;
32
33
  case 'ask_rule':
33
34
  return `${mode}: an ask rule matched ${one}, and this mode refuses anything that would ask (an ask rule outranks allow rules) — narrow or remove that ask rule, or use another permission mode to answer it`;
35
+ case 'engine_ask_rule':
36
+ return `${mode}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, and this mode refuses anything that would ask — the user's settings cannot approve it; use another permission mode to answer it`;
34
37
  case 'budget':
35
38
  return `${mode}: ${one} needed approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls${budgetKnob !== undefined ? ` — raise ${budgetKnob} before launch to change the cap` : ''}`;
36
39
  default:
@@ -44,6 +47,8 @@ export function policyDenyReason(facts) {
44
47
  return `${lane}: ${one} needed a person's approval, no permission rule answered it, and ${tail}; a matching allow rule in settings${flag} can approve calls like this ahead of time`;
45
48
  case 'ask_rule':
46
49
  return `${lane}: an ask rule matched ${one}, so it needed a person's answer, and ${tail}; an ask rule outranks allow rules, so narrow or remove that ask rule, or run the task interactively to answer it`;
50
+ case 'engine_ask_rule':
51
+ return `${lane}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, so it needed a person's answer, and ${tail}; the user's settings cannot approve it - run the task interactively to answer it`;
47
52
  case 'budget':
48
53
  return `${lane}: ${one} needed a person's approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls; ${tail}${budgetKnob !== undefined ? `; raise ${budgetKnob} before launch to change the cap` : ''}`;
49
54
  default:
@@ -73,19 +78,6 @@ function denyStampedEnd(ev, attribution) {
73
78
  export const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool use]';
74
79
  const RUN_CANCELLED_ERROR_CODE = RUN_CANCELLED_CODE;
75
80
  export const ENGINE_ABORT_TOOL_RESULT = 'Operation aborted';
76
- export function toolEndOutputText(output) {
77
- if (typeof output === 'string')
78
- return output;
79
- if (!Array.isArray(output))
80
- return undefined;
81
- const parts = [];
82
- for (const b of output) {
83
- const bb = b;
84
- if (bb?.type === 'text' && typeof bb.text === 'string')
85
- parts.push(bb.text);
86
- }
87
- return parts.length > 0 ? parts.join('') : undefined;
88
- }
89
81
  function isAskToolLoose(name) {
90
82
  return typeof name === 'string' && name.replace(/[\s_-]+/g, '').toLowerCase() === 'askuserquestion';
91
83
  }
@@ -46,7 +46,7 @@ export interface PlanReviewOutcome {
46
46
  export declare const PLAN_REVIEW_MODE_AFTER_WORDS: readonly ["default", "acceptEdits"];
47
47
  export type PlanReviewModeAfter = (typeof PLAN_REVIEW_MODE_AFTER_WORDS)[number];
48
48
  export declare function isPlanReviewModeAfter(v: string | undefined): v is PlanReviewModeAfter;
49
- export declare const GATE_FAILURE_CODES: readonly ["no_pending", "binding_mismatch", "wrong_gate", "bad_plan_edit", "bad_plan_mode", "empty_answer"];
49
+ export declare const GATE_FAILURE_CODES: readonly ["no_pending", "binding_mismatch", "wrong_gate", "bad_plan_edit", "bad_plan_mode", "empty_answer", "binding_missing"];
50
50
  export type HitlSafetyCode = (typeof GATE_FAILURE_CODES)[number];
51
51
  export type HitlFailureStage = 'fetch' | 'input' | 'card' | 'decide' | 'orchestration';
52
52
  export declare const NO_APPROVAL_CARD_PORT_REASON = "no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)";
@@ -76,6 +76,10 @@ export type WireErrorCodeReading = {
76
76
  readonly state: 'unreadable';
77
77
  };
78
78
  export declare function readWireErrorCodeReading(e: unknown): WireErrorCodeReading;
79
+ export declare function readWireCodes(e: unknown): {
80
+ readonly errorCode?: string;
81
+ readonly blockedBy?: string;
82
+ };
79
83
  export declare function __setDecideTimeoutRetryBudgetForTests(ms?: number): void;
80
84
  export declare class DecideTransportRetryExhaustedError extends Error {
81
85
  readonly attempts: number;
@@ -116,7 +120,7 @@ export type FindPendingOutcome = {
116
120
  } | {
117
121
  ok: false;
118
122
  reason: string;
119
- code?: 'no_pending';
123
+ code?: 'no_pending' | 'wrong_gate';
120
124
  };
121
125
  export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
122
126
  signal?: AbortSignal;
@@ -4,8 +4,8 @@ import { hostLog } from '../host.js';
4
4
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
5
5
  import { abortableSleep } from '../abortableSleep.js';
6
6
  import { readOwnDataValue } from '../ownKey.js';
7
- import { THROWN_SLOT_UNREADABLE, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
8
- import { decisionConflictOf } from './decisionConflict.js';
7
+ import { THROWN_SLOT_UNREADABLE, readBlockedBy, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
8
+ import { RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
9
9
  import { suspendedReopenOf } from './suspendedReopen.js';
10
10
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
11
11
  const MAX_DENY_REASON_CHARS = 4096;
@@ -29,14 +29,15 @@ export const PLAN_REVIEW_MODE_AFTER_WORDS = Object.freeze(['default', 'acceptEdi
29
29
  export function isPlanReviewModeAfter(v) {
30
30
  return v !== undefined && PLAN_REVIEW_MODE_AFTER_WORDS.includes(v);
31
31
  }
32
- export const GATE_FAILURE_CODES = [
32
+ export const GATE_FAILURE_CODES = Object.freeze([
33
33
  'no_pending',
34
34
  'binding_mismatch',
35
35
  'wrong_gate',
36
36
  'bad_plan_edit',
37
37
  'bad_plan_mode',
38
38
  'empty_answer',
39
- ];
39
+ 'binding_missing',
40
+ ]);
40
41
  export const NO_APPROVAL_CARD_PORT_REASON = 'no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)';
41
42
  export const APPROVAL_CARD_PORT_KEY_MISMATCH_REASON = 'no approval card port installed for the default session key, but a non-default sessionKey has one — likely a keyed-install/default-read mismatch (installApprovalCardPortFor vs the zero-arg default-key read), not a missing install';
42
43
  export const NO_QUESTION_OVERLAY_REASON = 'no question overlay mounted (print/non-REPL mode)';
@@ -92,6 +93,15 @@ export function readWireErrorCodeReading(e) {
92
93
  return { state: 'unreadable' };
93
94
  return typeof raw === 'string' && raw.length > 0 ? { state: 'code', code: raw } : { state: 'absent' };
94
95
  }
96
+ export function readWireCodes(e) {
97
+ const reading = readWireErrorCodeReading(e);
98
+ if (reading.state !== 'code')
99
+ return {};
100
+ if (reading.code !== RESUME_POLICY_FOLD_CODE)
101
+ return { errorCode: reading.code };
102
+ const folded = readBlockedBy(e);
103
+ return folded.kind === 'named' ? { errorCode: reading.code, blockedBy: folded.code } : { errorCode: reading.code };
104
+ }
95
105
  const DECIDE_TRANSPORT_RETRY_BACKOFF_MS = 750;
96
106
  const DECIDE_RETRY_BACKOFF_MAX_MS = 5_000;
97
107
  const DECIDE_ATTEMPT_TIMEOUT_CAP_MS = 60_000;
@@ -224,10 +234,21 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
224
234
  hostLog('debug', `hitlBridge: approvals.list failed: ${t.log}`);
225
235
  return { ok: false, reason: `approvals.list failed: ${t.reason}` };
226
236
  }
227
- const decidable = rows.filter((r) => askParkForeignGateKind(r) === null);
237
+ const readings = rows.map((r) => ({ r, foreign: askParkForeignGateKind(r) }));
238
+ const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
228
239
  const identityRow = callId !== undefined
229
240
  ? decidable.find((r) => r.taskId === taskId && (r.boundCallId === callId || r.toolCallId === callId))
230
241
  : undefined;
242
+ const identityForeign = callId !== undefined && identityRow === undefined
243
+ ? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
244
+ : undefined;
245
+ if (identityForeign !== undefined) {
246
+ return {
247
+ ok: false,
248
+ reason: `the pending row this park names (call ${callId}) is parked on a '${String(identityForeign.foreign)}' gate — not an approval this leg decides; refusing to decide a different row`,
249
+ code: 'wrong_gate',
250
+ };
251
+ }
231
252
  const identityConflicts = identityRow !== undefined && !identityAccepts(identityRow);
232
253
  const pool = identityConflicts ? decidable.filter((r) => identityAccepts(r)) : decidable;
233
254
  const pending = (identityConflicts ? undefined : identityRow) ??
@@ -286,7 +307,7 @@ export class HitlBridge {
286
307
  if (p.boundCallId === undefined || p.boundInputHash === undefined) {
287
308
  throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
288
309
  `(boundCallId=${String(p.boundCallId)}, boundInputHash=${String(p.boundInputHash)}) — ` +
289
- 'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', 'binding_mismatch');
310
+ 'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', 'binding_missing');
290
311
  }
291
312
  return { boundCallId: p.boundCallId, boundInputHash: p.boundInputHash };
292
313
  }
@@ -500,7 +521,7 @@ function bindingMismatchReading(e) {
500
521
  const name = readThrownSlot(e, 'name');
501
522
  const family = (code.state === 'code' && code.code === 'approval_binding_mismatch') || name === 'ApprovalBindingMismatchError';
502
523
  if (family)
503
- return decisionConflictOf(e) !== 'answer_invalid' ? 'mismatch' : 'not';
524
+ return 'mismatch';
504
525
  if (code.state === 'unreadable')
505
526
  return 'unreadable';
506
527
  return 'not';
@@ -6,6 +6,7 @@ export interface LocalAllowRuleDeps {
6
6
  bashRuleContentHasDangerousBarePrefix(ruleContent: string): boolean;
7
7
  isDangerousBashPermission(toolName: string, ruleContent: string): boolean;
8
8
  isDangerousPowerShellPermission(toolName: string, ruleContent: string): boolean;
9
+ onFailure?(log: string): void;
9
10
  }
10
11
  export type LocalAllowRuleParse = {
11
12
  ok: true;
@@ -1,4 +1,4 @@
1
- import { wireFailureTextsOf } from '../wireFailureShape.js';
1
+ import { reasonOfThrownWithSink } from '../hostCallbackGuard.js';
2
2
  const BASH_TOOL_NAME_FOR_RULES = 'Bash';
3
3
  export function parseLocalAllowRule(rule, expectedToolName, deps) {
4
4
  if (typeof rule !== 'string' || rule === '') {
@@ -9,7 +9,7 @@ export function parseLocalAllowRule(rule, expectedToolName, deps) {
9
9
  value = deps.parseRule(rule);
10
10
  }
11
11
  catch (e) {
12
- return { ok: false, error: `rule text did not parse (${wireFailureTextsOf(e).reason})` };
12
+ return { ok: false, error: `rule text did not parse (${reasonOfThrownWithSink(e, (log) => deps.onFailure?.(log))})` };
13
13
  }
14
14
  if (typeof value.toolName !== 'string' || value.toolName === '') {
15
15
  return { ok: false, error: 'rule text carries no tool name' };