@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
@@ -0,0 +1,167 @@
1
+ import { CORE_ALLOW_RULE_MEMBERS } from '../generated/engineFactTables.js';
2
+ const KNOWN_MEMBERS = new Set(CORE_ALLOW_RULE_MEMBERS);
3
+ function freezeMember(m) {
4
+ return Object.freeze({ ...m });
5
+ }
6
+ function unreadable() {
7
+ return Object.freeze({ source: 'allow_rule_shadowed', members: Object.freeze([]), unreadable: true });
8
+ }
9
+ function readMemberRecord(r) {
10
+ if (typeof r !== 'object' || r === null || Array.isArray(r))
11
+ return freezeMember({ known: false, malformed: true });
12
+ let malformed = false;
13
+ const rawMember = Object.hasOwn(r, 'member') ? r.member : undefined;
14
+ const member = typeof rawMember === 'string' && rawMember !== '' ? rawMember : undefined;
15
+ if (member === undefined)
16
+ malformed = true;
17
+ let entries;
18
+ const rawEntries = Object.hasOwn(r, 'entries') ? r.entries : undefined;
19
+ if (Array.isArray(rawEntries)) {
20
+ const length = rawEntries.length;
21
+ if (typeof length === 'number' && Number.isSafeInteger(length) && length >= 0) {
22
+ const out = [];
23
+ let ok = true;
24
+ for (let i = 0; i < length; i++) {
25
+ const d = Object.getOwnPropertyDescriptor(rawEntries, i);
26
+ if (d === undefined || !('value' in d)) {
27
+ ok = false;
28
+ break;
29
+ }
30
+ const e = d.value;
31
+ if (typeof e !== 'string') {
32
+ ok = false;
33
+ break;
34
+ }
35
+ out.push(e);
36
+ }
37
+ if (ok && rawEntries.length === length)
38
+ entries = Object.freeze(out);
39
+ else
40
+ malformed = true;
41
+ }
42
+ else
43
+ malformed = true;
44
+ }
45
+ else
46
+ malformed = true;
47
+ let omitted;
48
+ if (Object.hasOwn(r, 'omitted')) {
49
+ const o = r.omitted;
50
+ if (typeof o === 'number' && Number.isSafeInteger(o) && o > 0)
51
+ omitted = o;
52
+ else if (o !== undefined)
53
+ malformed = true;
54
+ }
55
+ return freezeMember({
56
+ ...(member !== undefined ? { member } : {}),
57
+ known: member !== undefined && KNOWN_MEMBERS.has(member),
58
+ ...(entries !== undefined ? { entries } : {}),
59
+ ...(omitted !== undefined ? { omitted } : {}),
60
+ ...(malformed ? { malformed: true } : {}),
61
+ });
62
+ }
63
+ function readNewKeyValue(raw) {
64
+ if (raw === undefined)
65
+ return undefined;
66
+ if (!Array.isArray(raw))
67
+ return unreadable();
68
+ const length = raw.length;
69
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length <= 0)
70
+ return unreadable();
71
+ const members = [];
72
+ for (let i = 0; i < length; i++)
73
+ members.push(readMemberAt(raw, i));
74
+ if (raw.length !== length)
75
+ return unreadable();
76
+ return Object.freeze({ source: 'allow_rule_shadowed', members: Object.freeze(members) });
77
+ }
78
+ function readLegacyValue(raw) {
79
+ if (typeof raw !== 'string' || raw.trim() === '')
80
+ return undefined;
81
+ return Object.freeze({
82
+ source: 'legacy_shadowed_rule',
83
+ members: Object.freeze([freezeMember({ member: 'persisted', known: true, entries: Object.freeze([raw]) })]),
84
+ });
85
+ }
86
+ function ownValue(v, key) {
87
+ return Object.hasOwn(v, key) ? v[key] : undefined;
88
+ }
89
+ function keyValue(v, key) {
90
+ return v[key];
91
+ }
92
+ function readMemberAt(arr, i) {
93
+ const d = Object.getOwnPropertyDescriptor(arr, i);
94
+ return d !== undefined && 'value' in d ? readMemberRecord(d.value) : freezeMember({ known: false, malformed: true });
95
+ }
96
+ export function readAllowRuleShadowFromWire(carrier, leg) {
97
+ try {
98
+ if (typeof carrier !== 'object' || carrier === null)
99
+ return undefined;
100
+ let host = carrier;
101
+ if (leg === 'row') {
102
+ const rd = keyValue(carrier, 'riskDescriptor');
103
+ host = typeof rd === 'object' && rd !== null ? rd : undefined;
104
+ }
105
+ if (host === undefined)
106
+ return undefined;
107
+ const fresh = readNewKeyValue(keyValue(host, 'allowRuleShadowed'));
108
+ if (fresh !== undefined)
109
+ return fresh;
110
+ return readLegacyValue(keyValue(host, leg === 'row' ? 'shadowedRule' : 'persistedRuleShadowed'));
111
+ }
112
+ catch {
113
+ return unreadable();
114
+ }
115
+ }
116
+ function readStampedValue(raw) {
117
+ if (typeof raw !== 'object' || raw === null)
118
+ return unreadable();
119
+ const source = ownValue(raw, 'source');
120
+ const members = ownValue(raw, 'members');
121
+ if ((source !== 'allow_rule_shadowed' && source !== 'legacy_shadowed_rule') || !Array.isArray(members))
122
+ return unreadable();
123
+ const length = members.length;
124
+ if (typeof length !== 'number' || !Number.isSafeInteger(length))
125
+ return unreadable();
126
+ const out = [];
127
+ for (let i = 0; i < length; i++) {
128
+ const r = readMemberAt(members, i);
129
+ const d = Object.getOwnPropertyDescriptor(members, i);
130
+ const was = d !== undefined && 'value' in d && typeof d.value === 'object' && d.value !== null && d.value.malformed === true;
131
+ out.push(was && r.malformed !== true ? freezeMember({ ...r, malformed: true }) : r);
132
+ }
133
+ if (members.length !== length)
134
+ return unreadable();
135
+ return Object.freeze({
136
+ source,
137
+ members: Object.freeze(out),
138
+ ...(ownValue(raw, 'unreadable') === true || out.length === 0 ? { unreadable: true } : {}),
139
+ });
140
+ }
141
+ export function readApprovalAllowRuleShadow(card) {
142
+ try {
143
+ if (typeof card !== 'object' || card === null)
144
+ return undefined;
145
+ const stamped = keyValue(card, 'allowRuleShadow');
146
+ if (stamped !== undefined)
147
+ return readStampedValue(stamped);
148
+ return readAllowRuleShadowFromWire(card, 'frame');
149
+ }
150
+ catch {
151
+ return unreadable();
152
+ }
153
+ }
154
+ export function approvalAllowRuleShadowed(card) {
155
+ return readApprovalAllowRuleShadow(card) !== undefined;
156
+ }
157
+ export function legacyShadowedRuleTextOf(r) {
158
+ if (r === undefined)
159
+ return undefined;
160
+ for (const m of r.members) {
161
+ if (m.member !== 'persisted' || m.entries === undefined)
162
+ continue;
163
+ const first = m.entries[0];
164
+ return typeof first === 'string' && first.trim() !== '' ? first : undefined;
165
+ }
166
+ return undefined;
167
+ }
@@ -0,0 +1,14 @@
1
+ export type AskClassPairReading = {
2
+ readonly state: 'pair';
3
+ readonly askClass: string;
4
+ readonly classes: readonly string[];
5
+ } | {
6
+ readonly state: 'absent';
7
+ } | {
8
+ readonly state: 'malformed';
9
+ };
10
+ export declare function readAskClassPairState(v: object | null | undefined): AskClassPairReading;
11
+ export declare function readAskClassPair(v: object | null | undefined): {
12
+ askClass: string;
13
+ classes: readonly string[];
14
+ } | undefined;
@@ -0,0 +1,42 @@
1
+ const ABSENT = Object.freeze({ state: 'absent' });
2
+ const MALFORMED = Object.freeze({ state: 'malformed' });
3
+ export function readAskClassPairState(v) {
4
+ if (typeof v !== 'object' || v === null)
5
+ return ABSENT;
6
+ const hasAskClass = Object.hasOwn(v, 'askClass');
7
+ const hasClasses = Object.hasOwn(v, 'classes');
8
+ if (!hasAskClass && !hasClasses)
9
+ return ABSENT;
10
+ if (!hasAskClass || !hasClasses)
11
+ return MALFORMED;
12
+ const askClass = v.askClass;
13
+ if (typeof askClass !== 'string' || askClass === '')
14
+ return MALFORMED;
15
+ const raw = v.classes;
16
+ if (!Array.isArray(raw))
17
+ return MALFORMED;
18
+ const length = raw.length;
19
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length <= 0)
20
+ return MALFORMED;
21
+ const classes = [];
22
+ const seen = new Set();
23
+ for (let i = 0; i < length; i++) {
24
+ const d = Object.getOwnPropertyDescriptor(raw, i);
25
+ if (d === undefined || !('value' in d))
26
+ return MALFORMED;
27
+ const c = d.value;
28
+ if (typeof c !== 'string' || c === '' || seen.has(c))
29
+ return MALFORMED;
30
+ seen.add(c);
31
+ classes.push(c);
32
+ }
33
+ if (raw.length !== length)
34
+ return MALFORMED;
35
+ if (classes[0] !== askClass)
36
+ return MALFORMED;
37
+ return Object.freeze({ state: 'pair', askClass, classes: Object.freeze(classes) });
38
+ }
39
+ export function readAskClassPair(v) {
40
+ const r = readAskClassPairState(v);
41
+ return r.state === 'pair' ? { askClass: r.askClass, classes: r.classes } : undefined;
42
+ }
@@ -116,6 +116,7 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
116
116
  led.forgetRunScopedState();
