@sema-agent/client-core 0.110.0 → 0.111.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 (176) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +50 -18
  3. package/dist/abortableSleep.d.ts +7 -0
  4. package/dist/abortableSleep.js +37 -0
  5. package/dist/adapt/arms.js +25 -4
  6. package/dist/adapt/toolCards.d.ts +3 -1
  7. package/dist/adapt/toolCards.js +4 -2
  8. package/dist/adapter/activeRunSelfHeal.d.ts +39 -0
  9. package/dist/adapter/activeRunSelfHeal.js +171 -3
  10. package/dist/adapter/downstream/eventToSdkMessage.js +10 -10
  11. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
  12. package/dist/adapter/downstream/terminalToSdkResult.js +11 -0
  13. package/dist/adapter/runStream.d.ts +5 -0
  14. package/dist/adapter/runStream.js +15 -3
  15. package/dist/attachmentRoute.d.ts +20 -0
  16. package/dist/attachmentRoute.js +39 -0
  17. package/dist/attachmentsWireCaps.js +1 -1
  18. package/dist/builtinOutputStyles.d.ts +12 -0
  19. package/dist/builtinOutputStyles.js +147 -0
  20. package/dist/compactionStatus.d.ts +9 -0
  21. package/dist/compactionStatus.js +29 -5
  22. package/dist/controlRouter.js +2 -1
  23. package/dist/coreValuePorts.d.ts +39 -1
  24. package/dist/coreValuePorts.js +10 -0
  25. package/dist/displayUntrusted.d.ts +1 -0
  26. package/dist/displayUntrusted.js +6 -0
  27. package/dist/engineHealthFacts.d.ts +11 -0
  28. package/dist/engineHealthFacts.js +21 -0
  29. package/dist/engineHealthProbeVerdict.d.ts +23 -0
  30. package/dist/engineHealthProbeVerdict.js +115 -0
  31. package/dist/engineNoticeCodes.js +5 -7
  32. package/dist/engineRepoint.d.ts +12 -5
  33. package/dist/engineRepoint.js +44 -19
  34. package/dist/engineRowRef.d.ts +3 -0
  35. package/dist/engineRowRef.js +11 -1
  36. package/dist/engineSwapCrossing.d.ts +57 -0
  37. package/dist/engineSwapCrossing.js +297 -0
  38. package/dist/engineUnavailable.d.ts +44 -0
  39. package/dist/engineUnavailable.js +492 -0
  40. package/dist/eventLeg.js +3 -3
  41. package/dist/generated/sdkFactTables.d.ts +1 -1
  42. package/dist/generated/sdkFactTables.js +1 -0
  43. package/dist/generated/settingsSchemaTables.d.ts +4 -0
  44. package/dist/generated/settingsSchemaTables.js +1 -0
  45. package/dist/hitl/approvalDelivery.d.ts +1 -0
  46. package/dist/hitl/approvalDelivery.js +1 -1
  47. package/dist/hitl/approvalResolution.d.ts +1 -1
  48. package/dist/hitl/approvalResolution.js +2 -1
  49. package/dist/hitl/askGateWire.js +5 -6
  50. package/dist/hitl/decisionRefusal.d.ts +2 -0
  51. package/dist/hitl/decisionRefusal.js +7 -8
  52. package/dist/hitl/decisionRefusalCarrier.d.ts +3 -0
  53. package/dist/hitl/decisionRefusalCarrier.js +348 -0
  54. package/dist/hitl/decisionSend.d.ts +7 -0
  55. package/dist/hitl/decisionSend.js +34 -12
  56. package/dist/hitl/editedApprovalAcceptance.js +1 -1
  57. package/dist/hitl/elicitationRefusal.d.ts +4 -0
  58. package/dist/hitl/elicitationRefusal.js +23 -0
  59. package/dist/hitl/frameRouter.d.ts +2 -2
  60. package/dist/hitl/frameRouter.js +8 -15
  61. package/dist/hitl/gateIdentity.d.ts +5 -0
  62. package/dist/hitl/gateIdentity.js +23 -0
  63. package/dist/hitl/hitlBridge.d.ts +6 -0
  64. package/dist/hitl/hitlBridge.js +10 -4
  65. package/dist/hitl/hitlHostSurface.d.ts +2 -1
  66. package/dist/hitl/hitlHostSurface.js +7 -2
  67. package/dist/hitl/localAllowRedemption.d.ts +67 -0
  68. package/dist/hitl/localAllowRedemption.js +256 -0
  69. package/dist/hitl/parkResolver.js +7 -4
  70. package/dist/hitl/persistedRulesWire.d.ts +16 -0
  71. package/dist/hitl/persistedRulesWire.js +70 -0
  72. package/dist/hitl/planReviewWire.d.ts +3 -0
  73. package/dist/hitl/planReviewWire.js +23 -6
  74. package/dist/hitl/policyDenyCause.d.ts +1 -1
  75. package/dist/hitl/policyDenyCause.js +1 -1
  76. package/dist/hitl/resumeRunningCard.d.ts +1 -0
  77. package/dist/hitl/resumeRunningCard.js +1 -0
  78. package/dist/hitl/screenCardCarriers.d.ts +15 -0
  79. package/dist/hitl/screenCardCarriers.js +41 -0
  80. package/dist/hitl/screenDecisionCards.d.ts +98 -0
  81. package/dist/hitl/screenDecisionCards.js +362 -0
  82. package/dist/hitl/standingRows.d.ts +3 -1
  83. package/dist/hitl/standingRows.js +18 -0
  84. package/dist/hitl/toolApprovalWire.d.ts +30 -8
  85. package/dist/hitl/toolApprovalWire.js +264 -121
  86. package/dist/hooksWireCaps.js +3 -3
  87. package/dist/hostSettingNoticeArm.d.ts +3 -0
  88. package/dist/hostSettingNoticeArm.js +12 -0
  89. package/dist/hostTurnSettings.d.ts +3 -0
  90. package/dist/hostTurnSettings.js +38 -5
  91. package/dist/humanTurnRead.js +11 -1
  92. package/dist/i18n/messages.en.d.ts +129 -0
  93. package/dist/i18n/messages.en.js +135 -7
  94. package/dist/i18n/messages.zh.js +142 -6
  95. package/dist/i18n/translate.d.ts +3 -0
  96. package/dist/i18n/translate.js +78 -10
  97. package/dist/i18n/zh.d.ts +1 -0
  98. package/dist/i18n/zh.js +1 -0
  99. package/dist/index.d.ts +43 -17
  100. package/dist/index.js +30 -12
  101. package/dist/interactiveHalt.js +2 -1
  102. package/dist/interactiveInterrupt.js +20 -2
  103. package/dist/liveModelCatalog.js +2 -2
  104. package/dist/localEngineSeatReading.d.ts +2 -2
  105. package/dist/localEngineSeatReading.js +5 -3
  106. package/dist/localEngineSpawn.d.ts +0 -7
  107. package/dist/localEngineSpawn.js +0 -92
  108. package/dist/localEngineStep.d.ts +2 -2
  109. package/dist/localEngineStep.js +2 -0
  110. package/dist/mcpReconnect.js +3 -3
  111. package/dist/mcpWireCaps.js +1 -1
  112. package/dist/model/engineModelWords.d.ts +3 -0
  113. package/dist/model/engineModelWords.js +13 -0
  114. package/dist/model/subagentModelWord.d.ts +21 -2
  115. package/dist/model/subagentModelWord.js +100 -29
  116. package/dist/modelCapabilityProbe.js +3 -40
  117. package/dist/notifications.d.ts +19 -0
  118. package/dist/notifications.js +114 -12
  119. package/dist/ownKey.d.ts +6 -0
  120. package/dist/ownKey.js +31 -0
  121. package/dist/pendingUserRow.d.ts +4 -0
  122. package/dist/pendingUserRow.js +16 -0
  123. package/dist/permissionsDeclaration.d.ts +1 -0
  124. package/dist/permissionsDeclaration.js +11 -1
  125. package/dist/planReviewCardFacts.d.ts +3 -0
  126. package/dist/planReviewCardFacts.js +52 -2
  127. package/dist/pluginHooksWire.d.ts +9 -1
  128. package/dist/pluginHooksWire.js +40 -6
  129. package/dist/printToolResultFrame.js +4 -2
  130. package/dist/promptBlockedCopy.js +11 -6
  131. package/dist/promptSuggestionBatch.d.ts +7 -0
  132. package/dist/promptSuggestionBatch.js +9 -0
  133. package/dist/promptSuggestionsTail.d.ts +39 -0
  134. package/dist/promptSuggestionsTail.js +116 -0
  135. package/dist/request/taskRequest.js +10 -6
  136. package/dist/requestCoordinate.d.ts +1 -0
  137. package/dist/requestCoordinate.js +13 -0
  138. package/dist/rewindArchiveReading.js +4 -4
  139. package/dist/runCancelContext.d.ts +2 -1
  140. package/dist/runCancelContext.js +1 -0
  141. package/dist/runPermissionModeSwitch.js +2 -2
  142. package/dist/scenarioRefusal.js +11 -13
  143. package/dist/scenarioSkills.js +11 -6
  144. package/dist/seam.d.ts +16 -2
  145. package/dist/seam.js +10 -0
  146. package/dist/seatContract.d.ts +109 -9
  147. package/dist/seatContract.js +218 -15
  148. package/dist/sessionCostReading.d.ts +70 -0
  149. package/dist/sessionCostReading.js +150 -0
  150. package/dist/sessionHooksRoster.d.ts +4 -0
  151. package/dist/sessionHooksRoster.js +158 -0
  152. package/dist/sessionMemoryStatus.js +2 -2
  153. package/dist/sessionReadRedaction.d.ts +7 -0
  154. package/dist/sessionReadRedaction.js +26 -0
  155. package/dist/sessionSyncFileHistory.js +8 -9
  156. package/dist/settingsHooksSalvage.js +2 -1
  157. package/dist/sseIdleTriage.js +2 -1
  158. package/dist/storeDurabilityWord.d.ts +15 -0
  159. package/dist/storeDurabilityWord.js +18 -0
  160. package/dist/storePostureCapability.d.ts +7 -3
  161. package/dist/storePostureCapability.js +69 -39
  162. package/dist/subagent/engineSubagentResume.js +2 -2
  163. package/dist/subagent/engineSubagentTail.js +2 -1
  164. package/dist/subagentContentStore.d.ts +11 -0
  165. package/dist/subagentContentStore.js +28 -2
  166. package/dist/toolResult.d.ts +1 -0
  167. package/dist/toolResult.js +2 -0
  168. package/dist/userAttachmentEnvelope.d.ts +2 -1
  169. package/dist/userAttachmentEnvelope.js +10 -0
  170. package/dist/wireErrorTriage.js +2 -1
  171. package/dist/wireFailureShape.d.ts +4 -0
  172. package/dist/wireFailureShape.js +141 -19
  173. package/dist/wireRefusalCopy.d.ts +6 -5
  174. package/dist/wireRefusalCopy.js +49 -92
  175. package/docs/INTEGRATION-CLIENTS.md +2118 -17
  176. package/package.json +6 -2
