@sema-agent/client-core 0.97.1 → 0.98.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 +408 -0
- package/README.md +74 -37
- package/dist/adapt/arms.d.ts +1 -0
- package/dist/adapt/arms.js +128 -34
- package/dist/adapt/ids.d.ts +12 -1
- package/dist/adapt/ids.js +59 -8
- package/dist/adapt/instanceLedger.d.ts +50 -0
- package/dist/adapt/instanceLedger.js +147 -0
- package/dist/adapt/textStream.d.ts +1 -0
- package/dist/adapt/textStream.js +24 -7
- package/dist/adapt/toolCards.d.ts +27 -5
- package/dist/adapt/toolCards.js +39 -6
- package/dist/adapt/wireShapes.js +6 -2
- package/dist/adapt.d.ts +2 -0
- package/dist/adapt.js +8 -3
- package/dist/adapter/activeRunSelfHeal.d.ts +5 -0
- package/dist/adapter/activeRunSelfHeal.js +51 -28
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -5
- package/dist/adapter/downstream/eventToSdkMessage.js +27 -34
- package/dist/adapter/downstream/terminalToSdkResult.js +11 -4
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +5 -2
- package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -0
- package/dist/adapter/runStream.js +29 -13
- package/dist/adapter/types.d.ts +4 -4
- package/dist/agentSession/backgroundView.js +2 -1
- package/dist/agentSession/engineAgentRegistryRead.d.ts +2 -2
- package/dist/agentSession/engineAgentRegistryRead.js +12 -13
- package/dist/agentsWireCaps.d.ts +8 -8
- package/dist/agentsWireCaps.js +168 -175
- package/dist/approvalDecidedByCapability.d.ts +0 -4
- package/dist/approvalDecidedByCapability.js +0 -6
- package/dist/approvalsStreamLiveCapability.d.ts +0 -4
- package/dist/approvalsStreamLiveCapability.js +0 -6
- package/dist/assistantInbox.d.ts +34 -0
- package/dist/assistantInbox.js +118 -0
- package/dist/centerWiringCapability.js +9 -1
- package/dist/clientSlice.d.ts +0 -9
- package/dist/clientSlice.js +0 -3
- package/dist/cloudConfigWireCaps.d.ts +4 -2
- package/dist/cloudConfigWireCaps.js +81 -61
- package/dist/compensations.js +1 -1
- package/dist/connectorStatus.d.ts +21 -0
- package/dist/connectorStatus.js +112 -0
- package/dist/controlRouter.d.ts +14 -1
- package/dist/controlRouter.js +68 -17
- package/dist/coreValuePorts.d.ts +3 -3
- package/dist/decideReceipt.d.ts +2 -2
- package/dist/decideReceipt.js +5 -2
- package/dist/declared/supportFloorDeclared.js +3 -3
- package/dist/deviceExecutorManagementCapability.d.ts +0 -4
- package/dist/deviceExecutorManagementCapability.js +0 -6
- package/dist/deviceRebind.d.ts +49 -1
- package/dist/deviceRebind.js +221 -3
- package/dist/displayUntrusted.d.ts +1 -0
- package/dist/displayUntrusted.js +250 -76
- package/dist/engineAgentAbsence.d.ts +1 -1
- package/dist/engineAgentPanelStore.d.ts +5 -4
- package/dist/engineAgentPanelStore.js +3 -12
- package/dist/engineCapReader.d.ts +0 -4
- package/dist/engineCapReader.js +1 -5
- package/dist/engineCapsCache.d.ts +4 -0
- package/dist/engineCapsCache.js +17 -12
- package/dist/engineClosedSets.d.ts +1 -0
- package/dist/engineClosedSets.js +4 -1
- package/dist/engineErrorCodes.d.ts +9 -2
- package/dist/engineErrorCodes.js +9 -1
- package/dist/engineIdentity.d.ts +1 -0
- package/dist/engineIdentity.js +8 -0
- package/dist/engineRowRef.d.ts +28 -0
- package/dist/engineRowRef.js +111 -0
- package/dist/engineSessionMessages.d.ts +67 -0
- package/dist/engineSessionMessages.js +364 -0
- package/dist/engineToolDirectory.d.ts +0 -9
- package/dist/engineToolDirectory.js +1 -94
- package/dist/engineUrl.d.ts +18 -0
- package/dist/engineUrl.js +52 -0
- package/dist/env/uiLanguage.d.ts +9 -0
- package/dist/env/uiLanguage.js +35 -0
- package/dist/eventLeg.d.ts +92 -0
- package/dist/eventLeg.js +285 -0
- package/dist/executionLaneCapability.d.ts +0 -4
- package/dist/executionLaneCapability.js +3 -16
- package/dist/fileHistoryCaptureCapability.d.ts +0 -4
- package/dist/fileHistoryCaptureCapability.js +5 -7
- package/dist/fleet/fleetLedger.d.ts +0 -3
- package/dist/fleet/fleetLedger.js +21 -36
- package/dist/fleet/fleetProjection.d.ts +2 -2
- package/dist/fleet/fleetProjection.js +10 -8
- package/dist/frameBinding.d.ts +5 -0
- package/dist/frameBinding.js +23 -0
- package/dist/gateFailureCodes.d.ts +2 -0
- package/dist/gateFailureCodes.js +15 -0
- package/dist/gateOutcome.d.ts +3 -1
- package/dist/gateOutcome.js +25 -6
- package/dist/gateVocabulary.d.ts +5 -0
- package/dist/gateVocabulary.js +64 -2
- package/dist/generated/engineAskFaceTables.js +2 -2
- package/dist/generated/engineFactTables.d.ts +6 -3
- package/dist/generated/engineFactTables.js +22 -4
- package/dist/generated/engineNoticeTables.js +9 -0
- package/dist/generated/toolNameTables.d.ts +1 -0
- package/dist/generated/toolNameTables.js +1 -0
- package/dist/hitl/allowRuleShadow.d.ts +6 -1
- package/dist/hitl/allowRuleShadow.js +21 -17
- package/dist/hitl/approvalCardReading.d.ts +15 -0
- package/dist/hitl/approvalCardReading.js +154 -0
- package/dist/hitl/approvalCardReasons.d.ts +13 -0
- package/dist/hitl/approvalCardReasons.js +120 -0
- package/dist/hitl/approvalHolds.d.ts +47 -15
- package/dist/hitl/approvalHolds.js +134 -28
- package/dist/hitl/approvalResolution.d.ts +51 -26
- package/dist/hitl/approvalResolution.js +86 -122
- package/dist/hitl/approvalsFeed.js +6 -18
- package/dist/hitl/askClassPair.d.ts +2 -0
- package/dist/hitl/askClassPair.js +1 -0
- package/dist/hitl/askGateWire.d.ts +20 -2
- package/dist/hitl/askGateWire.js +49 -1
- package/dist/hitl/askParkRowRouting.d.ts +5 -0
- package/dist/hitl/askParkRowRouting.js +32 -1
- package/dist/hitl/askSettlementLine.d.ts +18 -0
- package/dist/hitl/askSettlementLine.js +79 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
- package/dist/hitl/automaticAnswerVerdict.js +21 -5
- package/dist/hitl/crashConverged.d.ts +11 -20
- package/dist/hitl/crashConverged.js +56 -0
- package/dist/hitl/crashConvergedNotice.d.ts +3 -1
- package/dist/hitl/crashConvergedNotice.js +48 -11
- package/dist/hitl/decisionConflict.js +3 -10
- package/dist/hitl/decisionRefusal.d.ts +40 -1
- package/dist/hitl/decisionRefusal.js +199 -42
- package/dist/hitl/frameRouter.d.ts +5 -2
- package/dist/hitl/frameRouter.js +20 -16
- package/dist/hitl/gateLedger.d.ts +2 -0
- package/dist/hitl/gateLedger.js +50 -13
- package/dist/hitl/hitlBridge.d.ts +1 -2
- package/dist/hitl/hitlBridge.js +10 -7
- package/dist/hitl/livePendingAsk.d.ts +3 -3
- package/dist/hitl/livePendingAsk.js +7 -18
- package/dist/hitl/parkResolver.d.ts +2 -24
- package/dist/hitl/parkResolver.js +61 -97
- package/dist/hitl/persistedRulesWire.d.ts +24 -0
- package/dist/hitl/persistedRulesWire.js +122 -2
- package/dist/hitl/planReviewWire.d.ts +26 -13
- package/dist/hitl/planReviewWire.js +112 -78
- package/dist/hitl/refusedToolNameProse.d.ts +0 -1
- package/dist/hitl/refusedToolNameProse.js +0 -3
- package/dist/hitl/replayedApproval.d.ts +4 -3
- package/dist/hitl/replayedApproval.js +7 -4
- package/dist/hitl/sessionPolicyDeliverable.js +2 -4
- package/dist/hitl/sessionPolicyWire.d.ts +32 -20
- package/dist/hitl/sessionPolicyWire.js +162 -52
- package/dist/hitl/standingRows.d.ts +20 -2
- package/dist/hitl/standingRows.js +69 -8
- package/dist/hitl/toolApprovalWire.d.ts +87 -152
- package/dist/hitl/toolApprovalWire.js +591 -380
- package/dist/hookMatcherVerdict.d.ts +6 -1
- package/dist/hookMatcherVerdict.js +14 -7
- package/dist/hooksWireCaps.d.ts +2 -2
- package/dist/hooksWireCaps.js +59 -55
- package/dist/i18n/messages.en.d.ts +121 -0
- package/dist/i18n/messages.en.js +119 -0
- package/dist/i18n/messages.zh.d.ts +3 -0
- package/dist/i18n/messages.zh.js +120 -0
- package/dist/i18n/translate.d.ts +25 -0
- package/dist/i18n/translate.js +127 -0
- package/dist/index.d.ts +53 -20
- package/dist/index.js +38 -11
- package/dist/inheritEnvCapability.d.ts +31 -0
- package/dist/inheritEnvCapability.js +104 -0
- package/dist/liveModelCatalog.d.ts +1 -2
- package/dist/liveModelCatalog.js +2 -6
- package/dist/liveQuestionStore.d.ts +2 -21
- package/dist/mcpLiveness.d.ts +2 -1
- package/dist/mcpProbeWire.d.ts +6 -1
- package/dist/mcpProbeWire.js +1 -1
- package/dist/mcpWireCaps.d.ts +13 -2
- package/dist/mcpWireCaps.js +48 -0
- package/dist/memoryEntriesWire.d.ts +4 -9
- package/dist/model/modelSupplyRules.d.ts +0 -2
- package/dist/model/modelSupplyRules.js +3 -12
- package/dist/model/modelsPolicy.d.ts +0 -5
- package/dist/model/modelsPolicy.js +6 -36
- package/dist/model/subagentModelWord.d.ts +31 -0
- package/dist/model/subagentModelWord.js +261 -0
- package/dist/notifications.d.ts +6 -0
- package/dist/notifications.js +71 -23
- package/dist/outputStyleSeatCapability.d.ts +0 -3
- package/dist/outputStyleSeatCapability.js +0 -3
- package/dist/ownKey.d.ts +15 -0
- package/dist/ownKey.js +60 -3
- package/dist/panelReduce.d.ts +15 -9
- package/dist/panelReduce.js +680 -549
- package/dist/panelStateHolder.d.ts +3 -7
- package/dist/panelStateHolder.js +14 -130
- package/dist/peerLaneCapability.d.ts +0 -4
- package/dist/peerLaneCapability.js +0 -6
- package/dist/permissionRulesWriteCapability.d.ts +0 -4
- package/dist/permissionRulesWriteCapability.js +0 -6
- package/dist/permissionWireCaps.d.ts +2 -2
- package/dist/permissionWireCaps.js +2 -0
- package/dist/permissionsAllowGrantCapability.d.ts +0 -4
- package/dist/permissionsAllowGrantCapability.js +0 -6
- package/dist/permissionsDeclaration.d.ts +0 -11
- package/dist/permissionsDeclaration.js +60 -53
- package/dist/pluginHooksWire.d.ts +1 -1
- package/dist/pluginHooksWire.js +3 -6
- package/dist/preemptWire.d.ts +44 -0
- package/dist/preemptWire.js +83 -0
- package/dist/printToolResultFrame.js +15 -4
- package/dist/replayBody.d.ts +3 -0
- package/dist/replayBody.js +14 -0
- package/dist/request/taskRequest.d.ts +0 -1
- package/dist/request/taskRequest.js +0 -3
- package/dist/resumeRefusalCopy.d.ts +2 -1
- package/dist/resumeRefusalCopy.js +38 -2
- package/dist/retryStatus.d.ts +8 -0
- package/dist/retryStatus.js +106 -1
- package/dist/rewindArchiveCapability.d.ts +0 -4
- package/dist/rewindArchiveCapability.js +2 -10
- package/dist/rewindArchiveReading.d.ts +0 -1
- package/dist/rewindArchiveReading.js +0 -3
- package/dist/runCancelContext.d.ts +2 -1
- package/dist/runCancelContext.js +4 -2
- package/dist/runReattachPolicy.d.ts +31 -0
- package/dist/runReattachPolicy.js +46 -0
- package/dist/runTerminal.d.ts +3 -6
- package/dist/runTerminal.js +8 -9
- package/dist/sandboxWire.d.ts +3 -1
- package/dist/sandboxWire.js +76 -27
- package/dist/scenarioWire.js +1 -1
- package/dist/seam.d.ts +24 -2
- package/dist/seam.js +15 -3
- package/dist/seatContract.d.ts +53 -10
- package/dist/seatContract.js +132 -81
- package/dist/sessionPolicyCapability.d.ts +0 -4
- package/dist/sessionPolicyCapability.js +0 -6
- package/dist/sessionSyncFileHistoryCapability.d.ts +0 -3
- package/dist/sessionSyncFileHistoryCapability.js +0 -3
- package/dist/skillToolListsCapability.d.ts +0 -3
- package/dist/skillToolListsCapability.js +0 -3
- package/dist/sqlEngineCapability.d.ts +0 -4
- package/dist/sqlEngineCapability.js +0 -6
- package/dist/steerPriorityCapability.d.ts +0 -3
- package/dist/steerPriorityCapability.js +0 -3
- package/dist/streamDrainingLegs.d.ts +4 -0
- package/dist/streamDrainingLegs.js +6 -1
- package/dist/subagent/engineCompactWire.d.ts +0 -1
- package/dist/subagent/engineCompactWire.js +7 -5
- package/dist/subagent/engineTaskHandleWire.js +4 -7
- package/dist/subagentContentStore.js +1 -11
- package/dist/submitReopen.d.ts +24 -0
- package/dist/submitReopen.js +75 -0
- package/dist/terminalStatuses.d.ts +3 -0
- package/dist/terminalStatuses.js +10 -2
- package/dist/toolResult.d.ts +10 -1
- package/dist/toolResult.js +34 -6
- package/dist/toolRoster.d.ts +0 -2
- package/dist/toolRoster.js +0 -2
- package/dist/turnFailurePresentation.d.ts +28 -0
- package/dist/turnFailurePresentation.js +205 -0
- package/dist/userInvokedSkillsCapability.d.ts +0 -3
- package/dist/userInvokedSkillsCapability.js +0 -3
- package/dist/webSearchBackendCapability.d.ts +0 -4
- package/dist/webSearchBackendCapability.js +0 -6
- package/dist/wireErrorTriage.js +5 -24
- package/dist/wireFailureShape.d.ts +62 -5
- package/dist/wireFailureShape.js +105 -5
- package/dist/wireRefusalCopy.d.ts +12 -2
- package/dist/wireRefusalCopy.js +50 -52
- package/dist/workflowsJournalCapability.d.ts +0 -4
- package/dist/workflowsJournalCapability.js +0 -6
- package/dist/writeProtectionCapability.d.ts +0 -4
- package/dist/writeProtectionCapability.js +0 -6
- package/docs/INTEGRATION-CLIENTS.md +1253 -46
- package/package.json +5 -4
- package/dist/attachmentSeatCapability.d.ts +0 -12
- package/dist/attachmentSeatCapability.js +0 -23
- package/dist/hitl/approvalOutcomeNote.d.ts +0 -10
- package/dist/hitl/approvalOutcomeNote.js +0 -29
- package/dist/sessionReadRedactionCapability.d.ts +0 -13
- package/dist/sessionReadRedactionCapability.js +0 -35
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import { APPROVAL_CARD_PORT_KEY_MISMATCH_REASON, DEFAULT_DENY_REASON,
|
|
1
|
+
import { APPROVAL_CARD_PORT_KEY_MISMATCH_REASON, DEFAULT_DENY_REASON, HitlBridge, NO_APPROVAL_CARD_PORT_REASON, denyReasonForWire, findPendingForTask, readWireErrorCodeReading, } from './hitlBridge.js';
|
|
2
2
|
import { askParkRowArm } from './askParkRowRouting.js';
|
|
3
3
|
import { parkGateArmOf } from './frameRouter.js';
|
|
4
4
|
import { seatSessionRowOf } from './standingRows.js';
|
|
5
5
|
import { hostLog } from '../host.js';
|
|
6
6
|
import { guardedRead, readAskOrigin, readOwnDataNonEmptyString, readOwnDataValue, readOwnNonEmptyString } from '../ownKey.js';
|
|
7
|
-
import { readAskClassPair } from './askClassPair.js';
|
|
8
|
-
import {
|
|
7
|
+
import { DEPLOYMENT_LIST_ASK_CLASSES, readAskClassPair } from './askClassPair.js';
|
|
8
|
+
import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
|
|
9
|
+
import { approvalWindowKeysOf, readRequiresRealApproval } from './approvalCardReading.js';
|
|
10
|
+
import { readAllowRuleShadowFromWire, retiredAllowRuleShadowKeyPresent, RETIRED_SHADOW_EVIDENCE_LABEL } from './allowRuleShadow.js';
|
|
9
11
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
10
12
|
import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
11
13
|
import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
|
|
12
14
|
import { INTERRUPT_DENY_REASON, observeCancelByDeny, surfaceApprovalParking, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
|
|
13
15
|
import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
|
|
14
16
|
import { displayUntrusted } from '../displayUntrusted.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
+
import { frameBindingOf } from '../frameBinding.js';
|
|
18
|
+
import { approvalDecisionHolds, approvalHoldFor, awaitApprovalHolder, claimApprovalCardFor, holdLiveCard, joinLiveCard, liveCardHoldOf, clearSettledApprovals, noteHolderSettledApproval, settledApprovalOf, settledForDurableLane, settledLiveCardOutcome } from './approvalHolds.js';
|
|
19
|
+
import { joinedResolution, resolutionOfDecisionFailure } from './approvalResolution.js';
|
|
20
|
+
import { parkRowDeadlineState } from './parkExpiryJudgements.js';
|
|
17
21
|
import { isApprovalMandateWord } from '../gateVocabulary.js';
|
|
18
22
|
import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
|
|
19
23
|
import { approvalDecidedByAdmitted } from '../approvalDecidedByCapability.js';
|
|
20
24
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { readDecisionRefusalAsk } from './decisionRefusal.js';
|
|
24
|
-
import { DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
25
|
+
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
26
|
+
import { DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED } from '../engineErrorCodes.js';
|
|
25
27
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
26
28
|
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, ASK_ORIGINS, TOOL_APPROVAL_CARD_WORDS, } from '@sema-agent/sdk';
|
|
27
29
|
export function toolNameIsFsWrite(name) {
|
|
@@ -82,6 +84,14 @@ export function readApprovalDenySettledBy(decision) {
|
|
|
82
84
|
: undefined;
|
|
83
85
|
}
|
|
84
86
|
export const RETRACTED_CARD_DECISION_KIND = 'retracted';
|
|
87
|
+
function parkRowOf(taskId, row, gatedCallId) {
|
|
88
|
+
const d = parkRowDeadlineState(row, Date.now());
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
taskId,
|
|
91
|
+
...(gatedCallId !== undefined && gatedCallId !== '' ? { gatedCallId } : {}),
|
|
92
|
+
...(d.kind !== 'none' ? { deadlineMs: d.deadlineMs } : {}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
85
95
|
const cardPortByKey = createSessionSlot();
|
|
86
96
|
const cardPortMissesByKey = new Map();
|
|
87
97
|
function bumpCardPortMiss(sessionKey) {
|
|
@@ -149,14 +159,6 @@ export function rememberRefusalOf(error) {
|
|
|
149
159
|
function rememberRefusalIn(reading) {
|
|
150
160
|
return reading.state === 'code' ? REMEMBER_REFUSAL_CODES.find((c) => c === reading.code) : undefined;
|
|
151
161
|
}
|
|
152
|
-
function rememberFallbackKind(e) {
|
|
153
|
-
const reading = readWireErrorCodeReading(e);
|
|
154
|
-
if (rememberRefusalIn(reading) !== undefined)
|
|
155
|
-
return 'refused';
|
|
156
|
-
if (reading.state !== 'absent')
|
|
157
|
-
return undefined;
|
|
158
|
-
return readToolApprovalRespondRefusal(e)?.status === 400 ? 'legacy' : undefined;
|
|
159
|
-
}
|
|
160
162
|
function logAfterDecided(msg) {
|
|
161
163
|
try {
|
|
162
164
|
hostLog('debug', msg);
|
|
@@ -223,7 +225,7 @@ function heldDurableDecision(holdKey, readMark) {
|
|
|
223
225
|
const recent = approvalDecisionHolds.settled.get(holdKey);
|
|
224
226
|
return recent !== undefined && recent.at > readMark ? recent.hold : undefined;
|
|
225
227
|
}
|
|
226
|
-
function holdDurableDecision(holdKey, callerSignal, decide) {
|
|
228
|
+
function holdDurableDecision(holdKey, identity, callerSignal, decide) {
|
|
227
229
|
const h = approvalDecisionHolds;
|
|
228
230
|
const interruption = new AbortController();
|
|
229
231
|
let settled = false;
|
|
@@ -241,7 +243,7 @@ function holdDurableDecision(holdKey, callerSignal, decide) {
|
|
|
241
243
|
resolveOutcome = resolve;
|
|
242
244
|
rejectOutcome = reject;
|
|
243
245
|
});
|
|
244
|
-
const hold = { lane: 'durable', outcome, presented: false, interrupt, cardSignal: interruption.signal };
|
|
246
|
+
const hold = { lane: 'durable', identity: Object.freeze({ ...identity }), phase: 'card', outcome, presented: false, interrupt, cardSignal: interruption.signal };
|
|
245
247
|
h.inFlight.set(holdKey, hold);
|
|
246
248
|
const release = () => {
|
|
247
249
|
if (h.inFlight.get(holdKey) === hold)
|
|
@@ -283,7 +285,7 @@ async function joinDurableDecision(hold, deps, signal) {
|
|
|
283
285
|
catch {
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
|
-
return
|
|
288
|
+
return joinedResolution(o);
|
|
287
289
|
}
|
|
288
290
|
export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId, parkToolName) {
|
|
289
291
|
const decidedByReading = decidedByReadingSnapshotOf(deps);
|
|
@@ -299,19 +301,22 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
299
301
|
closeDurableRead(readMark);
|
|
300
302
|
}
|
|
301
303
|
if (!found.ok)
|
|
302
|
-
return { kind: '
|
|
304
|
+
return { kind: 'not_sent', lane: 'decide', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
|
|
303
305
|
if (held !== undefined)
|
|
304
306
|
return joinDurableDecision(held, deps, signal);
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
307
|
+
const rowIdentity = parkRowHolderIdentity(found.pending, found.gatedCallId);
|
|
308
|
+
const otherHolder = approvalHoldFor(rowIdentity, (h) => h.lane === 'durable');
|
|
309
|
+
if (otherHolder !== undefined) {
|
|
310
|
+
const wait = await awaitApprovalHolder(otherHolder, signal, 'decide');
|
|
311
|
+
if (wait.kind === 'settled')
|
|
312
|
+
return joinedResolution(wait.outcome, found.gatedCallId);
|
|
313
|
+
if (wait.kind === 'left')
|
|
314
|
+
return { kind: 'not_sent', lane: 'decide', cause: 'retracted', ...(found.gatedCallId !== undefined ? { gatedCallId: found.gatedCallId } : {}), joinedInFlight: true };
|
|
310
315
|
return surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId, parkToolName);
|
|
311
316
|
}
|
|
312
317
|
const callKey = approvalCallKey(found.gatedCallId, taskId);
|
|
313
318
|
const fetched = found;
|
|
314
|
-
return holdDurableDecision(durableHoldKey(fetched.pending, callKey), signal, async (hold) => {
|
|
319
|
+
return holdDurableDecision(durableHoldKey(fetched.pending, callKey), rowIdentity, signal, async (hold) => {
|
|
315
320
|
const slot = {};
|
|
316
321
|
const out = await presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, fetched, decidedByReading, callKey, hold, slot);
|
|
317
322
|
noteCallOutcome(slot, out);
|
|
@@ -319,34 +324,17 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
319
324
|
});
|
|
320
325
|
}
|
|
321
326
|
function parkRowHolderIdentity(row, gatedCallId) {
|
|
327
|
+
const askSig = carrierAskSig(row);
|
|
322
328
|
return {
|
|
323
329
|
...(typeof row.taskId === 'string' && row.taskId !== '' ? { taskId: row.taskId } : {}),
|
|
324
330
|
...(gatedCallId !== undefined && gatedCallId !== '' ? { toolCallId: gatedCallId } : {}),
|
|
325
331
|
...(typeof row.sessionId === 'string' && row.sessionId !== '' ? { sessionId: row.sessionId } : {}),
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
function fsOutcomeOfJoinedLiveCard(o, gatedCallId) {
|
|
329
|
-
if (o.aborted === true)
|
|
330
|
-
return { kind: 'aborted', gatedCallId, joinedInFlight: true };
|
|
331
|
-
if (o.decision === 'unresolved')
|
|
332
|
-
return { kind: 'retracted', gatedCallId, joinedInFlight: true };
|
|
333
|
-
return {
|
|
334
|
-
kind: 'decided',
|
|
335
|
-
gatedCallId,
|
|
336
|
-
...(o.decision === 'deny'
|
|
337
|
-
? {
|
|
338
|
-
denied: true,
|
|
339
|
-
...(o.denySettledBy !== undefined ? { denySettledBy: o.denySettledBy } : {}),
|
|
340
|
-
...(o.denyReason !== undefined ? { denyReason: o.denyReason } : {}),
|
|
341
|
-
}
|
|
342
|
-
: {}),
|
|
343
|
-
...(o.denyAttributionDropped === true ? { denyAttributionDropped: true } : {}),
|
|
344
|
-
...(o.denyAttributionRenamed === true ? { denyAttributionRenamed: true } : {}),
|
|
345
|
-
joinedInFlight: true,
|
|
332
|
+
...(askSig !== undefined ? { askSig } : {}),
|
|
346
333
|
};
|
|
347
334
|
}
|
|
348
335
|
async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, found, decidedByReading, callKey, hold, ledgerSlot) {
|
|
349
336
|
const { pending, gatedCallId } = found;
|
|
337
|
+
const at = { lane: 'decide', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
350
338
|
const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
|
|
351
339
|
const streamArgs = gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined;
|
|
352
340
|
const rowInput = pending.input;
|
|
@@ -359,7 +347,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
359
347
|
: undefined;
|
|
360
348
|
const args = streamArgs !== undefined && streamArgs !== null ? streamArgs : rowArgsGap !== undefined ? {} : rowInput;
|
|
361
349
|
if (typeof args !== 'object' || args === null) {
|
|
362
|
-
return { kind: '
|
|
350
|
+
return { kind: 'not_sent', ...at, cause: 'no_input', detail: 'gate has no tool input payload' };
|
|
363
351
|
}
|
|
364
352
|
const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
|
|
365
353
|
const ruleOffersReadOnly = readRuleOffers(pending.ruleOffers);
|
|
@@ -381,7 +369,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
381
369
|
}
|
|
382
370
|
: {}),
|
|
383
371
|
...(pending.governanceForced === true ? { governanceForced: true } : {}),
|
|
384
|
-
...(pending
|
|
372
|
+
...(readRequiresRealApproval(pending) ? { requiresRealApproval: true } : {}),
|
|
385
373
|
...(hasOwnStrictTrue(pending, 'mandated') ? { mandated: true } : {}),
|
|
386
374
|
...((w) => (w !== undefined ? { mandate: w } : {}))(readApprovalMandate(pending)),
|
|
387
375
|
...((o) => (o !== undefined ? { origin: o } : {}))(readAskOrigin(pending)),
|
|
@@ -402,92 +390,81 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
402
390
|
: {}),
|
|
403
391
|
allowSessionCarried: true,
|
|
404
392
|
grantBinding: readApprovalGrantBinding(pending),
|
|
393
|
+
parkRow: parkRowOf(taskId, pending, gatedCallId),
|
|
405
394
|
}));
|
|
395
|
+
hold.phase = 'respond';
|
|
406
396
|
if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
|
|
407
397
|
hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
|
|
408
398
|
surfaceEditRefusedOnBlindAsk();
|
|
409
|
-
return { kind: '
|
|
399
|
+
return { kind: 'not_sent', ...at, cause: 'edit_refused', detail: PARK_EDIT_REFUSED_REASON };
|
|
410
400
|
}
|
|
411
401
|
const decideSpan = bridge.openDecideOperation();
|
|
412
402
|
const decideOpts = { ...(signal ? { signal } : {}), operation: decideSpan };
|
|
413
403
|
try {
|
|
414
404
|
switch (card.kind) {
|
|
415
405
|
case 'failed':
|
|
416
|
-
return { kind: '
|
|
406
|
+
return { kind: 'not_sent', ...at, cause: 'card_unavailable', ...(typeof card.reason === 'string' && card.reason !== '' ? { detail: card.reason } : {}) };
|
|
417
407
|
case 'retracted':
|
|
418
408
|
hostLog('debug', `liveHitlAskWire: approval card retracted without a decision for ${gatedCallId ?? '(no call)'} — nothing sent`);
|
|
419
|
-
return { kind: '
|
|
409
|
+
return { kind: 'not_sent', ...at, cause: 'retracted' };
|
|
420
410
|
case 'aborted':
|
|
421
411
|
{
|
|
422
412
|
const cancelDeny = bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending);
|
|
423
413
|
hold.trailing = cancelDeny;
|
|
424
414
|
observeCancelByDeny(cancelDeny, taskId);
|
|
425
415
|
}
|
|
426
|
-
return { kind: '
|
|
416
|
+
return { kind: 'unsettled', ...at, cause: 'interrupted' };
|
|
427
417
|
case 'allow':
|
|
428
418
|
try {
|
|
429
419
|
let rememberRefused = false;
|
|
430
|
-
|
|
420
|
+
const approveBody = answerPayloadFor(card, 'decide');
|
|
421
|
+
const { remember: _remember, ...plainApproveBody } = approveBody;
|
|
422
|
+
void _remember;
|
|
423
|
+
emitAnswerCommitted(callKey, 'decide', card, approveBody);
|
|
424
|
+
if (approveBody.remember === 'session') {
|
|
431
425
|
let sessionReceipt;
|
|
432
426
|
let sessionDecided = false;
|
|
433
427
|
try {
|
|
434
|
-
const raw = await bridge.decideTool(
|
|
428
|
+
const raw = await bridge.decideTool(approveBody, gatedCallId, decideOpts, pending);
|
|
435
429
|
sessionReceipt = readDecideReceipt(raw);
|
|
436
430
|
sessionDecided = true;
|
|
437
431
|
}
|
|
438
432
|
catch (e) {
|
|
439
|
-
|
|
440
|
-
if (fallbackKind === undefined)
|
|
433
|
+
if (rememberRefusalOf(e) === undefined)
|
|
441
434
|
throw e;
|
|
442
|
-
rememberRefused =
|
|
435
|
+
rememberRefused = true;
|
|
443
436
|
logAfterDecided(`liveToolApprovalWire: decide(approve+remember) refused the remember part (${String(e)}) — falling back to plain approve`);
|
|
444
437
|
}
|
|
445
438
|
if (sessionDecided) {
|
|
446
439
|
if (sessionReceipt?.rememberApplied === false)
|
|
447
440
|
surfaceRememberNotApplied();
|
|
448
|
-
return { kind: 'decided',
|
|
441
|
+
return { kind: 'decided', ...at, decision: 'allow', ...(sessionReceipt !== undefined ? { receipt: sessionReceipt } : {}) };
|
|
449
442
|
}
|
|
450
443
|
}
|
|
451
|
-
const raw = await bridge.decideTool(
|
|
444
|
+
const raw = await bridge.decideTool(plainApproveBody, gatedCallId, decideOpts, pending);
|
|
452
445
|
const receipt = readDecideReceipt(raw);
|
|
453
446
|
if (rememberRefused)
|
|
454
447
|
surfaceRememberNotApplied();
|
|
455
|
-
return { kind: 'decided',
|
|
448
|
+
return { kind: 'decided', ...at, decision: 'allow', ...(receipt !== undefined ? { receipt } : {}) };
|
|
456
449
|
}
|
|
457
450
|
catch (e) {
|
|
458
|
-
const currentPending = readDecideCurrentPending(e);
|
|
459
|
-
const wireCodes = readWireCodes(e);
|
|
460
|
-
const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
|
|
461
|
-
const safetyCode = hitlSafetyCodeOf(e);
|
|
462
451
|
const failureTexts = wireFailureTextsOf(e);
|
|
463
452
|
hostLog('debug', `liveToolApprovalWire: decide(approve) failed: ${failureTexts.log}`);
|
|
464
|
-
return {
|
|
465
|
-
kind: 'failed',
|
|
466
|
-
stage: 'decide',
|
|
467
|
-
gatedCallId,
|
|
468
|
-
reason: `decide(approve) failed: ${failureTexts.reason}${decideFailureNote(e, 'approve')}`,
|
|
469
|
-
...(thrownIsInstanceOf(e, DecideTransportRetryExhaustedError) ? { retryExhausted: true } : {}),
|
|
470
|
-
...(currentPending !== undefined ? { currentPending } : {}),
|
|
471
|
-
...wireCodes,
|
|
472
|
-
...(wireRetriable ? { retriable: true } : {}),
|
|
473
|
-
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
474
|
-
};
|
|
453
|
+
return resolutionOfDecisionFailure(e, { lane: 'decide', route: 'approval_decide', gatedCallId, detail: `decide(approve) failed: ${failureTexts.reason}${decideFailureNote(e, 'approve')}` });
|
|
475
454
|
}
|
|
476
455
|
case 'deny': {
|
|
477
456
|
const denySettledByForWire = readApprovalDenySettledBy(card);
|
|
478
457
|
const attributionKeys = decisionAttributionKeyOf(decidedByReading, denySettledByForWire);
|
|
479
|
-
const denyReasonSnapshot = typeof
|
|
458
|
+
const denyReasonSnapshot = ((r) => (typeof r === 'string' ? r : undefined))(card.reason);
|
|
480
459
|
let lastSendCarriedAttribution = false;
|
|
481
460
|
try {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
reason: denyReasonForWire(denyReasonSnapshot, `task ${taskId}`) ?? DEFAULT_DENY_REASON,
|
|
485
|
-
...attributionKeys,
|
|
486
|
-
};
|
|
461
|
+
const denyPayload = { decision: 'deny', reason: answerPayloadFor({ kind: 'deny', ...(denyReasonSnapshot !== undefined ? { reason: denyReasonSnapshot } : {}) }, 'decide').reason };
|
|
462
|
+
const denyBody = { ...denyPayload, ...attributionKeys };
|
|
487
463
|
let attributionDropped = false;
|
|
488
464
|
let attributionRenamed = false;
|
|
489
465
|
let raw;
|
|
490
466
|
lastSendCarriedAttribution = carriesAttributionKey(attributionKeys);
|
|
467
|
+
emitAnswerCommitted(callKey, 'decide', card, denyPayload);
|
|
491
468
|
try {
|
|
492
469
|
raw = await bridge.decideTool(denyBody, gatedCallId, decideOpts, pending);
|
|
493
470
|
}
|
|
@@ -527,39 +504,25 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
527
504
|
}
|
|
528
505
|
}
|
|
529
506
|
const receipt = readDecideReceipt(raw);
|
|
530
|
-
const
|
|
531
|
-
const denyReason = denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== '' ? denyReasonSnapshot : undefined;
|
|
507
|
+
const deny = denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed);
|
|
532
508
|
return {
|
|
533
509
|
kind: 'decided',
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
...(
|
|
537
|
-
...(denyReason !== undefined ? { denyReason } : {}),
|
|
538
|
-
...(attributionDropped ? { denyAttributionDropped: true } : {}),
|
|
539
|
-
...(attributionRenamed ? { denyAttributionRenamed: true } : {}),
|
|
510
|
+
...at,
|
|
511
|
+
decision: 'deny',
|
|
512
|
+
...(deny !== undefined ? { deny } : {}),
|
|
540
513
|
...(receipt !== undefined ? { receipt } : {}),
|
|
541
514
|
};
|
|
542
515
|
}
|
|
543
516
|
catch (e) {
|
|
544
|
-
const currentPending = readDecideCurrentPending(e);
|
|
545
|
-
const wireCodes = readWireCodes(e);
|
|
546
|
-
const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
|
|
547
|
-
const safetyCode = hitlSafetyCodeOf(e);
|
|
548
|
-
const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
|
|
549
517
|
const failureTexts = wireFailureTextsOf(e);
|
|
550
518
|
hostLog('debug', `liveToolApprovalWire: decide(deny) failed: ${failureTexts.log}`);
|
|
551
|
-
return {
|
|
552
|
-
|
|
553
|
-
|
|
519
|
+
return resolutionOfDecisionFailure(e, {
|
|
520
|
+
lane: 'decide',
|
|
521
|
+
route: 'approval_decide',
|
|
554
522
|
gatedCallId,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
...wireCodes,
|
|
559
|
-
...(wireRetriable ? { retriable: true } : {}),
|
|
560
|
-
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
561
|
-
...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
|
|
562
|
-
};
|
|
523
|
+
detail: `decide(deny) failed: ${failureTexts.reason}${decideFailureNote(e, 'deny')}`,
|
|
524
|
+
attributionCarried: lastSendCarriedAttribution,
|
|
525
|
+
});
|
|
563
526
|
}
|
|
564
527
|
}
|
|
565
528
|
}
|
|
@@ -610,6 +573,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
610
573
|
'probeReason',
|
|
611
574
|
'rememberRefused',
|
|
612
575
|
'boundGrantDigest',
|
|
576
|
+
'boundGrantAbsence',
|
|
613
577
|
'boundGrants',
|
|
614
578
|
'classifierNoRuling',
|
|
615
579
|
'ruleEvidence',
|
|
@@ -621,7 +585,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
621
585
|
'outcome',
|
|
622
586
|
'cause',
|
|
623
587
|
];
|
|
624
|
-
const _mirrorPin = [true
|
|
588
|
+
const _mirrorPin = [true];
|
|
625
589
|
void _mirrorPin;
|
|
626
590
|
const MANDATED_ABSENCE_WORD = 'mandated';
|
|
627
591
|
export function approvalIsMandated(v) {
|
|
@@ -686,20 +650,76 @@ function isToolApprovalDelegation(v) {
|
|
|
686
650
|
return d.agentName === undefined || typeof d.agentName === 'string';
|
|
687
651
|
}
|
|
688
652
|
export function isFromSubagent(frame) {
|
|
689
|
-
|
|
690
|
-
return true;
|
|
691
|
-
return typeof frame.sourceTaskId === 'string' && frame.sourceTaskId.length > 0;
|
|
653
|
+
return frame.fromSubagent === true;
|
|
692
654
|
}
|
|
693
|
-
const RESPOND_DECISIONS = ['allow', 'allow_session', 'deny'];
|
|
655
|
+
export const RESPOND_DECISIONS = Object.freeze(['allow', 'allow_session', 'deny']);
|
|
694
656
|
const _respondDecisionPins = [true, true];
|
|
695
657
|
void _respondDecisionPins;
|
|
696
658
|
function isRespondDecision(v) {
|
|
697
659
|
return typeof v === 'string' && RESPOND_DECISIONS.includes(v);
|
|
698
660
|
}
|
|
661
|
+
function respondDecisionOfAnswer(answer) {
|
|
662
|
+
if (answer.kind === 'allow')
|
|
663
|
+
return answer.allowSession ? 'allow_session' : 'allow';
|
|
664
|
+
return answer.kind === 'deny' ? 'deny' : undefined;
|
|
665
|
+
}
|
|
666
|
+
function respondNoteGate(reason, capable) {
|
|
667
|
+
if (reason === undefined || reason.trim() === '')
|
|
668
|
+
return { dropped: 'blank' };
|
|
669
|
+
if (!capable)
|
|
670
|
+
return { dropped: 'capability' };
|
|
671
|
+
if (reason.length > MAX_RESPOND_NOTE_CHARS)
|
|
672
|
+
return { dropped: 'oversize' };
|
|
673
|
+
return { note: reason };
|
|
674
|
+
}
|
|
675
|
+
export function answerPayloadFor(answer, lane, caps) {
|
|
676
|
+
const word = respondDecisionOfAnswer(answer);
|
|
677
|
+
if (word === undefined)
|
|
678
|
+
return undefined;
|
|
679
|
+
if (lane === 'decide') {
|
|
680
|
+
if (answer.kind !== 'allow') {
|
|
681
|
+
const reason = answer.kind === 'deny' && typeof answer.reason === 'string' ? answer.reason : undefined;
|
|
682
|
+
return Object.freeze({ decision: 'deny', reason: denyReasonForWire(reason, 'answer') ?? DEFAULT_DENY_REASON });
|
|
683
|
+
}
|
|
684
|
+
return Object.freeze({
|
|
685
|
+
decision: 'approve',
|
|
686
|
+
...(word === 'allow_session' ? { remember: 'session' } : {}),
|
|
687
|
+
...(answer.updatedInput !== undefined ? { updatedInput: answer.updatedInput } : {}),
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
if (answer.kind === 'allow')
|
|
691
|
+
return Object.freeze({ decision: word, ...(answer.updatedInput !== undefined ? { updatedInput: answer.updatedInput } : {}) });
|
|
692
|
+
const gate = respondNoteGate(answer.kind === 'deny' && typeof answer.reason === 'string' ? answer.reason : undefined, caps?.approvalDecisionNoteCapable === true);
|
|
693
|
+
return Object.freeze({ decision: word, ...('note' in gate ? { note: gate.note } : {}) });
|
|
694
|
+
}
|
|
695
|
+
const ANSWER_CARRIAGE_NONE = Object.freeze({ allowNote: 'none' });
|
|
696
|
+
export function answerCarriage(card) {
|
|
697
|
+
void card;
|
|
698
|
+
return ANSWER_CARRIAGE_NONE;
|
|
699
|
+
}
|
|
700
|
+
export function observeApprovalAnswerCommitted(listener) {
|
|
701
|
+
answerCommittedListeners.add(listener);
|
|
702
|
+
return () => {
|
|
703
|
+
answerCommittedListeners.delete(listener);
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const answerCommittedListeners = new Set();
|
|
707
|
+
function emitAnswerCommitted(callKey, lane, answer, payload) {
|
|
708
|
+
if (answerCommittedListeners.size === 0)
|
|
709
|
+
return;
|
|
710
|
+
const event = Object.freeze({ callKey, lane, answer: Object.freeze({ ...answer }), payload });
|
|
711
|
+
for (const l of [...answerCommittedListeners])
|
|
712
|
+
invokeHostCallbackIsolated('liveToolApprovalWire: answer-committed listener', () => l(event));
|
|
713
|
+
}
|
|
714
|
+
export function isToolApprovalRespondReplayed(ack) {
|
|
715
|
+
return typeof ack === 'object' && ack !== null && ack.idempotent === true;
|
|
716
|
+
}
|
|
699
717
|
export function readToolApprovalRespondAck(v) {
|
|
700
718
|
if (v === null || typeof v !== 'object')
|
|
701
719
|
return undefined;
|
|
702
720
|
const o = v;
|
|
721
|
+
if (o.idempotent === true)
|
|
722
|
+
return o.delivery === undefined ? readReplayedAck(o) : undefined;
|
|
703
723
|
if (o.delivery !== 'applied')
|
|
704
724
|
return undefined;
|
|
705
725
|
if (typeof o.approvalId !== 'string' || o.approvalId.length === 0)
|
|
@@ -773,6 +793,48 @@ export function readToolApprovalRespondParking(v) {
|
|
|
773
793
|
},
|
|
774
794
|
};
|
|
775
795
|
}
|
|
796
|
+
function replayClaimOf(raw) {
|
|
797
|
+
const approvalId = guardedRead(() => readOwnDataNonEmptyString(raw, 'approvalId'));
|
|
798
|
+
const d = guardedRead(() => readOwnDataValue(raw, 'decision'));
|
|
799
|
+
let carriesDelivery = true;
|
|
800
|
+
try {
|
|
801
|
+
carriesDelivery = typeof raw === 'object' && raw !== null && Object.hasOwn(raw, 'delivery');
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
carriesDelivery = true;
|
|
805
|
+
}
|
|
806
|
+
return { ...(approvalId !== undefined ? { approvalId } : {}), ...(d === 'allow' || d === 'deny' ? { decision: d } : {}), carriesDelivery };
|
|
807
|
+
}
|
|
808
|
+
function ownIdempotentTrue(raw) {
|
|
809
|
+
if (typeof raw !== 'object' || raw === null)
|
|
810
|
+
return false;
|
|
811
|
+
try {
|
|
812
|
+
const d = Object.getOwnPropertyDescriptor(raw, 'idempotent');
|
|
813
|
+
return d !== undefined && Object.hasOwn(d, 'value') && d.value === true;
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function readReplayedAck(o) {
|
|
820
|
+
if (typeof o.approvalId !== 'string' || o.approvalId.length === 0)
|
|
821
|
+
return undefined;
|
|
822
|
+
if (typeof o.askId !== 'string' || o.askId.length === 0)
|
|
823
|
+
return undefined;
|
|
824
|
+
if (o.decision !== 'allow' && o.decision !== 'deny')
|
|
825
|
+
return undefined;
|
|
826
|
+
if (typeof o.decidedAtMs !== 'number' || !Number.isFinite(o.decidedAtMs) || o.decidedAtMs < 0)
|
|
827
|
+
return undefined;
|
|
828
|
+
return {
|
|
829
|
+
approvalId: o.approvalId,
|
|
830
|
+
askId: o.askId,
|
|
831
|
+
idempotent: true,
|
|
832
|
+
decision: o.decision,
|
|
833
|
+
decidedAtMs: o.decidedAtMs,
|
|
834
|
+
...(typeof o.decisionNote === 'string' && o.decisionNote !== '' ? { decisionNote: o.decisionNote } : {}),
|
|
835
|
+
...(o.rememberApplied === false ? { rememberApplied: false } : {}),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
776
838
|
function isNonEmptyStringArray(v) {
|
|
777
839
|
return Array.isArray(v) && v.length > 0 && v.every(x => typeof x === 'string' && x !== '');
|
|
778
840
|
}
|
|
@@ -852,58 +914,16 @@ function readDecidedByReadingOf(holder) {
|
|
|
852
914
|
function decidedByReadingSnapshotOf(holder) {
|
|
853
915
|
return approvalDecidedByAdmitted(readDecidedByReadingOf(holder)) ? Object.freeze({ kind: 'present', value: true }) : undefined;
|
|
854
916
|
}
|
|
855
|
-
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
}
|
|
860
|
-
catch {
|
|
861
|
-
return undefined;
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
const pickMachine = (key) => guardedRead(() => (typeof err === 'object' && err !== null ? readOwnDataValue(err, key) : undefined));
|
|
865
|
-
const rawStatus = pickMachine('status');
|
|
866
|
-
const rawCode = pickMachine('errorCode');
|
|
867
|
-
const status = typeof rawStatus === 'number' && Number.isFinite(rawStatus) ? rawStatus : undefined;
|
|
868
|
-
const errorCode = typeof rawCode === 'string' && rawCode.length > 0 ? rawCode : undefined;
|
|
869
|
-
const message = refusalTextOf(err, pick('message'));
|
|
870
|
-
if (status === undefined && errorCode === undefined && message === undefined)
|
|
917
|
+
function denyFactsOf(settledBy, reasonSnapshot, dropped, renamed) {
|
|
918
|
+
const reason = reasonSnapshot !== undefined && reasonSnapshot.trim() !== '' ? reasonSnapshot : undefined;
|
|
919
|
+
const attributionKey = dropped ? 'dropped' : renamed ? 'renamed' : undefined;
|
|
920
|
+
if (settledBy === undefined && reason === undefined && attributionKey === undefined)
|
|
871
921
|
return undefined;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
const unreadable = machine !== undefined && readDecisionRefusalAsk(machine).reading === 'unreadable';
|
|
877
|
-
const rest = {
|
|
878
|
-
...(status !== undefined ? { status } : {}),
|
|
879
|
-
...(errorCode !== undefined ? { errorCode } : {}),
|
|
880
|
-
...(message !== undefined ? { message } : {}),
|
|
881
|
-
...(retriable ? { retriable: true } : {}),
|
|
882
|
-
...(cause !== undefined || field !== undefined
|
|
883
|
-
? { extra: { ...(cause !== undefined ? { cause } : {}), ...(field !== undefined ? { field } : {}) } }
|
|
884
|
-
: {}),
|
|
885
|
-
...(unreadable ? { unreadable: true } : {}),
|
|
922
|
+
return {
|
|
923
|
+
...(settledBy !== undefined ? { settledBy } : {}),
|
|
924
|
+
...(reason !== undefined ? { reason } : {}),
|
|
925
|
+
...(attributionKey !== undefined ? { attributionKey } : {}),
|
|
886
926
|
};
|
|
887
|
-
if (status !== undefined)
|
|
888
|
-
return { ...rest, status };
|
|
889
|
-
if (errorCode !== undefined)
|
|
890
|
-
return { ...rest, errorCode };
|
|
891
|
-
return { ...rest, message: message };
|
|
892
|
-
}
|
|
893
|
-
const USELESS_REFUSAL_TEXTS = new Set(['[object Object]', 'null', 'undefined', '']);
|
|
894
|
-
function refusalTextOf(err, message) {
|
|
895
|
-
if (typeof message === 'string' && message.length > 0)
|
|
896
|
-
return message;
|
|
897
|
-
if (typeof err === 'string' && err.length > 0)
|
|
898
|
-
return err;
|
|
899
|
-
let coerced;
|
|
900
|
-
try {
|
|
901
|
-
coerced = String(err);
|
|
902
|
-
}
|
|
903
|
-
catch {
|
|
904
|
-
return undefined;
|
|
905
|
-
}
|
|
906
|
-
return USELESS_REFUSAL_TEXTS.has(coerced) ? undefined : coerced;
|
|
907
927
|
}
|
|
908
928
|
const MACHINE_CODE_SHAPE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
909
929
|
function logSafeErrorCode(code) {
|
|
@@ -1021,28 +1041,67 @@ export function readApprovalBoundGrants(v) {
|
|
|
1021
1041
|
return undefined;
|
|
1022
1042
|
return Object.freeze({ skill, allowedTools, disallowedTools });
|
|
1023
1043
|
}
|
|
1044
|
+
const BOUND_GRANT_NOT_BINDABLE = 'not_bindable';
|
|
1045
|
+
const SERVER_CLASS_READ_CAP = 64;
|
|
1024
1046
|
function readApprovalGrantBinding(carrier) {
|
|
1025
1047
|
if (typeof carrier !== 'object' || carrier === null)
|
|
1026
1048
|
return 'unreadable';
|
|
1027
1049
|
try {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
}
|
|
1032
|
-
const tool = ownDataValue(carrier, 'toolName');
|
|
1033
|
-
if (typeof tool !== 'string' || tool.length === 0)
|
|
1050
|
+
const card = cardGrantBindingOf(carrier);
|
|
1051
|
+
const mayCall = serverMayReadCallLaw(carrier);
|
|
1052
|
+
if (cardGrantBindingOf(carrier) !== card)
|
|
1034
1053
|
return 'unreadable';
|
|
1035
|
-
|
|
1054
|
+
if (!mayCall)
|
|
1055
|
+
return card;
|
|
1056
|
+
const call = callGrantBindingOf(carrier);
|
|
1057
|
+
if (callGrantBindingOf(carrier) !== call || cardGrantBindingOf(carrier) !== card)
|
|
1058
|
+
return 'unreadable';
|
|
1059
|
+
return card === 'none' && call === 'none' ? 'none' : 'unreadable';
|
|
1036
1060
|
}
|
|
1037
1061
|
catch {
|
|
1038
1062
|
return 'unreadable';
|
|
1039
1063
|
}
|
|
1040
1064
|
}
|
|
1065
|
+
function cardGrantBindingOf(carrier) {
|
|
1066
|
+
if (Object.hasOwn(carrier, 'boundGrantDigest') || 'boundGrantDigest' in carrier) {
|
|
1067
|
+
const digest = ownDataValue(carrier, 'boundGrantDigest');
|
|
1068
|
+
return typeof digest === 'string' && digest.length > 0 ? 'bound' : 'unreadable';
|
|
1069
|
+
}
|
|
1070
|
+
const seen = carrier;
|
|
1071
|
+
return ownDataValue(carrier, 'boundGrantAbsence') === BOUND_GRANT_NOT_BINDABLE && seen.boundGrantAbsence === BOUND_GRANT_NOT_BINDABLE && seen.boundGrantDigest === undefined
|
|
1072
|
+
? 'none'
|
|
1073
|
+
: 'unreadable';
|
|
1074
|
+
}
|
|
1075
|
+
function callGrantBindingOf(carrier) {
|
|
1076
|
+
const tool = ownDataValue(carrier, 'toolName');
|
|
1077
|
+
return typeof tool === 'string' && tool.length > 0 && tool !== CORE_SKILL_TOOL_NAME && carrier.toolName === tool ? 'none' : 'unreadable';
|
|
1078
|
+
}
|
|
1079
|
+
function serverMayReadCallLaw(carrier) {
|
|
1080
|
+
const seen = carrier;
|
|
1081
|
+
const classes = seen.classes;
|
|
1082
|
+
if (!Array.isArray(classes))
|
|
1083
|
+
return false;
|
|
1084
|
+
const listClass = (c) => DEPLOYMENT_LIST_ASK_CLASSES.includes(c);
|
|
1085
|
+
if (listClass(seen.askClass))
|
|
1086
|
+
return true;
|
|
1087
|
+
const length = classes.length;
|
|
1088
|
+
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length > SERVER_CLASS_READ_CAP)
|
|
1089
|
+
return true;
|
|
1090
|
+
for (let i = 0; i < length; i++)
|
|
1091
|
+
if (listClass(classes[i]))
|
|
1092
|
+
return true;
|
|
1093
|
+
let n = 0;
|
|
1094
|
+
for (const c of classes) {
|
|
1095
|
+
if (listClass(c) || ++n > SERVER_CLASS_READ_CAP)
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1041
1100
|
const UNREADABLE_EVIDENCE_KEYS = Object.freeze({
|
|
1042
1101
|
frame: Object.freeze([
|
|
1043
1102
|
['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['requiresRealApproval', 'requiresRealApproval'],
|
|
1044
1103
|
['mandated', 'mandated'], ['mandate', 'mandate'], ['origin', 'origin'], ['ruleOffersAbsence', 'ruleOffersAbsence'], ['ruleOffers', 'ruleOffers'],
|
|
1045
|
-
['allowRuleShadowed', 'allowRuleShadow'], ['
|
|
1104
|
+
['allowRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
|
|
1046
1105
|
]),
|
|
1047
1106
|
row: Object.freeze([
|
|
1048
1107
|
['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['requiresRealApproval', 'requiresRealApproval'],
|
|
@@ -1051,8 +1110,9 @@ const UNREADABLE_EVIDENCE_KEYS = Object.freeze({
|
|
|
1051
1110
|
]),
|
|
1052
1111
|
v1: Object.freeze([
|
|
1053
1112
|
['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['ruleOffersAbsence', 'ruleOffersAbsence'], ['ruleOffers', 'ruleOffersReadOnly'],
|
|
1054
|
-
['allowRuleShadowed', 'allowRuleShadow'], ['
|
|
1113
|
+
['allowRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
|
|
1055
1114
|
]),
|
|
1115
|
+
generic: Object.freeze([['ruleOffersAbsence', 'ruleOffersAbsence'], ['askClass', 'askClass'], ['classes', 'classes']]),
|
|
1056
1116
|
});
|
|
1057
1117
|
function carrierHas(carrier, key) {
|
|
1058
1118
|
if (typeof carrier !== 'object' || carrier === null)
|
|
@@ -1075,17 +1135,21 @@ function v1RiskUnreadable(card) {
|
|
|
1075
1135
|
return true;
|
|
1076
1136
|
}
|
|
1077
1137
|
}
|
|
1078
|
-
function withUnreadableEvidence(carrier, leg, req) {
|
|
1079
|
-
const keys = new Set();
|
|
1138
|
+
function withUnreadableEvidence(carrier, leg, req, reported = []) {
|
|
1139
|
+
const keys = new Set(reported);
|
|
1080
1140
|
for (const [from, to] of UNREADABLE_EVIDENCE_KEYS[leg]) {
|
|
1081
1141
|
if (carrierHas(carrier, from) && !Object.hasOwn(req, to))
|
|
1082
1142
|
keys.add(from);
|
|
1083
1143
|
}
|
|
1084
1144
|
if (leg === 'row') {
|
|
1085
1145
|
const rd = ownDataValue(carrier, 'riskDescriptor');
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1146
|
+
if (carrierHas(rd, 'allowRuleShadowed') && !Object.hasOwn(req, 'allowRuleShadow'))
|
|
1147
|
+
keys.add('riskDescriptor.allowRuleShadowed');
|
|
1148
|
+
}
|
|
1149
|
+
if (leg !== 'generic' && !Object.hasOwn(req, 'allowRuleShadow')) {
|
|
1150
|
+
const shadowLeg = leg === 'row' ? 'row' : 'frame';
|
|
1151
|
+
if (retiredAllowRuleShadowKeyPresent(carrier, shadowLeg))
|
|
1152
|
+
keys.add(RETIRED_SHADOW_EVIDENCE_LABEL[shadowLeg]);
|
|
1089
1153
|
}
|
|
1090
1154
|
if (leg === 'v1' && v1RiskUnreadable(carrier))
|
|
1091
1155
|
keys.add('risk');
|
|
@@ -1130,7 +1194,18 @@ const CALL_LEDGER_MAX_CALLS = 256;
|
|
|
1130
1194
|
const CALL_LEDGER_MAX_ASKS_PER_CALL = 8;
|
|
1131
1195
|
const callCardLedger = new Map();
|
|
1132
1196
|
function askSignatureOf(req) {
|
|
1133
|
-
return
|
|
1197
|
+
return askSigOf(req.classes, req.origin, req.mandate);
|
|
1198
|
+
}
|
|
1199
|
+
function askSigOf(classes, origin, mandate) {
|
|
1200
|
+
return JSON.stringify([classes !== undefined ? [...classes] : null, origin ?? null, mandate ?? null]);
|
|
1201
|
+
}
|
|
1202
|
+
function carrierAskSig(v) {
|
|
1203
|
+
try {
|
|
1204
|
+
return askSigOf(readAskClassPair(v)?.classes, readAskOrigin(v), readApprovalMandate(v));
|
|
1205
|
+
}
|
|
1206
|
+
catch {
|
|
1207
|
+
return undefined;
|
|
1208
|
+
}
|
|
1134
1209
|
}
|
|
1135
1210
|
function priorCardView(a) {
|
|
1136
1211
|
return Object.freeze({
|
|
@@ -1154,33 +1229,65 @@ function noteCardOnCall(callId, approvalId, req) {
|
|
|
1154
1229
|
}
|
|
1155
1230
|
}
|
|
1156
1231
|
let at = asks.findIndex((a) => a.sig === sig);
|
|
1232
|
+
let supersedes;
|
|
1157
1233
|
if (at < 0) {
|
|
1158
1234
|
asks.push({ sig, ...(approvalId !== undefined ? { approvalId } : {}), ...(req.origin !== undefined ? { origin: req.origin } : {}), ...(req.mandate !== undefined ? { mandate: req.mandate } : {}) });
|
|
1159
1235
|
while (asks.length > CALL_LEDGER_MAX_ASKS_PER_CALL)
|
|
1160
1236
|
asks.shift();
|
|
1161
1237
|
at = asks.length - 1;
|
|
1162
1238
|
}
|
|
1163
|
-
else
|
|
1164
|
-
asks[at]
|
|
1239
|
+
else {
|
|
1240
|
+
supersedes = supersedesOf(asks[at]);
|
|
1241
|
+
delete asks[at].answered;
|
|
1242
|
+
delete asks[at].answerFate;
|
|
1243
|
+
if (asks[at].approvalId === undefined && approvalId !== undefined)
|
|
1244
|
+
asks[at].approvalId = approvalId;
|
|
1165
1245
|
}
|
|
1166
1246
|
const entry = asks[at];
|
|
1167
1247
|
const prior = asks.slice(0, at);
|
|
1168
|
-
return { entry, ...(prior.length > 0 ? { prior: Object.freeze(prior.map(priorCardView)) } : {}) };
|
|
1248
|
+
return { entry, ...(prior.length > 0 ? { prior: Object.freeze(prior.map(priorCardView)) } : {}), ...(supersedes !== undefined ? { supersedes } : {}) };
|
|
1249
|
+
}
|
|
1250
|
+
function supersedesOf(a) {
|
|
1251
|
+
const settled = settledApprovalOf(a.approvalId);
|
|
1252
|
+
return Object.freeze({
|
|
1253
|
+
...(a.approvalId !== undefined ? { approvalId: a.approvalId } : {}),
|
|
1254
|
+
...(settled?.parked === true ? { parked: true } : {}),
|
|
1255
|
+
...(a.answered !== undefined && a.outcome === undefined ? { priorAnswer: a.answered, priorAnswerOutcome: a.answerFate ?? 'unconfirmed' } : {}),
|
|
1256
|
+
});
|
|
1169
1257
|
}
|
|
1170
1258
|
async function surfaceCallTrackedCard(callId, approvalId, slot, req) {
|
|
1171
1259
|
if (callId === undefined || callId === '')
|
|
1172
1260
|
return surfaceApprovalCard(req);
|
|
1173
|
-
const { entry, prior } = noteCardOnCall(callId, approvalId, req);
|
|
1261
|
+
const { entry, prior, supersedes } = noteCardOnCall(callId, approvalId, req);
|
|
1174
1262
|
slot.entry = entry;
|
|
1175
|
-
|
|
1263
|
+
const decision = await surfaceApprovalCard(prior === undefined && supersedes === undefined ? req : { ...req, ...(prior !== undefined ? { priorCardsForCall: prior } : {}), ...(supersedes !== undefined ? { supersedes } : {}) });
|
|
1264
|
+
const answered = respondDecisionOfAnswer(decision);
|
|
1265
|
+
if (answered !== undefined)
|
|
1266
|
+
entry.answered = answered;
|
|
1267
|
+
return decision;
|
|
1176
1268
|
}
|
|
1177
|
-
function noteCallOutcome(slot,
|
|
1269
|
+
function noteCallOutcome(slot, r) {
|
|
1178
1270
|
const entry = slot.entry;
|
|
1179
1271
|
if (entry === undefined)
|
|
1180
1272
|
return;
|
|
1181
|
-
|
|
1182
|
-
if (r.kind === 'decided')
|
|
1273
|
+
if (r.kind === 'decided') {
|
|
1183
1274
|
entry.outcome = r.decision === 'deny' ? 'denied' : 'allowed';
|
|
1275
|
+
delete entry.answerFate;
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
const unsettledFate = {
|
|
1279
|
+
engine_refused: 'not_accepted',
|
|
1280
|
+
parking_in_progress: 'not_accepted',
|
|
1281
|
+
engine_error: 'unconfirmed',
|
|
1282
|
+
no_answer: 'unconfirmed',
|
|
1283
|
+
transport_exhausted: 'unconfirmed',
|
|
1284
|
+
interrupted: 'unconfirmed',
|
|
1285
|
+
orchestration: 'unconfirmed',
|
|
1286
|
+
unreadable: 'unconfirmed',
|
|
1287
|
+
};
|
|
1288
|
+
const next = r.kind === 'not_sent' ? 'not_sent' : Object.hasOwn(unsettledFate, r.cause) ? unsettledFate[r.cause] : 'unconfirmed';
|
|
1289
|
+
const fateRank = { not_sent: 0, not_accepted: 1, unconfirmed: 2 };
|
|
1290
|
+
entry.answerFate = entry.answerFate !== undefined && fateRank[entry.answerFate] > fateRank[next] ? entry.answerFate : next;
|
|
1184
1291
|
}
|
|
1185
1292
|
export function approvalPriorCardsDetail(card) {
|
|
1186
1293
|
const prior = ownDataValue(card, 'priorCardsForCall');
|
|
@@ -1208,6 +1315,30 @@ export function approvalPriorCardsDetail(card) {
|
|
|
1208
1315
|
}
|
|
1209
1316
|
return `This is a second approval for the same call: ${earlier}. This one is a separate check and needs its own answer.`;
|
|
1210
1317
|
}
|
|
1318
|
+
const PRIOR_ANSWER_PHRASE = Object.freeze({
|
|
1319
|
+
allow: 'Yes',
|
|
1320
|
+
allow_session: 'Yes for this session',
|
|
1321
|
+
deny: 'No',
|
|
1322
|
+
});
|
|
1323
|
+
export function approvalSupersedesDetail(card) {
|
|
1324
|
+
const sup = ownDataValue(card, 'supersedes');
|
|
1325
|
+
if (typeof sup !== 'object' || sup === null)
|
|
1326
|
+
return undefined;
|
|
1327
|
+
const parked = ownDataValue(sup, 'parked') === true;
|
|
1328
|
+
const prior = ownDataValue(sup, 'priorAnswer');
|
|
1329
|
+
const head = parked
|
|
1330
|
+
? 'This card replaces an earlier one for the same request, which moved to the engine\'s waiting queue.'
|
|
1331
|
+
: 'This card replaces an earlier one for the same request.';
|
|
1332
|
+
if (typeof prior !== 'string' || !Object.hasOwn(PRIOR_ANSWER_PHRASE, prior))
|
|
1333
|
+
return head;
|
|
1334
|
+
const said = PRIOR_ANSWER_PHRASE[prior];
|
|
1335
|
+
const fate = ownDataValue(sup, 'priorAnswerOutcome');
|
|
1336
|
+
if (fate === 'not_sent')
|
|
1337
|
+
return `${head} Your earlier answer (${said}) did not reach the engine, so answer again here.`;
|
|
1338
|
+
if (fate === 'not_accepted')
|
|
1339
|
+
return `${head} Your earlier answer (${said}) was not accepted by the engine, so answer again here.`;
|
|
1340
|
+
return `${head} Your earlier answer (${said}) was not confirmed by the engine — it may already have taken effect, so check the request before answering again here.`;
|
|
1341
|
+
}
|
|
1211
1342
|
function isNonNegativeFinite(v) {
|
|
1212
1343
|
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
1213
1344
|
}
|
|
@@ -1379,32 +1510,44 @@ function v1Bit(stamp) {
|
|
|
1379
1510
|
catch {
|
|
1380
1511
|
}
|
|
1381
1512
|
}
|
|
1382
|
-
|
|
1383
|
-
if
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const
|
|
1387
|
-
const
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1513
|
+
const SUSPENDED_ASK_ARGS_NOTE = "this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
|
|
1514
|
+
'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
|
|
1515
|
+
'Edits are not accepted on this card: there is no original input to edit';
|
|
1516
|
+
function cardFactsOf(carrier, leg, opts) {
|
|
1517
|
+
const c = carrier;
|
|
1518
|
+
const full = leg !== 'generic';
|
|
1519
|
+
const asFrame = carrier;
|
|
1520
|
+
const fromSubagent = full
|
|
1521
|
+
? (() => {
|
|
1522
|
+
try {
|
|
1523
|
+
return isFromSubagent(asFrame);
|
|
1524
|
+
}
|
|
1525
|
+
catch {
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
})()
|
|
1529
|
+
: false;
|
|
1395
1530
|
const argsPart = (() => {
|
|
1396
1531
|
try {
|
|
1397
|
-
|
|
1532
|
+
let stream;
|
|
1533
|
+
let blind = false;
|
|
1534
|
+
if (leg === 'frame' && typeof opts === 'object' && opts !== null) {
|
|
1535
|
+
stream = opts.streamArgs;
|
|
1536
|
+
blind = opts.argsUnavailable === true;
|
|
1537
|
+
}
|
|
1538
|
+
const raw = stream !== undefined && stream !== null ? stream : c.args !== undefined && c.args !== null ? c.args : undefined;
|
|
1398
1539
|
if (typeof raw === 'object' && raw !== null)
|
|
1399
|
-
return { args: raw };
|
|
1540
|
+
return blind ? { args: raw, argsUnavailable: true } : { args: raw };
|
|
1400
1541
|
const fallback = argsFallbackOf(c.message, c.argsOmitted);
|
|
1401
|
-
|
|
1542
|
+
const note = blind ? SUSPENDED_ASK_ARGS_NOTE : fallback.omittedNote;
|
|
1543
|
+
return { args: fallback.args, argsUnavailable: true, ...(note !== undefined ? { wireNote: note } : {}) };
|
|
1402
1544
|
}
|
|
1403
1545
|
catch {
|
|
1404
1546
|
return { args: {}, argsUnavailable: true };
|
|
1405
1547
|
}
|
|
1406
1548
|
})();
|
|
1407
|
-
const out = { toolName: '',
|
|
1549
|
+
const out = { toolName: '', args: argsPart.args };
|
|
1550
|
+
let realApprovalUnreadable = false;
|
|
1408
1551
|
v1Bit(() => {
|
|
1409
1552
|
if (typeof c.toolName === 'string')
|
|
1410
1553
|
out.toolName = c.toolName;
|
|
@@ -1417,44 +1560,79 @@ export function approvalCardFactsOfV1(card) {
|
|
|
1417
1560
|
if (fromSubagent)
|
|
1418
1561
|
out.workerBadge = subagentBadgeFor(asFrame);
|
|
1419
1562
|
});
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1563
|
+
if (argsPart.wireNote !== undefined)
|
|
1564
|
+
out.wireNote = argsPart.wireNote;
|
|
1565
|
+
if (argsPart.argsUnavailable === true)
|
|
1566
|
+
out.argsUnavailable = true;
|
|
1567
|
+
if (full) {
|
|
1568
|
+
v1Bit(() => {
|
|
1569
|
+
if (c.governanceForced === true)
|
|
1570
|
+
out.governanceForced = true;
|
|
1571
|
+
});
|
|
1572
|
+
v1Bit(() => {
|
|
1573
|
+
const d = c.delegation;
|
|
1574
|
+
if (fromSubagent && isToolApprovalDelegation(d))
|
|
1575
|
+
out.delegation = d;
|
|
1576
|
+
});
|
|
1577
|
+
v1Bit(() => {
|
|
1578
|
+
const offers = readRuleOffers(c.ruleOffers);
|
|
1579
|
+
if (offers === undefined)
|
|
1580
|
+
return;
|
|
1581
|
+
if (leg === 'frame')
|
|
1582
|
+
out.ruleOffers = offers;
|
|
1583
|
+
else
|
|
1584
|
+
out.ruleOffersReadOnly = offers;
|
|
1585
|
+
});
|
|
1586
|
+
v1Bit(() => {
|
|
1587
|
+
const shadow = readAllowRuleShadowFromWire(carrier, 'frame');
|
|
1588
|
+
if (shadow !== undefined)
|
|
1589
|
+
out.allowRuleShadow = shadow;
|
|
1590
|
+
});
|
|
1591
|
+
v1Bit(() => {
|
|
1592
|
+
const pc = c.probeCause;
|
|
1593
|
+
if (isWireRecordCarrier(pc))
|
|
1594
|
+
out.probeCause = pc;
|
|
1595
|
+
});
|
|
1596
|
+
if (leg === 'frame') {
|
|
1597
|
+
v1Bit(() => {
|
|
1598
|
+
const r = readProbeReason(carrier);
|
|
1599
|
+
if (r !== undefined)
|
|
1600
|
+
out.probeReason = r;
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
v1Bit(() => {
|
|
1604
|
+
if (readApprovalRememberRefused(carrier))
|
|
1605
|
+
out.rememberRefused = true;
|
|
1606
|
+
});
|
|
1607
|
+
v1Bit(() => {
|
|
1608
|
+
const re = c.ruleEvidence;
|
|
1609
|
+
if (isWireRecordCarrier(re))
|
|
1610
|
+
out.ruleEvidence = re;
|
|
1611
|
+
});
|
|
1612
|
+
try {
|
|
1613
|
+
if (readRequiresRealApproval(carrier))
|
|
1614
|
+
out.requiresRealApproval = true;
|
|
1615
|
+
}
|
|
1616
|
+
catch {
|
|
1617
|
+
realApprovalUnreadable = true;
|
|
1618
|
+
}
|
|
1619
|
+
if (leg === 'frame') {
|
|
1620
|
+
v1Bit(() => {
|
|
1621
|
+
if (hasOwnStrictTrue(carrier, 'mandated'))
|
|
1622
|
+
out.mandated = true;
|
|
1623
|
+
});
|
|
1624
|
+
v1Bit(() => {
|
|
1625
|
+
const w = readApprovalMandate(carrier);
|
|
1626
|
+
if (w !== undefined)
|
|
1627
|
+
out.mandate = w;
|
|
1628
|
+
});
|
|
1629
|
+
v1Bit(() => {
|
|
1630
|
+
const rc = readReadRootCandidate(c.readRootCandidate);
|
|
1631
|
+
if (rc !== undefined)
|
|
1632
|
+
out.readRootCandidate = rc;
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1458
1636
|
v1Bit(() => {
|
|
1459
1637
|
if (c.inputHasBidi === true)
|
|
1460
1638
|
out.inputHasBidi = true;
|
|
@@ -1469,43 +1647,115 @@ export function approvalCardFactsOfV1(card) {
|
|
|
1469
1647
|
if (dlf !== undefined)
|
|
1470
1648
|
out.denialLimitFallback = dlf;
|
|
1471
1649
|
});
|
|
1650
|
+
if (leg === 'frame') {
|
|
1651
|
+
v1Bit(() => {
|
|
1652
|
+
const o = readAskOrigin(carrier);
|
|
1653
|
+
if (o !== undefined)
|
|
1654
|
+
out.origin = o;
|
|
1655
|
+
});
|
|
1656
|
+
v1Bit(() => {
|
|
1657
|
+
const w = readApprovalCardWord(carrier);
|
|
1658
|
+
if (w !== undefined)
|
|
1659
|
+
out.approvalCard = w;
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1472
1662
|
v1Bit(() => {
|
|
1473
|
-
const pair = readAskClassPair(
|
|
1663
|
+
const pair = readAskClassPair(carrier);
|
|
1474
1664
|
if (pair !== undefined) {
|
|
1475
1665
|
out.askClass = pair.askClass;
|
|
1476
1666
|
out.classes = pair.classes;
|
|
1477
1667
|
}
|
|
1478
1668
|
});
|
|
1479
|
-
|
|
1480
|
-
|
|
1669
|
+
if (leg === 'frame') {
|
|
1670
|
+
v1Bit(() => {
|
|
1671
|
+
const g = readApprovalBoundGrants(carrier);
|
|
1672
|
+
if (g !== undefined)
|
|
1673
|
+
out.boundGrants = g;
|
|
1674
|
+
});
|
|
1675
|
+
v1Bit(() => {
|
|
1676
|
+
const w = readApprovalClassifierNoRuling(carrier);
|
|
1677
|
+
if (w !== undefined)
|
|
1678
|
+
out.classifierNoRuling = w;
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
v1Bit(() => {
|
|
1682
|
+
const msg = ownDataValue(carrier, 'message');
|
|
1683
|
+
if (typeof msg === 'string' && msg.trim() !== '')
|
|
1684
|
+
out.message = msg;
|
|
1685
|
+
});
|
|
1686
|
+
if (leg === 'generic')
|
|
1687
|
+
out.cardPayloadUnreadable = true;
|
|
1688
|
+
if (leg === 'frame')
|
|
1689
|
+
out.allowSessionCarried = true;
|
|
1690
|
+
out.grantBinding = readApprovalGrantBinding(carrier);
|
|
1691
|
+
return withUnreadableEvidence(carrier, leg, out, realApprovalUnreadable ? ['requiresRealApproval'] : []);
|
|
1692
|
+
}
|
|
1693
|
+
export function approvalCardFactsOfV1(card) {
|
|
1694
|
+
if (typeof card !== 'object' || card === null)
|
|
1695
|
+
return undefined;
|
|
1696
|
+
return Object.freeze(cardFactsOf(card, 'v1'));
|
|
1697
|
+
}
|
|
1698
|
+
export function approvalCardFactsOfFrame(frame, opts) {
|
|
1699
|
+
if (typeof frame !== 'object' || frame === null)
|
|
1700
|
+
return undefined;
|
|
1701
|
+
return Object.freeze(cardFactsOf(frame, 'frame', opts));
|
|
1702
|
+
}
|
|
1703
|
+
export function approvalCardFactsOfGeneric(card) {
|
|
1704
|
+
if (typeof card !== 'object' || card === null)
|
|
1705
|
+
return undefined;
|
|
1706
|
+
return Object.freeze(cardFactsOf(card, 'generic'));
|
|
1481
1707
|
}
|
|
1482
1708
|
const MAX_RESPOND_NOTE_CHARS = 2048;
|
|
1483
1709
|
export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamArgs, signal, lane, run) {
|
|
1710
|
+
const receivedAtMs = Date.now();
|
|
1484
1711
|
const approvalId = frame.approvalId;
|
|
1712
|
+
const entry = { frame, approvalId, identity: liveCardIdentityOf(approvalId, frame, run), respond, streamArgs, signal, lane, receivedAtMs };
|
|
1713
|
+
return enterLiveCard(entry, 0);
|
|
1714
|
+
}
|
|
1715
|
+
const LIVE_ENTRY_MAX_ROUNDS = 8;
|
|
1716
|
+
function enterLiveCard(e, round) {
|
|
1717
|
+
const { approvalId, signal } = e;
|
|
1485
1718
|
const held = liveCardHoldOf(liveFrameCallKey(approvalId));
|
|
1486
1719
|
if (held !== undefined)
|
|
1487
1720
|
return joinLiveCard(held, signal);
|
|
1488
1721
|
const settled = settledLiveCardOutcome(approvalId);
|
|
1489
1722
|
if (settled !== undefined)
|
|
1490
1723
|
return Promise.resolve(settled);
|
|
1491
|
-
|
|
1724
|
+
const other = round < LIVE_ENTRY_MAX_ROUNDS ? approvalHoldFor(e.identity, (h) => h.lane === 'live') : undefined;
|
|
1725
|
+
if (other !== undefined)
|
|
1726
|
+
return joinOtherLaneHolder(e, other, round);
|
|
1727
|
+
return holdLiveCard(e.identity, signal, async (hold) => {
|
|
1492
1728
|
const slot = {};
|
|
1493
|
-
const out = await presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, slot);
|
|
1729
|
+
const out = await presentToolApprovalFrameAndRespond(e.frame, approvalId, e.respond, e.streamArgs, signal, e.lane, hold, slot, e.receivedAtMs);
|
|
1494
1730
|
noteCallOutcome(slot, out);
|
|
1495
1731
|
return out;
|
|
1496
1732
|
});
|
|
1497
1733
|
}
|
|
1734
|
+
async function joinOtherLaneHolder(e, other, round) {
|
|
1735
|
+
const wait = await awaitApprovalHolder(other, e.signal, 'respond');
|
|
1736
|
+
if (wait.kind === 'settled') {
|
|
1737
|
+
noteHolderSettledApproval(e.approvalId, wait.how, wait.outcome);
|
|
1738
|
+
return joinedResolution(wait.outcome);
|
|
1739
|
+
}
|
|
1740
|
+
if (wait.kind === 'left')
|
|
1741
|
+
return { kind: 'not_sent', lane: 'respond', cause: 'retracted', joinedInFlight: true };
|
|
1742
|
+
return enterLiveCard(e, round + 1);
|
|
1743
|
+
}
|
|
1498
1744
|
function liveCardIdentityOf(approvalId, frame, run) {
|
|
1499
1745
|
const toolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
|
|
1500
1746
|
const fromSubagent = isFromSubagent(frame);
|
|
1501
|
-
const
|
|
1747
|
+
const bound = fromSubagent ? undefined : frameBindingOf(frame);
|
|
1748
|
+
const source = bound ?? run;
|
|
1749
|
+
const ownRun = fromSubagent ? frame.sourceTaskId : source?.taskId;
|
|
1502
1750
|
const taskId = typeof ownRun === 'string' && ownRun !== '' ? ownRun : undefined;
|
|
1503
|
-
const sessionId = !fromSubagent && typeof
|
|
1751
|
+
const sessionId = !fromSubagent && typeof source?.sessionId === 'string' && source.sessionId !== '' ? source.sessionId : undefined;
|
|
1752
|
+
const askSig = carrierAskSig(frame);
|
|
1504
1753
|
return {
|
|
1505
1754
|
approvalId,
|
|
1506
1755
|
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
1507
1756
|
...(taskId !== undefined ? { taskId } : {}),
|
|
1508
1757
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1758
|
+
...(askSig !== undefined ? { askSig } : {}),
|
|
1509
1759
|
};
|
|
1510
1760
|
}
|
|
1511
1761
|
function readApprovalHolderIdentity(v) {
|
|
@@ -1527,107 +1777,51 @@ function readApprovalHolderIdentity(v) {
|
|
|
1527
1777
|
}
|
|
1528
1778
|
export async function joinApprovalHolder(identity, signal) {
|
|
1529
1779
|
const who = readApprovalHolderIdentity(identity);
|
|
1530
|
-
const hold =
|
|
1780
|
+
const hold = approvalHoldFor(who);
|
|
1531
1781
|
if (hold === undefined) {
|
|
1532
1782
|
const entry = settledApprovalOf(who.approvalId);
|
|
1533
|
-
return settledForDurableLane(entry) && entry !== undefined ? { kind: 'joined', outcome:
|
|
1783
|
+
return settledForDurableLane(entry) && entry !== undefined ? { kind: 'joined', outcome: joinedResolution(entry.outcome) } : { kind: 'free' };
|
|
1534
1784
|
}
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1785
|
+
const wait = await awaitApprovalHolder(hold, signal, 'decide');
|
|
1786
|
+
if (wait.kind === 'settled')
|
|
1787
|
+
return { kind: 'joined', outcome: joinedResolution(wait.outcome) };
|
|
1788
|
+
if (wait.kind === 'left')
|
|
1789
|
+
return { kind: 'joined', outcome: { kind: 'not_sent', lane: 'decide', cause: 'retracted', joinedInFlight: true } };
|
|
1790
|
+
return { kind: 'free' };
|
|
1791
|
+
}
|
|
1792
|
+
export function claimApprovalCard(identity, signal) {
|
|
1793
|
+
return claimApprovalCardFor(readApprovalHolderIdentity(identity), signal);
|
|
1537
1794
|
}
|
|
1538
|
-
async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot) {
|
|
1795
|
+
async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot, receivedAtMs) {
|
|
1539
1796
|
const frameToolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
|
|
1540
1797
|
const decidedByReading = decidedByReadingSnapshotOf(lane);
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
if (typeof args !== 'object' || args === null) {
|
|
1550
|
-
argsFallback = true;
|
|
1551
|
-
const fallback = argsFallbackOf(frame.message, frame.argsOmitted);
|
|
1552
|
-
args = fallback.args;
|
|
1553
|
-
if (fallback.omittedNote !== undefined) {
|
|
1554
|
-
wireNote = fallback.omittedNote;
|
|
1555
|
-
hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to ${fallback.pathRecovered ? 'the message-derived path' : 'an empty input (no path could be derived from the message)'}`);
|
|
1556
|
-
}
|
|
1557
|
-
if (lane?.argsUnavailable === true) {
|
|
1558
|
-
wireNote =
|
|
1559
|
-
"this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
|
|
1560
|
-
'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
|
|
1561
|
-
'Edits are not accepted on this card: there is no original input to edit';
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
const argsNotRealInput = argsFallback || lane?.argsUnavailable === true;
|
|
1565
|
-
const ruleOffers = readRuleOffers(frame.ruleOffers);
|
|
1566
|
-
const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
|
|
1567
|
-
const frameShadow = readAllowRuleShadowFromWire(frame, 'frame');
|
|
1568
|
-
const card = await surfaceCallTrackedCard(frameToolCallId, approvalId, ledgerSlot, withUnreadableEvidence(frame, 'frame', {
|
|
1569
|
-
toolName,
|
|
1570
|
-
args: args,
|
|
1798
|
+
const facts = cardFactsOf(frame, 'frame', { streamArgs, ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}) });
|
|
1799
|
+
const argsNotRealInput = facts.argsUnavailable === true;
|
|
1800
|
+
const ruleOffers = facts.ruleOffers;
|
|
1801
|
+
if (argsNotRealInput && lane?.argsUnavailable !== true && facts.wireNote !== undefined) {
|
|
1802
|
+
hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to ${Object.hasOwn(facts.args, 'file_path') ? 'the message-derived path' : 'an empty input (no path could be derived from the message)'}`);
|
|
1803
|
+
}
|
|
1804
|
+
const card = await surfaceCallTrackedCard(frameToolCallId, approvalId, ledgerSlot, {
|
|
1805
|
+
...facts,
|
|
1571
1806
|
callKey: liveFrameCallKey(approvalId),
|
|
1572
|
-
...(frameToolCallId !== undefined
|
|
1573
|
-
? { toolCallId: frameToolCallId }
|
|
1574
|
-
: {}),
|
|
1575
1807
|
signal: hold.cardSignal,
|
|
1576
|
-
...(
|
|
1577
|
-
|
|
1578
|
-
...(argsNotRealInput ? { argsUnavailable: true } : {}),
|
|
1579
|
-
...(frame.governanceForced === true ? { governanceForced: true } : {}),
|
|
1580
|
-
...(lane?.windowIsCurrent === true
|
|
1581
|
-
? {
|
|
1582
|
-
...(isNonNegativeFinite(frame.expiresInMs) ? { expiresInMs: frame.expiresInMs } : {}),
|
|
1583
|
-
...(isNonNegativeFinite(frame.expiresAtMs) ? { expiresAtMs: frame.expiresAtMs } : {}),
|
|
1584
|
-
...(isNonNegativeFinite(frame.serverNowMs) ? { serverNowMs: frame.serverNowMs } : {}),
|
|
1585
|
-
...(isNonNegativeFinite(frame.expiresInMs) || isNonNegativeFinite(frame.expiresAtMs)
|
|
1586
|
-
? { receivedAtMs: Date.now() }
|
|
1587
|
-
: {}),
|
|
1588
|
-
}
|
|
1589
|
-
: {}),
|
|
1590
|
-
...(isFromSubagent(frame) && isToolApprovalDelegation(frame.delegation)
|
|
1591
|
-
? { delegation: frame.delegation }
|
|
1592
|
-
: {}),
|
|
1593
|
-
...(ruleOffers !== undefined ? { ruleOffers } : {}),
|
|
1594
|
-
...((sh) => (sh !== undefined ? { allowRuleShadow: sh } : {}))(frameShadow),
|
|
1595
|
-
...(isWireRecordCarrier(frame.probeCause) ? { probeCause: frame.probeCause } : {}),
|
|
1596
|
-
...((r) => (r !== undefined ? { probeReason: r } : {}))(readProbeReason(frame)),
|
|
1597
|
-
...(readApprovalRememberRefused(frame) ? { rememberRefused: true } : {}),
|
|
1598
|
-
...(isWireRecordCarrier(frame.ruleEvidence) ? { ruleEvidence: frame.ruleEvidence } : {}),
|
|
1599
|
-
...(frame.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1600
|
-
...(hasOwnStrictTrue(frame, 'mandated') ? { mandated: true } : {}),
|
|
1601
|
-
...((w) => (w !== undefined ? { mandate: w } : {}))(readApprovalMandate(frame)),
|
|
1602
|
-
...(() => { const rc = readReadRootCandidate(frame.readRootCandidate); return rc !== undefined ? { readRootCandidate: rc } : {}; })(),
|
|
1603
|
-
...(frame.inputHasBidi === true ? { inputHasBidi: true } : {}),
|
|
1604
|
-
...(typeof frame.ruleOffersAbsence === 'string' && frame.ruleOffersAbsence !== ''
|
|
1605
|
-
? { ruleOffersAbsence: frame.ruleOffersAbsence }
|
|
1606
|
-
: {}),
|
|
1607
|
-
...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
|
|
1608
|
-
...((o) => (o !== undefined ? { origin: o } : {}))(readAskOrigin(frame)),
|
|
1609
|
-
...((w) => (w !== undefined ? { approvalCard: w } : {}))(readApprovalCardWord(frame)),
|
|
1610
|
-
...(readAskClassPair(frame) ?? {}),
|
|
1611
|
-
...((g) => (g !== undefined ? { boundGrants: g } : {}))(readApprovalBoundGrants(frame)),
|
|
1612
|
-
...((w) => (w !== undefined ? { classifierNoRuling: w } : {}))(readApprovalClassifierNoRuling(frame)),
|
|
1613
|
-
allowSessionCarried: true,
|
|
1614
|
-
grantBinding: readApprovalGrantBinding(frame),
|
|
1615
|
-
}));
|
|
1808
|
+
...approvalWindowKeysOf(frame, { windowIsCurrent: lane?.windowIsCurrent === true, receivedAtMs }),
|
|
1809
|
+
});
|
|
1616
1810
|
if (hold.superseded !== undefined) {
|
|
1617
1811
|
hostLog('debug', `liveToolApprovalWire: ${approvalId} was settled by the engine while its card was up${hold.superseded.parked ? ' (parked)' : ''} — card withdrawn; ` +
|
|
1618
1812
|
`the card came back ${card.kind}; nothing sent`);
|
|
1619
1813
|
if (hold.superseded.parked && (card.kind === 'allow' || card.kind === 'deny'))
|
|
1620
1814
|
surfaceApprovalParking();
|
|
1621
|
-
return {
|
|
1815
|
+
return { kind: 'not_sent', lane: 'respond', cause: 'retracted' };
|
|
1622
1816
|
}
|
|
1623
1817
|
if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
|
|
1624
1818
|
hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on a card whose arguments are not the tool's real input (${lane?.argsUnavailable === true ? 'args-unavailable ask' : 'arguments omitted or absent on the frame'}) — nothing sent (the ask stays pending)`);
|
|
1625
1819
|
surfaceEditRefusedOnBlindAsk();
|
|
1626
|
-
return {
|
|
1820
|
+
return { kind: 'not_sent', lane: 'respond', cause: 'edit_refused' };
|
|
1627
1821
|
}
|
|
1628
1822
|
if (card.kind === RETRACTED_CARD_DECISION_KIND) {
|
|
1629
1823
|
hostLog('debug', `liveToolApprovalWire: ${approvalId} card retracted without a decision${card.reason ? ` (${card.reason})` : ''} — nothing sent`);
|
|
1630
|
-
return {
|
|
1824
|
+
return { kind: 'not_sent', lane: 'respond', cause: 'retracted' };
|
|
1631
1825
|
}
|
|
1632
1826
|
if (card.kind === 'aborted') {
|
|
1633
1827
|
hold.phase = 'respond';
|
|
@@ -1642,33 +1836,40 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
|
|
|
1642
1836
|
hold.trailing = interruptDeny.then((body) => readToolApprovalRespondParking(body) === undefined, () => false);
|
|
1643
1837
|
observeCancelByDeny(interruptDeny, approvalId);
|
|
1644
1838
|
hostLog('debug', `liveToolApprovalWire: ${approvalId} card aborted — one tool-level deny issued to settle the pending ask`);
|
|
1645
|
-
return {
|
|
1839
|
+
return { kind: 'unsettled', lane: 'respond', cause: 'interrupted' };
|
|
1646
1840
|
}
|
|
1647
1841
|
hold.phase = 'respond';
|
|
1648
|
-
const
|
|
1842
|
+
const noteCapable = lane?.approvalDecisionNoteCapable === true;
|
|
1843
|
+
const denyReasonRead = card.kind === 'deny' ? ((r) => (typeof r === 'string' ? r : undefined))(card.reason) : undefined;
|
|
1844
|
+
const answerSnapshot = card.kind === 'deny'
|
|
1845
|
+
? ((by) => ({ kind: 'deny', ...(denyReasonRead !== undefined ? { reason: denyReasonRead } : {}), ...(by !== undefined ? { settledBy: by } : {}) }))(readApprovalDenySettledBy(card))
|
|
1846
|
+
: card;
|
|
1847
|
+
const basePayload = answerPayloadFor(answerSnapshot, 'respond', { approvalDecisionNoteCapable: noteCapable });
|
|
1848
|
+
const decision = basePayload?.decision ?? 'deny';
|
|
1849
|
+
if (card.kind === 'allow' && typeof card.note === 'string' && card.note.trim() !== '' && answerCarriage({ callKey: liveFrameCallKey(approvalId) }).allowNote === 'none') {
|
|
1850
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} card carried a note on Yes (len=${card.note.length}) — this engine has no field that brings it to the model, so it was not sent`);
|
|
1851
|
+
}
|
|
1649
1852
|
const denySettledByForWire = card.kind === 'deny' ? readApprovalDenySettledBy(card) : undefined;
|
|
1650
1853
|
const attributionKeys = decision === 'deny' ? decisionAttributionKeyOf(decidedByReading, denySettledByForWire) : {};
|
|
1651
1854
|
const sentAttributionKey = carriesAttributionKey(attributionKeys);
|
|
1652
1855
|
let lastSendCarriedAttribution = sentAttributionKey;
|
|
1653
|
-
const denyReasonSnapshot =
|
|
1856
|
+
const denyReasonSnapshot = denyReasonRead;
|
|
1654
1857
|
if (card.kind === 'failed') {
|
|
1655
1858
|
hostLog('debug', `liveToolApprovalWire: approval card unavailable (${card.reason}) — fail-closed deny for ${approvalId}`);
|
|
1656
1859
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1860
|
+
const note = basePayload?.note;
|
|
1861
|
+
const noteGate = respondNoteGate(denyReasonSnapshot, noteCapable);
|
|
1862
|
+
if ('dropped' in noteGate && noteGate.dropped !== 'blank' && denyReasonSnapshot !== undefined) {
|
|
1863
|
+
if (noteGate.dropped === 'capability') {
|
|
1660
1864
|
hostLog('debug', `liveToolApprovalWire: ${approvalId} card supplied a deny reason (len=${denyReasonSnapshot.length}) but the ` +
|
|
1661
1865
|
'approvalDecisionNote capability is not confirmed for this engine — note not sent (an unknown key would be ' +
|
|
1662
1866
|
'silently swallowed by an older server while still acking 200)');
|
|
1663
1867
|
}
|
|
1664
|
-
else
|
|
1868
|
+
else {
|
|
1665
1869
|
hostLog('debug', `liveToolApprovalWire: ${approvalId} deny reason exceeds the decision_note cap ` +
|
|
1666
1870
|
`(len=${denyReasonSnapshot.length} > ${MAX_RESPOND_NOTE_CHARS}) — note not sent at all (the server rejects the WHOLE ` +
|
|
1667
1871
|
'respond with 400 over an oversize note, and a silently halved audit reason is worse than none)');
|
|
1668
1872
|
}
|
|
1669
|
-
else {
|
|
1670
|
-
note = denyReasonSnapshot;
|
|
1671
|
-
}
|
|
1672
1873
|
}
|
|
1673
1874
|
try {
|
|
1674
1875
|
let persistRule;
|
|
@@ -1735,7 +1936,7 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
|
|
|
1735
1936
|
}
|
|
1736
1937
|
const respondOpts = {
|
|
1737
1938
|
...(signal && !signal.aborted ? { signal } : {}),
|
|
1738
|
-
...(
|
|
1939
|
+
...(basePayload?.updatedInput !== undefined ? { updatedInput: basePayload.updatedInput } : {}),
|
|
1739
1940
|
...(persistRule !== undefined ? { persistRule } : {}),
|
|
1740
1941
|
...(persistRuleEdited !== undefined ? { persistRuleEdited } : {}),
|
|
1741
1942
|
...(persistRuleBatchOfferIndex !== undefined ? { persistRuleBatchOfferIndex } : {}),
|
|
@@ -1745,6 +1946,8 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
|
|
|
1745
1946
|
let attributionDropped = false;
|
|
1746
1947
|
let attributionRenamed = false;
|
|
1747
1948
|
let raw;
|
|
1949
|
+
if (basePayload !== undefined)
|
|
1950
|
+
emitAnswerCommitted(liveFrameCallKey(approvalId), 'respond', answerSnapshot, basePayload);
|
|
1748
1951
|
try {
|
|
1749
1952
|
raw = await respond(approvalId, decision, respondOpts);
|
|
1750
1953
|
}
|
|
@@ -1790,41 +1993,56 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
|
|
|
1790
1993
|
hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered 202 parking_in_progress${parking.retryAfterMs !== undefined ? ` (retryAfterMs=${parking.retryAfterMs})` : ''} — ` +
|
|
1791
1994
|
`the answer did NOT take effect${parking.decidePath !== undefined ? ' (the engine pointed at a decide path for the parked card)' : ''}; not re-sent, not reported as decided; the user is told to answer again on the parked card`);
|
|
1792
1995
|
surfaceApprovalParking(parking.retryAfterMs);
|
|
1793
|
-
return {
|
|
1996
|
+
return { kind: 'unsettled', lane: 'respond', cause: 'parking_in_progress', parking };
|
|
1794
1997
|
}
|
|
1795
1998
|
hostLog('error', `liveToolApprovalWire: 202 parking body for ${approvalId} is ${parkingBody.kind === 'parking' ? `for another approval (${parkingBody.view.approvalId})` : 'unreadable (approvalId is not usable)'} — ` +
|
|
1796
1999
|
'reporting the answer as NOT settled (never as decided, never as parked); not re-sent');
|
|
1797
|
-
return {
|
|
2000
|
+
return { kind: 'unsettled', lane: 'respond', cause: 'unreadable' };
|
|
1798
2001
|
}
|
|
1799
2002
|
const parsed = readToolApprovalRespondAck(raw);
|
|
2003
|
+
const replayedForThisCard = parsed !== undefined && isToolApprovalRespondReplayed(parsed) && parsed.approvalId === approvalId;
|
|
2004
|
+
const claimsReplay = ownIdempotentTrue(raw) || (parsed !== undefined && isToolApprovalRespondReplayed(parsed));
|
|
2005
|
+
if (claimsReplay && !replayedForThisCard) {
|
|
2006
|
+
const claimed = replayClaimOf(raw);
|
|
2007
|
+
if (claimed.approvalId === approvalId && claimed.decision !== undefined && !claimed.carriesDelivery) {
|
|
2008
|
+
hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay (body not in the full replay shape) — the approval was already decided ` +
|
|
2009
|
+
`(${claimed.decision}); this answer was not delivered, reporting the earlier decision`);
|
|
2010
|
+
return { kind: 'decided', lane: 'respond', decision: claimed.decision };
|
|
2011
|
+
}
|
|
2012
|
+
hostLog('error', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay of an earlier decision that cannot be read or names another approval — ` +
|
|
2013
|
+
'reporting the answer as NOT settled (never as the decision this client sent); not re-sent');
|
|
2014
|
+
return { kind: 'unsettled', lane: 'respond', cause: 'unreadable' };
|
|
2015
|
+
}
|
|
1800
2016
|
let ack = parsed;
|
|
1801
|
-
if (parsed !== undefined && (parsed.approvalId !== approvalId || parsed.decision !== decision)) {
|
|
2017
|
+
if (parsed !== undefined && (parsed.approvalId !== approvalId || (!isToolApprovalRespondReplayed(parsed) && parsed.decision !== decision))) {
|
|
1802
2018
|
hostLog('error', `liveToolApprovalWire: DISCARDING respond ack for ${approvalId} — it does not correlate ` +
|
|
1803
2019
|
`(ack.approvalId=${parsed.approvalId} ack.decision=${parsed.decision}, sent decision=${decision}); ` +
|
|
1804
2020
|
'treating as "no ack" (unknown) — never surfacing a safety notice off an unrelated receipt');
|
|
1805
2021
|
ack = undefined;
|
|
1806
2022
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
2023
|
+
const replayed = ack !== undefined && isToolApprovalRespondReplayed(ack) ? ack : undefined;
|
|
2024
|
+
let applied = ack !== undefined && replayed === undefined ? ack : undefined;
|
|
2025
|
+
if (applied !== undefined &&
|
|
2026
|
+
(applied.persistedRule !== undefined || applied.persistedRules !== undefined || applied.persistedRuleAnchors !== undefined)) {
|
|
1809
2027
|
const sentEditedArm = persistRule !== undefined && persistRuleEdited === true;
|
|
1810
2028
|
const expectedBatchCount = batchArmTarget?.rules.length;
|
|
1811
2029
|
const anchorKeyOnWire = typeof raw === 'object' &&
|
|
1812
2030
|
raw !== null &&
|
|
1813
2031
|
Object.prototype.hasOwnProperty.call(raw, 'persistedRuleAnchors');
|
|
1814
|
-
const anchors =
|
|
2032
|
+
const anchors = applied.persistedRuleAnchors;
|
|
1815
2033
|
const anchorsOk = anchors !== undefined
|
|
1816
2034
|
? persistRuleBatchOfferIndex !== undefined &&
|
|
1817
2035
|
expectedBatchCount !== undefined &&
|
|
1818
2036
|
anchors.every(a => a.offerIndex === persistRuleBatchOfferIndex && a.memberIndex < expectedBatchCount)
|
|
1819
2037
|
: !anchorKeyOnWire;
|
|
1820
|
-
const batchEchoOk =
|
|
2038
|
+
const batchEchoOk = applied.persistedRules !== undefined &&
|
|
1821
2039
|
expectedBatchCount !== undefined &&
|
|
1822
|
-
|
|
2040
|
+
applied.persistedRules.length === expectedBatchCount &&
|
|
1823
2041
|
anchorsOk;
|
|
1824
2042
|
const strayEchoes = [];
|
|
1825
|
-
if (
|
|
2043
|
+
if (applied.persistedRule !== undefined && !sentEditedArm)
|
|
1826
2044
|
strayEchoes.push('persistedRule');
|
|
1827
|
-
if (
|
|
2045
|
+
if (applied.persistedRules !== undefined && !batchEchoOk)
|
|
1828
2046
|
strayEchoes.push('persistedRules');
|
|
1829
2047
|
if (anchors !== undefined && !(anchorsOk && batchEchoOk))
|
|
1830
2048
|
strayEchoes.push('persistedRuleAnchors');
|
|
@@ -1833,60 +2051,53 @@ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, st
|
|
|
1833
2051
|
`${approvalId} — they do not correlate with the persistence arm actually sent ` +
|
|
1834
2052
|
`(editedArm=${String(sentEditedArm)} batchArmMembers=${String(expectedBatchCount ?? 'none')} ` +
|
|
1835
2053
|
`sentOfferIndex=${String(persistRuleBatchOfferIndex ?? 'none')} ` +
|
|
1836
|
-
`echoedRules=${String(
|
|
2054
|
+
`echoedRules=${String(applied.persistedRules?.length ?? 'none')} ` +
|
|
1837
2055
|
`echoedAnchorOffer=${String(anchors?.[0]?.offerIndex ?? 'none')}); never telling the user a rule ` +
|
|
1838
2056
|
'was stored off a receipt that does not line up with what this client actually sent');
|
|
1839
|
-
const { persistedRule: _pr, persistedRules: _prs, persistedRuleAnchors: _pra, ...rest } =
|
|
2057
|
+
const { persistedRule: _pr, persistedRules: _prs, persistedRuleAnchors: _pra, ...rest } = applied;
|
|
1840
2058
|
void _pr;
|
|
1841
2059
|
void _prs;
|
|
1842
2060
|
void _pra;
|
|
1843
|
-
|
|
2061
|
+
applied = {
|
|
1844
2062
|
...rest,
|
|
1845
|
-
...(
|
|
1846
|
-
...(
|
|
2063
|
+
...(applied.persistedRule !== undefined && sentEditedArm ? { persistedRule: applied.persistedRule } : {}),
|
|
2064
|
+
...(applied.persistedRules !== undefined && batchEchoOk ? { persistedRules: applied.persistedRules } : {}),
|
|
1847
2065
|
...(anchors !== undefined && anchorsOk && batchEchoOk ? { persistedRuleAnchors: anchors } : {}),
|
|
1848
2066
|
};
|
|
2067
|
+
ack = applied;
|
|
1849
2068
|
}
|
|
1850
2069
|
}
|
|
1851
|
-
if (ruleArmDroppedForCaps)
|
|
2070
|
+
if (replayed === undefined && ruleArmDroppedForCaps)
|
|
1852
2071
|
surfaceRuleArmNotSent();
|
|
1853
|
-
if (ruleArmDroppedForCheck !== undefined)
|
|
2072
|
+
if (replayed === undefined && ruleArmDroppedForCheck !== undefined)
|
|
1854
2073
|
surfaceRuleArmRejected(ruleArmDroppedForCheck);
|
|
1855
|
-
if (decision === 'allow_session' &&
|
|
2074
|
+
if (decision === 'allow_session' && applied?.rememberApplied === false) {
|
|
1856
2075
|
logAfterDecided(`liveToolApprovalWire: ${approvalId} allow_session ack rememberApplied=false — grant not stored, surfacing honest notice`);
|
|
1857
2076
|
surfaceRememberNotApplied();
|
|
1858
2077
|
}
|
|
1859
|
-
if (respondOpts.updatedInput !== undefined &&
|
|
2078
|
+
if (respondOpts.updatedInput !== undefined && applied?.updatedInputForwarded === false) {
|
|
1860
2079
|
logAfterDecided(`liveToolApprovalWire: ${approvalId} edited args were NOT forwarded (ack.updatedInputForwarded=false) — the tool runs on the ORIGINAL input`);
|
|
1861
2080
|
surfaceEditNotForwarded();
|
|
1862
2081
|
}
|
|
1863
|
-
if (note !== undefined &&
|
|
2082
|
+
if (note !== undefined && applied !== undefined && applied.noteRecorded !== true) {
|
|
1864
2083
|
hostLog('debug', `liveToolApprovalWire: ${approvalId} decision note was sent but not persisted ` +
|
|
1865
|
-
`(ack.noteRecorded=${String(
|
|
2084
|
+
`(ack.noteRecorded=${String(applied.noteRecorded)}) — decision stood; the audit note did not land on the ask row`);
|
|
1866
2085
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
: {}),
|
|
1875
|
-
}
|
|
1876
|
-
: {};
|
|
1877
|
-
return ack !== undefined ? { decision, ack, ...denyAttribution } : { decision, ...denyAttribution };
|
|
2086
|
+
if (replayed !== undefined) {
|
|
2087
|
+
hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay — the approval was already decided (${replayed.decision}); ` +
|
|
2088
|
+
'this answer was not delivered, reporting the earlier decision');
|
|
2089
|
+
return { kind: 'decided', lane: 'respond', decision: replayed.decision, ack: replayed };
|
|
2090
|
+
}
|
|
2091
|
+
const deny = decision === 'deny' ? denyFactsOf(denySettledByForWire, denyReasonSnapshot, attributionDropped, attributionRenamed) : undefined;
|
|
2092
|
+
return { kind: 'decided', lane: 'respond', decision, ...(ack !== undefined ? { ack } : {}), ...(deny !== undefined ? { deny } : {}) };
|
|
1878
2093
|
}
|
|
1879
2094
|
catch (e) {
|
|
1880
|
-
const
|
|
1881
|
-
const
|
|
2095
|
+
const r = resolutionOfDecisionFailure(e, { lane: 'respond', route: 'live_respond', attributionCarried: lastSendCarriedAttribution });
|
|
2096
|
+
const refusal = r.kind === 'unsettled' ? r.refusal : undefined;
|
|
1882
2097
|
hostLog('debug', `liveToolApprovalWire: respond(${decision}) failed for ${approvalId} ` +
|
|
1883
|
-
`(status=${
|
|
1884
|
-
`messageLen=${
|
|
1885
|
-
'the refusal
|
|
1886
|
-
return
|
|
1887
|
-
decision: 'unresolved',
|
|
1888
|
-
...(respondRefusal !== undefined ? { respondRefusal } : {}),
|
|
1889
|
-
...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
|
|
1890
|
-
};
|
|
2098
|
+
`(status=${refusal?.status ?? 'none'} errorCode=${logSafeErrorCode(refusal?.errorCode)} ` +
|
|
2099
|
+
`messageLen=${refusal?.message?.length ?? 0}) — engine self-settles (TTL/abort); ` +
|
|
2100
|
+
'the refusal reading is handed back on resolution.refusal for the host to surface');
|
|
2101
|
+
return r;
|
|
1891
2102
|
}
|
|
1892
2103
|
}
|