@sema-agent/client-core 0.107.0 → 0.109.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 (165) hide show
  1. package/CHANGELOG.md +239 -0
  2. package/README.md +54 -36
  3. package/dist/adapt/arms.js +23 -8
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +37 -16
  6. package/dist/adapter/activeRunSelfHeal.d.ts +2 -0
  7. package/dist/adapter/activeRunSelfHeal.js +21 -4
  8. package/dist/adapter/downstream/eventToSdkMessage.js +2 -1
  9. package/dist/agentSession/engineAgentRegistryRead.js +2 -0
  10. package/dist/bashExitReading.d.ts +12 -0
  11. package/dist/bashExitReading.js +44 -0
  12. package/dist/checkpointRowVerdict.d.ts +6 -1
  13. package/dist/checkpointRowVerdict.js +30 -2
  14. package/dist/cloudConfigWireCaps.d.ts +10 -0
  15. package/dist/cloudConfigWireCaps.js +33 -0
  16. package/dist/compensations.js +1 -1
  17. package/dist/coreValuePorts.d.ts +17 -1
  18. package/dist/coreValuePorts.js +5 -0
  19. package/dist/decideReceipt.js +2 -1
  20. package/dist/engineAgentAbsence.d.ts +2 -0
  21. package/dist/engineAgentAbsence.js +28 -0
  22. package/dist/engineErrorCodes.d.ts +9 -0
  23. package/dist/engineErrorCodes.js +9 -0
  24. package/dist/engineHealthFacts.d.ts +17 -0
  25. package/dist/engineHealthFacts.js +42 -3
  26. package/dist/engineNoticeCodes.d.ts +147 -1
  27. package/dist/engineNoticeCodes.js +694 -57
  28. package/dist/engineTargetSeat.js +1 -0
  29. package/dist/errorSupplementRoles.js +6 -1
  30. package/dist/eventLeg.js +1 -1
  31. package/dist/executionLaneWords.js +2 -1
  32. package/dist/fleetTaskDesc.js +3 -3
  33. package/dist/gateOutcome.d.ts +10 -1
  34. package/dist/gateOutcome.js +48 -1
  35. package/dist/generated/engineFactTables.d.ts +2 -0
  36. package/dist/generated/engineFactTables.js +11 -0
  37. package/dist/generated/engineNoticeTables.js +6 -0
  38. package/dist/generated/settingsSchemaFns.d.ts +4 -2
  39. package/dist/generated/settingsSchemaFns.js +9 -3
  40. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  41. package/dist/generated/settingsSchemaMcpRequestEntry.js +9 -18
  42. package/dist/generated/settingsSchemaReservedMemberName.d.ts +1 -0
  43. package/dist/generated/settingsSchemaReservedMemberName.js +7 -0
  44. package/dist/generated/settingsSchemaTables.d.ts +2 -0
  45. package/dist/generated/settingsSchemaTables.js +10 -0
  46. package/dist/generated/settingsSchemaWireName.d.ts +1 -0
  47. package/dist/generated/settingsSchemaWireName.js +8 -0
  48. package/dist/headlessReconnectWire.js +12 -7
  49. package/dist/hitl/approvalAskers.d.ts +13 -0
  50. package/dist/hitl/approvalAskers.js +79 -0
  51. package/dist/hitl/approvalCardReasons.js +22 -4
  52. package/dist/hitl/approvalRequestFrameReading.d.ts +14 -0
  53. package/dist/hitl/approvalRequestFrameReading.js +79 -0
  54. package/dist/hitl/approvalResolution.d.ts +3 -0
  55. package/dist/hitl/approvalResolution.js +22 -4
  56. package/dist/hitl/approvalsFeed.js +4 -13
  57. package/dist/hitl/askParkRowRouting.js +10 -3
  58. package/dist/hitl/automaticAnswerVerdict.d.ts +5 -2
  59. package/dist/hitl/automaticAnswerVerdict.js +16 -2
  60. package/dist/hitl/decisionConflict.d.ts +2 -1
  61. package/dist/hitl/decisionConflict.js +4 -1
  62. package/dist/hitl/decisionRefusal.d.ts +1 -0
  63. package/dist/hitl/decisionRefusal.js +5 -2
  64. package/dist/hitl/decisionSend.js +2 -6
  65. package/dist/hitl/frameRouter.d.ts +3 -2
  66. package/dist/hitl/frameRouter.js +37 -7
  67. package/dist/hitl/gateLedger.d.ts +2 -0
  68. package/dist/hitl/gateLedger.js +3 -1
  69. package/dist/hitl/hitlBridge.d.ts +4 -0
  70. package/dist/hitl/hitlBridge.js +11 -8
  71. package/dist/hitl/livePendingAsk.js +17 -16
  72. package/dist/hitl/localAllowRule.js +2 -1
  73. package/dist/hitl/noAllowCause.d.ts +1 -1
  74. package/dist/hitl/noAllowCause.js +4 -2
  75. package/dist/hitl/parkExpiryJudgements.js +26 -1
  76. package/dist/hitl/parkOwnership.d.ts +2 -0
  77. package/dist/hitl/parkOwnership.js +48 -8
  78. package/dist/hitl/parkResolver.js +39 -30
  79. package/dist/hitl/parkRowBirthWait.d.ts +2 -0
  80. package/dist/hitl/parkRowBirthWait.js +8 -1
  81. package/dist/hitl/parkedUnresumable.d.ts +13 -0
  82. package/dist/hitl/parkedUnresumable.js +80 -0
  83. package/dist/hitl/permissionRuleScope.d.ts +4 -0
  84. package/dist/hitl/permissionRuleScope.js +31 -0
  85. package/dist/hitl/persistedRulesWire.d.ts +7 -1
  86. package/dist/hitl/persistedRulesWire.js +25 -8
  87. package/dist/hitl/planReviewWire.js +12 -16
  88. package/dist/hitl/policyDenyCause.d.ts +3 -0
  89. package/dist/hitl/policyDenyCause.js +16 -0
  90. package/dist/hitl/sessionAllowRoute.js +5 -4
  91. package/dist/hitl/sessionPolicyDeliverable.js +1 -1
  92. package/dist/hitl/standingRows.d.ts +1 -5
  93. package/dist/hitl/standingRows.js +15 -14
  94. package/dist/hitl/toolApprovalWire.d.ts +16 -4
  95. package/dist/hitl/toolApprovalWire.js +57 -17
  96. package/dist/hooksReaderGenerations.d.ts +1 -0
  97. package/dist/hooksReaderGenerations.js +12 -0
  98. package/dist/hooksWireCaps.js +12 -7
  99. package/dist/hostSettingsSource.js +2 -0
  100. package/dist/i18n/messages.en.d.ts +206 -1
  101. package/dist/i18n/messages.en.js +209 -4
  102. package/dist/i18n/messages.zh.js +214 -4
  103. package/dist/index.d.ts +35 -8
  104. package/dist/index.js +20 -5
  105. package/dist/liveModelCatalog.d.ts +4 -2
  106. package/dist/liveModelCatalog.js +164 -26
  107. package/dist/localEngineJudge.d.ts +75 -0
  108. package/dist/localEngineJudge.js +242 -0
  109. package/dist/localEngineOccupancy.d.ts +88 -0
  110. package/dist/localEngineOccupancy.js +269 -0
  111. package/dist/localEngineSeatReading.d.ts +29 -0
  112. package/dist/localEngineSeatReading.js +294 -0
  113. package/dist/localEngineSpawn.d.ts +113 -0
  114. package/dist/localEngineSpawn.js +486 -0
  115. package/dist/localEngineStep.d.ts +286 -0
  116. package/dist/localEngineStep.js +1086 -0
  117. package/dist/localEngineStopLadder.d.ts +29 -0
  118. package/dist/localEngineStopLadder.js +139 -0
  119. package/dist/localEngineSwapCard.d.ts +43 -0
  120. package/dist/localEngineSwapCard.js +205 -0
  121. package/dist/localEngineTiming.d.ts +8 -0
  122. package/dist/localEngineTiming.js +8 -0
  123. package/dist/mcpReconnect.js +12 -5
  124. package/dist/mcpWireCaps.js +2 -2
  125. package/dist/memoryEntriesWire.d.ts +1 -1
  126. package/dist/memoryEntriesWire.js +5 -0
  127. package/dist/model/catalogDefault.d.ts +25 -0
  128. package/dist/model/catalogDefault.js +44 -0
  129. package/dist/noticePresentation.d.ts +2 -0
  130. package/dist/noticePresentation.js +79 -1
  131. package/dist/ownKey.d.ts +10 -0
  132. package/dist/ownKey.js +15 -1
  133. package/dist/permissionWireCaps.d.ts +1 -0
  134. package/dist/permissionWireCaps.js +11 -0
  135. package/dist/pluginHooksWire.d.ts +5 -3
  136. package/dist/pluginHooksWire.js +116 -7
  137. package/dist/queuedDelivery.d.ts +1 -0
  138. package/dist/queuedDelivery.js +12 -0
  139. package/dist/resultFrameKeys.js +4 -2
  140. package/dist/resumeRefusalCopy.js +15 -8
  141. package/dist/ruleReadings.js +1 -1
  142. package/dist/runPermissionModeSwitch.d.ts +2 -1
  143. package/dist/runPermissionModeSwitch.js +16 -6
  144. package/dist/seam.d.ts +13 -2
  145. package/dist/seam.js +8 -1
  146. package/dist/seatContract.d.ts +103 -12
  147. package/dist/seatContract.js +344 -14
  148. package/dist/sessionBackground.js +2 -0
  149. package/dist/sessionEngine.d.ts +2 -1
  150. package/dist/sessionEngine.js +5 -0
  151. package/dist/settingsHooksSalvage.d.ts +5 -0
  152. package/dist/settingsHooksSalvage.js +54 -0
  153. package/dist/subagentContentStore.d.ts +12 -0
  154. package/dist/subagentContentStore.js +73 -12
  155. package/dist/supportFloor.js +2 -0
  156. package/dist/taskNotificationAdmission.d.ts +19 -0
  157. package/dist/taskNotificationAdmission.js +84 -0
  158. package/dist/toolResult.js +20 -6
  159. package/dist/webSearchWireCaps.js +9 -4
  160. package/dist/wireFailureShape.d.ts +20 -0
  161. package/dist/wireFailureShape.js +37 -6
  162. package/dist/wireRefusalCopy.d.ts +23 -2
  163. package/dist/wireRefusalCopy.js +93 -4
  164. package/docs/INTEGRATION-CLIENTS.md +1959 -15
  165. package/package.json +4 -4