@@ -1,8 +1,8 @@
1
1
  import { AUTO_MODE_UNAVAILABLE_CAUSES } from './autoModeUnavailable.js';
2
- import { displayUntrusted } from './displayUntrusted.js';
2
+ import { displayUntrusted, displayUntrustedCapped } from './displayUntrusted.js';
3
3
  import { CORE_ENGINE_NOTICE_CODES, CORE_MCP_INJECTION_DROP_REASONS, CORE_NOTICE_AUDIENCE, CORE_USER_INPUT_UNDRAINED_FAMILIES, } from './generated/engineNoticeTables.js';
4
4
  import { CORE_APPROVAL_REMEDY_KINDS, CORE_ASK_UNRESOLVABLE_CAUSES, CORE_DISCARDED_DELIVERY_PATHS, CORE_DURABLE_MANDATE_SOURCES, CORE_PERMISSION_MODE_FOLD_REASONS, } from './generated/engineFactTables.js';
5
- import { msg, renderMessage } from './i18n/translate.js';
5
+ import { msg, renderLanguageOf, renderMessage } from './i18n/translate.js';
6
6
  import { ownDataField, readOwnDataValue } from './ownKey.js';
7
7
  import { inValueOf, slotReadOf } from './wireFailureShape.js';
8
8
  export const ENGINE_NOTICE_CODES = CORE_ENGINE_NOTICE_CODES;
