@sema-agent/client-core 0.98.0 → 0.99.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 (192) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +35 -22
  3. package/dist/adapt/arms.js +61 -20
  4. package/dist/adapt/panelTasks.d.ts +1 -1
  5. package/dist/adapt/panelTasks.js +2 -2
  6. package/dist/adapter/activeRunSelfHeal.d.ts +4 -3
  7. package/dist/adapter/activeRunSelfHeal.js +162 -293
  8. package/dist/adapter/downstream/eventToSdkMessage.js +52 -4
  9. package/dist/adapter/runStream.js +52 -37
  10. package/dist/adapter/types.js +2 -1
  11. package/dist/agentSession/engineSessionBackgroundStop.js +16 -8
  12. package/dist/agentsWireCaps.d.ts +0 -3
  13. package/dist/agentsWireCaps.js +33 -24
  14. package/dist/cloudConfigWireCaps.d.ts +1 -1
  15. package/dist/cloudConfigWireCaps.js +26 -4
  16. package/dist/controlRouter.d.ts +4 -2
  17. package/dist/controlRouter.js +39 -51
  18. package/dist/coreValuePorts.d.ts +26 -1
  19. package/dist/coreValuePorts.js +10 -0
  20. package/dist/corruptSessionCopy.d.ts +2 -1
  21. package/dist/corruptSessionCopy.js +3 -3
  22. package/dist/declared/supportFloorDeclared.js +1 -1
  23. package/dist/detachWire.js +3 -2
  24. package/dist/devicesFace.d.ts +34 -0
  25. package/dist/devicesFace.js +90 -0
  26. package/dist/displayUntrusted.d.ts +1 -0
  27. package/dist/displayUntrusted.js +3 -0
  28. package/dist/engineAgentPanelStore.d.ts +7 -0
  29. package/dist/engineAgentPanelStore.js +14 -0
  30. package/dist/engineCapReader.d.ts +2 -1
  31. package/dist/engineCapReader.js +7 -5
  32. package/dist/engineCapsCache.d.ts +8 -0
  33. package/dist/engineCapsCache.js +59 -6
  34. package/dist/engineCapsChange.d.ts +2 -0
  35. package/dist/engineCapsChange.js +9 -0
  36. package/dist/engineErrorCodes.d.ts +2 -1
  37. package/dist/engineErrorCodes.js +2 -7
  38. package/dist/engineRowRef.js +3 -0
  39. package/dist/engineSessionMessages.d.ts +1 -0
  40. package/dist/engineSessionMessages.js +7 -1
  41. package/dist/engineTargetSeat.d.ts +27 -0
  42. package/dist/engineTargetSeat.js +199 -0
  43. package/dist/engineWireFor.js +3 -8
  44. package/dist/env/uiLanguage.d.ts +2 -1
  45. package/dist/env/uiLanguage.js +3 -2
  46. package/dist/eventLeg.d.ts +8 -0
  47. package/dist/eventLeg.js +61 -41
  48. package/dist/fileHistoryCaptureCapability.d.ts +2 -1
  49. package/dist/fileHistoryCaptureCapability.js +12 -18
  50. package/dist/fileHistoryLegNotice.d.ts +2 -1
  51. package/dist/fileHistoryLegNotice.js +3 -3
  52. package/dist/frameBinding.d.ts +3 -0
  53. package/dist/frameBinding.js +12 -0
  54. package/dist/gateOutcome.js +16 -2
  55. package/dist/gateVocabulary.d.ts +8 -7
  56. package/dist/gateVocabulary.js +71 -73
  57. package/dist/generated/engineFactTables.d.ts +17 -1
  58. package/dist/generated/engineFactTables.js +29 -0
  59. package/dist/generated/engineNoticeTables.js +9 -3
  60. package/dist/generated/settingsSchemaFns.d.ts +6 -0
  61. package/dist/generated/settingsSchemaFns.js +57 -0
  62. package/dist/generated/settingsSchemaHooksClassifier.d.ts +2 -0
  63. package/dist/generated/settingsSchemaHooksClassifier.js +254 -0
  64. package/dist/generated/settingsSchemaTables.d.ts +1 -0
  65. package/dist/generated/settingsSchemaTables.js +4 -0
  66. package/dist/generated/settingsSchemaTypes.d.ts +105 -0
  67. package/dist/generated/settingsSchemaTypes.js +1 -0
  68. package/dist/hitl/allowRuleShadow.d.ts +2 -1
  69. package/dist/hitl/allowRuleShadow.js +17 -14
  70. package/dist/hitl/approvalCardReasons.d.ts +3 -2
  71. package/dist/hitl/approvalCardReasons.js +27 -28
  72. package/dist/hitl/approvalHolds.d.ts +3 -0
  73. package/dist/hitl/approvalHolds.js +73 -29
  74. package/dist/hitl/askClassPair.d.ts +1 -1
  75. package/dist/hitl/askClassPair.js +16 -0
  76. package/dist/hitl/askGateWire.js +2 -1
  77. package/dist/hitl/askSettlementLine.d.ts +14 -4
  78. package/dist/hitl/askSettlementLine.js +67 -47
  79. package/dist/hitl/automaticAnswerVerdict.d.ts +2 -1
  80. package/dist/hitl/automaticAnswerVerdict.js +8 -8
  81. package/dist/hitl/crashConvergedNotice.d.ts +5 -4
  82. package/dist/hitl/crashConvergedNotice.js +28 -41
  83. package/dist/hitl/frameRouter.js +7 -8
  84. package/dist/hitl/hitlBridge.d.ts +1 -0
  85. package/dist/hitl/hitlBridge.js +3 -3
  86. package/dist/hitl/persistedRulesWire.d.ts +6 -5
  87. package/dist/hitl/persistedRulesWire.js +55 -73
  88. package/dist/hitl/planReviewWire.d.ts +5 -0
  89. package/dist/hitl/planReviewWire.js +8 -0
  90. package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
  91. package/dist/hitl/sessionPolicyDeliverable.js +46 -3
  92. package/dist/hitl/sessionPolicyWire.d.ts +2 -1
  93. package/dist/hitl/sessionPolicyWire.js +23 -24
  94. package/dist/hitl/suspendedReopen.d.ts +2 -1
  95. package/dist/hitl/suspendedReopen.js +7 -10
  96. package/dist/hitl/toolApprovalWire.d.ts +17 -5
  97. package/dist/hitl/toolApprovalWire.js +79 -40
  98. package/dist/hooksWireCaps.js +145 -56
  99. package/dist/host.d.ts +21 -0
  100. package/dist/hostTurnSettings.d.ts +71 -0
  101. package/dist/hostTurnSettings.js +291 -0
  102. package/dist/humanTurn.d.ts +14 -1
  103. package/dist/humanTurn.js +100 -12
  104. package/dist/humanTurnRead.d.ts +2 -0
  105. package/dist/humanTurnRead.js +22 -1
  106. package/dist/i18n/messages.en.d.ts +582 -0
  107. package/dist/i18n/messages.en.js +582 -0
  108. package/dist/i18n/messages.zh.js +718 -1
  109. package/dist/index.d.ts +29 -13
  110. package/dist/index.js +18 -9
  111. package/dist/inheritEnvCapability.d.ts +3 -2
  112. package/dist/inheritEnvCapability.js +3 -3
  113. package/dist/inheritEnvWire.d.ts +3 -2
  114. package/dist/inheritEnvWire.js +12 -14
  115. package/dist/interactiveHalt.js +17 -6
  116. package/dist/liveModelCatalog.d.ts +2 -1
  117. package/dist/liveModelCatalog.js +14 -8
  118. package/dist/mcpPanel.d.ts +10 -1
  119. package/dist/mcpPanel.js +40 -13
  120. package/dist/mcpProbeCapability.d.ts +2 -1
  121. package/dist/mcpProbeCapability.js +7 -11
  122. package/dist/mcpProbeWire.d.ts +3 -1
  123. package/dist/mcpProbeWire.js +5 -0
  124. package/dist/mcpWireCaps.d.ts +34 -0
  125. package/dist/mcpWireCaps.js +117 -17
  126. package/dist/memoryEntriesWire.js +5 -10
  127. package/dist/model/onboardJudgement.d.ts +93 -0
  128. package/dist/model/onboardJudgement.js +415 -0
  129. package/dist/model/tierVocabulary.d.ts +2 -0
  130. package/dist/model/tierVocabulary.js +17 -5
  131. package/dist/modelCapabilityProbe.d.ts +64 -0
  132. package/dist/modelCapabilityProbe.js +600 -0
  133. package/dist/modelIdentityResolvability.d.ts +3 -0
  134. package/dist/modelIdentityResolvability.js +26 -17
  135. package/dist/ownKey.d.ts +7 -0
  136. package/dist/ownKey.js +34 -0
  137. package/dist/panelReduce.d.ts +6 -1
  138. package/dist/panelReduce.js +50 -3
  139. package/dist/permissionRuleIssue.js +2 -0
  140. package/dist/permissionRulesWriteCapability.d.ts +2 -1
  141. package/dist/permissionRulesWriteCapability.js +8 -6
  142. package/dist/pluginHooksWire.d.ts +46 -3
  143. package/dist/pluginHooksWire.js +217 -11
  144. package/dist/printModelStatus.d.ts +63 -0
  145. package/dist/printModelStatus.js +155 -0
  146. package/dist/request/taskRequest.d.ts +11 -5
  147. package/dist/request/taskRequest.js +112 -16
  148. package/dist/resumeRefusalCopy.d.ts +12 -9
  149. package/dist/resumeRefusalCopy.js +70 -143
  150. package/dist/retryStatus.d.ts +37 -0
  151. package/dist/retryStatus.js +79 -0
  152. package/dist/scenarioRefusal.d.ts +9 -0
  153. package/dist/scenarioRefusal.js +111 -0
  154. package/dist/seam.d.ts +25 -1
  155. package/dist/seam.js +10 -1
  156. package/dist/seatContract.d.ts +260 -10
  157. package/dist/seatContract.js +692 -19
  158. package/dist/selfOrchestrationDenial.js +7 -19
  159. package/dist/sessionIdSegment.d.ts +6 -0
  160. package/dist/sessionIdSegment.js +24 -0
  161. package/dist/sessionMemoryStatus.js +6 -11
  162. package/dist/sessionPolicyCapability.d.ts +2 -1
  163. package/dist/sessionPolicyCapability.js +8 -6
  164. package/dist/settingsHooksSalvage.d.ts +44 -0
  165. package/dist/settingsHooksSalvage.js +211 -0
  166. package/dist/skillToolListsCapability.d.ts +2 -1
  167. package/dist/skillToolListsCapability.js +8 -6
  168. package/dist/sseIdleTriage.js +3 -2
  169. package/dist/subagent/engineSubagentResume.js +15 -9
  170. package/dist/subagentContentStore.d.ts +3 -0
  171. package/dist/subagentContentStore.js +22 -0
  172. package/dist/toolHistoryMismatch.js +3 -2
  173. package/dist/toolRoster.d.ts +3 -2
  174. package/dist/toolRoster.js +21 -20
  175. package/dist/turnFailurePresentation.d.ts +12 -1
  176. package/dist/turnFailurePresentation.js +212 -15
  177. package/dist/usageAnalytics.js +6 -11
  178. package/dist/userAttachmentEnvelope.d.ts +1 -0
  179. package/dist/userAttachmentEnvelope.js +37 -7
  180. package/dist/userAttachmentSeat.d.ts +21 -0
  181. package/dist/userAttachmentSeat.js +75 -0
  182. package/dist/userInvokedSkillsCapability.d.ts +11 -1
  183. package/dist/userInvokedSkillsCapability.js +31 -7
  184. package/dist/wireErrorTriage.d.ts +7 -0
  185. package/dist/wireErrorTriage.js +23 -1
  186. package/dist/wireFailureShape.js +2 -1
  187. package/dist/wireRefusalCopy.d.ts +13 -1
  188. package/dist/wireRefusalCopy.js +24 -28
  189. package/dist/workflowsJournalCapability.d.ts +2 -1
  190. package/dist/workflowsJournalCapability.js +8 -6
  191. package/docs/INTEGRATION-CLIENTS.md +542 -30
  192. package/package.json +5 -5