117
117
  }
118
118
  const seq = handoff !== undefined ? undefined : led.lastSeq();
119
+ led.beginStreamSegment();
119
120
  stream = deps.runsEvents(taskId.current, {
120
121
  ...(seq !== undefined ? { lastEventId: seq } : {}),
121
122
  ...(opts?.signal ? { signal: opts.signal } : {}),
@@ -1,3 +1,4 @@
1
+ import { type AutomaticAnswerRoad, type AutomaticAnswerUnknownWhy } from './automaticAnswerVerdict.js';
1
2
  export interface AskPostureFacts {
2
3
  readonly settingsAskRuleMatched?: boolean;
3
4
  }
@@ -5,10 +6,14 @@ export type AskPostureSurvivalRoad = 'origin' | 'settings_ask_rule';
5
6
  export type AskPostureVerdict = {
6
7
  readonly kind: 'must_ask';
7
8
  readonly by: AskPostureSurvivalRoad;
9
+ readonly road?: AutomaticAnswerRoad;
10
+ readonly askClass?: string;
8
11
  } | {
9
12
  readonly kind: 'posture_may_answer';
13
+ readonly releaseReason?: string;
10
14
  } | {
11
15
  readonly kind: 'unknown';
16
+ readonly why?: AutomaticAnswerUnknownWhy;
12
17
  };
13
18
  export declare function askSurvivesPosture(req: {
14
19
  readonly origin?: string;
@@ -1,4 +1,5 @@
1
1
  import { readOwnNonEmptyString } from '../ownKey.js';
2
+ import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
2
3
  const ORIGIN_SURVIVES_POSTURE = Object.freeze({
3
4
  content_question: true,
4
5
  ancestor_marked: false,
@@ -17,6 +18,22 @@ const MUST_ASK_BY_SETTINGS = Object.freeze({ kind: 'must_ask', by: 'settings_ask
17
18
  const POSTURE_MAY_ANSWER = Object.freeze({ kind: 'posture_may_answer' });
18
19
  const UNKNOWN = Object.freeze({ kind: 'unknown' });
19
20
  export function askSurvivesPosture(req, facts) {
21
+ const old = legacyPostureVerdict(req, facts);
22
+ const neu = automaticAnswerVerdict(req, 'mode_posture', facts ?? undefined);
23
+ if (old.kind === 'must_ask' || neu.kind === 'must_ask') {
24
+ const by = old.kind === 'must_ask' ? old.by : neu.kind === 'must_ask' && neu.road === 'settings_ask_rule' ? 'settings_ask_rule' : 'origin';
25
+ const road = neu.kind === 'must_ask' ? neu.road : by === 'settings_ask_rule' ? 'settings_ask_rule' : 'origin';
26
+ const askClass = neu.kind === 'must_ask' ? neu.askClass : undefined;
27
+ return Object.freeze({ kind: 'must_ask', by, road, ...(askClass !== undefined ? { askClass } : {}) });
28
+ }
29
+ if (old.kind === 'unknown' || neu.kind === 'unknown') {
30
+ return neu.kind === 'unknown' ? Object.freeze({ kind: 'unknown', why: neu.why }) : UNKNOWN;
31
+ }
32
+ return neu.kind === 'may_answer' && neu.releaseReason !== undefined
33
+ ? Object.freeze({ kind: 'posture_may_answer', releaseReason: neu.releaseReason })
34
+ : POSTURE_MAY_ANSWER;
35
+ }
36
+ function legacyPostureVerdict(req, facts) {
20
37
  const origin = readOwnNonEmptyString(req, 'origin');
21
38
  const rawMatched = typeof facts === 'object' && facts !== null ? facts.settingsAskRuleMatched : undefined;
22
39
  const matched = typeof rawMatched === 'boolean' ? rawMatched : undefined;
@@ -0,0 +1,21 @@
1
+ import { type CoreModeArbitrationRow } from '../generated/engineFactTables.js';
2
+ export type AutomaticAnswerLane = 'mode_posture' | 'standing_rule';
3
+ export interface AutomaticAnswerFacts {
4
+ readonly settingsAskRuleMatched?: boolean;
5
+ }
6
+ export type AutomaticAnswerRoad = 'origin' | 'requires_real_approval' | 'governance' | 'mandate_word' | 'mandated' | 'allow_rule_shadow' | 'ask_class' | 'settings_ask_rule';
7
+ export type AutomaticAnswerUnknownWhy = 'card_unreadable' | 'lane_not_recognised' | 'classes_malformed' | 'classes_absent' | 'class_not_in_table' | 'host_not_reported';
8
+ export type AutomaticAnswerVerdict = {
9
+ readonly kind: 'must_ask';
10
+ readonly road: AutomaticAnswerRoad;
11
+ readonly askClass?: string;
12
+ readonly origin?: string;
13
+ } | {
14
+ readonly kind: 'may_answer';
15
+ readonly releaseReason?: string;
16
+ } | {
17
+ readonly kind: 'unknown';
18
+ readonly why: AutomaticAnswerUnknownWhy;
19
+ };
20
+ export declare function foldModeArbitrationRow(classes: Iterable<string>): CoreModeArbitrationRow;
21
+ export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
@@ -0,0 +1,143 @@
1
+ import { CORE_MODE_ARBITRATION, CORE_MODE_RELEASE_REASONS } from '../generated/engineFactTables.js';
2
+ import { readOwnNonEmptyString } from '../ownKey.js';
3
+ import { readAskClassPairState } from './askClassPair.js';
4
+ import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
5
+ import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
6
+ const TABLE = CORE_MODE_ARBITRATION;
7
+ const ORIGIN_IMPLIES_CLASS = Object.freeze({
8
+ content_question: 'content_question',
9
+ org_unavailable: 'governance_unavailable',
10
+ org_rule: 'org_rule',
11
+ hook: 'hook',
12
+ ask_rule: 'ask_rule',
13
+ denial_limit_fallback: 'denial_limit_fallback',
14
+ });
15
+ const MANDATE_WORD_IMPLIES_CLASS = Object.freeze({
16
+ operator_always: 'shell_floor',
17
+ write_protection_unresolved: 'write_protection_unresolved',
18
+ });
19
+ const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
20
+ export function foldModeArbitrationRow(classes) {
21
+ let any = false;
22
+ let bypassImmune = false;
23
+ let classifierEligible = true;
24
+ let sandboxAdmission = true;
25
+ let ruleMayClear = true;
26
+ let hookAskFloor = false;
27
+ for (const c of classes) {
28
+ if (!Object.hasOwn(TABLE, c))
29
+ continue;
30
+ const row = TABLE[c];
31
+ any = true;
32
+ bypassImmune ||= row.bypassImmune;
33
+ classifierEligible &&= row.classifierEligible;
34
+ sandboxAdmission &&= row.sandboxAdmission;
35
+ ruleMayClear &&= row.ruleMayClear;
36
+ hookAskFloor ||= row.hookAskFloor;
37
+ }
38
+ if (!any)
39
+ return ORDINARY_ROW;
40
+ return Object.freeze({ bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor });
41
+ }
42
+ function laneBlocks(lane, row) {
43
+ return lane === 'mode_posture' ? row.bypassImmune : !row.ruleMayClear;
44
+ }
45
+ function mustAsk(road, extra) {
46
+ return Object.freeze({ kind: 'must_ask', road, ...(extra?.askClass !== undefined ? { askClass: extra.askClass } : {}), ...(extra?.origin !== undefined ? { origin: extra.origin } : {}) });
47
+ }
48
+ const UNKNOWN = Object.freeze({
49
+ card_unreadable: Object.freeze({ kind: 'unknown', why: 'card_unreadable' }),
50
+ lane_not_recognised: Object.freeze({ kind: 'unknown', why: 'lane_not_recognised' }),
51
+ classes_malformed: Object.freeze({ kind: 'unknown', why: 'classes_malformed' }),
52
+ classes_absent: Object.freeze({ kind: 'unknown', why: 'classes_absent' }),
53
+ class_not_in_table: Object.freeze({ kind: 'unknown', why: 'class_not_in_table' }),
54
+ host_not_reported: Object.freeze({ kind: 'unknown', why: 'host_not_reported' }),
55
+ });
56
+ const MAY_ANSWER = Object.freeze({ kind: 'may_answer' });
57
+ function evidenceView(card, ...keys) {
58
+ const out = {};
59
+ for (const k of keys) {
60
+ const v = card[k];
61
+ if (v !== undefined)
62
+ out[k] = v;
63
+ }
64
+ return out;
65
+ }
66
+ function readHostAskRuleMatched(facts) {
67
+ if (typeof facts !== 'object' || facts === null)
68
+ return undefined;
69
+ try {
70
+ const d = Object.getOwnPropertyDescriptor(facts, 'settingsAskRuleMatched');
71
+ return d !== undefined && 'value' in d && typeof d.value === 'boolean' ? d.value : undefined;
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ export function automaticAnswerVerdict(card, lane, facts) {
78
+ if (lane !== 'mode_posture' && lane !== 'standing_rule')
79
+ return UNKNOWN.lane_not_recognised;
80
+ try {
81
+ if (typeof card !== 'object' || card === null)
82
+ return UNKNOWN.card_unreadable;
83
+ const origin = readOwnNonEmptyString(evidenceView(card, 'origin'), 'origin');
84
+ if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
85
+ const cls = ORIGIN_IMPLIES_CLASS[origin];
86
+ if (laneBlocks(lane, TABLE[cls]))
87
+ return mustAsk('origin', { origin, askClass: cls });
88
+ }
89
+ if (lane === 'standing_rule' && evidenceView(card, 'requiresRealApproval').requiresRealApproval === true)
90
+ return mustAsk('requires_real_approval');
91
+ if (evidenceView(card, 'governanceForced').governanceForced === true)
92
+ return mustAsk('governance');
93
+ if (lane === 'mode_posture') {
94
+ const word = readApprovalMandate(evidenceView(card, 'mandate'));
95
+ if (word !== undefined && Object.hasOwn(MANDATE_WORD_IMPLIES_CLASS, word)) {
96
+ const cls = MANDATE_WORD_IMPLIES_CLASS[word];
97
+ if (laneBlocks(lane, TABLE[cls]))
98
+ return mustAsk('mandate_word', { askClass: cls });
99
+ }
100
+ }
101
+ else {
102
+ const mandateView = evidenceView(card, 'mandated', 'ruleOffersAbsence', 'mandate');
103
+ if (approvalIsMandated(mandateView))
104
+ return mustAsk('mandated');
105
+ if (approvalAllowRuleShadowed(card) || readOwnNonEmptyString(mandateView, 'ruleOffersAbsence') === 'shadowed')
106
+ return mustAsk('allow_rule_shadow');
107
+ }
108
+ const pair = readAskClassPairState(card);
109
+ if (pair.state === 'malformed')
110
+ return UNKNOWN.classes_malformed;
111
+ if (pair.state === 'absent')
112
+ return UNKNOWN.classes_absent;
113
+ const folded = foldModeArbitrationRow(pair.classes);
114
+ let anyKnown = false;
115
+ let firstBlocking;
116
+ let anyUnknown = false;
117
+ for (const c of pair.classes) {
118
+ if (!Object.hasOwn(TABLE, c)) {
119
+ anyUnknown = true;
120
+ continue;
121
+ }
122
+ anyKnown = true;
123
+ if (firstBlocking === undefined && laneBlocks(lane, TABLE[c]))
124
+ firstBlocking = c;
125
+ }
126
+ if (anyKnown && laneBlocks(lane, folded))
127
+ return mustAsk('ask_class', firstBlocking !== undefined ? { askClass: firstBlocking } : undefined);
128
+ if (anyUnknown)
129
+ return UNKNOWN.class_not_in_table;
130
+ if (lane === 'standing_rule')
131
+ return MAY_ANSWER;
132
+ const matched = readHostAskRuleMatched(facts);
133
+ if (matched === true)
134
+ return mustAsk('settings_ask_rule');
135
+ if (matched !== false)
136
+ return UNKNOWN.host_not_reported;
137
+ const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => pair.classes.includes(r));
138
+ return releaseReason !== undefined ? Object.freeze({ kind: 'may_answer', releaseReason }) : MAY_ANSWER;
139
+ }
140
+ catch {
141
+ return UNKNOWN.card_unreadable;
142
+ }
143
+ }
@@ -16,6 +16,7 @@ export interface CrashConvergedRow {
16
16
  readonly resumeSafe: boolean;
17
17
  readonly needsHumanReason?: 'approved_then_interrupted' | 'pending_unsafe' | 'unstable_row' | 'contradictory';
18
18
  }
19
+ export declare const isCrashConvergedCause: (v: unknown) => v is CrashConvergedRow["cause"];
19
20
  export interface ApprovalsListEnvelope {
20
21
  pending: PendingCheckpoint[];
21
22
  livePending?: unknown[];
@@ -2,7 +2,7 @@ const CRASH_CONVERGED_CAUSE_WORDS = Object.freeze({
2
2
  crashed_before_park: true,
3
3
  shutdown_before_park: true,
4
4
  });
5
- const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
5
+ export const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
6
6
  const isString = (v) => typeof v === 'string';
7
7
  const isNumber = (v) => typeof v === 'number';
8
8
  const nonEmptyString = (v) => typeof v === 'string' && v.length > 0;
@@ -2,6 +2,7 @@ import type { AgentEvent } from '@sema-agent/sdk';
2
2
  import type { DecideOperationObserver, HitlClientLike } from './hitlBridge.js';
3
3
  import { type RespondToolApprovalFn, type ToolApprovalFrame, type ToolApprovalFrameLaneOpts, type ToolApprovalFrameOutcome } from './toolApprovalWire.js';
4
4
  import type { GateLedger } from './gateLedger.js';
5
+ import { type ReplayedApprovalDisposition } from './replayedApproval.js';
5
6
  export declare const HITL_REJECT_MESSAGE = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
6
7
  export declare const HITL_POLICY_DENY_MESSAGE: string;
7
8
  export declare const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
@@ -23,6 +24,7 @@ export interface AskGateWireDeps {
23
24
  approvalLane?: ToolApprovalFrameLaneOpts;
24
25
  onToolApprovalOutcome?: (frame: ToolApprovalFrame, outcome: ToolApprovalFrameOutcome) => void | Promise<void>;
25
26
  onDecideOperation?: DecideOperationObserver;
27
+ onReplayedApproval?: (frame: ToolApprovalFrame, disposition: ReplayedApprovalDisposition) => void | Promise<void>;
26
28
  }
27
29
  type GateKind = 'ask' | 'fs';
28
30
  export interface GatePark {
@@ -3,8 +3,10 @@ import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolAppro
3
3
  import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
4
4
  import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
5
5
  import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
6
- import { surfaceForCurrentSession } from './hitlHostSurface.js';
7
- import { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
6
+ import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
7
+ import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
8
+ import { replayedApprovalDisposition } from './replayedApproval.js';
9
+ import { settleWithinDeadline } from '../abortableSleep.js';
8
10
  import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
9
11
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
10
12
  import { RUN_CANCELLED_CODE } from '../runCancelContext.js';
@@ -233,22 +235,36 @@ async function routeToolApprovalFrame(ev, ctx) {
233
235
  hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} but no respond wire — engine self-settles (TTL deny)`);
234
236
  return { kind: 'skip' };
235
237
  }
236
- if (isAskToolLoose(ev.toolName)) {
237
- try {
238
- await deps.respondToolApproval(ev.approvalId, 'allow', {
239
- ...(ctx.signal && !ctx.signal.aborted ? { signal: ctx.signal } : {}),
240
- });
241
- hostLog('debug', `liveHitlAskWire: ask-tool approval frame ${ev.approvalId} → allow(问题门放行,候 park 弹卡)`);
242
- }
243
- catch (e) {
244
- hostLog('debug', `liveHitlAskWire: ask-tool approval allow failed for ${ev.approvalId}: ${String(e)} — engine self-settles (TTL)`);
238
+ if (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId)) {
239
+ const disposition = await judgeLedgerApprovalFrame(ev, ctx);
240
+ if (disposition.kind !== 'live') {
241
+ hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
242
+ `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
243
+ ' — not raised as a question, no decision sent');
244
+ if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
245
+ surfaceReplayedApprovalListUnreadable();
246
+ if (deps.onReplayedApproval !== undefined) {
247
+ try {
248
+ const out = deps.onReplayedApproval(ev, disposition);
249
+ if (out && typeof out.then === 'function') {
250
+ void Promise.resolve(out).catch((e) => {
251
+ hostLog('debug', `liveHitlAskWire: onReplayedApproval rejected: ${String(e).slice(0, 160)}`);
252
+ });
253
+ }
254
+ }
255
+ catch (e) {
256
+ hostLog('debug', `liveHitlAskWire: onReplayedApproval threw: ${String(e).slice(0, 160)}`);
257
+ }
258
+ }
259
+ return { kind: 'skip' };
245
260
  }
246
- return { kind: 'skip' };
247
261
  }
248
262
  const fromSubagent = isFromSubagent(ev);
249
263
  hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} tool=${String(ev.toolName)}${fromSubagent ? ` from-subagent=${String(ev.sourceTaskId ?? ev.sourceAgentName ?? 'explicit')}` : ''}`);
250
- const gatedCallId = !fromSubagent ? led.lastPendingFsCall() : undefined;
251
- const outcome = await surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, gatedCallId), ctx.signal, deps.approvalLane);
264
+ const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
265
+ const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
266
+ const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
267
+ const outcome = await surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane);
252
268
  const { decision } = outcome;
253
269
  if (deps.onToolApprovalOutcome !== undefined) {
254
270
  try {
@@ -275,6 +291,30 @@ async function routeToolApprovalFrame(ev, ctx) {
275
291
  }
276
292
  return { kind: 'skip' };
277
293
  }
294
+ const REPLAYED_APPROVAL_LIST_DEADLINE_MS = 3000;
295
+ async function judgeLedgerApprovalFrame(ev, ctx) {
296
+ const outer = ctx.signal;
297
+ const got = await settleWithinDeadline(REPLAYED_APPROVAL_LIST_DEADLINE_MS, (deadline) => {
298
+ const ac = new AbortController();
299
+ const release = () => {
300
+ deadline.removeEventListener('abort', forward);
301
+ outer?.removeEventListener('abort', forward);
302
+ };
303
+ const forward = () => {
304
+ ac.abort();
305
+ release();
306
+ };
307
+ deadline.addEventListener('abort', forward, { once: true });
308
+ outer?.addEventListener('abort', forward, { once: true });
309
+ if (outer?.aborted === true)
310
+ forward();
311
+ return Promise.resolve()
312
+ .then(() => ctx.deps.client.approvals.list({ signal: ac.signal }))
313
+ .finally(release);
314
+ });
315
+ const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
316
+ return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
317
+ }
278
318
  function routeToolStart(ev, callId, led) {
279
319
  led.noteStart(callId, ev);
280
320
  if (!led.markStarted(callId))
@@ -355,5 +395,17 @@ export async function routeFrame(ev, ctx) {
355
395
  if (ev.type === 'failed') {
356
396
  return { kind: 'end', events: [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal }), ev] };
357
397
  }
398
+ if (ev.type === 'approval_request') {
399
+ try {
400
+ if (!hasLedgerPositionKey(ev)) {
401
+ const id = ev.approvalId;
402
+ if (typeof id === 'string' && id !== '')
403
+ led.notePreambleApproval(id);
404
+ }
405
+ }
406
+ catch (e) {
407
+ hostLog('debug', `liveHitlAskWire: approval_request frame unreadable — not counted as preamble evidence: ${String(e).slice(0, 120)}`);
408
+ }
409
+ }
358
410
  return { kind: 'yield', events: [ev] };
359
411
  }
@@ -13,10 +13,16 @@ export interface AskAnsweredOutput {
13
13
  notes?: string;
14
14
  }>;
15
15
  }
16
+ export declare function durableSeqOf(ev: object): string | undefined;
17
+ export declare function hasLedgerPositionKey(ev: object): boolean;
18
+ export declare const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
16
19
  export interface GateLedger {
17
20
  noteSeq(ev: AgentEvent): void;
18
21
  lastSeq(): string | undefined;
19
22
  forgetRunScopedState(): void;
23
+ notePreambleApproval(approvalId: string): void;
24
+ preambleApprovals(): ReadonlySet<string>;
25
+ beginStreamSegment(): void;
20
26
  markStarted(callId: string): boolean;
21
27
  noteGatedStart(callId: string, ev: AgentEvent): void;
22
28
  gatedStartArgs(): ReadonlyMap<string, unknown>;