@sema-agent/client-core 0.91.0 → 0.92.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 (123) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +31 -21
  3. package/dist/adapt/arms.js +4 -12
  4. package/dist/adapt/panelTasks.d.ts +0 -2
  5. package/dist/adapt/panelTasks.js +6 -26
  6. package/dist/adapt.js +2 -2
  7. package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
  8. package/dist/adapter/activeRunSelfHeal.js +5 -27
  9. package/dist/adapter/downstream/terminalToSdkResult.js +16 -7
  10. package/dist/adapter/runStream.js +3 -1
  11. package/dist/askSurvivesStandingAnswer.d.ts +4 -1
  12. package/dist/askSurvivesStandingAnswer.js +13 -0
  13. package/dist/autoModeUnavailable.js +3 -2
  14. package/dist/cloudConfigWireCaps.js +5 -4
  15. package/dist/compensations.js +5 -5
  16. package/dist/controlRouter.d.ts +23 -23
  17. package/dist/controlRouter.js +114 -49
  18. package/dist/coreValuePorts.d.ts +4 -2
  19. package/dist/coreValuePorts.js +5 -0
  20. package/dist/decideFailureNote.js +4 -2
  21. package/dist/decideReceipt.d.ts +9 -0
  22. package/dist/decideReceipt.js +62 -1
  23. package/dist/displayUntrusted.js +2 -1
  24. package/dist/effectiveFacts.d.ts +14 -4
  25. package/dist/effectiveFacts.js +45 -5
  26. package/dist/engineAgentAbsence.js +2 -1
  27. package/dist/engineCapReader.js +16 -10
  28. package/dist/engineCapsCache.d.ts +2 -0
  29. package/dist/engineCapsCache.js +13 -2
  30. package/dist/engineErrorCodes.d.ts +14 -0
  31. package/dist/engineErrorCodes.js +37 -0
  32. package/dist/engineNoticeCodes.d.ts +14 -0
  33. package/dist/engineNoticeCodes.js +71 -0
  34. package/dist/executionLaneCapability.js +2 -0
  35. package/dist/gateOutcome.js +2 -17
  36. package/dist/gateVocabulary.d.ts +2 -4
  37. package/dist/gateVocabulary.js +3 -11
  38. package/dist/generated/engineFactTables.d.ts +16 -1
  39. package/dist/generated/engineFactTables.js +81 -0
  40. package/dist/generated/engineNoticeTables.js +3 -0
  41. package/dist/generated/settingsSchemaTables.d.ts +8 -0
  42. package/dist/generated/settingsSchemaTables.js +40 -0
  43. package/dist/hitl/allowRuleShadow.d.ts +21 -0
  44. package/dist/hitl/allowRuleShadow.js +167 -0
  45. package/dist/hitl/askClassPair.d.ts +14 -0
  46. package/dist/hitl/askClassPair.js +42 -0
  47. package/dist/hitl/askGateWire.js +1 -0
  48. package/dist/hitl/askSurvivesPosture.d.ts +5 -0
  49. package/dist/hitl/askSurvivesPosture.js +17 -0
  50. package/dist/hitl/automaticAnswerVerdict.d.ts +21 -0
  51. package/dist/hitl/automaticAnswerVerdict.js +143 -0
  52. package/dist/hitl/crashConverged.d.ts +1 -0
  53. package/dist/hitl/crashConverged.js +1 -1
  54. package/dist/hitl/frameRouter.d.ts +2 -0
  55. package/dist/hitl/frameRouter.js +66 -14
  56. package/dist/hitl/gateLedger.d.ts +6 -0
  57. package/dist/hitl/gateLedger.js +34 -5
  58. package/dist/hitl/hitlHostSurface.d.ts +2 -0
  59. package/dist/hitl/hitlHostSurface.js +7 -0
  60. package/dist/hitl/livePendingAsk.js +24 -5
  61. package/dist/hitl/parkResolver.js +3 -1
  62. package/dist/hitl/persistedRulesWire.d.ts +4 -1
  63. package/dist/hitl/persistedRulesWire.js +9 -2
  64. package/dist/hitl/planReviewWire.js +2 -2
  65. package/dist/hitl/replayedApproval.d.ts +21 -0
  66. package/dist/hitl/replayedApproval.js +89 -0
  67. package/dist/hitl/sessionPolicyDeliverable.js +20 -2
  68. package/dist/hitl/sessionPolicyWire.d.ts +8 -0
  69. package/dist/hitl/sessionPolicyWire.js +12 -2
  70. package/dist/hitl/toolApprovalWire.d.ts +6 -1
  71. package/dist/hitl/toolApprovalWire.js +20 -40
  72. package/dist/hookMatcherVerdict.js +2 -1
  73. package/dist/humanTurn.js +3 -4
  74. package/dist/index.d.ts +15 -5
  75. package/dist/index.js +12 -3
  76. package/dist/inheritEnvWire.js +3 -4
  77. package/dist/mcpLiveness.js +2 -1
  78. package/dist/memoryVerbsWire.d.ts +1 -1
  79. package/dist/memoryVerbsWire.js +3 -0
  80. package/dist/model/catalog.js +2 -1
  81. package/dist/model/providerPresets.d.ts +3 -1
  82. package/dist/model/tierVocabulary.d.ts +2 -1
  83. package/dist/model/tierVocabulary.js +3 -8
  84. package/dist/peerFrames.js +2 -2
  85. package/dist/permissionRuleIssue.js +9 -6
  86. package/dist/permissionsDeclaration.d.ts +7 -1
  87. package/dist/permissionsDeclaration.js +29 -20
  88. package/dist/pluginHooksWire.js +2 -11
  89. package/dist/promptProfileWireCaps.d.ts +2 -1
  90. package/dist/protocolSegment.d.ts +9 -0
  91. package/dist/protocolSegment.js +28 -0
  92. package/dist/request/taskRequest.js +1 -15
  93. package/dist/ruleCompileVerdict.d.ts +2 -0
  94. package/dist/ruleCompileVerdict.js +13 -0
  95. package/dist/ruleRemovalConsequence.d.ts +2 -1
  96. package/dist/runCancelContext.d.ts +12 -0
  97. package/dist/runCancelContext.js +83 -1
  98. package/dist/seam.js +1 -1
  99. package/dist/seatContract.d.ts +2 -1
  100. package/dist/sessionMemoryStatus.d.ts +2 -4
  101. package/dist/sessionSyncFileHistoryCapability.d.ts +18 -0
  102. package/dist/sessionSyncFileHistoryCapability.js +50 -0
  103. package/dist/skillToolListsCapability.d.ts +18 -0
  104. package/dist/skillToolListsCapability.js +50 -0
  105. package/dist/sqlEngineCapability.js +1 -8
  106. package/dist/steerPriorityCapability.d.ts +18 -0
  107. package/dist/steerPriorityCapability.js +80 -0
  108. package/dist/steerReceipt.d.ts +9 -0
  109. package/dist/steerReceipt.js +25 -0
  110. package/dist/subagent/engineSubagentSteer.js +2 -9
  111. package/dist/supportFloor.js +2 -2
  112. package/dist/toolResult.d.ts +8 -0
  113. package/dist/toolResult.js +47 -3
  114. package/dist/types/engineState.d.ts +2 -12
  115. package/dist/userInvokedSkillsCapability.d.ts +18 -0
  116. package/dist/userInvokedSkillsCapability.js +50 -0
  117. package/dist/wireFailureShape.d.ts +2 -0
  118. package/dist/wireFailureShape.js +4 -0
  119. package/dist/workflowsJournalCapability.d.ts +18 -0
  120. package/dist/workflowsJournalCapability.js +50 -0
  121. package/dist/writeProtectionCapability.d.ts +2 -4
  122. package/docs/INTEGRATION-CLIENTS.md +624 -12
  123. package/package.json +2 -2