@@ -1,6 +1,6 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { liveFrameCallKey } from './gateIdentity.js';
3
- import { decideReceiptReopen } from '../decideReceipt.js';
3
+ import { decideReceiptReopen, readDecideReceipt } from '../decideReceipt.js';
4
4
  export const APPROVAL_SETTLED_HOWS = Object.freeze(['answered', 'interrupted', 'closed_by_engine']);
5
5
  export const SETTLED_APPROVALS_MAX_ENTRIES = 4096;
6
6
  const nonEmpty = (v) => typeof v === 'string' && v !== '';
@@ -73,7 +73,7 @@ export async function awaitApprovalHolder(hold, signal, asker) {
73
73
  }
74
74
  if (r.kind !== 'unsettled' || r.cause !== 'interrupted' || hold.trailing === undefined)
75
75
  return HOLDER_FREE;
76
- const landed = await hold.trailing.then(() => true, () => false);
76
+ const landed = await hold.trailing.then((raw) => decideReceiptReopen(readDecideReceipt(raw)) === null, () => false);
77
77
  return landed ? Object.freeze({ kind: 'settled', how: 'interrupted', outcome: r }) : HOLDER_FREE;
78
78
  }
79
79
  finally {
@@ -98,7 +98,7 @@ async function awaitHostHolder(hold, signal, asker) {
98
98
  unsubscribe();
99
99
  }
100
100
  }
