@sema-agent/client-core 0.106.0 → 0.107.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.
- package/CHANGELOG.md +81 -0
- package/README.md +20 -16
- package/dist/adapter/activeRunSelfHeal.d.ts +8 -0
- package/dist/adapter/activeRunSelfHeal.js +50 -22
- package/dist/checkpointRowVerdict.d.ts +6 -0
- package/dist/checkpointRowVerdict.js +9 -0
- package/dist/decideReceipt.js +1 -1
- package/dist/displayUntrusted.d.ts +5 -3
- package/dist/displayUntrusted.js +50 -41
- package/dist/engineNoticeCodes.d.ts +12 -0
- package/dist/engineNoticeCodes.js +117 -6
- package/dist/errorSupplementRoles.js +2 -1
- package/dist/executionLaneCapability.js +3 -2
- package/dist/executionLaneWords.d.ts +1 -0
- package/dist/executionLaneWords.js +3 -0
- package/dist/generated/engineNoticeTables.js +9 -3
- package/dist/hitl/approvalResolution.d.ts +5 -1
- package/dist/hitl/approvalResolution.js +18 -1
- package/dist/hitl/approvalsFeed.js +2 -1
- package/dist/hitl/askParkRowRouting.js +12 -0
- package/dist/hitl/decisionSend.d.ts +2 -0
- package/dist/hitl/decisionSend.js +20 -6
- package/dist/hitl/hitlBridge.d.ts +5 -1
- package/dist/hitl/hitlBridge.js +27 -2
- package/dist/hitl/livePendingAsk.js +4 -2
- package/dist/hitl/parkOwnership.js +4 -5
- package/dist/hitl/parkResolver.js +9 -5
- package/dist/hitl/parkRowBirthWait.d.ts +7 -0
- package/dist/hitl/parkRowBirthWait.js +1 -1
- package/dist/hitl/parkRowDelegation.d.ts +6 -0
- package/dist/hitl/parkRowDelegation.js +33 -0
- package/dist/hitl/persistedRulesWire.d.ts +15 -3
- package/dist/hitl/persistedRulesWire.js +102 -20
- package/dist/hitl/planReviewWire.js +4 -2
- package/dist/hitl/sessionAllowRoute.d.ts +5 -0
- package/dist/hitl/sessionAllowRoute.js +42 -0
- package/dist/hitl/standingRows.js +10 -10
- package/dist/hitl/toolApprovalWire.js +22 -8
- package/dist/i18n/messages.en.d.ts +6 -0
- package/dist/i18n/messages.en.js +8 -2
- package/dist/i18n/messages.zh.js +9 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/injectionLaneNotice.js +2 -1
- package/dist/noticePresentation.js +2 -0
- package/dist/sdkWireTransit.d.ts +1 -1
- package/dist/seatContract.d.ts +1 -0
- package/dist/seatContract.js +3 -1
- package/dist/wireRefusalCopy.d.ts +6 -1
- package/dist/wireRefusalCopy.js +7 -1
- package/docs/INTEGRATION-CLIENTS.md +382 -13
- package/package.json +5 -5
|
@@ -3,20 +3,21 @@ import { displayUntrusted } from './displayUntrusted.js';
|
|
|
3
3
|
import { CORE_ENGINE_NOTICE_CODES, CORE_MCP_INJECTION_DROP_REASONS, CORE_NOTICE_AUDIENCE, CORE_USER_INPUT_UNDRAINED_FAMILIES, } from './generated/engineNoticeTables.js';
|
|
4
4
|
import { CORE_APPROVAL_REMEDY_KINDS, CORE_ASK_UNRESOLVABLE_CAUSES, CORE_DURABLE_MANDATE_SOURCES, CORE_PERMISSION_MODE_FOLD_REASONS, } from './generated/engineFactTables.js';
|
|
5
5
|
import { msg, renderMessage } from './i18n/translate.js';
|
|
6
|
-
import { readOwnDataValue } from './ownKey.js';
|
|
6
|
+
import { ownDataField, readOwnDataValue } from './ownKey.js';
|
|
7
7
|
export const ENGINE_NOTICE_CODES = CORE_ENGINE_NOTICE_CODES;
|
|
8
8
|
const CATALOG = new Set(ENGINE_NOTICE_CODES);
|
|
9
9
|
function audienceRow(table, code) {
|
|
10
10
|
return Object.hasOwn(table, code) ? table[code] : undefined;
|
|
11
11
|
}
|
|
12
|
+
const LEGACY_NOTICE_AUDIENCE = Object.freeze({ 'context.image_removed': 'user', 'session.records_skipped': 'user' });
|
|
12
13
|
export function engineNoticeInCatalog(code) {
|
|
13
|
-
return typeof code === 'string' && CATALOG.has(code);
|
|
14
|
+
return typeof code === 'string' && (CATALOG.has(code) || Object.hasOwn(LEGACY_NOTICE_AUDIENCE, code));
|
|
14
15
|
}
|
|
15
16
|
export const ENGINE_NOTICE_AUDIENCE = CORE_NOTICE_AUDIENCE;
|
|
16
17
|
export function noticeAudienceOf(code) {
|
|
17
18
|
if (typeof code !== 'string')
|
|
18
19
|
return 'operator';
|
|
19
|
-
return audienceRow(ENGINE_NOTICE_AUDIENCE, code) ?? 'operator';
|
|
20
|
+
return audienceRow(ENGINE_NOTICE_AUDIENCE, code) ?? audienceRow(LEGACY_NOTICE_AUDIENCE, code) ?? 'operator';
|
|
20
21
|
}
|
|
21
22
|
export const MCP_INJECTION_DROP_REASONS = CORE_MCP_INJECTION_DROP_REASONS;
|
|
22
23
|
const DROP_REASONS = new Set(MCP_INJECTION_DROP_REASONS);
|
|
@@ -536,6 +537,15 @@ function optionalOwnSeat(o, key) {
|
|
|
536
537
|
return undefined;
|
|
537
538
|
}
|
|
538
539
|
}
|
|
540
|
+
const UNREADABLE = Symbol('unreadable');
|
|
541
|
+
function ownSeatOrUnreadable(o, key) {
|
|
542
|
+
try {
|
|
543
|
+
return ownSeat(o, key);
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
return UNREADABLE;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
539
549
|
function readToolTurnRepairedOf(notice) {
|
|
540
550
|
if (typeof notice !== 'object' || notice === null || Array.isArray(notice))
|
|
541
551
|
return undefined;
|
|
@@ -716,7 +726,10 @@ function readUserInputUndrainedOf(notice) {
|
|
|
716
726
|
const taskId = nonEmpty(optionalOwnSeat(o, 'taskId'));
|
|
717
727
|
const sessionId = nonEmpty(optionalOwnSeat(o, 'sessionId'));
|
|
718
728
|
const runId = nonEmpty(optionalOwnSeat(o, 'runId'));
|
|
719
|
-
const
|
|
729
|
+
const childSeat = ownSeatOrUnreadable(o, 'childSessionId');
|
|
730
|
+
const childSessionId = nonEmpty(childSeat);
|
|
731
|
+
const childSeatProven = childSeat === undefined || childSessionId !== undefined;
|
|
732
|
+
const inputIds = childSeatProven ? exactInputIdList(optionalOwnSeat(o, 'inputIds'), count) : undefined;
|
|
720
733
|
return {
|
|
721
734
|
kind,
|
|
722
735
|
count,
|
|
@@ -725,8 +738,96 @@ function readUserInputUndrainedOf(notice) {
|
|
|
725
738
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
726
739
|
...(runId !== undefined ? { runId } : {}),
|
|
727
740
|
...(childSessionId !== undefined ? { childSessionId } : {}),
|
|
741
|
+
...(inputIds !== undefined ? { inputIds } : {}),
|
|
728
742
|
};
|
|
729
743
|
}
|
|
744
|
+
function exactInputIdList(v, count) {
|
|
745
|
+
try {
|
|
746
|
+
if (!Array.isArray(v) || ownDataField(v, 'length') !== count)
|
|
747
|
+
return undefined;
|
|
748
|
+
const out = [];
|
|
749
|
+
const seen = new Set();
|
|
750
|
+
for (let i = 0; i < count; i++) {
|
|
751
|
+
const d = Object.getOwnPropertyDescriptor(v, i);
|
|
752
|
+
const id = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
753
|
+
if (typeof id !== 'string' || id.length === 0 || seen.has(id))
|
|
754
|
+
return undefined;
|
|
755
|
+
seen.add(id);
|
|
756
|
+
out.push(id);
|
|
757
|
+
}
|
|
758
|
+
return Object.freeze(out);
|
|
759
|
+
}
|
|
760
|
+
catch {
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function strictSeat(o, key) {
|
|
765
|
+
try {
|
|
766
|
+
return ownDataField(o, key);
|
|
767
|
+
}
|
|
768
|
+
catch {
|
|
769
|
+
return UNREADABLE;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function ledgerIdSet(ledger, key) {
|
|
773
|
+
const list = strictSeat(ledger, key);
|
|
774
|
+
if (list === undefined)
|
|
775
|
+
return new Set();
|
|
776
|
+
try {
|
|
777
|
+
if (!Array.isArray(list))
|
|
778
|
+
return UNREADABLE;
|
|
779
|
+
const out = new Set();
|
|
780
|
+
for (const k of Object.getOwnPropertyNames(list)) {
|
|
781
|
+
if (k === 'length' || String(Number(k) >>> 0) !== k || Number(k) === 4294967295)
|
|
782
|
+
continue;
|
|
783
|
+
const id = strictSeat(list, k);
|
|
784
|
+
if (id === UNREADABLE)
|
|
785
|
+
return UNREADABLE;
|
|
786
|
+
if (typeof id === 'string' && id.length > 0)
|
|
787
|
+
out.add(id);
|
|
788
|
+
}
|
|
789
|
+
return out;
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
return UNREADABLE;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
export function undrainedInputsVerdict(facts, ledger) {
|
|
796
|
+
if (facts === null || typeof facts !== 'object')
|
|
797
|
+
return undefined;
|
|
798
|
+
try {
|
|
799
|
+
if (Array.isArray(facts))
|
|
800
|
+
return undefined;
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
return undefined;
|
|
804
|
+
}
|
|
805
|
+
const count = positiveInt(strictSeat(facts, 'count'));
|
|
806
|
+
if (count === undefined)
|
|
807
|
+
return undefined;
|
|
808
|
+
const nothing = Object.freeze({ redeliver: Object.freeze([]), askUser: Object.freeze([]), unattributed: count });
|
|
809
|
+
if (strictSeat(facts, 'cause') !== 'leg_end' || strictSeat(facts, 'childSessionId') !== undefined)
|
|
810
|
+
return nothing;
|
|
811
|
+
const ids = exactInputIdList(strictSeat(facts, 'inputIds'), count);
|
|
812
|
+
if (ids === undefined)
|
|
813
|
+
return nothing;
|
|
814
|
+
if (ledger !== null && ledger !== undefined && typeof ledger !== 'object')
|
|
815
|
+
return nothing;
|
|
816
|
+
const l = ledger ?? {};
|
|
817
|
+
const handed = ledgerIdSet(l, 'handed');
|
|
818
|
+
const redelivered = ledgerIdSet(l, 'redelivered');
|
|
819
|
+
if (handed === UNREADABLE || redelivered === UNREADABLE)
|
|
820
|
+
return nothing;
|
|
821
|
+
const redeliver = [];
|
|
822
|
+
const askUser = [];
|
|
823
|
+
for (const id of ids) {
|
|
824
|
+
if (redelivered.has(id))
|
|
825
|
+
askUser.push(id);
|
|
826
|
+
else if (handed.has(id))
|
|
827
|
+
redeliver.push(id);
|
|
828
|
+
}
|
|
829
|
+
return Object.freeze({ redeliver: Object.freeze(redeliver), askUser: Object.freeze(askUser), unattributed: count - redeliver.length - askUser.length });
|
|
830
|
+
}
|
|
730
831
|
const messagesPhrase = (n, noun) => `${n} ${noun}${n === 1 ? '' : 's'}`;
|
|
731
832
|
const verbBe = (n) => (n === 1 ? 'was' : 'were');
|
|
732
833
|
const itThem = (n) => (n === 1 ? 'it' : 'them');
|
|
@@ -904,18 +1005,27 @@ function readContextMediaRemovedOf(notice) {
|
|
|
904
1005
|
if (typeof notice !== 'object' || notice === null || Array.isArray(notice))
|
|
905
1006
|
return undefined;
|
|
906
1007
|
const n = notice;
|
|
907
|
-
|
|
1008
|
+
const code = ownSeat(n, 'code');
|
|
1009
|
+
if (code !== 'context.media_removed' && code !== 'context.image_removed')
|
|
908
1010
|
return undefined;
|
|
909
1011
|
const d = ownSeat(n, 'detail');
|
|
910
1012
|
if (typeof d !== 'object' || d === null || Array.isArray(d))
|
|
911
1013
|
return undefined;
|
|
912
1014
|
const o = d;
|
|
1015
|
+
const count = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
1016
|
+
if (code === 'context.image_removed') {
|
|
1017
|
+
const legacyImages = ownSeat(o, 'images');
|
|
1018
|
+
const legacySessionId = nonEmpty(ownSeat(o, 'sessionId'));
|
|
1019
|
+
const legacyRunId = nonEmpty(ownSeat(o, 'runId'));
|
|
1020
|
+
if (!count(legacyImages) || legacyImages < 1 || legacySessionId === undefined || legacyRunId === undefined)
|
|
1021
|
+
return undefined;
|
|
1022
|
+
return { reason: 'rejected_by_provider', images: legacyImages, documents: 0, sessionId: legacySessionId, runId: legacyRunId };
|
|
1023
|
+
}
|
|
913
1024
|
const reason = nonEmpty(ownSeat(o, 'reason'));
|
|
914
1025
|
const images = ownSeat(o, 'images');
|
|
915
1026
|
const documents = ownSeat(o, 'documents');
|
|
916
1027
|
const sessionId = nonEmpty(ownSeat(o, 'sessionId'));
|
|
917
1028
|
const runId = nonEmpty(ownSeat(o, 'runId'));
|
|
918
|
-
const count = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
919
1029
|
if (reason === undefined || !count(images) || !count(documents) || images + documents < 1 || sessionId === undefined || runId === undefined)
|
|
920
1030
|
return undefined;
|
|
921
1031
|
return { reason, images, documents, sessionId, runId };
|
|
@@ -1009,6 +1119,7 @@ export const ENGINE_NOTICE_FACT_READERS = Object.freeze({
|
|
|
1009
1119
|
'route.stream_end_ignored': readStreamEndIgnored,
|
|
1010
1120
|
'mcp.authorization_required': readMcpAuthorizationRequired,
|
|
1011
1121
|
'context.media_removed': readContextMediaRemoved,
|
|
1122
|
+
'context.image_removed': readContextMediaRemoved,
|
|
1012
1123
|
'session.lineage_rerooted': readSessionLineageRerooted,
|
|
1013
1124
|
});
|
|
1014
1125
|
const _factReaderTablePin = true;
|
|
@@ -5,7 +5,7 @@ import { resumeReopenContent } from './hitl/suspendedReopen.js';
|
|
|
5
5
|
import { idempotencyPayloadMismatchContent } from './engineErrorCodes.js';
|
|
6
6
|
import { inheritEnvRefusalContent } from './inheritEnvWire.js';
|
|
7
7
|
import { projectSeedRefusalContent } from './projectSeed.js';
|
|
8
|
-
import { usageWindowExhaustedContent, fleetLeaseUnavailableContent, settingsPermissionBucketRefusalContent, fieldLimitRefusalContent, autonomyLaneRefusalContent, unsupportedRuleRefusalContent, admissionWallRefusalContent, hostPostureRefusalContent, toolDeclarationRefusalContent, } from './wireRefusalCopy.js';
|
|
8
|
+
import { usageWindowExhaustedContent, fleetLeaseUnavailableContent, settingsPermissionBucketRefusalContent, fieldLimitRefusalContent, autonomyLaneRefusalContent, sessionArchiveStampedContent, unsupportedRuleRefusalContent, admissionWallRefusalContent, hostPostureRefusalContent, toolDeclarationRefusalContent, } from './wireRefusalCopy.js';
|
|
9
9
|
const ROLE_BY_READER = new Map([
|
|
10
10
|
[corruptSessionContent, 'replacement'],
|
|
11
11
|
[corruptStoredRowContent, 'replacement'],
|
|
@@ -19,6 +19,7 @@ const ROLE_BY_READER = new Map([
|
|
|
19
19
|
[settingsPermissionBucketRefusalContent, 'replacement'],
|
|
20
20
|
[fieldLimitRefusalContent, 'replacement'],
|
|
21
21
|
[autonomyLaneRefusalContent, 'replacement'],
|
|
22
|
+
[sessionArchiveStampedContent, 'replacement'],
|
|
22
23
|
[idempotencyPayloadMismatchContent, 'replacement'],
|
|
23
24
|
[unsupportedRuleRefusalContent, 'replacement'],
|
|
24
25
|
[projectSeedRefusalContent, 'replacement'],
|
|
@@ -2,6 +2,7 @@ import { capForDisplay } from './fleetTaskDesc.js';
|
|
|
2
2
|
import { engineWireTargetBaseUrl } from './engineWireTarget.js';
|
|
3
3
|
import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
|
|
4
4
|
import { ownDataField } from './ownKey.js';
|
|
5
|
+
import { executionLaneWordIsNone } from './executionLaneWords.js';
|
|
5
6
|
export function projectExecutionLaneCapability(caps) {
|
|
6
7
|
try {
|
|
7
8
|
if (caps === null || typeof caps !== 'object')
|
|
@@ -100,8 +101,8 @@ export function executionLaneDoctorDetail(reading) {
|
|
|
100
101
|
if (reading.view.toolsOnThisHost) {
|
|
101
102
|
return `execution lane ${lane} — tools run on the engine host (same filesystem), so skills that have a directory of their own can carry a baseDir`;
|
|
102
103
|
}
|
|
103
|
-
if (reading.view.provider
|
|
104
|
-
return
|
|
104
|
+
if (executionLaneWordIsNone(reading.view.provider)) {
|
|
105
|
+
return `no execution lane on this deployment (${lane}) — the engine has no machine of its own for running tools here, so its built-in file and shell tools are not mounted and skill baseDir is never sent; whether a given run had tools is answered by its tool roster`;
|
|
105
106
|
}
|
|
106
107
|
return `execution lane ${lane} — tools run on a different machine than the engine, so skill baseDir is never sent on this deployment`;
|
|
107
108
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function executionLaneWordIsNone(provider: string): boolean;
|
|
@@ -51,6 +51,7 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
51
51
|
"checkpoint.execution_outcome_unrecorded",
|
|
52
52
|
"delegation.transcript_integrity",
|
|
53
53
|
"delegation.ask_unresolvable",
|
|
54
|
+
"delegation.resume_lane_missing",
|
|
54
55
|
"mcp.injection_dropped",
|
|
55
56
|
"mcp.revocation_probe_failed",
|
|
56
57
|
"mcp.server_redialed",
|
|
@@ -60,6 +61,8 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
60
61
|
"workflow.agent_option_ignored",
|
|
61
62
|
"memory.session_polluted",
|
|
62
63
|
"memory.harvest_quarantined",
|
|
64
|
+
"memory.harvest_refused",
|
|
65
|
+
"memory.harvest_conflict",
|
|
63
66
|
"memory.delegation_static_mark_waived",
|
|
64
67
|
"memory.content_class_declared",
|
|
65
68
|
"memory.hold_opened",
|
|
@@ -110,7 +113,6 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
110
113
|
"fs.staging_file_swept",
|
|
111
114
|
"session.lineage_rerooted",
|
|
112
115
|
"instructions.source_changed",
|
|
113
|
-
"session.records_skipped",
|
|
114
116
|
"memory.layer_locked_legacy",
|
|
115
117
|
"server.model_unreachable",
|
|
116
118
|
"permission.entry_mode_clamped",
|
|
@@ -168,6 +170,7 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
168
170
|
"checkpoint.execution_outcome_unrecorded": "operator",
|
|
169
171
|
"delegation.transcript_integrity": "operator",
|
|
170
172
|
"delegation.ask_unresolvable": "user",
|
|
173
|
+
"delegation.resume_lane_missing": "operator",
|
|
171
174
|
"mcp.injection_dropped": "user",
|
|
172
175
|
"mcp.revocation_probe_failed": "operator",
|
|
173
176
|
"mcp.server_redialed": "operator",
|
|
@@ -177,6 +180,8 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
177
180
|
"workflow.agent_option_ignored": "operator",
|
|
178
181
|
"memory.session_polluted": "user",
|
|
179
182
|
"memory.harvest_quarantined": "user",
|
|
183
|
+
"memory.harvest_refused": "user",
|
|
184
|
+
"memory.harvest_conflict": "user",
|
|
180
185
|
"memory.delegation_static_mark_waived": "user",
|
|
181
186
|
"memory.content_class_declared": "operator",
|
|
182
187
|
"memory.hold_opened": "user",
|
|
@@ -227,7 +232,6 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
227
232
|
"fs.staging_file_swept": "operator",
|
|
228
233
|
"session.lineage_rerooted": "user",
|
|
229
234
|
"instructions.source_changed": "user",
|
|
230
|
-
"session.records_skipped": "user",
|
|
231
235
|
"memory.layer_locked_legacy": "operator",
|
|
232
236
|
"server.model_unreachable": "user",
|
|
233
237
|
"permission.entry_mode_clamped": "user",
|
|
@@ -291,6 +295,7 @@ export const CORE_NOTICE_MINTER = Object.freeze({
|
|
|
291
295
|
"checkpoint.execution_outcome_unrecorded": "engine",
|
|
292
296
|
"delegation.transcript_integrity": "engine",
|
|
293
297
|
"delegation.ask_unresolvable": "engine",
|
|
298
|
+
"delegation.resume_lane_missing": "engine",
|
|
294
299
|
"mcp.injection_dropped": "engine",
|
|
295
300
|
"mcp.revocation_probe_failed": "engine",
|
|
296
301
|
"mcp.server_redialed": "engine",
|
|
@@ -300,6 +305,8 @@ export const CORE_NOTICE_MINTER = Object.freeze({
|
|
|
300
305
|
"workflow.agent_option_ignored": "engine",
|
|
301
306
|
"memory.session_polluted": "engine",
|
|
302
307
|
"memory.harvest_quarantined": "engine",
|
|
308
|
+
"memory.harvest_refused": "engine",
|
|
309
|
+
"memory.harvest_conflict": "engine",
|
|
303
310
|
"memory.delegation_static_mark_waived": "engine",
|
|
304
311
|
"memory.content_class_declared": "engine",
|
|
305
312
|
"memory.hold_opened": "engine",
|
|
@@ -350,7 +357,6 @@ export const CORE_NOTICE_MINTER = Object.freeze({
|
|
|
350
357
|
"fs.staging_file_swept": "engine",
|
|
351
358
|
"session.lineage_rerooted": "engine",
|
|
352
359
|
"instructions.source_changed": "server",
|
|
353
|
-
"session.records_skipped": "server",
|
|
354
360
|
"memory.layer_locked_legacy": "server",
|
|
355
361
|
"server.model_unreachable": "server",
|
|
356
362
|
"permission.entry_mode_clamped": "server",
|
|
@@ -4,9 +4,10 @@ import type { AskAnsweredOutput } from './gateLedger.js';
|
|
|
4
4
|
import type { ApprovalDenySettledBy, ToolApprovalRespondAckView, ToolApprovalRespondDecision, ToolApprovalRespondParkingView } from './toolApprovalWire.js';
|
|
5
5
|
import { type DecisionRefusalReading, type RefusalRouteContext } from './decisionRefusal.js';
|
|
6
6
|
import { type DeliveredReading, type LadderSafetyStop } from './attributionLadder.js';
|
|
7
|
+
import { type CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
|
|
7
8
|
export declare const APPROVAL_RESOLUTION_LANES: readonly ["respond", "decide", "ask"];
|
|
8
9
|
export type ApprovalResolutionLane = (typeof APPROVAL_RESOLUTION_LANES)[number];
|
|
9
|
-
export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "wrong_gate", "fetch_failed", "no_input", "card_unavailable", "safety_stop"];
|
|
10
|
+
export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "wrong_gate", "fetch_failed", "no_input", "card_unavailable", "safety_stop", "row_unresumable"];
|
|
10
11
|
export type ApprovalNotSentCause = (typeof APPROVAL_NOT_SENT_CAUSES)[number];
|
|
11
12
|
export declare const APPROVAL_UNSETTLED_CAUSES: readonly ["engine_refused", "engine_error", "no_answer", "transport_exhausted", "parking_in_progress", "interrupted", "orchestration", "unreadable"];
|
|
12
13
|
export type ApprovalUnsettledCause = (typeof APPROVAL_UNSETTLED_CAUSES)[number];
|
|
@@ -32,6 +33,9 @@ export interface ApprovalResolutionNotSent extends ApprovalResolutionBase {
|
|
|
32
33
|
readonly kind: 'not_sent';
|
|
33
34
|
readonly cause: ApprovalNotSentCause;
|
|
34
35
|
readonly safetyCode?: Exclude<HitlSafetyCode, 'binding_mismatch' | 'no_pending' | 'wrong_gate'>;
|
|
36
|
+
readonly rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
37
|
+
readonly kind: 'ok';
|
|
38
|
+
}>;
|
|
35
39
|
readonly detail?: string;
|
|
36
40
|
}
|
|
37
41
|
export interface ApprovalResolutionUnsettled extends ApprovalResolutionBase {
|
|
@@ -4,6 +4,8 @@ import { isSlotCarrier, thrownIsInstanceOf } from '../wireFailureShape.js';
|
|
|
4
4
|
import { carrierReadOf, isLocalSafetyStop, localCodeReadOf, topCauseReadOf } from './attributionLadder.js';
|
|
5
5
|
import { directFailureReadingOf, transportReadingOf } from './approvalDelivery.js';
|
|
6
6
|
import { ownReadingSlot } from '../engineWireFor.js';
|
|
7
|
+
import { CHECKPOINT_ROW_BAR_SENTENCE } from '../checkpointRowVerdict.js';
|
|
8
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
7
9
|
export const APPROVAL_RESOLUTION_LANES = Object.freeze(['respond', 'decide', 'ask']);
|
|
8
10
|
export const APPROVAL_NOT_SENT_CAUSES = Object.freeze([
|
|
9
11
|
'retracted',
|
|
@@ -14,6 +16,7 @@ export const APPROVAL_NOT_SENT_CAUSES = Object.freeze([
|
|
|
14
16
|
'no_input',
|
|
15
17
|
'card_unavailable',
|
|
16
18
|
'safety_stop',
|
|
19
|
+
'row_unresumable',
|
|
17
20
|
]);
|
|
18
21
|
export const APPROVAL_UNSETTLED_CAUSES = Object.freeze([
|
|
19
22
|
'engine_refused',
|
|
@@ -49,6 +52,7 @@ const NOT_SENT_SENTENCES = Object.freeze({
|
|
|
49
52
|
no_input: 'the approval has no tool input or question to show, so no card was shown; nothing was sent',
|
|
50
53
|
card_unavailable: 'no approval card could be shown on this host; nothing was sent',
|
|
51
54
|
safety_stop: 'the decision was stopped by a local safety check before it was sent; nothing was sent',
|
|
55
|
+
row_unresumable: CHECKPOINT_ROW_BAR_SENTENCE,
|
|
52
56
|
});
|
|
53
57
|
function nonEmpty(v) {
|
|
54
58
|
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
@@ -77,8 +81,21 @@ export function approvalResolutionDetail(r) {
|
|
|
77
81
|
if (cause === undefined || !Object.hasOwn(NOT_SENT_SENTENCES, cause))
|
|
78
82
|
return undefined;
|
|
79
83
|
const sentence = NOT_SENT_SENTENCES[cause];
|
|
84
|
+
if (cause === 'row_unresumable') {
|
|
85
|
+
let verdict;
|
|
86
|
+
try {
|
|
87
|
+
const d = Object.getOwnPropertyDescriptor(r, 'rowVerdict');
|
|
88
|
+
verdict = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
verdict = undefined;
|
|
92
|
+
}
|
|
93
|
+
return ownStr(verdict, 'kind') !== 'unreadable' ? (ownStr(verdict, 'sentence') ?? sentence) : sentence;
|
|
94
|
+
}
|
|
80
95
|
const code = cause === 'safety_stop' ? ownStr(r, 'safetyCode') : undefined;
|
|
81
|
-
|
|
96
|
+
if (code === undefined)
|
|
97
|
+
return sentence;
|
|
98
|
+
return code === 'binding_missing' ? `${sentence} (${code}) · ${renderMessage(msg('approval.decide.unboundWayOut'))}` : `${sentence} (${code})`;
|
|
82
99
|
}
|
|
83
100
|
if (kind !== 'unsettled')
|
|
84
101
|
return undefined;
|
|
@@ -3,6 +3,7 @@ import { unrefTimer } from '../unrefTimer.js';
|
|
|
3
3
|
import { readLivePendingRows } from './livePendingAsk.js';
|
|
4
4
|
import { createStreamDrainingLegs } from '../streamDrainingLegs.js';
|
|
5
5
|
import { approvalQueueRowFamilyOf, peerInboundQueueRowOf } from './approvalQueueRow.js';
|
|
6
|
+
import { parkRowDelegationLinksOf } from './parkRowDelegation.js';
|
|
6
7
|
import { checkpointRowVerdictOf } from '../checkpointRowVerdict.js';
|
|
7
8
|
const _realClientFitsFeedSlice = true;
|
|
8
9
|
void _realClientFitsFeedSlice;
|
|
@@ -49,7 +50,7 @@ function readDurablePendingRows(raw) {
|
|
|
49
50
|
}
|
|
50
51
|
function durableDigestOf(rows) {
|
|
51
52
|
return rows
|
|
52
|
-
.map(r => [r.sessionId ?? '', r.taskId ?? '', r.toolCallId ?? '', r.boundCallId ?? '', r.boundInputHash ?? '', r.toolName ?? '', JSON.stringify(checkpointRowVerdictOf(r) ?? null)]
|
|
53
|
+
.map(r => [r.sessionId ?? '', r.taskId ?? '', r.toolCallId ?? '', r.boundCallId ?? '', r.boundInputHash ?? '', r.toolName ?? '', JSON.stringify(checkpointRowVerdictOf(r) ?? null), JSON.stringify(parkRowDelegationLinksOf(r) ?? null)]
|
|
53
54
|
.join(''))
|
|
54
55
|
.sort()
|
|
55
56
|
.join('');
|
|
@@ -3,6 +3,8 @@ import { isAskTool, questionCardVerdict } from './frameRouter.js';
|
|
|
3
3
|
import { approvalCallKey } from './gateIdentity.js';
|
|
4
4
|
import { askQuestionsFromPending, pendingRowIsOwnedByThisSession } from './parkOwnership.js';
|
|
5
5
|
import { approvalQueueRowFamilyOf, checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
|
|
6
|
+
import { checkpointRowBarOf, checkpointRowBarSentence } from '../checkpointRowVerdict.js';
|
|
7
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
6
8
|
export const ASK_PARK_ROW_WAIT_MS = 15000;
|
|
7
9
|
export const ASK_PARK_ROW_POLL_MS = 500;
|
|
8
10
|
export function classifyAskParkRows(queueRows, taskId, deps) {
|
|
@@ -29,6 +31,14 @@ export function classifyAskParkRows(queueRows, taskId, deps) {
|
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
33
|
const foreignKind = familyUnreadable.has(pending) ? QUEUE_ROW_FAMILY_UNREADABLE_GATE : askParkForeignGateKind(pending);
|
|
34
|
+
const bar = foreignKind === null || foreignKind === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(pending) : undefined;
|
|
35
|
+
if (bar !== undefined) {
|
|
36
|
+
return {
|
|
37
|
+
kind: 'settled',
|
|
38
|
+
reason: renderMessage(msg('approval.rowUnresumable.askNotSurfaced', { taskId: String(pending.taskId), sentence: checkpointRowBarSentence(bar) })),
|
|
39
|
+
rowVerdict: bar,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
32
42
|
if (foreignKind !== null) {
|
|
33
43
|
return {
|
|
34
44
|
kind: 'unborn',
|
|
@@ -129,6 +139,8 @@ export function pendingRowIsOwnedAndDecidable(row, deps) {
|
|
|
129
139
|
return true;
|
|
130
140
|
if (leg !== 'approval')
|
|
131
141
|
return false;
|
|
142
|
+
if (checkpointRowBarOf(row) !== undefined)
|
|
143
|
+
return false;
|
|
132
144
|
return askParkRowArm(row) === 'question' ? askQuestionsFromPending(row) !== null : true;
|
|
133
145
|
}
|
|
134
146
|
export function countOwnedPendingRows(listed, deps) {
|
|
@@ -20,6 +20,7 @@ export interface HitlApprovalDecisionRequest {
|
|
|
20
20
|
readonly taskId?: string;
|
|
21
21
|
readonly toolCallId?: string;
|
|
22
22
|
readonly answer: ApprovalCardDecision;
|
|
23
|
+
readonly card?: object;
|
|
23
24
|
}
|
|
24
25
|
export interface HitlAskDecisionRequest {
|
|
25
26
|
readonly family: 'ask';
|
|
@@ -28,6 +29,7 @@ export interface HitlAskDecisionRequest {
|
|
|
28
29
|
readonly approvalId?: string;
|
|
29
30
|
readonly toolCallId?: string;
|
|
30
31
|
readonly answer: ApprovalCardDecision;
|
|
32
|
+
readonly card?: object;
|
|
31
33
|
}
|
|
32
34
|
export interface HitlElicitationDecisionRequest {
|
|
33
35
|
readonly family: 'elicitation';
|
|
@@ -10,7 +10,7 @@ import { readDecisionNoteAudit } from './approvalDecisionNoteAudit.js';
|
|
|
10
10
|
import { carrierReadOf, deliverWithAttributionLadder, isLocalSafetyStop } from './attributionLadder.js';
|
|
11
11
|
import { budgetSpent, deliverApprovalDecision, deliveryTiming, isAborted, newSendTally, PARKING_WAIT_CAP_MS, setDeliveryTimingForTests, sleepFor, settledTransportReadingOf, withTransportRetry, deliveryReadingOf, } from './approvalDelivery.js';
|
|
12
12
|
import { ASK_DECIDED_CODE, classifyDeliveredDecisionRefusal, decisionRefusalClosesOut, notPendingAfterRetry } from './decisionRefusal.js';
|
|
13
|
-
import {
|
|
13
|
+
import { sessionAllowEligible } from './sessionAllowRoute.js';
|
|
14
14
|
import { addDecisionOpKeys, closeDecisionOp, decisionLedgerKeysOf, decisionOpsWithKey, openDecisionOp } from './decisionLedger.js';
|
|
15
15
|
import { hostHoldIsActive, takeHostHoldLedgerOp, tryHoldApprovalNow } from './approvalHolds.js';
|
|
16
16
|
import { answerPayloadFor, decisionAttributionKeyOf, isToolApprovalRespondReplayed, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, } from './toolApprovalWire.js';
|
|
@@ -195,7 +195,7 @@ async function sendAskFamily(respondForFallback, decideAsk, req, ctx) {
|
|
|
195
195
|
if (payload === undefined)
|
|
196
196
|
return { family: 'ask', attempts: ctx.tally.attempts, kind: 'not_sent', cause: 'not_an_answer' };
|
|
197
197
|
let fallbackRespond = respondForFallback;
|
|
198
|
-
if (req.answer.kind === 'allow' && req.answer.allowSession === true && respondForFallback !== undefined && req.approvalId !== undefined
|
|
198
|
+
if (req.answer.kind === 'allow' && req.answer.allowSession === true && respondForFallback !== undefined && req.approvalId !== undefined) {
|
|
199
199
|
const viaAnswerGate = await sendApprovalFamily(respondForFallback, req.approvalId, req.answer, ctx);
|
|
200
200
|
if (!answerGateCannotTake(viaAnswerGate))
|
|
201
201
|
return { ...viaAnswerGate, family: 'ask', allowSessionCarried: true };
|
|
@@ -386,6 +386,14 @@ function snapshotAnswer(raw) {
|
|
|
386
386
|
}
|
|
387
387
|
return typeof kind === 'string' ? Object.freeze({ kind }) : undefined;
|
|
388
388
|
}
|
|
389
|
+
function sessionGatedAnswer(answer, card) {
|
|
390
|
+
if (answer.kind !== 'allow' || answer.allowSession !== true)
|
|
391
|
+
return { answer };
|
|
392
|
+
const why = card === undefined ? 'card_absent' : typeof card === 'object' && card !== null ? sessionAllowEligible(card) : 'card_unreadable';
|
|
393
|
+
if (why === 'given')
|
|
394
|
+
return { answer };
|
|
395
|
+
return { answer: Object.freeze({ ...answer, allowSession: false }), dropped: why };
|
|
396
|
+
}
|
|
389
397
|
function readRequest(req) {
|
|
390
398
|
if (typeof req !== 'object' || req === null)
|
|
391
399
|
return undefined;
|
|
@@ -399,17 +407,19 @@ function readRequest(req) {
|
|
|
399
407
|
const rawAnswer = guardedRead(() => readOwnDataValue(req, 'answer'));
|
|
400
408
|
if (typeof rawAnswer !== 'object' || rawAnswer === null)
|
|
401
409
|
return undefined;
|
|
402
|
-
const
|
|
403
|
-
if (
|
|
410
|
+
const snapshot = snapshotAnswer(rawAnswer);
|
|
411
|
+
if (snapshot === undefined)
|
|
404
412
|
return undefined;
|
|
413
|
+
const { answer, dropped } = sessionGatedAnswer(snapshot, guardedRead(() => readOwnDataValue(req, 'card')));
|
|
414
|
+
const droppedFact = dropped !== undefined ? { sessionAllowDropped: dropped } : {};
|
|
405
415
|
if (family === 'approval') {
|
|
406
416
|
const approvalId = s('approvalId');
|
|
407
|
-
return approvalId !== undefined ? { family, approvalId, taskId: s('taskId'), toolCallId: s('toolCallId'), answer } : undefined;
|
|
417
|
+
return approvalId !== undefined ? { family, approvalId, taskId: s('taskId'), toolCallId: s('toolCallId'), answer, ...droppedFact } : undefined;
|
|
408
418
|
}
|
|
409
419
|
if (family === 'ask') {
|
|
410
420
|
const taskId = s('taskId');
|
|
411
421
|
const askId = s('askId');
|
|
412
|
-
return taskId !== undefined && askId !== undefined ? { family, taskId, askId, approvalId: s('approvalId'), toolCallId: s('toolCallId'), answer } : undefined;
|
|
422
|
+
return taskId !== undefined && askId !== undefined ? { family, taskId, askId, approvalId: s('approvalId'), toolCallId: s('toolCallId'), answer, ...droppedFact } : undefined;
|
|
413
423
|
}
|
|
414
424
|
return undefined;
|
|
415
425
|
}
|
|
@@ -519,6 +529,10 @@ export async function sendHitlDecision(request, deps) {
|
|
|
519
529
|
out = outcomeOfThrown(e, 'elicitation', undefined, signal, ctx.tally, false);
|
|
520
530
|
}
|
|
521
531
|
}
|
|
532
|
+
if (req.family !== 'elicitation' && req.sessionAllowDropped !== undefined) {
|
|
533
|
+
hostLog('debug', `decisionSend: session-allow-not-eligible ${req.family}=${req.family === 'ask' ? req.askId : req.approvalId} why=${req.sessionAllowDropped} via=allow-once`);
|
|
534
|
+
out = { ...out, rememberDropped: true };
|
|
535
|
+
}
|
|
522
536
|
const ambiguous = out.kind === 'unconfirmed' || (out.kind === 'not_applied' && (out.cause === 'parking' || out.cause === 'refused' || out.cause === 'idempotency_crossed'));
|
|
523
537
|
if (ambiguous && out.cause !== 'aborted') {
|
|
524
538
|
const gate = req.family === 'elicitation'
|
|
@@ -3,6 +3,7 @@ import type { PermissionResult } from '@sema-agent/agent-types';
|
|
|
3
3
|
import { type CapturedLocalCode, type CauseSlotRead } from '../engineWireFor.js';
|
|
4
4
|
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
5
5
|
import { type SuspendedReopenReading } from './suspendedReopen.js';
|
|
6
|
+
import type { CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
|
|
6
7
|
export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
|
|
7
8
|
export declare const MAX_DENY_REASON_CHARS = 4096;
|
|
8
9
|
export declare function denyReasonForWire(reason: string | undefined, tag: string): string | undefined;
|
|
@@ -132,7 +133,10 @@ export type FindPendingOutcome = {
|
|
|
132
133
|
} | {
|
|
133
134
|
ok: false;
|
|
134
135
|
reason: string;
|
|
135
|
-
code?: 'no_pending' | 'wrong_gate';
|
|
136
|
+
code?: 'no_pending' | 'wrong_gate' | 'row_unresumable';
|
|
137
|
+
rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
138
|
+
readonly kind: 'ok';
|
|
139
|
+
}>;
|
|
136
140
|
};
|
|
137
141
|
export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
|
|
138
142
|
signal?: AbortSignal;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -11,6 +11,8 @@ import { suspendedReopenOf } from './suspendedReopen.js';
|
|
|
11
11
|
import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
|
|
12
12
|
import { askParkBlockGeneration, noteAskParkDecideSettled } from './askParkAutoReopenBlock.js';
|
|
13
13
|
import { checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
|
|
14
|
+
import { checkpointRowBarOf, checkpointRowBarSentence } from '../checkpointRowVerdict.js';
|
|
15
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
14
16
|
export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
|
|
15
17
|
export const MAX_DENY_REASON_CHARS = 4096;
|
|
16
18
|
export function denyReasonForWire(reason, tag) {
|
|
@@ -283,6 +285,9 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
283
285
|
? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
|
|
284
286
|
: undefined;
|
|
285
287
|
if (identityForeign !== undefined) {
|
|
288
|
+
const identityBar = identityForeign.foreign === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(identityForeign.r) : undefined;
|
|
289
|
+
if (identityBar !== undefined)
|
|
290
|
+
return { ok: false, reason: checkpointRowBarSentence(identityBar), code: 'row_unresumable', rowVerdict: identityBar };
|
|
286
291
|
return {
|
|
287
292
|
ok: false,
|
|
288
293
|
reason: `the pending row this park names (call ${callId}) is parked on a '${String(identityForeign.foreign)}' gate — not an approval this leg decides; refusing to decide a different row`,
|
|
@@ -294,8 +299,19 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
294
299
|
const pending = (identityConflicts ? undefined : identityRow) ??
|
|
295
300
|
pool.find((r) => r.taskId === taskId && matches(typeof r.toolName === 'string' ? r.toolName : undefined)) ??
|
|
296
301
|
pool.find((r) => r.taskId === taskId);
|
|
297
|
-
if (!pending)
|
|
302
|
+
if (!pending) {
|
|
303
|
+
for (const x of readings) {
|
|
304
|
+
if (x.r.taskId !== taskId || x.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
|
|
305
|
+
continue;
|
|
306
|
+
const unreadableBar = checkpointRowBarOf(x.r);
|
|
307
|
+
if (unreadableBar !== undefined)
|
|
308
|
+
return { ok: false, reason: checkpointRowBarSentence(unreadableBar), code: 'row_unresumable', rowVerdict: unreadableBar };
|
|
309
|
+
}
|
|
298
310
|
return { ok: false, reason: 'no pending checkpoint for this run (resolved/expired?)', code: 'no_pending' };
|
|
311
|
+
}
|
|
312
|
+
const bar = checkpointRowBarOf(pending);
|
|
313
|
+
if (bar !== undefined)
|
|
314
|
+
return { ok: false, reason: checkpointRowBarSentence(bar), code: 'row_unresumable', rowVerdict: bar };
|
|
299
315
|
return { ok: true, pending, gatedCallId: pending.toolCallId ?? pending.boundCallId ?? undefined };
|
|
300
316
|
}
|
|
301
317
|
export class HitlBridge {
|
|
@@ -349,7 +365,7 @@ export class HitlBridge {
|
|
|
349
365
|
if (typeof boundCallId !== 'string' || boundCallId.length === 0 || typeof boundInputHash !== 'string' || boundInputHash.length === 0) {
|
|
350
366
|
throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
|
|
351
367
|
`(boundCallId=${String(boundCallId)}, boundInputHash=${String(boundInputHash)}) — ` +
|
|
352
|
-
|
|
368
|
+
`the row itself carries no binding to echo, so ${renderMessage(msg('approval.decide.unboundWayOut'))}`, 'binding_missing');
|
|
353
369
|
}
|
|
354
370
|
return { boundCallId, boundInputHash };
|
|
355
371
|
}
|
|
@@ -358,6 +374,8 @@ export class HitlBridge {
|
|
|
358
374
|
if (!pending) {
|
|
359
375
|
throw new HitlSafetyError('no pending checkpoint to decide (resolved/expired under the human) — refetch + re-present', 'no_pending');
|
|
360
376
|
}
|
|
377
|
+
if (preResolvedPending !== undefined)
|
|
378
|
+
assertPendingRowResumable(preResolvedPending);
|
|
361
379
|
const decision = outcome.decision === 'approve'
|
|
362
380
|
? {
|
|
363
381
|
decision: 'approve',
|
|
@@ -383,6 +401,8 @@ export class HitlBridge {
|
|
|
383
401
|
if (!pending) {
|
|
384
402
|
throw new HitlSafetyError('no pending AskUserQuestion checkpoint to answer — refetch + re-present', 'no_pending');
|
|
385
403
|
}
|
|
404
|
+
if (preResolvedPending !== undefined)
|
|
405
|
+
assertPendingRowResumable(preResolvedPending);
|
|
386
406
|
const decision = {
|
|
387
407
|
decision: 'approve',
|
|
388
408
|
answer: { answers: answers.map((a) => marshalAnswer(a)) },
|
|
@@ -541,6 +561,11 @@ export function makeHitlCanUseTool(bridge, prompt) {
|
|
|
541
561
|
return decision;
|
|
542
562
|
};
|
|
543
563
|
}
|
|
564
|
+
function assertPendingRowResumable(pending) {
|
|
565
|
+
const bar = checkpointRowBarOf(pending);
|
|
566
|
+
if (bar !== undefined)
|
|
567
|
+
throw new Error(checkpointRowBarSentence(bar));
|
|
568
|
+
}
|
|
544
569
|
function assertAnswersSendable(answers) {
|
|
545
570
|
if (!Array.isArray(answers) || answers.length === 0) {
|
|
546
571
|
throw new HitlSafetyError('refusing to send an EMPTY AskUserQuestion answer set — `{answers:[]}` is the wire\'s DENY/NO_HUMAN form, ' +
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
|
|
2
2
|
import { appendOwnElement } from '../ownKey.js';
|
|
3
3
|
import { readRequiresRealApproval } from './approvalCardReading.js';
|
|
4
|
+
import { pendingRowIsOfSession } from './parkRowDelegation.js';
|
|
4
5
|
import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
|
|
5
6
|
import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
|
|
6
7
|
import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
|
|
@@ -124,7 +125,8 @@ export function countApprovalsAwaitingDecision(snapshot, filter) {
|
|
|
124
125
|
if (snapshot.kind === 'unknown')
|
|
125
126
|
return { durable: null, live: null, suspendedSubagent: null };
|
|
126
127
|
const bySession = (rows) => scope.kind === 'session' ? rows.filter(r => r.sessionId === scope.sessionId) : [...rows];
|
|
127
|
-
const
|
|
128
|
+
const durableOfSession = (rows) => scope.kind === 'session' ? rows.filter(r => pendingRowIsOfSession(r, scope.sessionId)).length : rows.length;
|
|
129
|
+
const durable = (snapshot.pendingDropped ?? 0) > 0 ? null : Array.isArray(snapshot.pending) ? durableOfSession(snapshot.pending) : null;
|
|
128
130
|
if (!Array.isArray(snapshot.livePending))
|
|
129
131
|
return { durable, live: null, suspendedSubagent: null };
|
|
130
132
|
if ((snapshot.livePendingDropped ?? 0) > 0)
|
|
@@ -138,7 +140,7 @@ export function livePendingRowToApprovalFrame(row) {
|
|
|
138
140
|
approvalId: row.approvalId,
|
|
139
141
|
toolName: row.toolName,
|
|
140
142
|
...(row.fromSubagent === true ? { fromSubagent: true } : {}),
|
|
141
|
-
...(row.originTaskId !== undefined ? {
|
|
143
|
+
...(row.originTaskId !== undefined ? { sourceSessionId: row.originTaskId } : {}),
|
|
142
144
|
...(row.governanceForced === true ? { governanceForced: true } : {}),
|
|
143
145
|
...(readRequiresRealApproval(row) ? { requiresRealApproval: true } : {}),
|
|
144
146
|
...liveFaceCarried(approvalLiveFaceOf(row)),
|