@sema-agent/client-core 0.98.0 → 0.100.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 +376 -0
- package/README.md +61 -35
- package/dist/adapt/arms.js +120 -55
- package/dist/adapt/panelTasks.d.ts +3 -2
- package/dist/adapt/panelTasks.js +34 -11
- package/dist/adapt/textStream.d.ts +1 -1
- package/dist/adapt/textStream.js +51 -28
- package/dist/adapter/activeRunSelfHeal.d.ts +11 -3
- package/dist/adapter/activeRunSelfHeal.js +210 -294
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +25 -6
- package/dist/adapter/downstream/eventToSdkMessage.js +176 -12
- package/dist/adapter/downstream/terminalToSdkResult.js +10 -0
- package/dist/adapter/downstream/wiringManifestView.d.ts +3 -1
- package/dist/adapter/downstream/wiringManifestView.js +1 -0
- package/dist/adapter/runStream.js +82 -40
- package/dist/adapter/types.d.ts +2 -0
- package/dist/adapter/types.js +2 -1
- package/dist/agentSession/engineSessionBackgroundStop.js +16 -8
- package/dist/agentsWireCaps.d.ts +7 -3
- package/dist/agentsWireCaps.js +133 -39
- package/dist/cloudConfigWireCaps.d.ts +10 -1
- package/dist/cloudConfigWireCaps.js +181 -65
- package/dist/compactionStatus.d.ts +34 -0
- package/dist/compactionStatus.js +196 -0
- package/dist/controlRouter.d.ts +4 -2
- package/dist/controlRouter.js +39 -51
- package/dist/coreValuePorts.d.ts +26 -1
- package/dist/coreValuePorts.js +10 -0
- package/dist/corruptSessionCopy.d.ts +2 -1
- package/dist/corruptSessionCopy.js +3 -3
- package/dist/decideFailureNote.js +3 -3
- package/dist/declared/supportFloorDeclared.js +1 -1
- package/dist/detachWire.js +3 -2
- package/dist/devicesFace.d.ts +34 -0
- package/dist/devicesFace.js +90 -0
- package/dist/displayUntrusted.d.ts +1 -0
- package/dist/displayUntrusted.js +153 -5
- package/dist/engineAgentPanelStore.d.ts +14 -4
- package/dist/engineAgentPanelStore.js +14 -0
- package/dist/engineCapReader.d.ts +2 -1
- package/dist/engineCapReader.js +7 -5
- package/dist/engineCapsCache.d.ts +8 -0
- package/dist/engineCapsCache.js +59 -6
- package/dist/engineCapsChange.d.ts +2 -0
- package/dist/engineCapsChange.js +9 -0
- package/dist/engineErrorCodes.d.ts +11 -2
- package/dist/engineErrorCodes.js +11 -12
- package/dist/engineRowRef.js +4 -0
- package/dist/engineSessionMessages.d.ts +1 -0
- package/dist/engineSessionMessages.js +7 -1
- package/dist/engineTargetSeat.d.ts +27 -0
- package/dist/engineTargetSeat.js +199 -0
- package/dist/engineToolDirectory.d.ts +3 -0
- package/dist/engineToolDirectory.js +4 -2
- package/dist/engineWireFor.js +3 -8
- package/dist/env/uiLanguage.d.ts +2 -1
- package/dist/env/uiLanguage.js +3 -2
- package/dist/eventLeg.d.ts +7 -5
- package/dist/eventLeg.js +68 -49
- package/dist/fileHistoryCaptureCapability.d.ts +2 -4
- package/dist/fileHistoryCaptureCapability.js +12 -22
- package/dist/fileHistoryLegNotice.d.ts +2 -1
- package/dist/fileHistoryLegNotice.js +3 -3
- package/dist/frameBinding.d.ts +3 -0
- package/dist/frameBinding.js +12 -0
- package/dist/gateOutcome.js +16 -2
- package/dist/gateVocabulary.d.ts +8 -7
- package/dist/gateVocabulary.js +71 -73
- package/dist/generated/engineFactTables.d.ts +18 -1
- package/dist/generated/engineFactTables.js +56 -0
- package/dist/generated/engineNoticeTables.js +12 -3
- package/dist/generated/settingsSchemaFns.d.ts +18 -0
- package/dist/generated/settingsSchemaFns.js +70 -0
- package/dist/generated/settingsSchemaHooksClassifier.d.ts +2 -0
- package/dist/generated/settingsSchemaHooksClassifier.js +254 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.js +162 -0
- package/dist/generated/settingsSchemaTables.d.ts +2 -0
- package/dist/generated/settingsSchemaTables.js +9 -0
- package/dist/generated/settingsSchemaTypes.d.ts +133 -0
- package/dist/generated/settingsSchemaTypes.js +1 -0
- package/dist/hitl/allowRuleShadow.d.ts +2 -1
- package/dist/hitl/allowRuleShadow.js +18 -15
- package/dist/hitl/approvalCardReasons.d.ts +3 -2
- package/dist/hitl/approvalCardReasons.js +29 -30
- package/dist/hitl/approvalHolds.d.ts +17 -0
- package/dist/hitl/approvalHolds.js +114 -29
- package/dist/hitl/askClassPair.d.ts +2 -2
- package/dist/hitl/askClassPair.js +16 -0
- package/dist/hitl/askGateWire.js +2 -1
- package/dist/hitl/askSettlementLine.d.ts +14 -4
- package/dist/hitl/askSettlementLine.js +67 -47
- package/dist/hitl/attributionLadder.d.ts +23 -0
- package/dist/hitl/attributionLadder.js +67 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +2 -1
- package/dist/hitl/automaticAnswerVerdict.js +8 -8
- package/dist/hitl/crashConvergedNotice.d.ts +5 -4
- package/dist/hitl/crashConvergedNotice.js +28 -41
- package/dist/hitl/decisionLedger.d.ts +55 -0
- package/dist/hitl/decisionLedger.js +156 -0
- package/dist/hitl/decisionRefusal.d.ts +1 -0
- package/dist/hitl/decisionRefusal.js +1 -1
- package/dist/hitl/decisionSend.d.ts +113 -0
- package/dist/hitl/decisionSend.js +579 -0
- package/dist/hitl/frameRouter.js +7 -8
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +21 -9
- package/dist/hitl/persistedRulesWire.d.ts +6 -5
- package/dist/hitl/persistedRulesWire.js +55 -73
- package/dist/hitl/planReviewWire.d.ts +5 -0
- package/dist/hitl/planReviewWire.js +53 -76
- package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
- package/dist/hitl/sessionPolicyDeliverable.js +49 -9
- package/dist/hitl/sessionPolicyWire.d.ts +2 -1
- package/dist/hitl/sessionPolicyWire.js +23 -24
- package/dist/hitl/suspendedReopen.d.ts +2 -1
- package/dist/hitl/suspendedReopen.js +7 -10
- package/dist/hitl/toolApprovalWire.d.ts +17 -5
- package/dist/hitl/toolApprovalWire.js +120 -147
- package/dist/hooksWireCaps.js +145 -56
- package/dist/host.d.ts +21 -0
- package/dist/hostTurnSettings.d.ts +110 -0
- package/dist/hostTurnSettings.js +692 -0
- package/dist/humanTurn.d.ts +15 -1
- package/dist/humanTurn.js +100 -12
- package/dist/humanTurnRead.d.ts +2 -0
- package/dist/humanTurnRead.js +22 -1
- package/dist/i18n/messages.en.d.ts +713 -0
- package/dist/i18n/messages.en.js +713 -0
- package/dist/i18n/messages.zh.js +854 -1
- package/dist/index.d.ts +45 -13
- package/dist/index.js +27 -9
- package/dist/inheritEnvCapability.d.ts +4 -2
- package/dist/inheritEnvCapability.js +12 -5
- package/dist/inheritEnvWire.d.ts +3 -2
- package/dist/inheritEnvWire.js +14 -16
- package/dist/injectionLaneNotice.d.ts +5 -0
- package/dist/injectionLaneNotice.js +46 -0
- package/dist/interactiveHalt.js +17 -6
- package/dist/liveModelCatalog.d.ts +2 -1
- package/dist/liveModelCatalog.js +14 -8
- package/dist/mcpPanel.d.ts +10 -1
- package/dist/mcpPanel.js +42 -13
- package/dist/mcpProbeCapability.d.ts +2 -1
- package/dist/mcpProbeCapability.js +7 -11
- package/dist/mcpProbeWire.d.ts +3 -1
- package/dist/mcpProbeWire.js +5 -0
- package/dist/mcpReconnect.js +2 -2
- package/dist/mcpToolToggles.d.ts +6 -0
- package/dist/mcpToolToggles.js +46 -0
- package/dist/mcpWireCaps.d.ts +43 -3
- package/dist/mcpWireCaps.js +194 -59
- package/dist/mcpWireTransport.d.ts +4 -0
- package/dist/mcpWireTransport.js +18 -0
- package/dist/memoryEntriesWire.js +5 -10
- package/dist/model/onboardJudgement.d.ts +93 -0
- package/dist/model/onboardJudgement.js +415 -0
- package/dist/model/tierVocabulary.d.ts +2 -0
- package/dist/model/tierVocabulary.js +17 -5
- package/dist/modelCapabilityProbe.d.ts +65 -0
- package/dist/modelCapabilityProbe.js +601 -0
- package/dist/modelIdentityResolvability.d.ts +5 -0
- package/dist/modelIdentityResolvability.js +49 -22
- package/dist/ownKey.d.ts +33 -0
- package/dist/ownKey.js +109 -0
- package/dist/panelReduce.d.ts +11 -1
- package/dist/panelReduce.js +95 -16
- package/dist/panelStateHolder.d.ts +1 -0
- package/dist/panelStateHolder.js +10 -1
- package/dist/permissionRuleIssue.d.ts +2 -1
- package/dist/permissionRuleIssue.js +23 -20
- package/dist/permissionRulesWriteCapability.d.ts +2 -1
- package/dist/permissionRulesWriteCapability.js +8 -6
- package/dist/pluginHooksWire.d.ts +46 -3
- package/dist/pluginHooksWire.js +220 -12
- package/dist/prepareRefusal.d.ts +8 -0
- package/dist/prepareRefusal.js +72 -0
- package/dist/printModelStatus.d.ts +63 -0
- package/dist/printModelStatus.js +155 -0
- package/dist/request/taskRequest.d.ts +16 -10
- package/dist/request/taskRequest.js +195 -58
- package/dist/resumeRefusalCopy.d.ts +14 -9
- package/dist/resumeRefusalCopy.js +89 -144
- package/dist/retryStatus.d.ts +55 -0
- package/dist/retryStatus.js +155 -19
- package/dist/ruleRefusalSentence.d.ts +4 -0
- package/dist/ruleRefusalSentence.js +58 -0
- package/dist/sandboxWire.d.ts +2 -1
- package/dist/sandboxWire.js +55 -21
- package/dist/scenarioRefusal.d.ts +10 -0
- package/dist/scenarioRefusal.js +111 -0
- package/dist/seam.d.ts +48 -9
- package/dist/seam.js +54 -5
- package/dist/seatContract.d.ts +272 -14
- package/dist/seatContract.js +711 -23
- package/dist/selfOrchestrationDenial.js +7 -19
- package/dist/sessionIdSegment.d.ts +6 -0
- package/dist/sessionIdSegment.js +24 -0
- package/dist/sessionMemoryStatus.js +6 -11
- package/dist/sessionPolicyCapability.d.ts +2 -1
- package/dist/sessionPolicyCapability.js +8 -6
- package/dist/settingsCredentialKeyPaths.d.ts +8 -0
- package/dist/settingsCredentialKeyPaths.js +219 -0
- package/dist/settingsHooksSalvage.d.ts +44 -0
- package/dist/settingsHooksSalvage.js +211 -0
- package/dist/skillToolListsCapability.d.ts +2 -1
- package/dist/skillToolListsCapability.js +8 -6
- package/dist/skillsWireCaps.d.ts +0 -3
- package/dist/skillsWireCaps.js +1 -2
- package/dist/sseIdleTriage.js +3 -2
- package/dist/subagent/engineSubagentResume.js +15 -9
- package/dist/subagentContentStore.d.ts +4 -0
- package/dist/subagentContentStore.js +157 -27
- package/dist/toolHistoryMismatch.js +3 -2
- package/dist/toolRoster.d.ts +3 -2
- package/dist/toolRoster.js +21 -20
- package/dist/toolsFlagNames.d.ts +8 -0
- package/dist/toolsFlagNames.js +110 -0
- package/dist/turnFailurePresentation.d.ts +12 -1
- package/dist/turnFailurePresentation.js +220 -15
- package/dist/usageAnalytics.js +6 -11
- package/dist/userAttachmentEnvelope.d.ts +1 -0
- package/dist/userAttachmentEnvelope.js +37 -7
- package/dist/userAttachmentSeat.d.ts +21 -0
- package/dist/userAttachmentSeat.js +75 -0
- package/dist/userInvokedSkillsCapability.d.ts +11 -1
- package/dist/userInvokedSkillsCapability.js +31 -7
- package/dist/wireErrorTriage.d.ts +7 -0
- package/dist/wireErrorTriage.js +23 -1
- package/dist/wireFailureShape.js +2 -1
- package/dist/wireRefusalCopy.d.ts +62 -16
- package/dist/wireRefusalCopy.js +307 -148
- package/dist/workflowsJournalCapability.d.ts +2 -1
- package/dist/workflowsJournalCapability.js +8 -6
- package/docs/INTEGRATION-CLIENTS.md +951 -42
- package/package.json +5 -5
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
|
|
2
|
+
import { hostLog } from '../host.js';
|
|
3
|
+
import { askGoneFromError } from '../decideReceipt.js';
|
|
4
|
+
import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
|
|
5
|
+
import { effectiveWireErrorCode, isWireTransportError } from '../wireErrorTriage.js';
|
|
6
|
+
import { ASK_DECISION_LANE_DISABLED, ASK_GONE, ASK_GONE_PARKED, ASK_IDEMPOTENCY_CONFLICT, ASK_NOT_FOUND, ASK_PARKING_RETRY } from '../engineErrorCodes.js';
|
|
7
|
+
import { readErrorNumber, readErrorString, readThrownSlot, readThrownStatusSlot, THROWN_SLOT_UNREADABLE } from '../wireFailureShape.js';
|
|
8
|
+
import { readDecisionNoteAudit } from './approvalDecisionNoteAudit.js';
|
|
9
|
+
import { deliverWithAttributionLadder } from './attributionLadder.js';
|
|
10
|
+
import { ASK_DECIDED_CODE, classifyDecisionRefusal, decisionRefusalClosesOut } from './decisionRefusal.js';
|
|
11
|
+
import { addDecisionOpKeys, closeDecisionOp, decisionLedgerKeysOf, decisionOpsWithKey, openDecisionOp } from './decisionLedger.js';
|
|
12
|
+
import { hostHoldIsActive, takeHostHoldLedgerOp, tryHoldApprovalNow } from './approvalHolds.js';
|
|
13
|
+
import { answerPayloadFor, decisionAttributionKeyOf, isToolApprovalRespondReplayed, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, } from './toolApprovalWire.js';
|
|
14
|
+
export const HITL_DECISION_FAMILIES = Object.freeze(['approval', 'ask', 'elicitation']);
|
|
15
|
+
export const HITL_DECISION_NOT_APPLIED_CAUSES = Object.freeze(['gone', 'parked', 'parking', 'lane_disabled', 'refused', 'idempotency_crossed']);
|
|
16
|
+
export const HITL_DECISION_UNCONFIRMED_CAUSES = Object.freeze(['transport', 'server_error', 'local_error', 'aborted', 'gone_after_retry']);
|
|
17
|
+
export const HITL_DECISION_NOT_SENT_CAUSES = Object.freeze(['aborted', 'duplicate_in_flight', 'invalid_request', 'no_verb', 'not_an_answer']);
|
|
18
|
+
const RETRY_DELAYS_MS = Object.freeze([500, 1_000, 2_000]);
|
|
19
|
+
const RETRY_BUDGET_MS = 8_000;
|
|
20
|
+
const PARKING_POLL_MAX = 2;
|
|
21
|
+
const PARKING_POLL_DELAY_MS = 400;
|
|
22
|
+
const PARKING_POLL_WAIT_CAP_MS = 5_000;
|
|
23
|
+
export const HITL_DECISION_READBACK_BUDGET_MS = 4_000;
|
|
24
|
+
const IDEMPOTENCY_KEY_MAX = 255;
|
|
25
|
+
let timingOverride;
|
|
26
|
+
export function __setHitlDecisionSendTimingForTests(t) {
|
|
27
|
+
timingOverride = t;
|
|
28
|
+
}
|
|
29
|
+
export function hitlAskIdempotencyKey(askId, verdict) {
|
|
30
|
+
const key = `sema-ask:${askId}:${verdict}`;
|
|
31
|
+
return key.length <= IDEMPOTENCY_KEY_MAX ? key : key.slice(0, IDEMPOTENCY_KEY_MAX);
|
|
32
|
+
}
|
|
33
|
+
function isAbortShaped(e) {
|
|
34
|
+
const name = readThrownSlot(e, 'name');
|
|
35
|
+
return name === 'TimeoutError' || name === 'AbortError';
|
|
36
|
+
}
|
|
37
|
+
function transportClassOf(e, signal) {
|
|
38
|
+
if (signal?.aborted === true)
|
|
39
|
+
return 'aborted';
|
|
40
|
+
const status = typeof e === 'object' && e !== null ? readThrownStatusSlot(e) : undefined;
|
|
41
|
+
if (status === THROWN_SLOT_UNREADABLE)
|
|
42
|
+
return 'local_error';
|
|
43
|
+
if (typeof status === 'number')
|
|
44
|
+
return status === 501 ? 'verdict' : status >= 500 ? 'server_5xx' : 'verdict';
|
|
45
|
+
return isWireTransportError(e) || isAbortShaped(e) ? 'transport' : 'local_error';
|
|
46
|
+
}
|
|
47
|
+
function attemptSignal(attempt, caller, startedAt, budgetMs) {
|
|
48
|
+
if (attempt === 1)
|
|
49
|
+
return caller;
|
|
50
|
+
const deadline = AbortSignal.timeout(Math.max(1, budgetMs - (Date.now() - startedAt)));
|
|
51
|
+
if (caller === undefined)
|
|
52
|
+
return deadline;
|
|
53
|
+
if (typeof AbortSignal.any === 'function')
|
|
54
|
+
return AbortSignal.any([caller, deadline]);
|
|
55
|
+
const ac = new AbortController();
|
|
56
|
+
const abort = (s) => ac.abort(s.reason);
|
|
57
|
+
if (caller.aborted)
|
|
58
|
+
abort(caller);
|
|
59
|
+
else
|
|
60
|
+
caller.addEventListener('abort', () => abort(caller), { once: true });
|
|
61
|
+
deadline.addEventListener('abort', () => abort(deadline), { once: true });
|
|
62
|
+
return ac.signal;
|
|
63
|
+
}
|
|
64
|
+
function budgetSpent(tally, extraMs) {
|
|
65
|
+
return Date.now() - tally.startedAt + extraMs >= (timingOverride?.budgetMs ?? RETRY_BUDGET_MS);
|
|
66
|
+
}
|
|
67
|
+
function isAborted(signal) {
|
|
68
|
+
return signal?.aborted === true;
|
|
69
|
+
}
|
|
70
|
+
function sleepFor(ms, signal) {
|
|
71
|
+
return abortableSleep(ms, signal ?? new AbortController().signal);
|
|
72
|
+
}
|
|
73
|
+
async function withTransportRetry(send, signal, tally) {
|
|
74
|
+
const delays = timingOverride?.delays ?? RETRY_DELAYS_MS;
|
|
75
|
+
const budgetMs = timingOverride?.budgetMs ?? RETRY_BUDGET_MS;
|
|
76
|
+
const startedAt = tally.startedAt;
|
|
77
|
+
for (let attempt = 1;; attempt++) {
|
|
78
|
+
tally.attempts++;
|
|
79
|
+
try {
|
|
80
|
+
return await send(attemptSignal(attempt, signal, startedAt, budgetMs));
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
const cls = transportClassOf(e, signal);
|
|
84
|
+
const delay = delays[attempt - 1] ?? 0;
|
|
85
|
+
const retriable = cls === 'transport' || cls === 'server_5xx';
|
|
86
|
+
const overBudget = Date.now() - startedAt + delay >= budgetMs;
|
|
87
|
+
if (!retriable || attempt > delays.length || overBudget)
|
|
88
|
+
throw e;
|
|
89
|
+
tally.retried = true;
|
|
90
|
+
await sleepFor(delay, signal);
|
|
91
|
+
if (signal?.aborted === true)
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function retryAfterMsOf(e) {
|
|
97
|
+
if (typeof e !== 'object' || e === null)
|
|
98
|
+
return undefined;
|
|
99
|
+
const ms = readErrorNumber(e, 'retryAfterMs');
|
|
100
|
+
if (ms !== undefined && Number.isFinite(ms) && ms >= 0)
|
|
101
|
+
return ms;
|
|
102
|
+
const sec = readErrorNumber(e, 'retryAfterSec');
|
|
103
|
+
return sec !== undefined && Number.isFinite(sec) && sec >= 0 ? sec * 1000 : undefined;
|
|
104
|
+
}
|
|
105
|
+
function actorLabelOf(e) {
|
|
106
|
+
const actor = readThrownSlot(e, 'actor', 'data');
|
|
107
|
+
if (typeof actor !== 'object' || actor === null)
|
|
108
|
+
return undefined;
|
|
109
|
+
const label = guardedRead(() => readOwnDataNonEmptyString(actor, 'label'));
|
|
110
|
+
if (label !== undefined)
|
|
111
|
+
return label;
|
|
112
|
+
const id = guardedRead(() => readOwnDataNonEmptyString(actor, 'id'));
|
|
113
|
+
return id !== undefined && id !== '_' ? id : undefined;
|
|
114
|
+
}
|
|
115
|
+
function verdictOf(word) {
|
|
116
|
+
return word === 'approve' || word === 'allow' || word === 'allow_session' ? 'approve' : word === 'deny' ? 'deny' : undefined;
|
|
117
|
+
}
|
|
118
|
+
function outcomeOfThrown(e, family, ours, signal, tally, noteCapable) {
|
|
119
|
+
const base = { family, attempts: tally.attempts };
|
|
120
|
+
const cls = transportClassOf(e, signal);
|
|
121
|
+
if (cls === 'aborted')
|
|
122
|
+
return { ...base, kind: 'unconfirmed', cause: 'aborted' };
|
|
123
|
+
if (cls === 'transport')
|
|
124
|
+
return { ...base, kind: 'unconfirmed', cause: 'transport' };
|
|
125
|
+
if (cls === 'local_error')
|
|
126
|
+
return { ...base, kind: 'unconfirmed', cause: 'local_error' };
|
|
127
|
+
const obj = e;
|
|
128
|
+
const status = readThrownStatusSlot(obj);
|
|
129
|
+
if (cls === 'server_5xx')
|
|
130
|
+
return { ...base, kind: 'unconfirmed', cause: 'server_error', status };
|
|
131
|
+
const code = effectiveWireErrorCode(e);
|
|
132
|
+
const at = { ...base, status, ...(code !== undefined ? { errorCode: code } : {}) };
|
|
133
|
+
if (family === 'ask') {
|
|
134
|
+
switch (code) {
|
|
135
|
+
case ASK_DECIDED_CODE: {
|
|
136
|
+
const first = verdictOf(readErrorString(obj, 'firstDecision'));
|
|
137
|
+
const decidedAtMs = readErrorNumber(obj, 'decidedAtMs');
|
|
138
|
+
const actorLabel = actorLabelOf(obj);
|
|
139
|
+
const note = readErrorString(obj, 'decisionNote');
|
|
140
|
+
return {
|
|
141
|
+
...base,
|
|
142
|
+
kind: 'settled_elsewhere',
|
|
143
|
+
...(first !== undefined ? { firstDecision: first } : {}),
|
|
144
|
+
...(first !== undefined && ours !== undefined && first !== ours ? { contrary: true } : {}),
|
|
145
|
+
...(decidedAtMs !== undefined ? { decidedAtMs } : {}),
|
|
146
|
+
...(actorLabel !== undefined ? { actorLabel } : {}),
|
|
147
|
+
...(noteCapable && note !== undefined ? { noteAudit: readDecisionNoteAudit({ decisionNote: note }) } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
case ASK_IDEMPOTENCY_CONFLICT:
|
|
151
|
+
return { ...at, kind: 'not_applied', cause: 'idempotency_crossed', ...refusalOf(e) };
|
|
152
|
+
case ASK_GONE_PARKED: {
|
|
153
|
+
const sessionId = readErrorString(obj, 'sessionId');
|
|
154
|
+
return { ...at, kind: 'not_applied', cause: 'parked', ...(sessionId !== undefined ? { sessionId } : {}) };
|
|
155
|
+
}
|
|
156
|
+
case ASK_GONE: {
|
|
157
|
+
const gone = askGoneFromError(e);
|
|
158
|
+
return { ...at, kind: 'not_applied', cause: 'gone', ...(gone !== null ? { gone } : {}) };
|
|
159
|
+
}
|
|
160
|
+
case ASK_PARKING_RETRY: {
|
|
161
|
+
const retryAfterMs = retryAfterMsOf(e);
|
|
162
|
+
return { ...at, kind: 'not_applied', cause: 'parking', ...(retryAfterMs !== undefined ? { retryAfterMs } : {}) };
|
|
163
|
+
}
|
|
164
|
+
case ASK_DECISION_LANE_DISABLED:
|
|
165
|
+
return { ...at, kind: 'not_applied', cause: 'lane_disabled' };
|
|
166
|
+
case ASK_NOT_FOUND:
|
|
167
|
+
return { ...at, kind: 'not_applied', cause: 'gone' };
|
|
168
|
+
default:
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (status === 404 || status === 410) {
|
|
173
|
+
return family !== 'ask' && tally.retried ? { ...base, kind: 'unconfirmed', cause: 'gone_after_retry', status } : { ...at, kind: 'not_applied', cause: 'gone' };
|
|
174
|
+
}
|
|
175
|
+
if (status === 501)
|
|
176
|
+
return { ...at, kind: 'not_applied', cause: 'lane_disabled' };
|
|
177
|
+
return { ...at, kind: 'not_applied', cause: 'refused', ...refusalOf(e) };
|
|
178
|
+
}
|
|
179
|
+
function refusalOf(e) {
|
|
180
|
+
const r = classifyDecisionRefusal(e);
|
|
181
|
+
return r !== undefined ? { refusal: r } : {};
|
|
182
|
+
}
|
|
183
|
+
async function sendApprovalFamily(respond, approvalId, answer, ctx) {
|
|
184
|
+
const payload = answerPayloadFor(answer, 'respond', { approvalDecisionNoteCapable: ctx.noteCapable });
|
|
185
|
+
if (payload === undefined)
|
|
186
|
+
return { family: 'approval', attempts: ctx.tally.attempts, kind: 'not_sent', cause: 'not_an_answer' };
|
|
187
|
+
const attribution = answer.kind === 'deny' ? decisionAttributionKeyOf(ctx.decidedBy, readApprovalDenySettledBy(answer)) : {};
|
|
188
|
+
const body = { ...payload, ...attribution };
|
|
189
|
+
const ladder = { lastCarried: false, dropped: false, renamed: false };
|
|
190
|
+
const ours = verdictOf(payload.decision);
|
|
191
|
+
const extras = bodyFacts(answer, payload, false);
|
|
192
|
+
let raw;
|
|
193
|
+
try {
|
|
194
|
+
raw = await deliverWithAttributionLadder(body, (b) => withTransportRetry((signal) => respond(approvalId, b, signal !== undefined ? { signal } : undefined), ctx.signal, ctx.tally), ctx.signal, ladder);
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
return { ...outcomeOfThrown(e, 'approval', ours, ctx.signal, ctx.tally, ctx.noteCapable), ...extras };
|
|
198
|
+
}
|
|
199
|
+
const facts = { ...extras, ...(ladder.dropped ? { attributionDropped: true } : {}), ...(ladder.renamed ? { attributionRenamed: true } : {}) };
|
|
200
|
+
const base = { family: 'approval', attempts: ctx.tally.attempts, ...facts };
|
|
201
|
+
const parking = readToolApprovalRespondParking(raw);
|
|
202
|
+
if (parking !== undefined) {
|
|
203
|
+
const view = parking.kind === 'parking' && parking.view.approvalId === approvalId ? parking.view : undefined;
|
|
204
|
+
return { ...base, kind: 'not_applied', cause: 'parking', ...(view !== undefined ? { parking: view, ...(view.retryAfterMs !== undefined ? { retryAfterMs: view.retryAfterMs } : {}) } : {}) };
|
|
205
|
+
}
|
|
206
|
+
const parsed = readToolApprovalRespondAck(raw);
|
|
207
|
+
if (parsed !== undefined && isToolApprovalRespondReplayed(parsed)) {
|
|
208
|
+
const first = parsed.approvalId === approvalId ? verdictOf(parsed.decision) : undefined;
|
|
209
|
+
return {
|
|
210
|
+
...base,
|
|
211
|
+
kind: 'settled_elsewhere',
|
|
212
|
+
...(first !== undefined ? { firstDecision: first } : {}),
|
|
213
|
+
...(first !== undefined && ours !== undefined && first !== ours ? { contrary: true } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (ownTrue(raw, 'idempotent'))
|
|
217
|
+
return { ...base, kind: 'settled_elsewhere' };
|
|
218
|
+
const ack = parsed !== undefined && parsed.approvalId === approvalId ? parsed : undefined;
|
|
219
|
+
return { ...base, kind: 'applied', ...(ack !== undefined ? { ack } : {}), ...(ctx.noteCapable ? { noteAudit: readDecisionNoteAudit(raw) } : {}) };
|
|
220
|
+
}
|
|
221
|
+
function bodyFacts(answer, payload, rememberUnsupported) {
|
|
222
|
+
const reason = answer.kind === 'deny' && typeof answer.reason === 'string' ? answer.reason : undefined;
|
|
223
|
+
return {
|
|
224
|
+
...(reason !== undefined && reason.trim() !== '' && payload.note === undefined ? { noteDropped: true } : {}),
|
|
225
|
+
...(rememberUnsupported && answer.kind === 'allow' && answer.allowSession === true ? { rememberDropped: true } : {}),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function ownTrue(raw, key) {
|
|
229
|
+
return typeof raw === 'object' && raw !== null && guardedRead(() => readOwnDataValue(raw, key)) === true;
|
|
230
|
+
}
|
|
231
|
+
async function sendAskFamily(respondForFallback, decideAsk, req, ctx) {
|
|
232
|
+
const payload = answerPayloadFor(req.answer, 'respond', { approvalDecisionNoteCapable: ctx.noteCapable });
|
|
233
|
+
if (payload === undefined)
|
|
234
|
+
return { family: 'ask', attempts: ctx.tally.attempts, kind: 'not_sent', cause: 'not_an_answer' };
|
|
235
|
+
const verdict = verdictOf(payload.decision) ?? 'deny';
|
|
236
|
+
const attribution = req.answer.kind === 'deny' ? decisionAttributionKeyOf(ctx.decidedBy, readApprovalDenySettledBy(req.answer)) : {};
|
|
237
|
+
const body = {
|
|
238
|
+
decision: verdict,
|
|
239
|
+
idempotencyKey: hitlAskIdempotencyKey(req.askId, verdict),
|
|
240
|
+
...(verdict === 'approve' && payload.updatedInput !== undefined ? { updatedInput: payload.updatedInput } : {}),
|
|
241
|
+
...(payload.note !== undefined ? { note: payload.note } : {}),
|
|
242
|
+
...attribution,
|
|
243
|
+
};
|
|
244
|
+
const extras = bodyFacts(req.answer, payload, true);
|
|
245
|
+
const ladder = { lastCarried: false, dropped: false, renamed: false };
|
|
246
|
+
let current = body;
|
|
247
|
+
const parkingDelay = timingOverride?.parkingDelayMs ?? PARKING_POLL_DELAY_MS;
|
|
248
|
+
for (let poll = 0;; poll++) {
|
|
249
|
+
try {
|
|
250
|
+
const raw = await deliverWithAttributionLadder(current, (b) => {
|
|
251
|
+
current = b;
|
|
252
|
+
return withTransportRetry((signal) => decideAsk(req.taskId, req.askId, b, signal !== undefined ? { signal } : undefined), ctx.signal, ctx.tally);
|
|
253
|
+
}, ctx.signal, ladder);
|
|
254
|
+
return {
|
|
255
|
+
family: 'ask',
|
|
256
|
+
attempts: ctx.tally.attempts,
|
|
257
|
+
...extras,
|
|
258
|
+
...(ladder.dropped ? { attributionDropped: true } : {}),
|
|
259
|
+
...(ladder.renamed ? { attributionRenamed: true } : {}),
|
|
260
|
+
kind: 'applied',
|
|
261
|
+
...(ctx.noteCapable ? { noteAudit: readDecisionNoteAudit(raw) } : {}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
const code = effectiveWireErrorCode(e);
|
|
266
|
+
const wait = Math.max(parkingDelay * (poll + 1), retryAfterMsOf(e) ?? 0);
|
|
267
|
+
if (code === ASK_PARKING_RETRY && poll < PARKING_POLL_MAX && !isAborted(ctx.signal) && wait <= PARKING_POLL_WAIT_CAP_MS && !budgetSpent(ctx.tally, wait)) {
|
|
268
|
+
await sleepFor(wait, ctx.signal);
|
|
269
|
+
if (!isAborted(ctx.signal))
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const out = outcomeOfThrown(e, 'ask', verdict, ctx.signal, ctx.tally, ctx.noteCapable);
|
|
273
|
+
if (out.kind === 'not_applied' && out.cause === 'lane_disabled' && code === ASK_DECISION_LANE_DISABLED && req.approvalId !== undefined) {
|
|
274
|
+
if (respondForFallback !== undefined && ctx.signal?.aborted !== true) {
|
|
275
|
+
const fb = await sendApprovalFamily(respondForFallback, req.approvalId, req.answer, ctx);
|
|
276
|
+
return { ...fb, family: 'ask', fallback: 'approval' };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return { ...out, ...extras };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function validElicitResponse(r) {
|
|
284
|
+
if (typeof r !== 'object' || r === null)
|
|
285
|
+
return undefined;
|
|
286
|
+
const action = guardedRead(() => readOwnDataValue(r, 'action'));
|
|
287
|
+
if (action !== 'accept' && action !== 'decline' && action !== 'cancel')
|
|
288
|
+
return undefined;
|
|
289
|
+
const content = guardedRead(() => readOwnDataValue(r, 'content'));
|
|
290
|
+
if (content === undefined)
|
|
291
|
+
return Object.freeze({ action });
|
|
292
|
+
if (action !== 'accept' || typeof content !== 'object' || content === null || Array.isArray(content))
|
|
293
|
+
return undefined;
|
|
294
|
+
const out = {};
|
|
295
|
+
try {
|
|
296
|
+
for (const k of Object.keys(content)) {
|
|
297
|
+
const v = readOwnDataValue(content, k);
|
|
298
|
+
if (typeof v === 'string' || typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v)))
|
|
299
|
+
Object.defineProperty(out, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
300
|
+
else if (Array.isArray(v)) {
|
|
301
|
+
const copy = [];
|
|
302
|
+
for (let i = 0; i < v.length; i++) {
|
|
303
|
+
const x = readOwnDataValue(v, String(i));
|
|
304
|
+
if (typeof x !== 'string')
|
|
305
|
+
return undefined;
|
|
306
|
+
copy.push(x);
|
|
307
|
+
}
|
|
308
|
+
Object.defineProperty(out, k, { value: copy, enumerable: true, writable: true, configurable: true });
|
|
309
|
+
}
|
|
310
|
+
else
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
return Object.freeze({ action, content: out });
|
|
318
|
+
}
|
|
319
|
+
function verbOf(holder, name) {
|
|
320
|
+
if (typeof holder !== 'object' || holder === null)
|
|
321
|
+
return undefined;
|
|
322
|
+
try {
|
|
323
|
+
const fn = holder[name];
|
|
324
|
+
if (typeof fn !== 'function')
|
|
325
|
+
return undefined;
|
|
326
|
+
const bound = (...args) => {
|
|
327
|
+
try {
|
|
328
|
+
return Promise.resolve(fn.apply(holder, args));
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
331
|
+
return Promise.reject(e);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
return bound;
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function signalOf(v) {
|
|
341
|
+
try {
|
|
342
|
+
return v instanceof AbortSignal ? v : undefined;
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function holdOf(v) {
|
|
349
|
+
if (typeof v !== 'object' || v === null)
|
|
350
|
+
return undefined;
|
|
351
|
+
try {
|
|
352
|
+
const h = v;
|
|
353
|
+
return typeof h.sending === 'function' && typeof h.release === 'function' ? h : undefined;
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function holdFits(handle, req) {
|
|
360
|
+
try {
|
|
361
|
+
const id = handle.identity;
|
|
362
|
+
if (typeof id.approvalId === 'string' && id.approvalId !== '')
|
|
363
|
+
return id.approvalId === req.approvalId;
|
|
364
|
+
if (typeof id.toolCallId !== 'string' || id.toolCallId === '' || id.toolCallId !== req.toolCallId)
|
|
365
|
+
return false;
|
|
366
|
+
return typeof id.taskId !== 'string' || id.taskId === '' || id.taskId === req.taskId;
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function callHold(call) {
|
|
373
|
+
try {
|
|
374
|
+
call();
|
|
375
|
+
}
|
|
376
|
+
catch (e) {
|
|
377
|
+
hostLog('debug', `decisionSend.callHold: ${String(e).slice(0, 120)}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function hostField(holder, key) {
|
|
381
|
+
if (typeof holder !== 'object' || holder === null)
|
|
382
|
+
return undefined;
|
|
383
|
+
try {
|
|
384
|
+
return holder[key];
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function snapshotAnswer(raw) {
|
|
391
|
+
const kind = guardedRead(() => readOwnDataValue(raw, 'kind'));
|
|
392
|
+
if (kind === 'allow') {
|
|
393
|
+
const allowSession = guardedRead(() => readOwnDataValue(raw, 'allowSession')) === true;
|
|
394
|
+
const edited = guardedRead(() => readOwnDataValue(raw, 'updatedInput'));
|
|
395
|
+
if (edited === undefined)
|
|
396
|
+
return Object.freeze({ kind: 'allow', allowSession });
|
|
397
|
+
let copy;
|
|
398
|
+
try {
|
|
399
|
+
const text = JSON.stringify(edited);
|
|
400
|
+
if (typeof text !== 'string')
|
|
401
|
+
return undefined;
|
|
402
|
+
copy = JSON.parse(text);
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
return Object.freeze({ kind: 'allow', allowSession, updatedInput: copy });
|
|
408
|
+
}
|
|
409
|
+
if (kind === 'deny') {
|
|
410
|
+
const reason = guardedRead(() => readOwnDataValue(raw, 'reason'));
|
|
411
|
+
const settledBy = readApprovalDenySettledBy(raw);
|
|
412
|
+
return Object.freeze({ kind: 'deny', ...(typeof reason === 'string' ? { reason } : {}), ...(settledBy !== undefined ? { settledBy } : {}) });
|
|
413
|
+
}
|
|
414
|
+
return typeof kind === 'string' ? Object.freeze({ kind }) : undefined;
|
|
415
|
+
}
|
|
416
|
+
function readRequest(req) {
|
|
417
|
+
if (typeof req !== 'object' || req === null)
|
|
418
|
+
return undefined;
|
|
419
|
+
const s = (k) => guardedRead(() => readOwnDataNonEmptyString(req, k));
|
|
420
|
+
const family = s('family');
|
|
421
|
+
if (family === 'elicitation') {
|
|
422
|
+
const elicitationId = s('elicitationId');
|
|
423
|
+
const response = validElicitResponse(guardedRead(() => readOwnDataValue(req, 'response')));
|
|
424
|
+
return elicitationId !== undefined && response !== undefined ? { family, elicitationId, taskId: s('taskId'), response } : undefined;
|
|
425
|
+
}
|
|
426
|
+
const rawAnswer = guardedRead(() => readOwnDataValue(req, 'answer'));
|
|
427
|
+
if (typeof rawAnswer !== 'object' || rawAnswer === null)
|
|
428
|
+
return undefined;
|
|
429
|
+
const answer = snapshotAnswer(rawAnswer);
|
|
430
|
+
if (answer === undefined)
|
|
431
|
+
return undefined;
|
|
432
|
+
if (family === 'approval') {
|
|
433
|
+
const approvalId = s('approvalId');
|
|
434
|
+
return approvalId !== undefined ? { family, approvalId, taskId: s('taskId'), toolCallId: s('toolCallId'), answer } : undefined;
|
|
435
|
+
}
|
|
436
|
+
if (family === 'ask') {
|
|
437
|
+
const taskId = s('taskId');
|
|
438
|
+
const askId = s('askId');
|
|
439
|
+
return taskId !== undefined && askId !== undefined ? { family, taskId, askId, approvalId: s('approvalId'), toolCallId: s('toolCallId'), answer } : undefined;
|
|
440
|
+
}
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
function latchKeysOf(r) {
|
|
444
|
+
if (r.family === 'elicitation')
|
|
445
|
+
return decisionLedgerKeysOf({ elicitationId: r.elicitationId });
|
|
446
|
+
if (r.family === 'ask')
|
|
447
|
+
return decisionLedgerKeysOf({ askId: r.askId, approvalId: r.approvalId });
|
|
448
|
+
return decisionLedgerKeysOf({ approvalId: r.approvalId });
|
|
449
|
+
}
|
|
450
|
+
function ledgerFamilyOf(family) {
|
|
451
|
+
return family === 'elicitation' ? 'elicitation' : 'tool_approval';
|
|
452
|
+
}
|
|
453
|
+
async function readBack(deps, gate) {
|
|
454
|
+
const port = hostField(deps, 'readPending');
|
|
455
|
+
if (typeof port !== 'function')
|
|
456
|
+
return undefined;
|
|
457
|
+
const settled = await settleWithinDeadline(HITL_DECISION_READBACK_BUDGET_MS, (signal) => Promise.resolve(port.call(deps, gate, { signal })));
|
|
458
|
+
if (settled.kind !== 'value')
|
|
459
|
+
return 'unknown';
|
|
460
|
+
return settled.value === 'pending' || settled.value === 'not_pending' ? settled.value : 'unknown';
|
|
461
|
+
}
|
|
462
|
+
export async function sendHitlDecision(request, deps) {
|
|
463
|
+
const familyRead = typeof request === 'object' && request !== null ? guardedRead(() => readOwnDataNonEmptyString(request, 'family')) : undefined;
|
|
464
|
+
const family = familyRead === 'ask' || familyRead === 'elicitation' ? familyRead : 'approval';
|
|
465
|
+
const notSent = (cause) => Object.freeze({ family, attempts: 0, kind: 'not_sent', cause });
|
|
466
|
+
let req;
|
|
467
|
+
try {
|
|
468
|
+
req = readRequest(request);
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
req = undefined;
|
|
472
|
+
}
|
|
473
|
+
if (req === undefined)
|
|
474
|
+
return notSent('invalid_request');
|
|
475
|
+
const signal = signalOf(hostField(deps, 'signal'));
|
|
476
|
+
const client = hostField(deps, 'client');
|
|
477
|
+
const toolApprovals = hostField(client, 'toolApprovals');
|
|
478
|
+
const respond = verbOf(toolApprovals, 'respond');
|
|
479
|
+
const decideAsk = verbOf(toolApprovals, 'decideAsk');
|
|
480
|
+
const elicit = verbOf(hostField(client, 'elicitations'), 'respond');
|
|
481
|
+
if ((req.family === 'approval' && respond === undefined) || (req.family === 'ask' && decideAsk === undefined) || (req.family === 'elicitation' && elicit === undefined))
|
|
482
|
+
return notSent('no_verb');
|
|
483
|
+
if (req.family !== 'elicitation' && req.answer.kind !== 'allow' && req.answer.kind !== 'deny')
|
|
484
|
+
return notSent('not_an_answer');
|
|
485
|
+
if (isAborted(signal))
|
|
486
|
+
return notSent('aborted');
|
|
487
|
+
let ownHold;
|
|
488
|
+
let hold;
|
|
489
|
+
if (req.family !== 'elicitation' && req.approvalId !== undefined) {
|
|
490
|
+
const given = holdOf(hostField(deps, 'hold'));
|
|
491
|
+
if (given !== undefined && hostHoldIsActive(given) && holdFits(given, req)) {
|
|
492
|
+
hold = given;
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const attempt = tryHoldApprovalNow({ approvalId: req.approvalId, ...(req.taskId !== undefined ? { taskId: req.taskId } : {}), ...(req.toolCallId !== undefined ? { toolCallId: req.toolCallId } : {}) }, signal);
|
|
496
|
+
if (attempt.kind === 'settled')
|
|
497
|
+
return Object.freeze({ family, attempts: 0, kind: 'settled_elsewhere' });
|
|
498
|
+
if (attempt.kind === 'busy')
|
|
499
|
+
return notSent('duplicate_in_flight');
|
|
500
|
+
ownHold = attempt.hold;
|
|
501
|
+
hold = ownHold;
|
|
502
|
+
}
|
|
503
|
+
callHold(() => hold?.sending());
|
|
504
|
+
}
|
|
505
|
+
const ledgerFamily = ledgerFamilyOf(req.family);
|
|
506
|
+
if (latchKeysOf(req).some((k) => decisionOpsWithKey(ledgerFamily, k).some((o) => o.phase === 'posting' && o.settledAt === null))) {
|
|
507
|
+
ownHold?.release();
|
|
508
|
+
return notSent('duplicate_in_flight');
|
|
509
|
+
}
|
|
510
|
+
const requestKeys = req.family === 'elicitation'
|
|
511
|
+
? decisionLedgerKeysOf({ taskId: req.taskId, elicitationId: req.elicitationId })
|
|
512
|
+
: decisionLedgerKeysOf({ taskId: req.taskId, approvalId: req.approvalId, callId: req.toolCallId, askId: req.family === 'ask' ? req.askId : undefined });
|
|
513
|
+
const taken = hold !== undefined ? takeHostHoldLedgerOp(hold) : undefined;
|
|
514
|
+
let op;
|
|
515
|
+
if (taken !== undefined && taken.settledAt === null) {
|
|
516
|
+
op = taken;
|
|
517
|
+
op.phase = 'posting';
|
|
518
|
+
op.ownCallOpen = true;
|
|
519
|
+
addDecisionOpKeys(op, requestKeys);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
op = openDecisionOp(ledgerFamily, requestKeys, 'posting', true);
|
|
523
|
+
}
|
|
524
|
+
const decidedBy = hostField(deps, 'approvalDecidedBy');
|
|
525
|
+
const ctx = {
|
|
526
|
+
signal,
|
|
527
|
+
tally: { attempts: 0, retried: false, startedAt: Date.now() },
|
|
528
|
+
noteCapable: hostField(deps, 'approvalDecisionNoteCapable') === true,
|
|
529
|
+
decidedBy: typeof decidedBy === 'object' && decidedBy !== null ? decidedBy : undefined,
|
|
530
|
+
};
|
|
531
|
+
let out;
|
|
532
|
+
try {
|
|
533
|
+
if (req.family === 'approval') {
|
|
534
|
+
out = await sendApprovalFamily(respond, req.approvalId, req.answer, ctx);
|
|
535
|
+
}
|
|
536
|
+
else if (req.family === 'ask') {
|
|
537
|
+
out = await sendAskFamily(respond, decideAsk, { taskId: req.taskId, askId: req.askId, approvalId: req.approvalId, answer: req.answer }, ctx);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
const { elicitationId, response } = req;
|
|
541
|
+
try {
|
|
542
|
+
await withTransportRetry((sig) => elicit(elicitationId, response, sig !== undefined ? { signal: sig } : undefined), signal, ctx.tally);
|
|
543
|
+
out = { family: 'elicitation', attempts: ctx.tally.attempts, kind: 'applied' };
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
out = outcomeOfThrown(e, 'elicitation', undefined, signal, ctx.tally, false);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const ambiguous = out.kind === 'unconfirmed' || (out.kind === 'not_applied' && (out.cause === 'parking' || out.cause === 'refused' || out.cause === 'idempotency_crossed'));
|
|
550
|
+
if (ambiguous && out.cause !== 'aborted') {
|
|
551
|
+
const gate = req.family === 'elicitation'
|
|
552
|
+
? { family: req.family, elicitationId: req.elicitationId, ...(req.taskId !== undefined ? { taskId: req.taskId } : {}) }
|
|
553
|
+
: {
|
|
554
|
+
family: req.family,
|
|
555
|
+
...(req.approvalId !== undefined ? { approvalId: req.approvalId } : {}),
|
|
556
|
+
...(req.family === 'ask' ? { askId: req.askId } : {}),
|
|
557
|
+
...(req.taskId !== undefined ? { taskId: req.taskId } : {}),
|
|
558
|
+
};
|
|
559
|
+
const pending = await readBack(deps, gate);
|
|
560
|
+
if (pending !== undefined) {
|
|
561
|
+
out = { ...out, pending };
|
|
562
|
+
if ((out.cause === 'refused' || out.cause === 'idempotency_crossed') && decisionRefusalClosesOut(out.refusal, pending))
|
|
563
|
+
out = { ...out, closesOut: true };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
out = { family: req.family, attempts: ctx.tally.attempts, kind: 'unconfirmed', cause: 'local_error' };
|
|
569
|
+
}
|
|
570
|
+
const notApplied = out.kind !== 'applied' &&
|
|
571
|
+
(out.pending === 'pending' ||
|
|
572
|
+
(out.kind === 'not_applied' && (out.cause === 'lane_disabled' || out.cause === 'idempotency_crossed' || (out.cause === 'refused' && out.refusal?.standing === 'refused-pending'))));
|
|
573
|
+
if (notApplied)
|
|
574
|
+
op.notApplied = true;
|
|
575
|
+
closeDecisionOp(op, 'package');
|
|
576
|
+
if (ownHold !== undefined)
|
|
577
|
+
ownHold.release(out.kind === 'applied' || out.kind === 'settled_elsewhere' ? 'answered' : undefined);
|
|
578
|
+
return Object.freeze(out);
|
|
579
|
+
}
|
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -8,11 +8,11 @@ import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateT
|
|
|
8
8
|
import { parkHeldCallIdOf } from '../engineRowRef.js';
|
|
9
9
|
import { inheritFrameBinding } from '../frameBinding.js';
|
|
10
10
|
import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
|
|
11
|
-
import { liveCardHoldOf, noteEngineClosedApproval,
|
|
11
|
+
import { liveCardHoldOf, noteEngineClosedApproval, settleEngineClosedApproval } from './approvalHolds.js';
|
|
12
12
|
import { approvalCompleteParked } from './approvalCompleteParked.js';
|
|
13
13
|
import { liveFrameCallKey } from './gateIdentity.js';
|
|
14
14
|
import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
|
|
15
|
-
import { replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
|
|
15
|
+
import { replayedApprovalClosesCardFrame, replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
|
|
16
16
|
import { settleWithinDeadline } from '../abortableSleep.js';
|
|
17
17
|
import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
|
|
18
18
|
import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
|
|
@@ -285,12 +285,9 @@ const TOOL_END_ARMS = [
|
|
|
285
285
|
async function routeToolApprovalFrame(ev, ctx) {
|
|
286
286
|
const { led, deps } = ctx;
|
|
287
287
|
if (ev.type === 'tool_approval_complete') {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
noteEngineClosedApproval(id, parked);
|
|
292
|
-
if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), parked)) {
|
|
293
|
-
hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${id} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
|
|
288
|
+
if (settleEngineClosedApproval(ev, approvalCompleteParked(ev))) {
|
|
289
|
+
const id = ev.approvalId;
|
|
290
|
+
hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${String(id)} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
|
|
294
291
|
}
|
|
295
292
|
return { kind: 'skip' };
|
|
296
293
|
}
|
|
@@ -307,6 +304,8 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
307
304
|
: `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
|
|
308
305
|
`${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
|
|
309
306
|
' — not raised as a question, no decision sent');
|
|
307
|
+
if (settledHere === undefined && replayedApprovalClosesCardFrame(disposition))
|
|
308
|
+
noteEngineClosedApproval(ev.approvalId, false);
|
|
310
309
|
if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
|
|
311
310
|
surfaceReplayedApprovalListUnreadable();
|
|
312
311
|
if (deps.onReplayedApproval !== undefined) {
|
|
@@ -3,6 +3,7 @@ import type { PermissionResult } from '@sema-agent/agent-types';
|
|
|
3
3
|
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
4
4
|
import { type SuspendedReopenReading } from './suspendedReopen.js';
|
|
5
5
|
export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
|
|
6
|
+
export declare const MAX_DENY_REASON_CHARS = 4096;
|
|
6
7
|
export declare function denyReasonForWire(reason: string | undefined, tag: string): string | undefined;
|
|
7
8
|
export interface HitlPermissionDecisionLike {
|
|
8
9
|
behavior: 'allow' | 'ask' | 'deny';
|