@@ -1,5 +1,18 @@
1
1
  export const SEMA_COLLATERAL_ABORT_KEY = '_sema_collateral_abort';
2
2
  export const SEMA_DENIAL_KIND_KEY = '_sema_denial_kind';
3
+ export function durableSeqOf(ev) {
4
+ const rawSeq = ev.id ?? ev.seq;
5
+ if (typeof rawSeq === 'string' && rawSeq.length > 0)
6
+ return rawSeq;
7
+ if (typeof rawSeq === 'number' && Number.isFinite(rawSeq))
8
+ return String(rawSeq);
9
+ return undefined;
10
+ }
11
+ export function hasLedgerPositionKey(ev) {
12
+ const { id, seq } = ev;
13
+ return (id !== undefined && id !== null) || (seq !== undefined && seq !== null);
14
+ }
15
+ export const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
3
16
  export const START_ARGS_MAX_ENTRIES = 256;
4
17
  function normalizeDenyAttribution(a) {
5
18
  if (a === undefined)
@@ -74,13 +87,12 @@ export function createGateLedger() {
74
87
  let denyStampNextFsEnd = false;
75
88
  let denyStampAttribution;
76
89
  let seq;
90
+ const preambleApprovalIds = new Set();
77
91
  return {
78
92
  noteSeq(ev) {
79
- const rawSeq = ev.id ?? ev.seq;
80
- if (typeof rawSeq === 'string' && rawSeq.length > 0)
81
- seq = rawSeq;
82
- else if (typeof rawSeq === 'number' && Number.isFinite(rawSeq))
83
- seq = String(rawSeq);
93
+ const s = durableSeqOf(ev);
94
+ if (s !== undefined)
95
+ seq = s;
84
96
  },
85
97
  lastSeq() {
86
98
  return seq;
@@ -91,6 +103,23 @@ export function createGateLedger() {
91
103
  gatedStartArgsByCall.clear();
92
104
  startArgsByCall.clear();
93
105
  },
106
+ notePreambleApproval(approvalId) {
107
+ if (typeof approvalId !== 'string' || approvalId === '')
108
+ return;
109
+ preambleApprovalIds.delete(approvalId);
110
+ preambleApprovalIds.add(approvalId);
111
+ if (preambleApprovalIds.size > PREAMBLE_APPROVALS_MAX_ENTRIES) {
112
+ const oldest = preambleApprovalIds.values().next().value;
113
+ if (oldest !== undefined)
114
+ preambleApprovalIds.delete(oldest);
115
+ }
116
+ },
117
+ preambleApprovals() {
118
+ return preambleApprovalIds;
119
+ },
120
+ beginStreamSegment() {
121
+ preambleApprovalIds.clear();
122
+ },
94
123
  markStarted(callId) {
95
124
  if (startedCalls.has(callId))
96
125
  return false;
@@ -28,6 +28,8 @@ export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow w
28
28
  export declare const EDIT_NOT_FORWARDED_WARN_TEXT = "your edited tool input was NOT forwarded \u2014 the tool is running with the ORIGINAL input you saw before editing";
29
29
  export declare function surfaceRememberNotApplied(): void;
30
30
  export declare function surfaceEditNotForwarded(): void;
31
+ export declare const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT: string;
32
+ export declare function surfaceReplayedApprovalListUnreadable(): void;
31
33
  export declare const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT: string;
32
34
  export declare function surfaceEditRefusedOnBlindAsk(): void;
33
35
  export declare const RULE_NOT_SENT_WARN_TEXT = "your \"don't ask again\" choice was NOT sent to this engine \u2014 support for that rule form could not be confirmed. The approval itself went through, but you will be asked again";
@@ -72,6 +72,13 @@ export function surfaceRememberNotApplied() {
72
72
  export function surfaceEditNotForwarded() {
73
73
  surfaceSelfClearingWarn(EDIT_NOT_FORWARDED_KEY, EDIT_NOT_FORWARDED_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
74
74
  }
75
+ export const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT = "Could not read the pending-approval list, so an approval request read back from this run's event log was not raised as a question, " +
76
+ 'and no decision was sent for it.';
77
+ const REPLAYED_APPROVAL_LIST_UNREADABLE_KEY = 'hitl-replayed-approval-list-unreadable';
78
+ const REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS = 10_000;
79
+ export function surfaceReplayedApprovalListUnreadable() {
80
+ surfaceSelfClearingWarn(REPLAYED_APPROVAL_LIST_UNREADABLE_KEY, REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT, REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS);
81
+ }
75
82
  export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT = 'That approval was NOT sent: this request does not expose its tool arguments here, so an edited approval cannot be applied. ' +
76
83
  'The request is still waiting — approve or deny it as it is.';
77
84
  const EDIT_REFUSED_ON_BLIND_ASK_KEY = 'hitl-edit-refused-blind-ask';
@@ -5,20 +5,39 @@ function nonEmptyString(v) {
5
5
  function nonNegativeFinite(v) {
6
6
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
7
7
  }
8
+ function snapshotMembers(raw) {
9
+ const length = raw.length;
10
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
11
+ return undefined;
12
+ const out = [];
13
+ for (let i = 0; i < length; i++) {
14
+ const d = Object.getOwnPropertyDescriptor(raw, i);
15
+ if (d === undefined || !('value' in d))
16
+ return undefined;
17
+ out.push(d.value);
18
+ }
19
+ return raw.length === length ? out : undefined;
20
+ }
8
21
  export function readLivePendingRows(body) {
9
22
  if (body === null || typeof body !== 'object' || Array.isArray(body))
10
23
  return { kind: 'malformed' };
11
- if (!('livePending' in body))
12
- return { kind: 'not_reported' };
13
- const raw = body.livePending;
24
+ const field = Object.getOwnPropertyDescriptor(body, 'livePending');
25
+ if (field === undefined)
26
+ return 'livePending' in body ? { kind: 'malformed' } : { kind: 'not_reported' };
27
+ if (!('value' in field))
28
+ return { kind: 'malformed' };
29
+ const raw = field.value;
14
30
  if (raw === undefined)
15
31
  return { kind: 'not_reported' };
16
32
  if (!Array.isArray(raw))
17
33
  return { kind: 'malformed' };
34
+ const members = snapshotMembers(raw);
35
+ if (members === undefined)
36
+ return { kind: 'malformed' };
18
37
  const rows = [];
19
38
  const seen = new Set();
20
39
  let dropped = 0;
21
- for (const item of raw) {
40
+ for (const item of members) {
22
41
  if (item === null || typeof item !== 'object' || Array.isArray(item)) {
23
42
  dropped++;
24
43
  continue;
@@ -46,7 +65,7 @@ export function readLivePendingRows(body) {
46
65
  ...(isToolApprovalFrame(o.frame) && o.frame.type === 'tool_approval' && o.frame.approvalId === o.approvalId ? { frame: o.frame } : {}),
47
66
  });
48
67
  }
49
- if (raw.length > 0 && rows.length === 0)
68
+ if (members.length > 0 && rows.length === 0)
50
69
  return { kind: 'malformed' };
51
70
  return { kind: 'present', rows, dropped };
52
71
  }
@@ -1,6 +1,6 @@
1
1
  import { putOwnKey } from '../ownKey.js';
2
2
  import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
3
- import { CHECKPOINT_UNSUPPORTED_VERSION, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
3
+ import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
4
4
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
5
5
  import { hostLog } from '../host.js';
6
6
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
@@ -58,6 +58,8 @@ function alreadyResolvedFailureVerdict(outcome) {
58
58
  return false;
59
59
  if ('errorCode' in outcome && isDecideParkMovedCode(outcome.errorCode))
60
60
  return true;
61
+ if ('errorCode' in outcome && outcome.errorCode === ASK_GONE)
62
+ return true;
61
63
  return isAlreadyResolvedGateReason(outcome.reason);
62
64
  }
63
65
  export function isGateStandingErrorCode(code, retriable) {
@@ -50,6 +50,9 @@ export type RulesFailure = {
50
50
  } | {
51
51
  kind: 'forbidden';
52
52
  message: string;
53
+ } | {
54
+ kind: 'operators-unconfigured';
55
+ message: string;
53
56
  } | {
54
57
  kind: 'body-shape';
55
58
  message: string;
@@ -100,7 +103,7 @@ export type PersistedRuleWriteBehavior = (typeof PERSISTED_RULE_WRITE_BEHAVIORS)
100
103
  export type PersistedRuleWriteRequest = RuleWriteRequest;
101
104
  export type PersistedRuleWriteWireResult = RuleWriteResult;
102
105
  export type PersistedRuleLiveness = RemovalLiveness;
103
- export declare const PERSISTED_RULE_WRITE_REFUSAL_CAUSES: readonly ["unwritable_behavior", "unreadable_identity", "unusable_principal", "client_too_old", "field_refused", "body_shape", "operator_only", "store_absent", "lane_too_old", "route_absent", "not_committed"];
106
+ export declare const PERSISTED_RULE_WRITE_REFUSAL_CAUSES: readonly ["unwritable_behavior", "unreadable_identity", "unusable_principal", "client_too_old", "field_refused", "body_shape", "operator_only", "operators_unconfigured", "store_absent", "lane_too_old", "route_absent", "not_committed"];
104
107
  export type PersistedRuleWriteRefusalCause = (typeof PERSISTED_RULE_WRITE_REFUSAL_CAUSES)[number];
105
108
  export declare const PERSISTED_RULE_WRITE_UNKNOWN_REASONS: readonly ["indeterminate", "malformed_result", "identity_mismatch", "unattributed_method_refusal", "unclassified"];
106
109
  export type PersistedRuleWriteUnknownReason = (typeof PERSISTED_RULE_WRITE_UNKNOWN_REASONS)[number];
@@ -1,5 +1,7 @@
1
1
  import { engineCapTrue } from '../engineCapsCache.js';
2
+ import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
2
3
  import { wireFailureShapeOf } from '../wireFailureShape.js';
4
+ import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
3
5
  import { hostLog } from '../host.js';
4
6
  import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
5
7
  import { readToolApprovalRespondAck } from './toolApprovalWire.js';
@@ -7,7 +9,7 @@ export function persistedRulesLaneAvailable(baseUrl) {
7
9
  return engineCapTrue(baseUrl, 'permissionRules');
8
10
  }
9
11
  export function persistedRulesGovernanceAvailable(baseUrl) {
10
- return engineCapTrue(baseUrl, 'permissionRulesRevoke') && engineCapTrue(baseUrl, 'permissionRules');
12
+ return engineCapTrue(baseUrl, 'permissionRules');
11
13
  }
12
14
  export function persistedRulesWriteAvailable(baseUrl) {
13
15
  return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
@@ -66,6 +68,8 @@ export function classifyRulesFailure(e) {
66
68
  return { kind: 'too-many-candidates', message };
67
69
  if (status === 403 && errorCode === 'auth.operator_only')
68
70
  return { kind: 'forbidden', message };
71
+ if (errorCode === CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED)
72
+ return { kind: 'operators-unconfigured', message };
69
73
  if (status === 405) {
70
74
  return errorCode === 'method_not_allowed'
71
75
  ? { kind: 'method-unsupported', message }
@@ -73,7 +77,7 @@ export function classifyRulesFailure(e) {
73
77
  }
74
78
  return { kind: 'error', message };
75
79
  }
76
- export const PERSISTED_RULE_BEHAVIORS = Object.freeze(['deny', 'ask', 'allow']);
80
+ export const PERSISTED_RULE_BEHAVIORS = CORE_RULE_BEHAVIORS;
77
81
  const BEHAVIOR_SET = new Set(PERSISTED_RULE_BEHAVIORS);
78
82
  function rowStr(row, key) {
79
83
  if (typeof row !== 'object' || row === null || Array.isArray(row))
@@ -221,6 +225,7 @@ export const PERSISTED_RULE_WRITE_REFUSAL_CAUSES = Object.freeze([
221
225
  'field_refused',
222
226
  'body_shape',
223
227
  'operator_only',
228
+ 'operators_unconfigured',
224
229
  'store_absent',
225
230
  'lane_too_old',
226
231
  'route_absent',
@@ -242,6 +247,8 @@ function writeOutcomeFromFailure(failure) {
242
247
  return { status: 'refused', cause: 'body_shape', message };
243
248
  case 'forbidden':
244
249
  return { status: 'refused', cause: 'operator_only', message };
250
+ case 'operators-unconfigured':
251
+ return { status: 'refused', cause: 'operators_unconfigured', message };
245
252
  case 'lane-unavailable':
246
253
  return { status: 'refused', cause: 'store_absent', message };
247
254
  case 'method-unsupported':
@@ -7,7 +7,7 @@ import { enqueuePlanReviewOutcome } from '../notifications.js';
7
7
  import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
8
8
  import { decideFailureNote } from '../decideFailureNote.js';
9
9
  import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError } from '../resumeRefusalCopy.js';
10
- import { REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
10
+ import { REQUEST_FIELD_CONFLICT, isUnavailableFaceCode } from '../engineErrorCodes.js';
11
11
  import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
12
12
  import { engineCapString } from '../engineCapsCache.js';
13
13
  import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
@@ -648,7 +648,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
648
648
  else if (typeof status === 'number') {
649
649
  const msg = e instanceof Error ? e.message : String(e);
650
650
  hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
651
- effect = status >= 400 && status < 500 ? 'not_applied' : 'unconfirmed';
651
+ effect = (status >= 400 && status < 500) || (typeof refusedCode === 'string' && isUnavailableFaceCode(refusedCode)) ? 'not_applied' : 'unconfirmed';
652
652
  outcome = `${`The plan_review decision failed: HTTP ${status} ${msg}`.trim()}${decideFailureNote(e)}`;
653
653
  }
654
654
  else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
@@ -0,0 +1,21 @@
1
+ import { type ApprovalsListEnvelope, type CrashConvergedRow } from './crashConverged.js';
2
+ export type ReplayedApprovalDisposition = {
3
+ readonly kind: 'live';
4
+ readonly basis: 'live_leg' | 'preamble' | 'pending_list';
5
+ } | {
6
+ readonly kind: 'decided';
7
+ readonly cause?: CrashConvergedRow['cause'];
8
+ } | {
9
+ readonly kind: 'unknown';
10
+ readonly why: 'unreadable_frame' | 'no_approval_id' | 'not_read' | 'list_unreadable' | 'list_not_reported' | 'list_incomplete';
11
+ };
12
+ export interface ReplayPendingBasis {
13
+ readonly preamble?: ReadonlySet<string>;
14
+ readonly approvals?: {
15
+ readonly read: 'ok';
16
+ readonly body: ApprovalsListEnvelope;
17
+ } | {
18
+ readonly read: 'failed';
19
+ };
20
+ }
21
+ export declare function replayedApprovalDisposition(frame: object, basis: ReplayPendingBasis): ReplayedApprovalDisposition;
@@ -0,0 +1,89 @@
1
+ import { hasLedgerPositionKey } from './gateLedger.js';
2
+ import { readLivePendingRows } from './livePendingAsk.js';
3
+ import { projectCrashConverged } from './crashConverged.js';
4
+ const live = (basis) => Object.freeze({ kind: 'live', basis });
5
+ const unknown = (why) => Object.freeze({ kind: 'unknown', why });
6
+ const decided = (cause) => Object.freeze(cause !== undefined ? { kind: 'decided', cause } : { kind: 'decided' });
7
+ function approvalIdOf(frame) {
8
+ const v = frame.approvalId;
9
+ return typeof v === 'string' && v !== '' ? v : undefined;
10
+ }
11
+ function crashConvergedCauseOf(body, approvalId) {
12
+ const projection = projectCrashConverged(body);
13
+ if (projection === undefined)
14
+ return undefined;
15
+ for (const row of [...projection.resumeSafe, ...projection.needsHuman]) {
16
+ if (row.approvalId === approvalId)
17
+ return row.cause;
18
+ }
19
+ return undefined;
20
+ }
21
+ export function replayedApprovalDisposition(frame, basis) {
22
+ let approvalId;
23
+ try {
24
+ if (typeof frame !== 'object' || frame === null)
25
+ return unknown('unreadable_frame');
26
+ if (!hasLedgerPositionKey(frame))
27
+ return live('live_leg');
28
+ approvalId = approvalIdOf(frame);
29
+ }
30
+ catch {
31
+ return unknown('unreadable_frame');
32
+ }
33
+ if (approvalId === undefined)
34
+ return unknown('no_approval_id');
35
+ let b;
36
+ let inPreamble = false;
37
+ try {
38
+ b = typeof basis === 'object' && basis !== null ? basis : undefined;
39
+ inPreamble = b?.preamble?.has(approvalId) === true;
40
+ }
41
+ catch {
42
+ inPreamble = false;
43
+ }
44
+ if (inPreamble)
45
+ return live('preamble');
46
+ let approvals;
47
+ try {
48
+ approvals = b?.approvals;
49
+ }
50
+ catch {
51
+ return unknown('not_read');
52
+ }
53
+ if (approvals === undefined || approvals === null)
54
+ return unknown('not_read');
55
+ let body;
56
+ try {
57
+ if (approvals.read !== 'ok')
58
+ return unknown('list_unreadable');
59
+ body = approvals.body;
60
+ }
61
+ catch {
62
+ return unknown('list_unreadable');
63
+ }
64
+ let reading;
65
+ try {
66
+ reading = readLivePendingRows(body);
67
+ }
68
+ catch {
69
+ return unknown('list_unreadable');
70
+ }
71
+ if (reading.kind === 'present' && reading.rows.some((r) => r.approvalId === approvalId))
72
+ return live('pending_list');
73
+ let cause;
74
+ try {
75
+ cause = crashConvergedCauseOf(body, approvalId);
76
+ }
77
+ catch {
78
+ cause = undefined;
79
+ }
80
+ if (cause !== undefined)
81
+ return decided(cause);
82
+ if (reading.kind === 'not_reported')
83
+ return unknown('list_not_reported');
84
+ if (reading.kind === 'malformed')
85
+ return unknown('list_unreadable');
86
+ if (reading.dropped > 0)
87
+ return unknown('list_incomplete');
88
+ return decided();
89
+ }
@@ -1,5 +1,7 @@
1
1
  import { CONFIG_LEGACY_TOOL_NAME, RULES_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
2
2
  import { rosterMountedNames } from './rosterMountedNames.js';
3
+ import { ruleCompileVerdict } from '../ruleCompileVerdict.js';
4
+ import { protocolSegmentOf } from '../protocolSegment.js';
3
5
  import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX, RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
4
6
  import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from '../generated/toolNameTables.js';
5
7
  const COVERING_NAMESPACE_PREFIXES = CORE_PROTOCOL_PREFIXES;
@@ -9,7 +11,7 @@ const WITHHELD_NOTICE = {
9
11
  qualified_deny: 'Not written to this session’s rule record: that record holds whole tool names and command names only, and this rule has a part in parentheses it cannot carry — recording just the name could block more than this rule does.',
10
12
  peer_wide: 'Not written to this session’s rule record: this rule names a server or agent peer without naming one of its tools, and an engine that matches that record by exact tool name would block none of that peer’s tools.',
11
13
  wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
12
- not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, or has spaces around the name.',
14
+ not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, has spaces around the name, is not a single tool name in rule syntax (for example it contains a backslash or a parenthesis, or is longer than a rule can be), or spells its server or agent peer part in a way the engine never uses when naming tools (for example with a dot or a space), so no tool would ever match it.',
13
15
  legacy_tool_name: `Not written to this session’s rule record: unless a tool by exactly this name is available, the engine refuses it — it is a retired tool name, or it contains "__" but does not start with ${PREFIXES_IN_PROSE} — and with it in that record every later run of this session would fail at startup until that entry is removed from the record. ${REFUSED_NAME_FIX}`,
14
16
  not_in_roster: 'Not written to this session’s rule record: the tool roster supplied for this check has no tool by exactly this name, as a name or an alias, with letter case counted — it may be misspelled, or name a tool this deployment does not provide — so recorded as it is it would block nothing here.',
15
17
  };
@@ -97,7 +99,23 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
97
99
  withheld.push({ rule: '', why: 'not_a_tool_name' });
98
100
  continue;
99
101
  }
100
- const why = withheldWhyOf(r) ?? (mounted !== undefined && !mounted.has(r) ? 'not_in_roster' : undefined);
102
+ let why = withheldWhyOf(r);
103
+ if (why === undefined) {
104
+ const grammar = ruleCompileVerdict(r, 'deny');
105
+ if (grammar === 'unknown')
106
+ return unreadable();
107
+ if (grammar === 'refused')
108
+ why = 'not_a_tool_name';
109
+ }
110
+ if (why === undefined && COVERING_NAMESPACE_PREFIXES.some((p) => r.startsWith(p))) {
111
+ const segment = protocolSegmentOf(r);
112
+ if (segment.kind === 'unknown')
113
+ return unreadable();
114
+ if (segment.kind === 'unminted')
115
+ why = 'not_a_tool_name';
116
+ }
117
+ if (why === undefined && mounted !== undefined && !mounted.has(r))
118
+ why = 'not_in_roster';
101
119
  if (why === undefined)
102
120
  exact.push(r);
103
121
  else {
@@ -1,6 +1,8 @@
1
1
  import type { SessionPermissionRules, StoredSessionRules } from '@sema-agent/sdk';
2
2
  import type { SessionPolicyReading } from '../sessionPolicyCapability.js';
3
3
  import type { ToolRosterView } from '../toolRoster.js';
4
+ import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
5
+ export type LegacyToolNameVerdict = Exclude<(typeof CORE_RULE_TOOL_NAME_VERDICTS)[number], 'current'>;
4
6
  export interface SessionPolicyFacade {
5
7
  getPolicy(sessionId: string, opts?: {
6
8
  signal?: AbortSignal;
@@ -43,6 +45,8 @@ export type SessionPolicyFailure = {
43
45
  message: string;
44
46
  field?: SessionPolicyRemovedEntry['field'];
45
47
  name?: string;
48
+ verdict?: LegacyToolNameVerdict;
49
+ currentName?: string;
46
50
  } | {
47
51
  kind: 'unauthorized';
48
52
  message: string;
@@ -95,6 +99,8 @@ export type SessionPolicyTightenOutcome = {
95
99
  message: string;
96
100
  field?: SessionPolicyRemovedEntry['field'];
97
101
  name?: string;
102
+ verdict?: LegacyToolNameVerdict;
103
+ currentName?: string;
98
104
  alreadyInRecord?: boolean;
99
105
  } | {
100
106
  kind: 'unknown';
@@ -148,6 +154,8 @@ export type SessionPolicyRemovalOutcome = {
148
154
  message: string;
149
155
  field?: SessionPolicyRemovedEntry['field'];
150
156
  name?: string;
157
+ verdict?: LegacyToolNameVerdict;
158
+ currentName?: string;
151
159
  };
152
160
  export type SessionPolicyRosterRequiredWhy = 'absent' | 'unreadable';
153
161
  export declare function removeRefusedSessionRules(facade: SessionPolicyFacade, sessionId: string, roster: ToolRosterView | undefined, opts?: {
@@ -4,6 +4,10 @@ import { isEngineRefusedToolName } from './sessionPolicyDeliverable.js';
4
4
  import { RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
5
5
  import { RULES_LEGACY_TOOL_NAME, isRulesRefusalCode } from '../engineErrorCodes.js';
6
6
  import { rosterMountedNames } from './rosterMountedNames.js';
7
+ import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
8
+ function isLegacyToolNameVerdict(v) {
9
+ return typeof v === 'string' && v !== 'current' && CORE_RULE_TOOL_NAME_VERDICTS.includes(v);
10
+ }
7
11
  const RULE_FIELD_PRESENCE = {
8
12
  toolAllow: true,
9
13
  toolDeny: true,
@@ -14,7 +18,7 @@ const RULE_FIELD_PRESENCE = {
14
18
  export const SESSION_POLICY_RULE_FIELDS = Object.freeze(Object.keys(RULE_FIELD_PRESENCE));
15
19
  const MAX_RULE_ENTRIES = 10_000;
16
20
  export function classifySessionPolicyFailure(e) {
17
- const { status, errorCode, message, bodyField, bodyName } = wireFailureShapeOf(e);
21
+ const { status, errorCode, message, bodyField, bodyName, bodyVerdict, bodyCurrentName } = wireFailureShapeOf(e);
18
22
  if (errorCode === undefined || errorCode === '')
19
23
  return { kind: 'no-verdict', message };
20
24
  if (errorCode === RULES_LEGACY_TOOL_NAME) {
@@ -23,6 +27,8 @@ export function classifySessionPolicyFailure(e) {
23
27
  message,
24
28
  ...(bodyField === 'toolDeny' || bodyField === 'toolAllow' ? { field: bodyField } : {}),
25
29
  ...(bodyName !== undefined && bodyName !== '' ? { name: bodyName } : {}),
30
+ ...(isLegacyToolNameVerdict(bodyVerdict) ? { verdict: bodyVerdict } : {}),
31
+ ...(bodyCurrentName !== undefined && bodyCurrentName !== '' ? { currentName: bodyCurrentName } : {}),
26
32
  };
27
33
  }
28
34
  if (isRulesRefusalCode(errorCode) && status !== undefined && status >= 400 && status < 500)
@@ -227,7 +233,7 @@ function unknownFrom(failure, leg) {
227
233
  }
228
234
  }
229
235
  function legacyToolNameOutcome(failure, prior, add) {
230
- const { field, name } = failure;
236
+ const { field, name, verdict, currentName } = failure;
231
237
  let alreadyInRecord;
232
238
  if (field !== undefined && name !== undefined) {
233
239
  const inPrior = Object.hasOwn(prior, field) ? prior[field] : undefined;
@@ -242,6 +248,8 @@ function legacyToolNameOutcome(failure, prior, add) {
242
248
  message: failure.message,
243
249
  ...(field !== undefined ? { field } : {}),
244
250
  ...(name !== undefined ? { name } : {}),
251
+ ...(verdict !== undefined ? { verdict } : {}),
252
+ ...(currentName !== undefined ? { currentName } : {}),
245
253
  ...(alreadyInRecord !== undefined ? { alreadyInRecord } : {}),
246
254
  };
247
255
  }
@@ -488,6 +496,8 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
488
496
  message: failure.message,
489
497
  ...(failure.field !== undefined ? { field: failure.field } : {}),
490
498
  ...(failure.name !== undefined ? { name: failure.name } : {}),
499
+ ...(failure.verdict !== undefined ? { verdict: failure.verdict } : {}),
500
+ ...(failure.currentName !== undefined ? { currentName: failure.currentName } : {}),
491
501
  },
492
502
  };
493
503
  }
@@ -1,4 +1,5 @@
1
1
  import { type GateCurrentPending, type HitlFailureStage, type HitlSafetyCode, type HitlClientLike, type DecideOperationObserver } from './hitlBridge.js';
2
+ import { type AllowRuleShadowedWireMember, type ApprovalAllowRuleShadow } from './allowRuleShadow.js';
2
3
  import { type RuleStoreUnreadableKind } from '../gateVocabulary.js';
3
4
  import { type ApprovalMandateWord } from '../gateVocabulary.js';
4
5
  import type { ToolPermissionRequest } from '../seatContract.js';
@@ -131,8 +132,10 @@ export interface ApprovalCardRequest {
131
132
  inputHasBidi?: true;
132
133
  hasBidiControls?: true;
133
134
  persistedRuleShadowed?: string;
135
+ allowRuleShadow?: ApprovalAllowRuleShadow;
134
136
  probeCause?: unknown;
135
137
  probeReason?: string;
138
+ rememberRefused?: true;
136
139
  ruleEvidence?: unknown;
137
140
  requiresRealApproval?: true;
138
141
  irreversibleParkGate?: true;
@@ -181,8 +184,10 @@ export interface ToolApprovalFrame {
181
184
  denialLimitFallback?: DenialLimitFallback;
182
185
  ruleStoreUnreadable?: string;
183
186
  persistedRuleShadowed?: string;
187
+ allowRuleShadowed?: readonly AllowRuleShadowedWireMember[];
184
188
  probeCause?: unknown;
185
189
  probeReason?: string;
190
+ rememberRefused?: true;
186
191
  ruleEvidence?: unknown;
187
192
  requiresRealApproval?: true;
188
193
  mandated?: true;
@@ -207,7 +212,7 @@ export interface ToolApprovalDelegation {
207
212
  depth: number;
208
213
  agentName?: string;
209
214
  }
210
- export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "ruleStoreUnreadable", "readRootCandidate", "mandated", "mandate", "origin", "askClass", "classes", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "probeReason", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
215
+ export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "ruleStoreUnreadable", "readRootCandidate", "mandated", "mandate", "origin", "askClass", "classes", "inputHasBidi", "parked", "persistedRuleShadowed", "allowRuleShadowed", "probeCause", "probeReason", "rememberRefused", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
211
216
  export interface ApprovalMandatedFacts {
212
217
  readonly mandated?: true;
213
218
  readonly ruleOffersAbsence?: string;