101
- function holdHostCard(identity) {
101
+ function holdHostCard(identity, signal) {
102
102
  const h = approvalDecisionHolds;
103
103
  const key = `hitl-host:${++h.clock}`;
104
104
  let resolveReleased = () => { };
@@ -109,41 +109,71 @@ function holdHostCard(identity) {
109
109
  const hold = { lane: 'host', identity: frozen, phase: 'card', released };
110
110
  h.inFlight.set(key, hold);
111
111
  let done = false;
112
- return Object.freeze({
112
+ let stopFollowing = () => { };
113
+ const release = (settled) => {
114
+ if (done)
115
+ return;
116
+ done = true;
117
+ stopFollowing();
118
+ const how = settled === 'answered' || settled === 'interrupted' ? settled : undefined;
119
+ if (how !== undefined && nonEmpty(frozen.approvalId))
120
+ noteSettled(frozen.approvalId, how, false, how === 'interrupted' ? interruptedThere('decide') : nothingSentHere('decide'), true);
121
+ if (h.inFlight.get(key) === hold)
122
+ h.inFlight.delete(key);
123
+ resolveReleased(how);
124
+ };
125
+ const handle = Object.freeze({
113
126
  identity: frozen,
114
127
  sending: () => {
115
- if (!done)
116
- hold.phase = 'respond';
117
- },
118
- release: (settled) => {
119
128
  if (done)
120
129
  return;
121
- done = true;
122
- const how = settled === 'answered' || settled === 'interrupted' ? settled : undefined;
123
- if (how !== undefined && nonEmpty(frozen.approvalId))
124
- noteSettled(frozen.approvalId, how, false, how === 'interrupted' ? interruptedThere('decide') : nothingSentHere('decide'), true);
125
- if (h.inFlight.get(key) === hold)
126
- h.inFlight.delete(key);
127
- resolveReleased(how);
130
+ hold.phase = 'respond';
131
+ stopFollowing();
128
132
  },
133
+ release,
134
+ });
135
+ stopFollowing = followCallerSignal(signal, () => {
136
+ if (!done && hold.phase === 'card')
137
+ release();
129
138
  });
139
+ return handle;
140
+ }
141
+ function callerSignalAborted(signal) {
142
+ if (signal === undefined || signal === null)
143
+ return false;
144
+ try {
145
+ return signal.aborted === true;
146
+ }
147
+ catch {
148
+ return false;
149
+ }
130
150
  }
131
- const CLAIM_MAX_ROUNDS = 8;
132
151
  export async function claimApprovalCardFor(who, signal) {
133
- for (let round = 0; round < CLAIM_MAX_ROUNDS; round++) {
134
- const entry = settledApprovalOf(who.approvalId);
135
- if (settledForDurableLane(entry) && entry !== undefined)
136
- return Object.freeze({ kind: 'joined', outcome: { ...entry.outcome, joinedInFlight: true } });
137
- const holder = approvalHoldFor(who);
138
- if (holder === undefined)
139
- return Object.freeze({ kind: 'held', hold: holdHostCard(who) });
140
- const wait = await awaitApprovalHolder(holder, signal, 'decide');
141
- if (wait.kind === 'settled')
142
- return Object.freeze({ kind: 'joined', outcome: { ...wait.outcome, joinedInFlight: true } });
143
- if (wait.kind === 'left')
144
- return Object.freeze({ kind: 'joined', outcome: { ...nothingSentHere('decide'), joinedInFlight: true } });
152
+ let leave = () => { };
153
+ const left = new Promise((resolve) => {
154
+ leave = () => resolve('aborted');
155
+ });
156
+ const unsubscribe = followCallerSignal(signal, leave);
157
+ try {
158
+ for (;;) {
159
+ if (callerSignalAborted(signal))
160
+ return Object.freeze({ kind: 'aborted' });
161
+ const entry = settledApprovalOf(who.approvalId);
162
+ if (settledForDurableLane(entry) && entry !== undefined)
163
+ return Object.freeze({ kind: 'joined', outcome: { ...entry.outcome, joinedInFlight: true } });
164
+ const holder = approvalHoldFor(who);
165
+ if (holder === undefined)
166
+ return Object.freeze({ kind: 'held', hold: holdHostCard(who, signal) });
167
+ const wait = await Promise.race([awaitApprovalHolder(holder, undefined, 'decide'), left]);
168
+ if (wait === 'aborted')
169
+ return Object.freeze({ kind: 'aborted' });
170
+ if (wait.kind === 'settled')
171
+ return Object.freeze({ kind: 'joined', outcome: { ...wait.outcome, joinedInFlight: true } });
172
+ }
173
+ }
174
+ finally {
175
+ unsubscribe();
145
176
  }
146
- return Object.freeze({ kind: 'held', hold: holdHostCard(who) });
147
177
  }
148
178
  export const approvalDecisionHolds = { inFlight: new Map(), settled: new Map(), openReads: new Set(), outcomes: new Map(), clock: 0 };
149
179
  export function settledApprovalOf(approvalId) {
@@ -180,6 +210,20 @@ function engineClosedOutcome() {
180
210
  export function noteEngineClosedApproval(approvalId, parked) {
181
211
  noteSettled(approvalId, 'closed_by_engine', parked, engineClosedOutcome());
182
212
  }
213
+ export function settleEngineClosedApproval(frame, parked) {
214
+ let id;
215
+ try {
216
+ const d = typeof frame === 'object' && frame !== null ? Object.getOwnPropertyDescriptor(frame, 'approvalId') : undefined;
217
+ id = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
218
+ }
219
+ catch {
220
+ return false;
221
+ }
222
+ if (!nonEmpty(id))
223
+ return false;
224
+ noteEngineClosedApproval(id, parked);
225
+ return supersedeLiveCard(liveFrameCallKey(id), parked);
226
+ }
183
227
  export function noteHolderSettledApproval(approvalId, how, outcome) {
184
228
  noteSettled(approvalId, how, false, outcome, true);
185
229
  }
@@ -1,4 +1,4 @@
1
- import type { CORE_MODE_ARBITRATION } from '../generated/engineFactTables.js';
1
+ import { CORE_MODE_ARBITRATION } from '../generated/engineFactTables.js';
2
2
  export declare const DEPLOYMENT_LIST_ASK_CLASSES: readonly (keyof typeof CORE_MODE_ARBITRATION)[];
3
3
  export type AskClassPairReading = {
4
4
  readonly state: 'pair';
@@ -1,3 +1,4 @@
1
+ import { CORE_MODE_ARBITRATION } from '../generated/engineFactTables.js';
1
2
  export const DEPLOYMENT_LIST_ASK_CLASSES = Object.freeze(['ask_rule']);
2
3
  const ABSENT = Object.freeze({ state: 'absent' });
3
4
  const MALFORMED = Object.freeze({ state: 'malformed' });
@@ -35,8 +36,23 @@ export function readAskClassPairState(v) {
35
36
  return MALFORMED;
36
37
  if (classes[0] !== askClass)
37
38
  return MALFORMED;
39
+ if (!inCanonicalOrder(classes))
40
+ return MALFORMED;
38
41
  return Object.freeze({ state: 'pair', askClass, classes: Object.freeze(classes) });
39
42
  }
43
+ function inCanonicalOrder(classes) {
44
+ const order = Object.keys(CORE_MODE_ARBITRATION);
45
+ let last = -1;
46
+ for (const c of classes) {
47
+ const rank = order.indexOf(c);
48
+ if (rank < 0)
49
+ continue;
50
+ if (rank <= last)
51
+ return false;
52
+ last = rank;
53
+ }
54
+ return true;
55
+ }
40
56
  export function readAskClassPair(v) {
41
57
  const r = readAskClassPairState(v);
42
58
  return r.state === 'pair' ? { askClass: r.askClass, classes: r.classes } : undefined;
@@ -6,6 +6,7 @@ import { abortableSleep } from '../abortableSleep.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { decideAcceptedNotResolved, readDecideReceipt } from '../decideReceipt.js';
8
8
  import { APPROVAL_EXPIRED } from '../engineErrorCodes.js';
9
+ import { isNotFoundFailure } from '../wireErrorTriage.js';
9
10
  import { readDecisionRefusalAsk } from './decisionRefusal.js';
10
11
  import { resumeReopenFromError } from './suspendedReopen.js';
11
12
  import { refusedParkFromError, resumeRefusalFromError } from '../resumeRefusalCopy.js';
@@ -192,7 +193,7 @@ export function lateDecideOutcomeOf(reply, opts) {
192
193
  const r = readDecisionRefusalAsk(e);
193
194
  if (r.reading !== 'settled' && !(r.reading === 'not_settled' && r.gateMoved))
194
195
  return LATE_UNKNOWN;
195
- const probeWorthy = status === 404 || status === 410 || (typeof code === 'string' && code.startsWith('not_found.'));
196
+ const probeWorthy = status === 404 || status === 410 || isNotFoundFailure(e) !== false;
196
197
  return Object.freeze(probeWorthy ? { kind: 'settled-elsewhere', probeWorthy: true } : { kind: 'settled-elsewhere' });
197
198
  }
198
199
  catch {
@@ -1,10 +1,11 @@
1
- export declare function askEndingCauseClause(cause: string | undefined): string | undefined;
1
+ import type { UiLanguage } from '../env/uiLanguage.js';
2
+ export declare function askEndingCauseClause(cause: string | undefined, lang?: UiLanguage): string | undefined;
2
3
  export interface AskSettlementInput {
3
4
  readonly cause?: string | undefined;
4
5
  readonly toolName?: string | undefined;
5
6
  readonly parked?: boolean | undefined;
6
7
  }
7
- export declare function askSettlementLine(settled: AskSettlementInput): string | undefined;
8
+ export declare function askSettlementLine(settled: AskSettlementInput, lang?: UiLanguage): string | undefined;
8
9
  export type AskAcceptedFollow = 'following' | 'already-following' | 'not-following';
9
10
  export interface AskAcceptedInput {
10
11
  readonly decision?: string | undefined;
@@ -13,6 +14,15 @@ export interface AskAcceptedInput {
13
14
  readonly follow?: AskAcceptedFollow | undefined;
14
15
  readonly executed?: string | undefined;
15
16
  }
16
- export declare function askAcceptedLine(accepted: AskAcceptedInput): string;
17
+ export declare function askAcceptedLine(accepted: AskAcceptedInput, lang?: UiLanguage): string;
17
18
  export type AskParkedCardForm = 'approval' | 'question' | 'queue_no_deadline';
18
- export declare function askParkedCardNote(form?: string | undefined, toolName?: string | undefined): string;
19
+ export declare function askParkedCardNote(form?: string | undefined, toolName?: string | undefined, lang?: UiLanguage): string;
20
+ export interface AskHumanSettledInput {
21
+ readonly kind?: string | undefined;
22
+ readonly decision?: string | undefined;
23
+ readonly taskId?: string | undefined;
24
+ readonly late?: boolean | undefined;
25
+ readonly follow?: AskAcceptedFollow | undefined;
26
+ readonly executed?: string | undefined;
27
+ }
28
+ export declare function askHumanSettlementLine(settled: AskHumanSettledInput, lang?: UiLanguage): string | undefined;
@@ -1,79 +1,99 @@
1
1
  import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
2
+ import { msg, renderMessage } from '../i18n/translate.js';
2
3
  import { guardedRead, readOwnDataValue } from '../ownKey.js';
3
- const ENDING_CAUSE_CLAUSES = Object.freeze({
4
- routing_failure_fail_closed: 'the engine could not route it to anywhere that can answer it, so it closed it',
5
- run_cancelled: 'the run that asked for it was cancelled',
6
- run_exited: 'the run that asked for it exited before anyone answered',
7
- batch_aborted: 'the batch of tool calls it belonged to was aborted',
8
- batch_sibling_withdrawn: 'another approval in the same batch was refused or withdrawn, which withdrew this one too',
9
- batch_bound_elsewhere: 'the decision for its batch was bound to a different approval',
10
- adhoc_leg_no_durable_domain: 'this kind of request cannot be kept waiting for an answer on this deployment',
11
- orphan_ttl_exceeded: "it was left without an owner past the engine's time limit",
12
- crashed_before_park: 'the engine process stopped before it could keep the request waiting',
13
- shutdown_before_park: 'the engine shut down before it could keep the request waiting',
14
- ttl_expired: 'nobody answered within the approval window',
15
- broadcast_lost: 'it never reached any open connection',
16
- stream_disconnected: 'the connection carrying this card dropped',
17
- undeliverable: 'it could not be delivered to any approval surface',
4
+ const ENDING_CAUSE_KEYS = Object.freeze({
5
+ routing_failure_fail_closed: 'approval.settle.cause.routingFailureFailClosed',
6
+ run_cancelled: 'approval.settle.cause.runCancelled',
7
+ run_exited: 'approval.settle.cause.runExited',
8
+ batch_aborted: 'approval.settle.cause.batchAborted',
9
+ batch_sibling_withdrawn: 'approval.settle.cause.batchSiblingWithdrawn',
10
+ batch_bound_elsewhere: 'approval.settle.cause.batchBoundElsewhere',
11
+ adhoc_leg_no_durable_domain: 'approval.settle.cause.adhocLegNoDurableDomain',
12
+ orphan_ttl_exceeded: 'approval.settle.cause.orphanTtlExceeded',
13
+ crashed_before_park: 'approval.settle.cause.crashedBeforePark',
14
+ shutdown_before_park: 'approval.settle.cause.shutdownBeforePark',
15
+ ttl_expired: 'approval.settle.cause.ttlExpired',
16
+ broadcast_lost: 'approval.settle.cause.broadcastLost',
17
+ stream_disconnected: 'approval.settle.cause.streamDisconnected',
18
+ undeliverable: 'approval.settle.cause.undeliverable',
18
19
  });
19
- export function askEndingCauseClause(cause) {
20
- return typeof cause === 'string' && Object.hasOwn(ENDING_CAUSE_CLAUSES, cause) ? ENDING_CAUSE_CLAUSES[cause] : undefined;
20
+ function endingCauseKeyOf(cause) {
21
+ return typeof cause === 'string' && Object.hasOwn(ENDING_CAUSE_KEYS, cause) ? ENDING_CAUSE_KEYS[cause] : undefined;
22
+ }
23
+ export function askEndingCauseClause(cause, lang) {
24
+ const key = endingCauseKeyOf(cause);
25
+ return key !== undefined ? renderMessage(msg(key), lang) : undefined;
21
26
  }
22
27
  function ownSlot(o, key) {
23
28
  if (typeof o !== 'object' || o === null)
24
29
  return undefined;
25
30
  return guardedRead(() => readOwnDataValue(o, key));
26
31
  }
27
- function subjectOf(toolName, named, fallback) {
32
+ function subjectOf(toolName, named, generic) {
28
33
  const quoted = typeof toolName === 'string' ? guardedRead(() => quoteUntrustedForSentence(toolName, 80)) : undefined;
29
- return quoted === undefined ? fallback : named(quoted);
34
+ return quoted === undefined ? msg(generic) : msg(named, { tool: quoted });
30
35
  }
31
- const SETTLEMENT_TAIL = ' Nothing was approved from this card; if the step is still needed, ask for it again.';
32
- export function askSettlementLine(settled) {
36
+ export function askSettlementLine(settled, lang) {
33
37
  if (ownSlot(settled, 'parked') === true)
34
38
  return undefined;
35
39
  const cause = ownSlot(settled, 'cause');
36
40
  if (typeof cause !== 'string' || cause === '')
37
41
  return undefined;
38
- const subject = subjectOf(ownSlot(settled, 'toolName'), (q) => `the approval request for ${q}`, 'this approval request');
39
- const clause = askEndingCauseClause(cause);
40
- const why = clause !== undefined ? `: ${clause}` : " (the engine's reason is one this build does not know yet)";
41
- return `${subject} ended on the engine without an answer${why}.${SETTLEMENT_TAIL}`;
42
+ const subject = subjectOf(ownSlot(settled, 'toolName'), 'approval.settle.subject.named', 'approval.settle.subject.generic');
43
+ const clauseKey = endingCauseKeyOf(cause);
44
+ return renderMessage(clauseKey !== undefined ? msg('approval.settle.endedKnown', { subject, clause: msg(clauseKey) }) : msg('approval.settle.endedUnknown', { subject }), lang);
42
45
  }
43
46
  const TASK_ID_MAX = 64;
44
- const EXECUTED_TAILS = Object.freeze({
45
- allowed: ' The engine also reports that the already-decided step was ALLOWED when it ran.',
46
- denied: ' The engine also reports that the already-decided step was DENIED at execution time (a deployment review can overturn an approval after the fact).',
47
+ const EXECUTED_TAIL_KEYS = Object.freeze({
48
+ allowed: 'approval.settle.executed.allowed',
49
+ denied: 'approval.settle.executed.denied',
47
50
  });
48
- const FOLLOW_CLAUSES = Object.freeze({
49
- following: 'sema is following that run here',
50
- 'already-following': 'this screen is already following that run',
51
+ const FOLLOW_CLAUSE_KEYS = Object.freeze({
52
+ following: 'approval.settle.follow.following',
53
+ 'already-following': 'approval.settle.follow.alreadyFollowing',
51
54
  });
52
- export function askAcceptedLine(accepted) {
55
+ export function askAcceptedLine(accepted, lang) {
53
56
  const decision = ownSlot(accepted, 'decision');
54
- const what = decision === 'approve' ? 'approved' : decision === 'deny' ? 'denied' : 'answered';
55
- const late = ownSlot(accepted, 'late') === false ? '' : ' late';
57
+ const what = msg(decision === 'approve' ? 'approval.settle.what.approved' : decision === 'deny' ? 'approval.settle.what.denied' : 'approval.settle.what.answered');
58
+ const late = ownSlot(accepted, 'late') === false ? '' : msg('approval.settle.late');
56
59
  const rawTask = ownSlot(accepted, 'taskId');
57
60
  const shownTask = typeof rawTask === 'string' ? guardedRead(() => displayUntrusted(rawTask, { max: TASK_ID_MAX, blanks: true })) : undefined;
58
- const where = shownTask === undefined || shownTask === '' ? '' : ` (task ${shownTask})`;
61
+ const where = shownTask === undefined || shownTask === '' ? '' : msg('approval.settle.where', { task: shownTask });
59
62
  const follow = ownSlot(accepted, 'follow');
60
63
  const executed = ownSlot(accepted, 'executed');
61
- const tail = executed === 'allowed' || executed === 'denied' ? EXECUTED_TAILS[executed] : '';
64
+ const tail = executed === 'allowed' || executed === 'denied' ? msg(EXECUTED_TAIL_KEYS[executed]) : '';
62
65
  if (follow === 'following' || follow === 'already-following') {
63
- return `${what}${late} — the engine took this decision${where}; ${FOLLOW_CLAUSES[follow]}, so what it does next shows up below.${tail}`;
66
+ return renderMessage(msg('approval.settle.acceptedFollowing', { what, late, where, follow: msg(FOLLOW_CLAUSE_KEYS[follow]), tail }), lang);
64
67
  }
65
- return (`${what}${late} — the engine took this decision${where}. Whether the parked call has resumed yet is not something this reply can say; ` +
66
- `this screen is not following that run, so what it does next will not show up here by itself.${tail}`);
68
+ return renderMessage(msg('approval.settle.acceptedNotFollowing', { what, late, where, tail }), lang);
67
69
  }
68
- const PARKED_CARD_NOTES = Object.freeze({
69
- approval: "This step was already parked in the engine's durable queue and waiting for a decision before this card went up. Yes approves it now and lets the step run late; No abandons the step — the model gets a refusal and can take another route.",
70
- question: "This step was already parked in the engine's durable queue and waiting for a decision before this card went up. Answering now sends your decision late and lets the parked step continue with it; closing this card without answering abandons the step — the model gets a refusal and can take another route.",
71
- queue_no_deadline: "{subject} is in the engine's durable queue (no deadline was reported for it). It waits for your decision — nothing has been decided on your behalf.",
70
+ const PARKED_CARD_NOTE_KEYS = Object.freeze({
71
+ approval: 'approval.settle.parkedNote.approval',
72
+ question: 'approval.settle.parkedNote.question',
73
+ queue_no_deadline: 'approval.settle.parkedNote.queueNoDeadline',
72
74
  });
73
- export function askParkedCardNote(form, toolName) {
75
+ export function askParkedCardNote(form, toolName, lang) {
74
76
  const key = form === 'question' || form === 'queue_no_deadline' ? form : 'approval';
75
- const template = PARKED_CARD_NOTES[key];
76
77
  if (key !== 'queue_no_deadline')
77
- return template;
78
- return template.split('{subject}').join(subjectOf(toolName, (q) => `the approval for ${q}`, 'this approval'));
78
+ return renderMessage(msg(PARKED_CARD_NOTE_KEYS[key]), lang);
79
+ return renderMessage(msg(PARKED_CARD_NOTE_KEYS[key], { subject: subjectOf(toolName, 'approval.settle.parkedSubject.named', 'approval.settle.parkedSubject.generic') }), lang);
80
+ }
81
+ export function askHumanSettlementLine(settled, lang) {
82
+ const kind = ownSlot(settled, 'kind');
83
+ if (kind === 'accepted-not-resolved')
84
+ return askAcceptedLine(settled, lang);
85
+ if (kind === 'expired-reaped')
86
+ return renderMessage(msg('approval.settleHuman.expiredReaped'), lang);
87
+ if (kind === 'settled-elsewhere')
88
+ return renderMessage(msg('approval.settleHuman.settledElsewhere'), lang);
89
+ if (kind !== 'resumed')
90
+ return undefined;
91
+ const decision = ownSlot(settled, 'decision');
92
+ const what = msg(decision === 'approve' ? 'approval.settle.what.approved' : decision === 'deny' ? 'approval.settle.what.denied' : 'approval.settle.what.answered');
93
+ const rawTask = ownSlot(settled, 'taskId');
94
+ const shownTask = typeof rawTask === 'string' ? guardedRead(() => displayUntrusted(rawTask, { max: TASK_ID_MAX, blanks: true })) : undefined;
95
+ const where = shownTask === undefined || shownTask === '' ? '' : msg('approval.settle.where', { task: shownTask });
96
+ const executed = ownSlot(settled, 'executed');
97
+ const tail = executed === 'allowed' || executed === 'denied' ? msg(EXECUTED_TAIL_KEYS[executed]) : '';
98
+ return renderMessage(msg(ownSlot(settled, 'late') === false ? 'approval.settleHuman.resumed' : 'approval.settleHuman.resumedLate', { what, where, tail }), lang);
79
99
  }
@@ -1,5 +1,6 @@
1
1
  import { type CoreModeArbitrationRow } from '../generated/engineFactTables.js';
2
2
  import { CORE_ASK_FACES } from '../generated/engineAskFaceTables.js';
3
+ import type { UiLanguage } from '../env/uiLanguage.js';
3
4
  export type AutomaticAnswerLane = 'mode_posture' | 'standing_rule' | 'permission_request_hook';
4
5
  export type AutomaticAnswerAskFace = (typeof CORE_ASK_FACES)[number];
5
6
  export interface AutomaticAnswerFacts {
@@ -22,6 +23,6 @@ export type AutomaticAnswerVerdict = {
22
23
  export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "target_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "unverifiable_shell_text" | "transcript_write" | "org_rule" | "rule_unreadable" | "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
24
  export declare function foldModeArbitrationRow(classes: Iterable<string>, askFace?: AutomaticAnswerAskFace): CoreModeArbitrationRow;
24
25
  export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
25
- export declare function mandatedApprovalDetailOf(card: object | null | undefined): string;
26
+ export declare function mandatedApprovalDetailOf(card: object | null | undefined, lang?: UiLanguage): string;
26
27
  export type PermissionRequestHookScope = 'allow_or_deny' | 'deny_only' | 'skip';
27
28
  export declare function permissionRequestHookScope(verdict: AutomaticAnswerVerdict | null | undefined): PermissionRequestHookScope;
@@ -1,6 +1,7 @@
1
1
  import { CORE_MODE_ARBITRATION, CORE_MODE_RELEASE_REASONS } from '../generated/engineFactTables.js';
2
2
  import { CORE_RELEASABLE_ASK_CLASSES_BY_FACE, CORE_SHELL_FACE_TOOL_NAMES } from '../generated/engineAskFaceTables.js';
3
3
  import { mandatedApprovalDetail } from '../gateVocabulary.js';
4
+ import { msg, renderMessage } from '../i18n/translate.js';
4
5
  import { readAskOrigin, readOwnNonEmptyString } from '../ownKey.js';
5
6
  import { readAskClassPairState } from './askClassPair.js';
6
7
  import { readRequiresRealApproval } from './approvalCardReading.js';
@@ -179,26 +180,25 @@ export function automaticAnswerVerdict(card, lane, facts) {
179
180
  return UNKNOWN.card_unreadable;
180
181
  }
181
182
  }
182
- export function mandatedApprovalDetailOf(card) {
183
+ export function mandatedApprovalDetailOf(card, lang) {
183
184
  try {
184
185
  if (typeof card !== 'object' || card === null)
185
- return mandatedApprovalDetail();
186
+ return mandatedApprovalDetail(lang);
186
187
  const pair = readAskClassPairState(card);
187
188
  if (pair.state !== 'pair')
188
- return mandatedApprovalDetail();
189
+ return mandatedApprovalDetail(lang);
189
190
  const classes = pair.classes;
190
191
  if (classes.some((c) => !Object.hasOwn(TABLE, c)))
191
- return mandatedApprovalDetail();
192
+ return mandatedApprovalDetail(lang);
192
193
  const row = foldModeArbitrationRow(classes);
193
194
  if (row.classifierEligible || row.sandboxAdmission)
194
- return mandatedApprovalDetail();
195
- return MANDATED_NO_AUTOMATIC_CHECK_DETAIL;
195
+ return mandatedApprovalDetail(lang);
196
+ return renderMessage(msg('approval.mandate.noAutomaticCheck'), lang);
196
197
  }
197
198
  catch {
198
- return mandatedApprovalDetail();
199
+ return mandatedApprovalDetail(lang);
199
200
  }
200
201
  }
201
- const MANDATED_NO_AUTOMATIC_CHECK_DETAIL = 'no saved allow rule and no remembered answer can retire this question, and no automatic check answers it; each call is settled on its own, by you here, and an answer covers only that call';
202
202
  export function permissionRequestHookScope(verdict) {
203
203
  try {
204
204
  const kind = typeof verdict === 'object' && verdict !== null ? verdict.kind : undefined;
@@ -1,6 +1,7 @@
1
+ import type { UiLanguage } from '../env/uiLanguage.js';
1
2
  import type { CrashConvergedProjection, CrashConvergedRowReading } from './crashConverged.js';
2
3
  export declare const NEEDS_HUMAN_REASON_ORDER: readonly ["approved_then_interrupted", "pending_unsafe", "contradictory", "unstable_row"];
3
- export declare function crashConvergedNeedsHumanSentence(reason: string | undefined, n: number, total: number): string | undefined;
4
- export declare function crashConvergedNotice(projection: CrashConvergedProjection | undefined): string | undefined;
5
- export declare function crashConvergedRowSentence(projection: CrashConvergedProjection | undefined, row: Pick<CrashConvergedRowReading, 'approvalId'>): string | undefined;
6
- export declare function crashConvergedCauseSentence(cause: string | undefined): string | undefined;
4
+ export declare function crashConvergedNeedsHumanSentence(reason: string | undefined, n: number, total: number, lang?: UiLanguage): string | undefined;
5
+ export declare function crashConvergedNotice(projection: CrashConvergedProjection | undefined, lang?: UiLanguage): string | undefined;
6
+ export declare function crashConvergedRowSentence(projection: CrashConvergedProjection | undefined, row: Pick<CrashConvergedRowReading, 'approvalId'>, lang?: UiLanguage): string | undefined;
7
+ export declare function crashConvergedCauseSentence(cause: string | undefined, lang?: UiLanguage): string | undefined;
@@ -1,3 +1,4 @@
1
+ import { msg, renderMessage } from '../i18n/translate.js';
1
2
  import { askEndingCauseClause } from './askSettlementLine.js';
2
3
  import { crashConvergedRowInProjection, isCrashConvergedCause, readCrashConvergedNoticeFacts } from './crashConverged.js';
3
4
  export const NEEDS_HUMAN_REASON_ORDER = Object.freeze(['approved_then_interrupted', 'pending_unsafe', 'contradictory', 'unstable_row']);
@@ -7,53 +8,40 @@ const _orderHasNoDuplicate = true;
7
8
  void _orderCoversReasons;
8
9
  void _orderInsideReasons;
9
10
  void _orderHasNoDuplicate;
10
- const RECORDED = 'based on what was recorded';
11
- const NO_AUTO_RERUN = 'sema has not re-run anything';
12
11
  const plural = (n, one, many) => (n === 1 ? one : many);
13
12
  const NEEDS_HUMAN_CLAUSES = Object.freeze({
14
- approved_then_interrupted: {
15
- one: 'had already been approved and may have been running when the previous engine run ended, so a side effect may have partly landed',
16
- many: 'had already been approved and may have been running when the previous engine run ended, so a side effect may have partly landed',
17
- },
18
- pending_unsafe: {
19
- one: `had not started, ${RECORDED}, but the engine did not mark it as safe to re-run`,
20
- many: `had not started, ${RECORDED}, but the engine did not mark them as safe to re-run`,
21
- },
22
- contradictory: { one: 'has a contradictory record', many: 'have contradictory records' },
23
- unstable_row: {
24
- one: 'has a record that read differently each time it was read',
25
- many: 'have records that read differently each time they were read',
26
- },
13
+ approved_then_interrupted: { one: 'session.crashConverged.reason.approvedThenInterrupted.one', many: 'session.crashConverged.reason.approvedThenInterrupted.many' },
14
+ pending_unsafe: { one: 'session.crashConverged.reason.pendingUnsafe.one', many: 'session.crashConverged.reason.pendingUnsafe.many' },
15
+ contradictory: { one: 'session.crashConverged.reason.contradictory.one', many: 'session.crashConverged.reason.contradictory.many' },
16
+ unstable_row: { one: 'session.crashConverged.reason.unstableRow.one', many: 'session.crashConverged.reason.unstableRow.many' },
27
17
  });
28
- const FALLBACK_CLAUSE = Object.freeze({
29
- one: 'had already been approved, or cannot be accounted for, so a side effect may have partly landed',
30
- many: 'had already been approved, or cannot be accounted for, so a side effect may have partly landed',
31
- });
32
- const RESUME_SAFE_CLAUSE = `had not started, ${RECORDED}, and can be re-run`;
33
- const countedClause = (clause, n, total) => `${n} of ${total} ${n === 1 ? clause.one : clause.many}`;
18
+ const FALLBACK_CLAUSE = Object.freeze({ one: 'session.crashConverged.fallback.one', many: 'session.crashConverged.fallback.many' });
19
+ const RESUME_SAFE_CLAUSE = 'session.crashConverged.resumeSafe';
20
+ const countedClause = (clause, n, total, lang) => renderMessage(msg('session.crashConverged.counted', { n, total, clause: msg(n === 1 ? clause.one : clause.many) }), lang);
34
21
  const isNeedsHumanReason = (v) => typeof v === 'string' && Object.hasOwn(NEEDS_HUMAN_CLAUSES, v);
35
- export function crashConvergedNeedsHumanSentence(reason, n, total) {
22
+ export function crashConvergedNeedsHumanSentence(reason, n, total, lang) {
36
23
  if (!isNeedsHumanReason(reason))
37
24
  return undefined;
38
25
  if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 1)
39
26
  return undefined;
40
27
  if (typeof total !== 'number' || !Number.isSafeInteger(total) || total < n)
41
28
  return undefined;
42
- return countedClause(NEEDS_HUMAN_CLAUSES[reason], n, total);
29
+ return countedClause(NEEDS_HUMAN_CLAUSES[reason], n, total, lang);
43
30
  }
44
- function needsHumanClauses(reasons, total) {
31
+ function needsHumanClauses(reasons, total, lang) {
45
32
  const clauses = [];
46
33
  for (const reason of NEEDS_HUMAN_REASON_ORDER) {
47
34
  const n = reasons.filter((r) => r === reason).length;
48
35
  if (n > 0)
49
- clauses.push(countedClause(NEEDS_HUMAN_CLAUSES[reason], n, total));
36
+ clauses.push(countedClause(NEEDS_HUMAN_CLAUSES[reason], n, total, lang));
50
37
  }
51
38
  const fallback = reasons.filter((r) => !isNeedsHumanReason(r)).length;
52
39
  if (fallback > 0)
53
- clauses.push(countedClause(FALLBACK_CLAUSE, fallback, total));
54
- return `${clauses.join('; ')} — check ${plural(reasons.length, 'it', 'them')} by hand before re-running`;
40
+ clauses.push(countedClause(FALLBACK_CLAUSE, fallback, total, lang));
41
+ const joined = clauses.join(renderMessage(msg('session.crashConverged.clauseJoin'), lang));
42
+ return renderMessage(msg(plural(reasons.length, 'session.crashConverged.needsHumanBody.one', 'session.crashConverged.needsHumanBody.many'), { clauses: joined }), lang);
55
43
  }
56
- export function crashConvergedNotice(projection) {
44
+ export function crashConvergedNotice(projection, lang) {
57
45
  const facts = readCrashConvergedNoticeFacts(projection);
58
46
  if (facts === undefined)
59
47
  return undefined;
@@ -61,16 +49,15 @@ export function crashConvergedNotice(projection) {
61
49
  if (total === 0 && dropped === 0)
62
50
  return undefined;
63
51
  if (total === 0) {
64
- return `the previous session left ${dropped} crash-converged approval ${plural(dropped, 'record', 'records')} that this version could not read. ${NO_AUTO_RERUN}.`;
52
+ return renderMessage(msg(plural(dropped, 'session.crashConverged.droppedOnly.one', 'session.crashConverged.droppedOnly.many'), { count: dropped }), lang);
65
53
  }
66
- const recordedAs = causes !== undefined && causes.length > 0 ? ` (recorded as ${causes.join(', ')})` : '';
67
- const head = `the previous engine run ended with ${total} ${plural(total, 'approval', 'approvals')} still waiting${recordedAs}, ` +
68
- `and the engine converged ${plural(total, 'it', 'them')} to denied`;
69
- const body = needsHumanReasons.length > 0 ? needsHumanClauses(needsHumanReasons, total) : `${RECORDED}, ${plural(total, 'it', 'they')} can be re-run`;
70
- const droppedClause = dropped > 0 ? ` (${dropped} further ${plural(dropped, 'record', 'records')} could not be read.)` : '';
71
- return `${head}; ${body}. ${NO_AUTO_RERUN}.${droppedClause}`;
54
+ const recordedAs = causes !== undefined && causes.length > 0 ? msg('session.crashConverged.recordedAs', { causes: causes.join(', ') }) : '';
55
+ const head = renderMessage(msg(plural(total, 'session.crashConverged.head.one', 'session.crashConverged.head.many'), { total, recorded: recordedAs }), lang);
56
+ const body = needsHumanReasons.length > 0 ? needsHumanClauses(needsHumanReasons, total, lang) : renderMessage(msg(plural(total, 'session.crashConverged.rerunnable.one', 'session.crashConverged.rerunnable.many')), lang);
57
+ const droppedClause = dropped > 0 ? msg(plural(dropped, 'session.crashConverged.droppedTail.one', 'session.crashConverged.droppedTail.many'), { count: dropped }) : '';
58
+ return renderMessage(msg('session.crashConverged.line', { head, body, dropped: droppedClause }), lang);
72
59
  }
73
- export function crashConvergedRowSentence(projection, row) {
60
+ export function crashConvergedRowSentence(projection, row, lang) {
74
61
  const hit = crashConvergedRowInProjection(projection, row);
75
62
  if (hit === undefined)
76
63
  return undefined;
@@ -82,13 +69,13 @@ export function crashConvergedRowSentence(projection, row) {
82
69
  return undefined;
83
70
  }
84
71
  if (hit.bucket === 'resumeSafe')
85
- return descriptor === undefined ? RESUME_SAFE_CLAUSE : undefined;
72
+ return descriptor === undefined ? renderMessage(msg(RESUME_SAFE_CLAUSE), lang) : undefined;
86
73
  const reason = descriptor !== undefined && Object.hasOwn(descriptor, 'value') ? descriptor.value : undefined;
87
- return (isNeedsHumanReason(reason) ? NEEDS_HUMAN_CLAUSES[reason] : FALLBACK_CLAUSE).one;
74
+ return renderMessage(msg((isNeedsHumanReason(reason) ? NEEDS_HUMAN_CLAUSES[reason] : FALLBACK_CLAUSE).one), lang);
88
75
  }
89
- export function crashConvergedCauseSentence(cause) {
76
+ export function crashConvergedCauseSentence(cause, lang) {
90
77
  if (typeof cause !== 'string' || cause === '')
91
78
  return undefined;
92
- const clause = isCrashConvergedCause(cause) ? askEndingCauseClause(cause) : undefined;
93
- return clause === undefined ? 'the decision converged automatically' : `${clause}, and the decision converged automatically`;
79
+ const clause = isCrashConvergedCause(cause) ? askEndingCauseClause(cause, lang) : undefined;
80
+ return renderMessage(clause === undefined ? msg('session.crashConverged.cause.neutral') : msg('session.crashConverged.cause.withClause', { clause }), lang);
94
81
  }
@@ -8,11 +8,11 @@ import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateT
8
8
  import { parkHeldCallIdOf } from '../engineRowRef.js';
9
9
  import { inheritFrameBinding } from '../frameBinding.js';
10
10
  import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
11
- import { liveCardHoldOf, noteEngineClosedApproval, supersedeLiveCard } from './approvalHolds.js';
11
+ import { liveCardHoldOf, noteEngineClosedApproval, settleEngineClosedApproval } from './approvalHolds.js';
12
12
  import { approvalCompleteParked } from './approvalCompleteParked.js';
13
13
  import { liveFrameCallKey } from './gateIdentity.js';
14
14
  import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
15
- import { replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
15
+ import { replayedApprovalClosesCardFrame, replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
16
16
  import { settleWithinDeadline } from '../abortableSleep.js';
17
17
  import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
18
18
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
@@ -285,12 +285,9 @@ const TOOL_END_ARMS = [
285
285
  async function routeToolApprovalFrame(ev, ctx) {
286
286
  const { led, deps } = ctx;
287
287
  if (ev.type === 'tool_approval_complete') {
288
- const id = ev.approvalId;
289
- const parked = approvalCompleteParked(ev);
290
- if (typeof id === 'string' && id !== '')
291
- noteEngineClosedApproval(id, parked);
292
- if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), parked)) {
293
- hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${id} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
288
+ if (settleEngineClosedApproval(ev, approvalCompleteParked(ev))) {
289
+ const id = ev.approvalId;
290
+ hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${String(id)} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
294
291
  }
295
292
  return { kind: 'skip' };
296
293
  }
@@ -307,6 +304,8 @@ async function routeToolApprovalFrame(ev, ctx) {
307
304
  : `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
308
305
  `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
309
306
  ' — not raised as a question, no decision sent');
307
+ if (settledHere === undefined && replayedApprovalClosesCardFrame(disposition))
308
+ noteEngineClosedApproval(ev.approvalId, false);
310
309
  if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
311
310
  surfaceReplayedApprovalListUnreadable();
312
311
  if (deps.onReplayedApproval !== undefined) {
@@ -3,6 +3,7 @@ import type { PermissionResult } from '@sema-agent/agent-types';
3
3
  import type { ApprovalsListEnvelope } from './crashConverged.js';
4
4
  import { type SuspendedReopenReading } from './suspendedReopen.js';
5
5
  export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
6
+ export declare const MAX_DENY_REASON_CHARS = 4096;
6
7
  export declare function denyReasonForWire(reason: string | undefined, tag: string): string | undefined;
7
8
  export interface HitlPermissionDecisionLike {
8
9
  behavior: 'allow' | 'ask' | 'deny';