@@ -1,10 +1,10 @@
1
1
  import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
2
- import { appendOwnElement } from '../ownKey.js';
2
+ import { appendOwnElement, readOwnDataNonEmptyString } from '../ownKey.js';
3
3
  import { readRequiresRealApproval } from './approvalCardReading.js';
4
4
  import { pendingRowIsOfSession } from './parkRowDelegation.js';
5
5
  import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
6
6
  import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
7
- import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
7
+ import { isToolApprovalFrame, surfaceApprovalFrameWithId, } from './toolApprovalWire.js';
8
8
  function nonEmptyString(v) {
9
9
  return typeof v === 'string' && v !== '';
10
10
  }
@@ -24,7 +24,9 @@ function snapshotMembers(raw) {
24
24
  }
25
25
  return raw.length === length ? out : undefined;
26
26
  }
27
- function openingFrameOf(f) {
27
+ function openingFrameOf(f, approvalId) {
28
+ if (typeof f !== 'object' || f === null || readOwnDataNonEmptyString(f, 'approvalId') !== approvalId)
29
+ return undefined;
28
30
  if (isToolApprovalFrame(f))
29
31
  return f.type === 'tool_approval' ? f : undefined;
30
32
  return isApprovalRequestFrameV1(f) ? f : undefined;
@@ -54,19 +56,19 @@ export function readLivePendingRows(body) {
54
56
  continue;
55
57
  }
56
58
  const o = item;
57
- if (!nonEmptyString(o.approvalId) || !nonEmptyString(o.toolName) || !nonNegativeFinite(o.ts) || !nonNegativeFinite(o.expiresAtMs)) {
59
+ const approvalId = readOwnDataNonEmptyString(o, 'approvalId');
60
+ if (approvalId === undefined || !nonEmptyString(o.toolName) || !nonNegativeFinite(o.ts) || !nonNegativeFinite(o.expiresAtMs)) {
58
61
  dropped++;
59
62
  continue;
60
63
  }
61
- if (seen.has(o.approvalId)) {
64
+ if (seen.has(approvalId)) {
62
65
  dropped++;
63
66
  continue;
64
67
  }
65
- seen.add(o.approvalId);
66
- const opening = openingFrameOf(o.frame);
67
- const frame = opening !== undefined && opening.approvalId === o.approvalId ? opening : undefined;
68
- rows.push({
69
- approvalId: o.approvalId,
68
+ seen.add(approvalId);
69
+ const frame = openingFrameOf(o.frame, approvalId);
70
+ appendOwnElement(rows, {
71
+ approvalId,
70
72
  toolName: o.toolName,
71
73
  ts: o.ts,
72
74
  expiresAtMs: o.expiresAtMs,
@@ -147,13 +149,12 @@ export function livePendingRowToApprovalFrame(row) {
147
149
  };
148
150
  }
149
151
  export function surfaceSuspendedAskAndRespond(row, respond, signal, lane) {
150
- if (row.frame !== undefined) {
151
- return surfaceToolApprovalFrameAndRespond(row.frame, respond, undefined, signal, lane);
152
+ const frame = row.frame;
153
+ const approvalId = row.approvalId;
154
+ if (frame !== undefined) {
155
+ return surfaceApprovalFrameWithId(frame, approvalId, respond, undefined, signal, lane, undefined);
152
156
  }
153
- return surfaceToolApprovalFrameAndRespond(livePendingRowToApprovalFrame(row), respond, undefined, signal, {
154
- ...lane,
155
- argsUnavailable: true,
156
- });
157
+ return surfaceApprovalFrameWithId(livePendingRowToApprovalFrame(row), approvalId, respond, undefined, signal, { ...lane, argsUnavailable: true }, undefined);
157
158
  }
158
159
  const TRACKER_UNSEEN_CLAIMS_MAX = 512;
159
160
  export function createSuspendedAskTracker(filter) {
@@ -1,4 +1,5 @@
1
1
  import { reasonOfThrownWithSink } from '../hostCallbackGuard.js';
2
+ import { isWholeToolRuleValue } from './permissionRuleScope.js';
2
3
  const BASH_TOOL_NAME_FOR_RULES = 'Bash';
3
4
  export function parseLocalAllowRule(rule, expectedToolName, deps) {
4
5
  if (typeof rule !== 'string' || rule === '') {
@@ -14,7 +15,7 @@ export function parseLocalAllowRule(rule, expectedToolName, deps) {
14
15
  if (typeof value.toolName !== 'string' || value.toolName === '') {
15
16
  return { ok: false, error: 'rule text carries no tool name' };
16
17
  }
17
- if (value.ruleContent === undefined || value.ruleContent === '') {
18
+ if (isWholeToolRuleValue(value)) {
18
19
  return {
19
20
  ok: false,
20
21
  error: 'candidate is a whole-tool allow rule (broader than this ask) — not offered locally',
@@ -1,4 +1,4 @@
1
- export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'engine_ask_rule' | 'budget' | 'other';
1
+ export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'engine_ask_rule' | 'budget' | 'whole_tool_rule_only' | 'other';
2
2
  export interface RuleLadderNoAllowFacts {
3
3
  readonly allowed?: boolean;
4
4
  readonly denied?: boolean;
@@ -1,4 +1,4 @@
1
- import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
1
+ import { askLevelVerdict } from './automaticAnswerVerdict.js';
2
2
  function ladderSays(ladder, key) {
3
3
  if (typeof ladder !== 'object' || ladder === null)
4
4
  return false;
@@ -17,11 +17,13 @@ export function noAllowCauseOf(card, ladder) {
17
17
  return 'ask_rule';
18
18
  if (ladderSays(ladder, 'budgetExhausted'))
19
19
  return 'budget';
20
- const standing = automaticAnswerVerdict(card, 'standing_rule');
20
+ const standing = askLevelVerdict(card, 'standing_rule');
21
21
  if (standing.kind === 'must_ask' && (standing.askClass === 'ask_rule' || standing.origin === 'ask_rule')) {
22
22
  return ladderSays(ladder, 'noAskRuleMatched') || ladderSays(ladder, 'noRuleMatched') ? 'engine_ask_rule' : 'ask_rule';
23
23
  }
24
24
  if (ladderSays(ladder, 'noRuleMatched') && standing.kind === 'may_answer')
25
25
  return 'no_allow_rule';
26
+ if (standing.kind === 'may_answer')
27
+ return 'whole_tool_rule_only';
26
28
  return 'other';
27
29
  }
@@ -1,11 +1,36 @@
1
+ import { isApprovalCompleteFrameV1 } from '@sema-agent/sdk';
1
2
  import { APPROVAL_EXPIRED } from '../engineErrorCodes.js';
2
3
  import { approvalCompleteParked } from './approvalCompleteParked.js';
3
4
  export function classifyApprovalCompleteFrame(frame) {
4
5
  if (typeof frame !== 'object' || frame === null)
5
6
  return null;
6
7
  const f = frame;
7
- if (f.type !== 'tool_approval_complete')
8
+ const type = f.type;
9
+ if (type === 'tool_approval_complete')
10
+ return dispositionOf(frame, f);
11
+ if (type === 'approval_complete')
12
+ return presentedFamilyDisposition(frame);
13
+ return null;
14
+ }
15
+ function presentedFamilyDisposition(frame) {
16
+ try {
17
+ const snapshot = ownDataSnapshot(frame);
18
+ return isApprovalCompleteFrameV1(snapshot) ? dispositionOf(snapshot, snapshot) : null;
19
+ }
20
+ catch {
8
21
  return null;
22
+ }
23
+ }
24
+ function ownDataSnapshot(frame) {
25
+ const snapshot = Object.create(null);
26
+ for (const key of Object.getOwnPropertyNames(frame)) {
27
+ const d = Object.getOwnPropertyDescriptor(frame, key);
28
+ if (d !== undefined && Object.hasOwn(d, 'value'))
29
+ snapshot[key] = d.value;
30
+ }
31
+ return snapshot;
32
+ }
33
+ function dispositionOf(frame, f) {
9
34
  const outcome = f.outcome;
10
35
  if (outcome === 'allowed' || outcome === 'denied')
11
36
  return 'settled';
@@ -6,5 +6,7 @@ export interface ParkOwnershipDeps {
6
6
  isOwnRun?: (taskIdTail: string) => boolean;
7
7
  }
8
8
  export declare function pendingRowIsOwnedByThisSession(row: PendingCheckpoint, deps?: ParkOwnershipDeps): boolean;
9
+ export declare function pendingRowOwnershipOf(row: object, deps?: ParkOwnershipDeps): 'owned' | 'foreign' | 'unknown';
10
+ export declare function usableAskQuestions(value: unknown): AskQuestion[] | null;
9
11
  export declare function askQuestionsFromPending(pending: PendingCheckpoint): AskQuestion[] | null;
10
12
  export declare function isHitlSafetyErrorLike(e: unknown): boolean;
@@ -3,31 +3,71 @@ import { rowIdTail } from '../workflow.js';
3
3
  import { hostSessionFor } from '../host.js';
4
4
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
5
5
  import { pendingRowIsOfSession } from './parkRowDelegation.js';
6
+ import { slotReadOf } from '../wireFailureShape.js';
6
7
  export function pendingRowIsOwnedByThisSession(row, deps) {
8
+ return ownershipVerdictOf(row, deps) === 'owned';
9
+ }
10
+ export function pendingRowOwnershipOf(row, deps) {
11
+ try {
12
+ return ownershipVerdictOf(row, deps);
13
+ }
14
+ catch {
15
+ return 'unknown';
16
+ }
17
+ }
18
+ function ownershipVerdictOf(row, deps) {
7
19
  const sessionKey = deps?.sessionKey ?? DEFAULT_SESSION_KEY;
8
20
  const rowTaskId = typeof row.taskId === 'string' ? row.taskId : '';
9
21
  if (rowTaskId.length > 0) {
10
22
  const tail = rowIdTail(rowTaskId);
11
23
  if (deps?.isOwnRun !== undefined && deps.isOwnRun(tail))
12
- return true;
24
+ return 'owned';
13
25
  if (sessionKey === DEFAULT_SESSION_KEY && isOwnEngineRun(tail))
14
- return true;
26
+ return 'owned';
15
27
  }
16
28
  const currentSessionId = deps?.currentSessionId ?? (() => hostSessionFor(sessionKey)?.currentSessionId());
17
29
  const sessionId = currentSessionId();
18
30
  if (typeof sessionId !== 'string' || sessionId.length === 0)
31
+ return 'unknown';
32
+ if (pendingRowIsOfSession(row, sessionId))
33
+ return 'owned';
34
+ return rowIsProvablyForeign(row, sessionId) ? 'foreign' : 'unknown';
35
+ }
36
+ function rowIsProvablyForeign(row, selfSessionId) {
37
+ const own = slotReadOf(row, 'sessionId');
38
+ if (own.presence !== 'present' || typeof own.value !== 'string' || own.value === '' || own.value === selfSessionId)
39
+ return false;
40
+ const agent = slotReadOf(row, 'agent');
41
+ if (agent.presence === 'absent')
42
+ return true;
43
+ if (agent.presence !== 'present' || typeof agent.value !== 'object' || agent.value === null)
19
44
  return false;
20
- return pendingRowIsOfSession(row, sessionId);
45
+ for (const key of ['parentSessionId', 'rootSessionId']) {
46
+ const link = slotReadOf(agent.value, key);
47
+ if (link.presence === 'absent')
48
+ continue;
49
+ if (link.presence !== 'present' || typeof link.value !== 'string' || link.value === selfSessionId)
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+ export function usableAskQuestions(value) {
55
+ if (!Array.isArray(value) || value.length === 0)
56
+ return null;
57
+ for (let i = 0; i < value.length; i++) {
58
+ if (!Object.hasOwn(value, i))
59
+ return null;
60
+ const q = value[i];
61
+ if (typeof q !== 'object' || q === null || typeof q.question !== 'string')
62
+ return null;
63
+ }
64
+ return value;
21
65
  }
22
66
  export function askQuestionsFromPending(pending) {
23
67
  const input = pending.input;
24
68
  if (typeof input !== 'object' || input === null)
25
69
  return null;
26
- const qs = input.questions;
27
- if (!Array.isArray(qs) || qs.length === 0)
28
- return null;
29
- const ok = qs.every(q => typeof q === 'object' && q !== null && typeof q.question === 'string');
30
- return ok ? qs : null;
70
+ return usableAskQuestions(input.questions);
31
71
  }
32
72
  export function isHitlSafetyErrorLike(e) {
33
73
  return typeof e === 'object' && e !== null && e.name === 'HitlSafetyError';
@@ -1,18 +1,21 @@
1
1
  import { putOwnKey, readOwnDataValue } from '../ownKey.js';
2
2
  import { HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, } from './hitlBridge.js';
3
- import { approvalResolutionDetail, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
3
+ import { approvalResolutionDetail, approvalResolutionStatusTag, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
4
4
  import { readDecisionRefusalAsk } from './decisionRefusal.js';
5
5
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
8
8
  import { INTERRUPT_DENY_REASON, observeCancelByDeny } from './hitlHostSurface.js';
9
- import { flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
9
+ import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
10
+ import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
10
11
  import { inheritFrameBinding } from '../frameBinding.js';
11
12
  import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
13
+ import { usableAskQuestions } from './parkOwnership.js';
12
14
  import { readDecideReceipt } from '../decideReceipt.js';
13
15
  import { decideFailureNote } from '../decideFailureNote.js';
14
16
  import { wireFailureTextsOf } from '../wireFailureShape.js';
15
17
  import { msg, renderMessage } from '../i18n/translate.js';
18
+ import { refusedParkFromError } from '../resumeRefusalCopy.js';
16
19
  export const MAX_GATE_HOPS = 2;
17
20
  export function nextHopBudget(prev, round) {
18
21
  const total = (prev?.total ?? 0) + 1;
@@ -67,12 +70,11 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
67
70
  return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
68
71
  const { pending, gatedCallId } = found;
69
72
  const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
70
- const fromArgs = gatedCallId
71
- ? askArgsByCall.get(gatedCallId)?.questions
72
- : undefined;
73
- const fromPending = pending.input?.questions;
74
- const questions = Array.isArray(fromArgs) && fromArgs.length > 0 ? fromArgs : fromPending;
75
- if (!Array.isArray(questions) || questions.length === 0) {
73
+ const rowQuestions = usableAskQuestions(pending.input?.questions);
74
+ const streamQuestions = usableAskQuestions(gatedCallId ? askArgsByCall.get(gatedCallId)?.questions : undefined);
75
+ const questionsFromModelCall = rowQuestions === null && streamQuestions !== null;
76
+ const questions = rowQuestions ?? streamQuestions;
77
+ if (questions === null) {
76
78
  const truncated = pending.input?.truncated === true;
77
79
  return {
78
80
  kind: 'not_sent',
@@ -105,7 +107,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
105
107
  }
106
108
  signal?.addEventListener('abort', onAbort, { once: true });
107
109
  onPresented?.();
108
- publishQuestionFrame({ type: 'question', questionId, questions });
110
+ publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}) });
109
111
  });
110
112
  const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
111
113
  if (answer === null) {
@@ -160,6 +162,10 @@ function failureReasonOf(r) {
160
162
  return '';
161
163
  return r.detail ?? approvalResolutionDetail(r) ?? r.cause;
162
164
  }
165
+ function failsoftReasonOf(r) {
166
+ const tag = approvalResolutionStatusTag(r);
167
+ return tag === '' ? failureReasonOf(r) : `${failureReasonOf(r)} ${tag}`;
168
+ }
163
169
  function orchestrationEnd(park, gatedCallId, detail) {
164
170
  return { kind: 'unsettled', lane: park.gate === 'fs' ? 'decide' : 'ask', cause: 'orchestration', ...(gatedCallId !== undefined ? { gatedCallId } : {}), detail };
165
171
  }
@@ -180,10 +186,7 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
180
186
  ...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
181
187
  }, taskId, argsByCallOf(led), ctx.signal, gatedCallId, park.toolName);
182
188
  if (outcome.kind === 'decided' && outcome.decision === 'deny' && outcome.gatedCallId) {
183
- led.markDenied(outcome.gatedCallId, {
184
- ...(outcome.deny?.settledBy !== undefined ? { settledBy: outcome.deny.settledBy } : {}),
185
- ...(outcome.deny?.reason !== undefined ? { reason: outcome.deny.reason } : {}),
186
- });
189
+ led.markDenied(outcome.gatedCallId, denyAttributionOf(outcome.deny));
187
190
  }
188
191
  return outcome;
189
192
  }
@@ -283,10 +286,9 @@ export async function resolvePark(park, ctx) {
283
286
  ? undefined
284
287
  : outcome.kind === 'not_sent' && outcome.cause === 'retracted'
285
288
  ? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
286
- : failureReasonOf(outcome);
289
+ : failsoftReasonOf(outcome);
287
290
  const noDecisionSurface = outcome.kind === 'not_sent' && outcome.cause === 'card_unavailable' && outcome.detail !== undefined && isNoDecisionSurfaceReason(outcome.detail);
288
- const undecidableHere = outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing'));
289
- return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface, undecidableHere) };
291
+ return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface, failsoftWayOutOf(outcome)) };
290
292
  }
291
293
  led.dropHeldForDecidedPark(outcome.gatedCallId, park.gatedCallId);
292
294
  if (outcome.gatedCallId) {
@@ -309,17 +311,27 @@ export async function resolvePark(park, ctx) {
309
311
  ...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
310
312
  };
311
313
  }
312
- function failsoftReasonSentence(park, failedReason, terminal, reasonCarriesWayOut = false) {
314
+ function failsoftWayOutOf(outcome) {
315
+ if (outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing')))
316
+ return 'reason';
317
+ if (outcome.kind === 'unsettled' && outcome.cause === 'engine_refused' && refusedParkFromError(outcome.refusal) !== null)
318
+ return 'cancel';
319
+ return 'card';
320
+ }
321
+ function failsoftReasonSentence(park, failedReason, terminal, wayOut = 'card') {
313
322
  if (terminal !== undefined)
314
323
  return terminal.errorMessage;
315
324
  if (failedReason === undefined)
316
325
  return undefined;
317
- if (reasonCarriesWayOut)
318
- return renderMessage(msg('approval.park.stillParked', { gate: park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion', reason: failedReason }));
326
+ const gate = park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion';
327
+ if (wayOut === 'reason')
328
+ return renderMessage(msg('approval.park.stillParked', { gate, reason: failedReason }));
329
+ if (wayOut === 'cancel')
330
+ return renderMessage(msg('approval.park.refusedNotResumable', { gate, reason: failedReason }));
319
331
  return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
320
332
  ' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
321
333
  }
322
- function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, reasonCarriesWayOut = false) {
334
+ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, wayOut = 'card') {
323
335
  const events = [
324
336
  ...flushHeldWithInterruptRewrite(ctx.led, {
325
337
  terminal: park.pendingDone,
@@ -327,7 +339,7 @@ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, no
327
339
  gateAbortedByUser,
328
340
  }),
329
341
  ];
330
- const reason = failsoftReasonSentence(park, failedReason, terminal, reasonCarriesWayOut);
342
+ const reason = failsoftReasonSentence(park, failedReason, terminal, wayOut);
331
343
  if (park.pendingDone) {
332
344
  events.push(reason === undefined || noDecisionSurface ? park.pendingDone : inheritFrameBinding(park.pendingDone, { ...park.pendingDone, _sema_park_failed_reason: reason }));
333
345
  }
@@ -356,15 +368,12 @@ export function stalledTerminal(park, ctx, stalledRounds, lastStallReason, decid
356
368
  return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, reason) };
357
369
  }
358
370
  function notifyParkReattach(ctx, attempt, max, reason) {
359
- const cb = ctx.deps.onParkReattach;
360
- if (typeof cb !== 'function')
361
- return;
362
- try {
363
- cb({ attempt, max, reason });
364
- }
365
- catch (e) {
366
- hostLog('debug', `liveHitlAskWire: onParkReattach threw (${String(e)}) — ignored`);
367
- }
371
+ invokeHostCallbackIsolated('liveHitlAskWire: onParkReattach', () => {
372
+ const cb = ctx.deps.onParkReattach;
373
+ if (typeof cb !== 'function' || ctx.signal?.aborted === true)
374
+ return;
375
+ return cb.call(ctx.deps, { attempt, max, reason });
376
+ });
368
377
  }
369
378
  function transportReasonToken(reason) {
370
379
  const m = /(ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|EAI_AGAIN|TimeoutError|AbortError|fetch failed|network)/i.exec(reason);
@@ -11,6 +11,7 @@ export type ParkRowProbe<Row> = {
11
11
  rowVerdict?: Exclude<CheckpointRowVerdictReading, {
12
12
  readonly kind: 'ok';
13
13
  }>;
14
+ rowTaskId?: string;
14
15
  };
15
16
  export type ParkRowWaitVerdict<Row> = {
16
17
  kind: 'row';
@@ -30,6 +31,7 @@ export type ParkRowWaitVerdict<Row> = {
30
31
  rowVerdict?: Exclude<CheckpointRowVerdictReading, {
31
32
  readonly kind: 'ok';
32
33
  }>;
34
+ rowTaskId?: string;
33
35
  } | {
34
36
  kind: 'aborted';
35
37
  waitedMs: number;
@@ -103,7 +103,14 @@ export async function waitForParkRowBirth(deps) {
103
103
  if (probed.kind === 'row')
104
104
  return { kind: 'row', row: probed.row, waitedMs: now() - startedAt, probes };
105
105
  if (probed.kind === 'settled') {
106
- return { kind: 'settled', reason: probed.reason, waitedMs: now() - startedAt, probes, ...(probed.rowVerdict !== undefined ? { rowVerdict: probed.rowVerdict } : {}) };
106
+ return {
107
+ kind: 'settled',
108
+ reason: probed.reason,
109
+ waitedMs: now() - startedAt,
110
+ probes,
111
+ ...(probed.rowVerdict !== undefined ? { rowVerdict: probed.rowVerdict } : {}),
112
+ ...(probed.rowTaskId !== undefined ? { rowTaskId: probed.rowTaskId } : {}),
113
+ };
107
114
  }
108
115
  lastReason = probed.reason;
109
116
  const remainingMs = budgetMs - (now() - startedAt);
@@ -0,0 +1,13 @@
1
+ import { type CheckpointRowBar } from '../checkpointRowVerdict.js';
2
+ import type { UiLanguage } from '../env/uiLanguage.js';
3
+ import type { OwnedPendingListView } from './askParkRowRouting.js';
4
+ import { type ParkOwnershipDeps } from './parkOwnership.js';
5
+ export interface ParkedUnresumableRow {
6
+ readonly rowVerdict: CheckpointRowBar;
7
+ readonly rowKey?: string;
8
+ readonly taskId?: string;
9
+ readonly toolName?: string;
10
+ readonly rowDeadline?: 'armed' | 'none';
11
+ }
12
+ export declare function parkedUnresumableRowsOf(listed: OwnedPendingListView | null | undefined, deps?: ParkOwnershipDeps): readonly ParkedUnresumableRow[];
13
+ export declare function parkedUnresumableLine(row: ParkedUnresumableRow, lang?: UiLanguage | (string & {})): string;
@@ -0,0 +1,80 @@
1
+ import { checkpointDeadlineStateOf, checkpointRowBarOf, checkpointRowBarSentence, rowBarWayOutContent, } from '../checkpointRowVerdict.js';
2
+ import { slotReadOf } from '../wireFailureShape.js';
3
+ import { QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
4
+ import { approvalCallKey } from './gateIdentity.js';
5
+ import { checkpointQueueReadingOf } from './hitlBridge.js';
6
+ import { pendingRowIsOwnedByThisSession } from './parkOwnership.js';
7
+ function ownNonEmpty(o, key) {
8
+ const r = slotReadOf(o, key);
9
+ return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
10
+ }
11
+ function memberOf(row, rowVerdict) {
12
+ const taskId = ownNonEmpty(row, 'taskId');
13
+ const callId = ownNonEmpty(row, 'toolCallId') ?? ownNonEmpty(row, 'boundCallId');
14
+ const rowKey = callId !== undefined || taskId !== undefined ? approvalCallKey(callId, taskId ?? '') : undefined;
15
+ const toolName = ownNonEmpty(row, 'toolName');
16
+ const deadline = checkpointDeadlineStateOf(row);
17
+ return Object.freeze({
18
+ rowVerdict,
19
+ ...(rowKey !== undefined ? { rowKey } : {}),
20
+ ...(taskId !== undefined ? { taskId } : {}),
21
+ ...(toolName !== undefined ? { toolName } : {}),
22
+ ...(deadline !== 'unknown' ? { rowDeadline: deadline } : {}),
23
+ });
24
+ }
25
+ export function parkedUnresumableRowsOf(listed, deps) {
26
+ if (typeof listed !== 'object' || listed === null || Array.isArray(listed)) {
27
+ throw new Error('approvals.list answered without a readable body — the rows this engine cannot continue are unknown');
28
+ }
29
+ const pending = listed.pending;
30
+ if (!Array.isArray(pending)) {
31
+ throw new Error('approvals.list answered without a readable `pending` array — the rows this engine cannot continue are unknown');
32
+ }
33
+ const out = [];
34
+ for (const row of pending) {
35
+ const member = parkedMemberOf(row, deps);
36
+ if (member !== undefined)
37
+ out.push(member);
38
+ }
39
+ return Object.freeze(out);
40
+ }
41
+ function parkedMemberOf(row, deps) {
42
+ try {
43
+ if (typeof row !== 'object' || row === null)
44
+ return undefined;
45
+ const reading = checkpointQueueReadingOf(row);
46
+ if (reading === undefined)
47
+ return undefined;
48
+ if (reading.foreign !== null && reading.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
49
+ return undefined;
50
+ const bar = checkpointRowBarOf(reading.r);
51
+ if (bar === undefined)
52
+ return undefined;
53
+ if (!pendingRowIsOwnedByThisSession(reading.r, deps))
54
+ return undefined;
55
+ return memberOf(reading.r, bar);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ const UNREADABLE_BAR = Object.freeze({ kind: 'unreadable' });
62
+ export function parkedUnresumableLine(row, lang) {
63
+ let bar = UNREADABLE_BAR;
64
+ let state = 'unknown';
65
+ try {
66
+ if (typeof row === 'object' && row !== null) {
67
+ const v = slotReadOf(row, 'rowVerdict');
68
+ if (v.presence === 'present')
69
+ bar = checkpointRowBarOf({ rowVerdict: v.value }) ?? UNREADABLE_BAR;
70
+ const d = slotReadOf(row, 'rowDeadline');
71
+ if (d.presence === 'present' && (d.value === 'armed' || d.value === 'none'))
72
+ state = d.value;
73
+ }
74
+ }
75
+ catch {
76
+ bar = UNREADABLE_BAR;
77
+ state = 'unknown';
78
+ }
79
+ return `${checkpointRowBarSentence(bar, lang)} · ${rowBarWayOutContent(bar, state, lang)}`;
80
+ }
@@ -0,0 +1,4 @@
1
+ import type { PermissionRuleValue } from '@sema-agent/agent-types';
2
+ export declare function isWholeToolRuleValue(value: PermissionRuleValue): boolean;
3
+ export type AllowRuleScope = 'whole_tool' | 'content' | 'unreadable';
4
+ export declare function allowRuleScopeOf(value: object | null | undefined): AllowRuleScope;
@@ -0,0 +1,31 @@
1
+ import { readOwnDataValue } from '../ownKey.js';
2
+ import { inSlotReadOf } from '../wireFailureShape.js';
3
+ export function isWholeToolRuleValue(value) {
4
+ return value.ruleContent === undefined || value.ruleContent === '';
5
+ }
6
+ export function allowRuleScopeOf(value) {
7
+ try {
8
+ if (typeof value !== 'object' || value === null)
9
+ return 'unreadable';
10
+ const toolName = readOwnDataValue(value, 'toolName');
11
+ if (typeof toolName !== 'string' || toolName === '')
12
+ return 'unreadable';
13
+ let ruleContent;
14
+ const slot = inSlotReadOf(value, 'ruleContent');
15
+ if (slot.state === 'present') {
16
+ const raw = slot.value;
17
+ if (raw !== undefined) {
18
+ if (typeof raw !== 'string' || raw === '')
19
+ return 'unreadable';
20
+ ruleContent = raw;
21
+ }
22
+ }
23
+ else if (slot.state !== 'absent')
24
+ return 'unreadable';
25
+ const snapshot = Object.freeze(Object.assign(Object.create(null), ruleContent !== undefined ? { toolName, ruleContent } : { toolName }));
26
+ return isWholeToolRuleValue(snapshot) ? 'whole_tool' : 'content';
27
+ }
28
+ catch {
29
+ return 'unreadable';
30
+ }
31
+ }
@@ -31,12 +31,18 @@ export interface CcImportRedeemRuleKey {
31
31
  readonly rule: string;
32
32
  readonly scope: string;
33
33
  }
34
+ export interface CcImportRedeemRefused {
35
+ readonly key?: CcImportRedeemRuleKey;
36
+ readonly reason?: string;
37
+ readonly detail?: string;
38
+ }
34
39
  export interface CcImportRedeemCounts {
35
40
  persisted: number;
36
41
  deduped: number;
37
42
  indeterminate: readonly CcImportRedeemIndeterminate[];
43
+ refused?: readonly CcImportRedeemRefused[];
38
44
  }
39
- export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'indeterminate';
45
+ export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'refused' | 'indeterminate';
40
46
  export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
41
47
  export type RulesFailure = {
42
48
  kind: 'lane-unavailable';