@@ -196,7 +196,6 @@ function readAskUnresolvableOf(notice) {
196
196
  };
197
197
  }
198
198
  export const DURABLE_MANDATE_SOURCES = CORE_DURABLE_MANDATE_SOURCES;
199
- const DURABLE_MANDATE_SOURCE_SET = new Set(DURABLE_MANDATE_SOURCES);
200
199
  export function readDurableGateUnavailable(notice) {
201
200
  if (typeof notice !== 'object' || notice === null || Array.isArray(notice))
202
201
  return undefined;
@@ -213,7 +212,7 @@ export function readDurableGateUnavailable(notice) {
213
212
  const causeIn = o.cause;
214
213
  const sessionId = typeof sessionIdIn === 'string' && sessionIdIn.length > 0 ? sessionIdIn : undefined;
215
214
  const runId = typeof runIdIn === 'string' && runIdIn.length > 0 ? runIdIn : undefined;
216
- const source = typeof sourceIn === 'string' && DURABLE_MANDATE_SOURCE_SET.has(sourceIn) ? sourceIn : undefined;
215
+ const source = typeof sourceIn === 'string' && sourceIn.length > 0 ? sourceIn : undefined;
217
216
  const cause = typeof causeIn === 'string' && causeIn.length > 0 ? causeIn : undefined;
218
217
  if (sessionId === undefined || runId === undefined || source === undefined || cause === undefined) {
219
218
  return undefined;
@@ -1417,15 +1416,14 @@ function shownName(v) {
1417
1416
  function shownText(v, max) {
1418
1417
  if (v === undefined)
1419
1418
  return undefined;
1420
- const whole = displayUntrusted(v);
1421
- const s = whole.length <= max ? whole : `${displayUntrusted(v, { max: max - 1 })}…`;
1419
+ const s = displayUntrustedCapped(v, max);
1422
1420
  return s.length > 0 ? s : undefined;
1423
1421
  }
1424
1422
  function wordLookup(table, word) {
1425
1423
  return Object.hasOwn(table, word) ? table[word] : undefined;
1426
1424
  }
1427
1425
  function joinSentences(parts, lang) {
1428
- return parts.filter((p) => p !== undefined && p.length > 0).join(lang === 'zh' ? '' : ' ');
1426
+ return parts.filter((p) => p !== undefined && p.length > 0).join(renderLanguageOf(lang) === 'zh' ? '' : ' ');
1429
1427
  }
1430
1428
  export function memorySessionPollutedSentence(facts, lang) {
1431
1429
  try {
@@ -15,7 +15,12 @@ export declare function deviceHelloReadingOf(frame: {} | null | undefined): Devi
15
15
  export interface EngineRepointInput {
16
16
  readonly from: string;
17
17
  readonly to: string;
18
- readonly expectDeviceId?: string;
18
+ readonly continuity?: EngineRepointContinuity;
19
+ }
20
+ export interface EngineRepointContinuity {
21
+ readonly deviceId: string;
22
+ readonly from: string;
23
+ readonly to: string;
19
24
  }
20
25
  export interface EngineRepointHandshakeTarget {
21
26
  readonly address: string;
@@ -26,8 +31,7 @@ export type EngineRepointHandshakePort = (target: EngineRepointHandshakeTarget,
26
31
  }) => Promise<{} | null | undefined> | {} | null | undefined;
27
32
  export interface EngineRepointRoster {
28
33
  readonly credential: 'operator' | 'non-operator';
29
- readonly address: string;
30
- readonly devices: DeviceListVerb | null | undefined;
34
+ readonly devices: (address: string) => DeviceListVerb | null | undefined;
31
35
  }
32
36
  export type EngineRepointRecordReading = {
33
37
  readonly kind: 'absent';
@@ -75,7 +79,7 @@ export type EngineRepointRosterProof = {
75
79
  readonly why: 'not-listed' | 'lane-absent';
76
80
  } | {
77
81
  readonly state: 'unreadable';
78
- readonly why: 'operator-credential' | 'credential-unknown' | 'address-unbound' | 'list-incomplete' | 'refused' | 'transport' | 'unreadable' | 'unarmed';
82
+ readonly why: 'operator-credential' | 'credential-unknown' | 'list-incomplete' | 'refused' | 'transport' | 'unreadable' | 'unarmed';
79
83
  };
80
84
  export interface EngineRepointProofs {
81
85
  readonly handshake: EngineRepointHandshakeProof;
@@ -94,11 +98,12 @@ export interface EngineRepointSessionTally {
94
98
  readonly unreadable: number;
95
99
  readonly storeFailed: number;
96
100
  }
97
- export type EngineRepointRefusal = 'address-invalid' | 'same-address' | 'record-absent' | 'record-not-active' | 'record-unreadable' | 'target-occupied' | 'unarmed';
101
+ export type EngineRepointRefusal = 'address-invalid' | 'same-address' | 'record-absent' | 'record-not-active' | 'record-unreadable' | 'target-occupied' | 'roster-unbound' | 'unarmed';
98
102
  export type EngineRepointResult = EngineRepointDone | EngineRepointPartial | EngineRepointUnverified | EngineRepointUndetermined | EngineRepointStoreFailed | EngineRepointRefused;
99
103
  export interface EngineRepointDone {
100
104
  readonly kind: 'repointed';
101
105
  readonly deviceId: string;
106
+ readonly continuity: EngineRepointContinuity;
102
107
  readonly record: 'moved' | 'already-there';
103
108
  readonly sessions: EngineRepointSessionTally;
104
109
  readonly proofs: EngineRepointProofs;
@@ -107,6 +112,7 @@ export interface EngineRepointPartial {
107
112
  readonly kind: 'partial';
108
113
  readonly stage: 'sessions' | 'sessions-list';
109
114
  readonly deviceId: string;
115
+ readonly continuity: EngineRepointContinuity;
110
116
  readonly record: 'moved' | 'already-there';
111
117
  readonly sessions: EngineRepointSessionTally;
112
118
  readonly proofs: EngineRepointProofs;
@@ -128,6 +134,7 @@ export interface EngineRepointStoreFailed {
128
134
  readonly kind: 'store-failed';
129
135
  readonly stage: 'record';
130
136
  readonly deviceId: string;
137
+ readonly continuity: EngineRepointContinuity;
131
138
  }
132
139
  export interface EngineRepointRefused {
133
140
  readonly kind: 'refused';
@@ -58,18 +58,27 @@ async function handshakeProofOf(port, target, signal) {
58
58
  return freeze({ state: 'unreadable', why: r.code });
59
59
  }
60
60
  const RO_PROVEN = freeze({ state: 'proven' });
61
- async function rosterProofOf(roster, target, deviceId, signal) {
61
+ function rosterSliceOf(roster, target) {
62
62
  if (!isObject(roster))
63
- return freeze({ state: 'unreadable', why: 'unarmed' });
63
+ return { kind: 'proof', proof: freeze({ state: 'unreadable', why: 'unarmed' }) };
64
64
  const credential = dataOf(roster, 'credential');
65
65
  if (credential === 'operator')
66
- return freeze({ state: 'unreadable', why: 'operator-credential' });
66
+ return { kind: 'proof', proof: freeze({ state: 'unreadable', why: 'operator-credential' }) };
67
67
  if (credential !== 'non-operator')
68
- return freeze({ state: 'unreadable', why: 'credential-unknown' });
69
- const listed = addressOf(dataOf(roster, 'address'), false);
70
- if (listed === undefined || engineNamespaceKeyFor(listed) !== engineNamespaceKeyFor(target))
71
- return freeze({ state: 'unreadable', why: 'address-unbound' });
72
- const devices = dataOf(roster, 'devices');
68
+ return { kind: 'proof', proof: freeze({ state: 'unreadable', why: 'credential-unknown' }) };
69
+ const factory = dataOf(roster, 'devices');
70
+ if (typeof factory !== 'function')
71
+ return { kind: 'unbound' };
72
+ let slice;
73
+ try {
74
+ slice = factory(target);
75
+ }
76
+ catch {
77
+ return { kind: 'proof', proof: freeze({ state: 'unreadable', why: 'unreadable' }) };
78
+ }
79
+ return slice === null || slice === undefined ? { kind: 'unbound' } : { kind: 'slice', devices: slice };
80
+ }
81
+ async function rosterProofOf(devices, deviceId, signal) {
73
82
  const out = await listDevices(devices, signal !== undefined ? { signal } : undefined);
74
83
  switch (out.kind) {
75
84
  case 'ok':
@@ -123,7 +132,17 @@ async function recordFactOf(read, port, address) {
123
132
  const deviceId = dataOf(raw, 'deviceId');
124
133
  return typeof deviceId === 'string' && deviceId !== '' ? freeze({ k: 'active', deviceId }) : UNREADABLE_RECORD;
125
134
  }
126
- function planOf(from, to, expectDeviceId) {
135
+ function continuityReadingOf(raw) {
136
+ if (!isObject(raw))
137
+ return undefined;
138
+ const deviceId = dataOf(raw, 'deviceId');
139
+ const f = addressOf(dataOf(raw, 'from'), false);
140
+ const t = addressOf(dataOf(raw, 'to'), true);
141
+ if (typeof deviceId !== 'string' || deviceId === '' || f === undefined || t === undefined)
142
+ return undefined;
143
+ return freeze({ deviceId, fromKey: engineNamespaceKeyFor(f), toKey: engineNamespaceKeyFor(t) });
144
+ }
145
+ function planOf(from, to, evidence, pair) {
127
146
  if (from.k === 'unreadable' || to.k === 'unreadable')
128
147
  return { kind: 'refused', reason: 'record-unreadable' };
129
148
  if (from.k === 'other')
@@ -135,8 +154,10 @@ function planOf(from, to, expectDeviceId) {
135
154
  return { kind: 'move', deviceId: from.deviceId };
136
155
  return { kind: 'refused', reason: 'target-occupied' };
137
156
  }
138
- if (to.k === 'active')
139
- return expectDeviceId !== undefined && to.deviceId === expectDeviceId ? { kind: 'finish', deviceId: to.deviceId } : { kind: 'refused', reason: 'record-absent' };
157
+ if (to.k === 'active') {
158
+ const bound = evidence !== undefined && to.deviceId === evidence.deviceId && evidence.fromKey === pair.fromKey && evidence.toKey === pair.toKey;
159
+ return bound ? { kind: 'finish', deviceId: to.deviceId } : { kind: 'refused', reason: 'record-absent' };
160
+ }
140
161
  if (to.k === 'other')
141
162
  return { kind: 'refused', reason: 'record-not-active', recordStatus: to.status };
142
163
  return { kind: 'refused', reason: 'record-absent' };
@@ -188,14 +209,17 @@ export async function repointEngine(input, ports) {
188
209
  const roster = dataOf(ports, 'roster');
189
210
  const signalRaw = dataOf(ports, 'signal');
190
211
  const signal = isObject(signalRaw) ? signalRaw : undefined;
191
- const expectRaw = dataOf(input, 'expectDeviceId');
192
- const expectDeviceId = typeof expectRaw === 'string' && expectRaw !== '' ? expectRaw : undefined;
212
+ const evidence = continuityReadingOf(dataOf(input, 'continuity'));
193
213
  const [fromFact, toFact] = await Promise.all([recordFactOf(read, recordPort, from), recordFactOf(read, recordPort, to)]);
194
- const plan = planOf(fromFact, toFact, expectDeviceId);
214
+ const plan = planOf(fromFact, toFact, evidence, { fromKey: engineNamespaceKeyFor(from), toKey: engineNamespaceKeyFor(to) });
195
215
  if (plan.kind === 'refused')
196
216
  return refusedOf(plan.reason, plan.recordStatus);
197
217
  const deviceId = plan.deviceId;
198
- const [hs, ro] = await Promise.all([handshakeProofOf(handshake, freeze({ address: to, deviceId }), signal), rosterProofOf(roster, to, deviceId, signal)]);
218
+ const continuity = freeze({ deviceId, from, to });
219
+ const slice = rosterSliceOf(roster, to);
220
+ if (slice.kind === 'unbound')
221
+ return refusedOf('roster-unbound');
222
+ const [hs, ro] = await Promise.all([handshakeProofOf(handshake, freeze({ address: to, deviceId }), signal), slice.kind === 'proof' ? slice.proof : rosterProofOf(slice.devices, deviceId, signal)]);
199
223
  const proofs = freeze({ handshake: hs, roster: ro });
200
224
  const verdict = engineRepointVerdictOf(proofs);
201
225
  if (verdict !== 'apply') {
@@ -217,7 +241,7 @@ export async function repointEngine(input, ports) {
217
241
  moved = afterFrom.k === 'absent' && afterTo.k === 'active' && afterTo.deviceId === deviceId;
218
242
  }
219
243
  if (!moved)
220
- return freeze({ kind: 'store-failed', stage: 'record', deviceId });
244
+ return freeze({ kind: 'store-failed', stage: 'record', deviceId, continuity });
221
245
  }
222
246
  const record = plan.kind === 'move' ? 'moved' : 'already-there';
223
247
  const counts = { moved: 0, unaffected: 0, conflict: 0, unreadable: 0, storeFailed: 0 };
@@ -229,7 +253,7 @@ export async function repointEngine(input, ports) {
229
253
  stores = undefined;
230
254
  }
231
255
  if (stores === undefined)
232
- return freeze({ kind: 'partial', stage: 'sessions-list', deviceId, record, sessions: tallyOf(counts), proofs });
256
+ return freeze({ kind: 'partial', stage: 'sessions-list', deviceId, continuity, record, sessions: tallyOf(counts), proofs });
233
257
  for (const store of stores) {
234
258
  const r = repointSessionEngine(store, from, to);
235
259
  if (r === 'moved')
@@ -245,8 +269,8 @@ export async function repointEngine(input, ports) {
245
269
  }
246
270
  const sessions = tallyOf(counts);
247
271
  return counts.conflict + counts.unreadable + counts.storeFailed > 0
248
- ? freeze({ kind: 'partial', stage: 'sessions', deviceId, record, sessions, proofs })
249
- : freeze({ kind: 'repointed', deviceId, record, sessions, proofs });
272
+ ? freeze({ kind: 'partial', stage: 'sessions', deviceId, continuity, record, sessions, proofs })
273
+ : freeze({ kind: 'repointed', deviceId, continuity, record, sessions, proofs });
250
274
  }
251
275
  catch {
252
276
  return refusedOf('unarmed');
@@ -259,6 +283,7 @@ const REFUSAL_KEYS = {
259
283
  'record-not-active': 'session.devices.repoint.refused.recordNotActive',
260
284
  'record-unreadable': 'session.devices.repoint.refused.recordUnreadable',
261
285
  'target-occupied': 'session.devices.repoint.refused.targetOccupied',
286
+ 'roster-unbound': 'session.devices.repoint.refused.rosterUnbound',
262
287
  unarmed: 'session.devices.repoint.refused.unarmed',
263
288
  };
264
289
  export function engineRepointMessageOf(result) {
@@ -1,4 +1,5 @@
1
1
  import type { AgentEvent } from '@sema-agent/sdk';
2
+ import { type GateOutcomeView } from './gateOutcome.js';
2
3
  export interface EngineRowRef {
3
4
  readonly eventId?: string;
4
5
  readonly taskId?: string;
@@ -22,7 +23,9 @@ export declare function terminalRunIdOf(ev: Extract<AgentEvent, {
22
23
  } | {
23
24
  type: 'failed';
24
25
  }>): string | undefined;
26
+ export declare const SEMA_REFUSAL_NOTED_KEY = "_sema_refusal_noted";
25
27
  export declare const TRANSCRIPT_ROW_SIDECAR_KEYS: readonly string[];
28
+ export declare function refusalNotedOf(denialKind: string | undefined, gate: GateOutcomeView | undefined): boolean;
26
29
  export declare function parkHeldCallIdOf(carrier: {} | null | undefined): string | undefined;
27
30
  export declare function parkedToolEndRoleOf(frame: {} | null | undefined): 'held' | 'collateral' | undefined;
28
31
  export declare function isUnsettledHeldToolEnd(frame: {} | null | undefined): boolean;
@@ -1,6 +1,7 @@
1
1
  import { guardedRead, readFrameStringKey, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
2
2
  import { GATE_PARKED_ERROR_CODE } from './engineErrorCodes.js';
3
- import { isCcToolDenialKind } from './gateVocabulary.js';
3
+ import { ccToolDenialKindForSettledBy, isCcToolDenialKind } from './gateVocabulary.js';
4
+ import { gateDeniedBy } from './gateOutcome.js';
4
5
  import { doneResultView } from './replayBody.js';
5
6
  import { TRANSCRIPT_ID_NAMESPACE, uuidV5 } from './uuidV5.js';
6
7
  const ENGINE_REF_KEY = '_sema_engine_ref';
@@ -77,6 +78,7 @@ export function terminalRunIdOf(ev) {
77
78
  const v = doneResultView(ev.result).body?.runId;
78
79
  return typeof v === 'string' && v.length > 0 ? v : undefined;
79
80
  }
81
+ export const SEMA_REFUSAL_NOTED_KEY = '_sema_refusal_noted';
80
82
  export const TRANSCRIPT_ROW_SIDECAR_KEYS = Object.freeze([
81
83
  '_sema_segment_id',
82
84
  '_sema_engine_ref',
@@ -93,6 +95,7 @@ export const TRANSCRIPT_ROW_SIDECAR_KEYS = Object.freeze([
93
95
  '_sema_interrupted_never_started',
94
96
  '_sema_approval_window_expired',
95
97
  '_sema_denial_limit_window_expired',
98
+ SEMA_REFUSAL_NOTED_KEY,
96
99
  '_sema_api_error_status',
97
100
  '_sema_api_error_code',
98
101
  '_sema_provider_condition',
@@ -105,6 +108,13 @@ export const TRANSCRIPT_ROW_SIDECAR_KEYS = Object.freeze([
105
108
  '_sema_run_origin',
106
109
  '_sema_run_origin_unreadable',
107
110
  ]);
111
+ export function refusalNotedOf(denialKind, gate) {
112
+ if (denialKind !== ccToolDenialKindForSettledBy('human'))
113
+ return false;
114
+ if (gateDeniedBy(gate) !== 'ask_resolution')
115
+ return false;
116
+ return gate?.settlement?.kind === 'human_refused' && gate.settlement.note !== undefined;
117
+ }
108
118
  export function parkHeldCallIdOf(carrier) {
109
119
  return readFrameStringKey(carrier, 'toolCallId');
110
120
  }
@@ -0,0 +1,57 @@
1
+ import type { ApprovalsFeedEmission } from './hitl/approvalsFeed.js';
2
+ import type { LocalEngineUpgradePath, LocalEngineVerdict } from './localEngineJudge.js';
3
+ import type { RequestLane } from './request/taskRequest.js';
4
+ import type { UiLanguage } from './env/uiLanguage.js';
5
+ export declare const ENGINE_DRAIN_CLASSES: readonly ["parked_tool_approvals"];
6
+ export type EngineDrainClass = (typeof ENGINE_DRAIN_CLASSES)[number];
7
+ export interface EngineDrainSession {
8
+ readonly sessionId: string;
9
+ readonly count: number;
10
+ }
11
+ export type EngineDrainReading = {
12
+ readonly class: EngineDrainClass;
13
+ readonly state: 'count';
14
+ readonly count: number;
15
+ readonly sessions: readonly EngineDrainSession[];
16
+ } | {
17
+ readonly class: EngineDrainClass;
18
+ readonly state: 'failed';
19
+ } | {
20
+ readonly class: EngineDrainClass;
21
+ readonly state: 'unsupported';
22
+ };
23
+ export type EngineSwapCrossing = 'newer' | 'same' | 'older' | 'none' | 'unknown';
24
+ export interface EngineSwapCrossingFacts {
25
+ readonly engine: LocalEngineVerdict;
26
+ readonly drain?: readonly EngineDrainReading[];
27
+ readonly informed?: string;
28
+ }
29
+ export type EngineSwapCrossingVerdict = {
30
+ readonly kind: 'clear';
31
+ readonly crossing: 'none' | 'same' | 'older' | 'unknown';
32
+ } | {
33
+ readonly kind: 'hold';
34
+ readonly reason: 'newer_engine';
35
+ readonly crossing: 'newer';
36
+ readonly running: string;
37
+ readonly own: string;
38
+ readonly source: 'running' | 'high-water';
39
+ } | {
40
+ readonly kind: 'hold';
41
+ readonly reason: 'drain_unconfirmed';
42
+ readonly crossing: 'older';
43
+ readonly classes: readonly EngineDrainReading[];
44
+ readonly fingerprint: string;
45
+ } | {
46
+ readonly kind: 'unknown';
47
+ readonly crossing: 'unknown';
48
+ readonly classes: readonly EngineDrainReading[];
49
+ readonly fingerprint: string;
50
+ } | {
51
+ readonly kind: 'swap_and_tell';
52
+ readonly crossing: 'older' | 'unknown';
53
+ readonly classes: readonly EngineDrainReading[];
54
+ };
55
+ export declare function engineSwapCrossingVerdict(facts: EngineSwapCrossingFacts): EngineSwapCrossingVerdict;
56
+ export declare function parkedToolApprovalsBySession(emission: ApprovalsFeedEmission | null | undefined): EngineDrainReading;
57
+ export declare function engineSwapCrossingText(verdict: EngineSwapCrossingVerdict, lane: Exclude<RequestLane, 'utility'>, upgrade: LocalEngineUpgradePath | undefined, lang?: UiLanguage): string | undefined;
@@ -0,0 +1,297 @@
1
+ import { checkpointQueueRowSortOf } from './hitl/approvalQueueRow.js';
2
+ import { durableDecideLegOf } from './adapter/activeRunSelfHeal.js';
3
+ import { checkpointRowVerdictOf } from './checkpointRowVerdict.js';
4
+ import { compareReleaseVersions } from './supportFloor.js';
5
+ import { engineIdentityOf } from './engineIdentity.js';
6
+ import { slotReadOf } from './wireFailureShape.js';
7
+ import { msg, renderMessage } from './i18n/translate.js';
8
+ export const ENGINE_DRAIN_CLASSES = Object.freeze(['parked_tool_approvals']);
9
+ function own(o, key) {
10
+ if (typeof o !== 'object' || o === null)
11
+ return undefined;
12
+ const r = slotReadOf(o, key);
13
+ return r.presence === 'present' ? r.value : undefined;
14
+ }
15
+ function isArray(v) {
16
+ try {
17
+ return Array.isArray(v);
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ function isObject(v) {
24
+ return typeof v === 'object' && v !== null && !isArray(v);
25
+ }
26
+ function isCount(v) {
27
+ return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
28
+ }
29
+ function nonEmpty(v) {
30
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
31
+ }
32
+ function elementsOf(v) {
33
+ if (!isArray(v))
34
+ return undefined;
35
+ const len = own(v, 'length');
36
+ if (!isCount(len))
37
+ return undefined;
38
+ const out = [];
39
+ for (let i = 0; i < len; i++) {
40
+ const r = slotReadOf(v, String(i));
41
+ if (r.presence !== 'present')
42
+ return undefined;
43
+ out.push(r.value);
44
+ }
45
+ return out;
46
+ }
47
+ function failedOf(c) {
48
+ return Object.freeze({ class: c, state: 'failed' });
49
+ }
50
+ function crossingOf(v) {
51
+ const kind = own(v, 'kind');
52
+ if (kind === 'none')
53
+ return { crossing: 'none' };
54
+ if (kind === 'attach')
55
+ return isObject(own(v, 'engine')) ? { crossing: 'same' } : { crossing: 'unknown' };
56
+ if (kind === 'ahead' || kind === 'behind') {
57
+ const running = own(v, 'running');
58
+ const mine = own(v, 'own');
59
+ if (typeof running !== 'string' || typeof mine !== 'string')
60
+ return { crossing: 'unknown' };
61
+ const cmp = compareReleaseVersions(running, mine);
62
+ if (kind === 'ahead')
63
+ return cmp === -1 && isObject(own(v, 'engine')) ? { crossing: 'older' } : { crossing: 'unknown' };
64
+ const source = own(v, 'source');
65
+ if (cmp !== 1 || (source !== 'running' && source !== 'high-water'))
66
+ return { crossing: 'unknown' };
67
+ return { crossing: 'newer', running, own: mine, source };
68
+ }
69
+ return { crossing: 'unknown' };
70
+ }
71
+ function targetOf(v) {
72
+ const engine = own(v, 'engine');
73
+ const anchored = own(v, 'anchored');
74
+ const src = isObject(engine) ? own(engine, 'identity') : isObject(anchored) ? anchored : v;
75
+ const id = engineIdentityOf({ pid: own(src, 'pid'), instanceId: own(src, 'instanceId'), startedAt: own(src, 'startedAt') });
76
+ return { ...(id.pid !== undefined ? { pid: id.pid } : {}), ...(id.instanceId !== undefined ? { instanceId: id.instanceId } : {}), ...(id.startedAt !== undefined ? { startedAt: id.startedAt } : {}) };
77
+ }
78
+ function drainEntryOf(e) {
79
+ if (!isObject(e))
80
+ return undefined;
81
+ const c = own(e, 'class');
82
+ if (!ENGINE_DRAIN_CLASSES.includes(c))
83
+ return undefined;
84
+ const cls = c;
85
+ const state = own(e, 'state');
86
+ if (state === 'failed')
87
+ return failedOf(cls);
88
+ if (state === 'unsupported')
89
+ return Object.freeze({ class: cls, state: 'unsupported' });
90
+ if (state !== 'count')
91
+ return undefined;
92
+ const count = own(e, 'count');
93
+ const rows = elementsOf(own(e, 'sessions'));
94
+ if (!isCount(count) || rows === undefined)
95
+ return undefined;
96
+ const sessions = [];
97
+ const seen = new Set();
98
+ let sum = 0;
99
+ for (const s of rows) {
100
+ const sessionId = nonEmpty(own(s, 'sessionId'));
101
+ const n = own(s, 'count');
102
+ if (sessionId === undefined || !isCount(n) || n === 0 || seen.has(sessionId))
103
+ return undefined;
104
+ seen.add(sessionId);
105
+ sum += n;
106
+ sessions.push(Object.freeze({ sessionId, count: n }));
107
+ }
108
+ if (sum !== count)
109
+ return undefined;
110
+ sessions.sort((a, b) => (a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0));
111
+ return Object.freeze({ class: cls, state: 'count', count, sessions: Object.freeze(sessions) });
112
+ }
113
+ function drainOf(raw) {
114
+ const entries = elementsOf(raw);
115
+ const byClass = new Map();
116
+ let bad = entries === undefined;
117
+ for (const e of entries ?? []) {
118
+ const r = drainEntryOf(e);
119
+ if (r === undefined || byClass.has(r.class)) {
120
+ bad = true;
121
+ break;
122
+ }
123
+ byClass.set(r.class, r);
124
+ }
125
+ return Object.freeze(ENGINE_DRAIN_CLASSES.map(c => (bad ? undefined : byClass.get(c)) ?? failedOf(c)));
126
+ }
127
+ function fingerprintOf(crossing, v, drain) {
128
+ const running = own(v, 'running');
129
+ const mine = own(v, 'own');
130
+ return JSON.stringify([
131
+ 'engine-swap-crossing/1',
132
+ crossing,
133
+ targetOf(v),
134
+ typeof running === 'string' ? running : null,
135
+ typeof mine === 'string' ? mine : null,
136
+ drain.map(r => (r.state === 'count' ? [r.class, r.state, r.count, r.sessions.map(s => [s.sessionId, s.count])] : [r.class, r.state])),
137
+ ]);
138
+ }
139
+ function judge(facts) {
140
+ const engine = own(facts, 'engine');
141
+ const d = crossingOf(engine);
142
+ if (d.crossing === 'newer') {
143
+ return Object.freeze({ kind: 'hold', reason: 'newer_engine', crossing: 'newer', running: d.running, own: d.own, source: d.source });
144
+ }
145
+ if (d.crossing === 'none' || d.crossing === 'same')
146
+ return Object.freeze({ kind: 'clear', crossing: d.crossing });
147
+ const drain = drainOf(own(facts, 'drain'));
148
+ const classes = Object.freeze(drain.filter(r => !(r.state === 'count' && r.count === 0)));
149
+ const holding = drain.some(r => r.state === 'failed' || (r.state === 'count' && r.count > 0));
150
+ const fingerprint = fingerprintOf(d.crossing, engine, drain);
151
+ const informedRaw = own(facts, 'informed');
152
+ const informed = typeof informedRaw === 'string' && informedRaw === fingerprint;
153
+ if (d.crossing === 'older') {
154
+ if (!holding)
155
+ return Object.freeze({ kind: 'clear', crossing: 'older' });
156
+ if (informed)
157
+ return Object.freeze({ kind: 'swap_and_tell', crossing: 'older', classes });
158
+ return Object.freeze({ kind: 'hold', reason: 'drain_unconfirmed', crossing: 'older', classes, fingerprint });
159
+ }
160
+ if (informed) {
161
+ return holding
162
+ ? Object.freeze({ kind: 'swap_and_tell', crossing: 'unknown', classes })
163
+ : Object.freeze({ kind: 'clear', crossing: 'unknown' });
164
+ }
165
+ return Object.freeze({ kind: 'unknown', crossing: 'unknown', classes, fingerprint });
166
+ }
167
+ const UNKNOWN_ALL_FAILED = () => {
168
+ const drain = Object.freeze(ENGINE_DRAIN_CLASSES.map(failedOf));
169
+ return Object.freeze({ kind: 'unknown', crossing: 'unknown', classes: drain, fingerprint: fingerprintOf('unknown', undefined, drain) });
170
+ };
171
+ export function engineSwapCrossingVerdict(facts) {
172
+ try {
173
+ return judge(facts);
174
+ }
175
+ catch {
176
+ return UNKNOWN_ALL_FAILED();
177
+ }
178
+ }
179
+ const PARKED = 'parked_tool_approvals';
180
+ function parkedOf(emission) {
181
+ if (!isObject(emission) || own(emission, 'kind') !== 'snapshot')
182
+ return failedOf(PARKED);
183
+ const droppedSlot = slotReadOf(emission, 'pendingDropped');
184
+ if (droppedSlot.presence === 'unreadable')
185
+ return failedOf(PARKED);
186
+ if (droppedSlot.presence === 'present' && droppedSlot.value !== undefined && !(isCount(droppedSlot.value) && droppedSlot.value === 0))
187
+ return failedOf(PARKED);
188
+ const rows = elementsOf(own(emission, 'pending'));
189
+ if (rows === undefined)
190
+ return failedOf(PARKED);
191
+ const tally = new Map();
192
+ for (const row of rows) {
193
+ if (!isObject(row))
194
+ return failedOf(PARKED);
195
+ const sort = checkpointQueueRowSortOf(row);
196
+ if (sort === 'excluded')
197
+ continue;
198
+ if (sort === 'unreadable')
199
+ return failedOf(PARKED);
200
+ const gate = slotReadOf(row, 'gateKind');
201
+ const leg = gate.presence === 'unreadable' ? 'unreadable' : durableDecideLegOf({ gateKind: typeof gate.value === 'string' ? gate.value : null });
202
+ if (leg === 'plan_review' || leg === 'other_gate')
203
+ continue;
204
+ const verdict = checkpointRowVerdictOf(row);
205
+ if (verdict !== undefined && verdict.kind !== 'ok')
206
+ continue;
207
+ if (leg !== 'approval')
208
+ return failedOf(PARKED);
209
+ const sessionId = nonEmpty(own(row, 'sessionId'));
210
+ if (sessionId === undefined)
211
+ return failedOf(PARKED);
212
+ tally.set(sessionId, (tally.get(sessionId) ?? 0) + 1);
213
+ }
214
+ const sessions = [...tally.entries()]
215
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
216
+ .map(([sessionId, count]) => Object.freeze({ sessionId, count }));
217
+ const count = sessions.reduce((n, s) => n + s.count, 0);
218
+ return Object.freeze({ class: PARKED, state: 'count', count, sessions: Object.freeze(sessions) });
219
+ }
220
+ export function parkedToolApprovalsBySession(emission) {
221
+ try {
222
+ return parkedOf(emission);
223
+ }
224
+ catch {
225
+ return failedOf(PARKED);
226
+ }
227
+ }
228
+ function say(key, lang, params) {
229
+ return renderMessage(msg(key, params), lang);
230
+ }
231
+ function classLine(r, lang) {
232
+ const state = own(r, 'state');
233
+ if (state === 'failed')
234
+ return say('localEngine.swapGuard.parked.failed', lang);
235
+ if (state === 'unsupported')
236
+ return say('localEngine.swapGuard.parked.unsupported', lang);
237
+ const count = own(r, 'count');
238
+ if (state !== 'count' || !isCount(count) || count === 0)
239
+ return undefined;
240
+ if (count === 1)
241
+ return say('localEngine.swapGuard.parked.one', lang);
242
+ const sessions = elementsOf(own(r, 'sessions'));
243
+ if (sessions !== undefined && sessions.length > 1)
244
+ return say('localEngine.swapGuard.parked.manySessions', lang, { count, sessions: sessions.length });
245
+ return say('localEngine.swapGuard.parked.oneSession', lang, { count });
246
+ }
247
+ function toldLine(r, lang) {
248
+ const state = own(r, 'state');
249
+ if (state === 'failed' || state === 'unsupported')
250
+ return say('localEngine.swapGuard.told.unknown', lang);
251
+ const count = own(r, 'count');
252
+ if (state !== 'count' || !isCount(count) || count === 0)
253
+ return undefined;
254
+ return count === 1 ? say('localEngine.swapGuard.told.one', lang) : say('localEngine.swapGuard.told.other', lang, { count });
255
+ }
256
+ function classesOf(v) {
257
+ return elementsOf(own(v, 'classes')) ?? [];
258
+ }
259
+ function joined(parts, lang) {
260
+ const out = parts.filter((p) => typeof p === 'string' && p.length > 0);
261
+ return out.length > 0 ? out.join(lang === 'zh' ? '' : ' ') : undefined;
262
+ }
263
+ export function engineSwapCrossingText(verdict, lane, upgrade, lang) {
264
+ try {
265
+ const kind = own(verdict, 'kind');
266
+ const reason = own(verdict, 'reason');
267
+ if (kind === 'hold' && reason === 'newer_engine') {
268
+ const running = own(verdict, 'running');
269
+ const mine = own(verdict, 'own');
270
+ const source = own(verdict, 'source');
271
+ if (typeof running !== 'string' || typeof mine !== 'string' || compareReleaseVersions(running, mine) !== 1)
272
+ return undefined;
273
+ if (source !== 'running' && source !== 'high-water')
274
+ return undefined;
275
+ const head = say(source === 'high-water' ? 'localEngine.swapGuard.newer.highWater' : 'localEngine.swapGuard.newer', lang, { running, own: mine });
276
+ const way = upgrade === 'cli-update'
277
+ ? say('localEngine.swapGuard.newer.cliUpdate', lang)
278
+ : upgrade === 'app-update'
279
+ ? say('localEngine.swapGuard.newer.appUpdate', lang)
280
+ : undefined;
281
+ return joined([head, way], lang);
282
+ }
283
+ const wayOut = () => say(lane === 'interactive' ? 'localEngine.swapGuard.wayOut.interactive' : 'localEngine.swapGuard.wayOut.print', lang);
284
+ if (kind === 'hold' && reason === 'drain_unconfirmed') {
285
+ return joined([say('localEngine.swapGuard.hold', lang), ...classesOf(verdict).map(r => classLine(r, lang)), wayOut()], lang);
286
+ }
287
+ if (kind === 'unknown') {
288
+ return joined([say('localEngine.swapGuard.unknown', lang), ...classesOf(verdict).map(r => classLine(r, lang)), wayOut()], lang);
289
+ }
290
+ if (kind === 'swap_and_tell')
291
+ return joined(classesOf(verdict).map(r => toldLine(r, lang)), lang);
292
+ return undefined;
293
+ }
294
+ catch {
295
+ return undefined;
296
+ }
297
+ }