@sema-agent/client-core 0.110.0 → 0.111.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 +169 -0
- package/README.md +50 -18
- package/dist/abortableSleep.d.ts +7 -0
- package/dist/abortableSleep.js +37 -0
- package/dist/adapt/arms.js +25 -4
- package/dist/adapt/toolCards.d.ts +3 -1
- package/dist/adapt/toolCards.js +4 -2
- package/dist/adapter/activeRunSelfHeal.d.ts +39 -0
- package/dist/adapter/activeRunSelfHeal.js +171 -3
- package/dist/adapter/downstream/eventToSdkMessage.js +10 -10
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
- package/dist/adapter/downstream/terminalToSdkResult.js +11 -0
- package/dist/adapter/runStream.d.ts +5 -0
- package/dist/adapter/runStream.js +15 -3
- package/dist/attachmentRoute.d.ts +20 -0
- package/dist/attachmentRoute.js +39 -0
- package/dist/attachmentsWireCaps.js +1 -1
- package/dist/builtinOutputStyles.d.ts +12 -0
- package/dist/builtinOutputStyles.js +147 -0
- package/dist/compactionStatus.d.ts +9 -0
- package/dist/compactionStatus.js +29 -5
- package/dist/controlRouter.js +2 -1
- package/dist/coreValuePorts.d.ts +39 -1
- package/dist/coreValuePorts.js +10 -0
- package/dist/displayUntrusted.d.ts +1 -0
- package/dist/displayUntrusted.js +6 -0
- package/dist/engineHealthFacts.d.ts +11 -0
- package/dist/engineHealthFacts.js +21 -0
- package/dist/engineHealthProbeVerdict.d.ts +23 -0
- package/dist/engineHealthProbeVerdict.js +115 -0
- package/dist/engineNoticeCodes.js +5 -7
- package/dist/engineRepoint.d.ts +12 -5
- package/dist/engineRepoint.js +44 -19
- package/dist/engineRowRef.d.ts +3 -0
- package/dist/engineRowRef.js +11 -1
- package/dist/engineSwapCrossing.d.ts +57 -0
- package/dist/engineSwapCrossing.js +297 -0
- package/dist/engineUnavailable.d.ts +44 -0
- package/dist/engineUnavailable.js +492 -0
- package/dist/eventLeg.js +3 -3
- package/dist/generated/sdkFactTables.d.ts +1 -1
- package/dist/generated/sdkFactTables.js +1 -0
- package/dist/generated/settingsSchemaTables.d.ts +4 -0
- package/dist/generated/settingsSchemaTables.js +1 -0
- package/dist/hitl/approvalDelivery.d.ts +1 -0
- package/dist/hitl/approvalDelivery.js +1 -1
- package/dist/hitl/approvalResolution.d.ts +1 -1
- package/dist/hitl/approvalResolution.js +2 -1
- package/dist/hitl/askGateWire.js +5 -6
- package/dist/hitl/decisionRefusal.d.ts +2 -0
- package/dist/hitl/decisionRefusal.js +7 -8
- package/dist/hitl/decisionRefusalCarrier.d.ts +3 -0
- package/dist/hitl/decisionRefusalCarrier.js +348 -0
- package/dist/hitl/decisionSend.d.ts +7 -0
- package/dist/hitl/decisionSend.js +34 -12
- package/dist/hitl/editedApprovalAcceptance.js +1 -1
- package/dist/hitl/elicitationRefusal.d.ts +4 -0
- package/dist/hitl/elicitationRefusal.js +23 -0
- package/dist/hitl/frameRouter.d.ts +2 -2
- package/dist/hitl/frameRouter.js +8 -15
- package/dist/hitl/gateIdentity.d.ts +5 -0
- package/dist/hitl/gateIdentity.js +23 -0
- package/dist/hitl/hitlBridge.d.ts +6 -0
- package/dist/hitl/hitlBridge.js +10 -4
- package/dist/hitl/hitlHostSurface.d.ts +2 -1
- package/dist/hitl/hitlHostSurface.js +7 -2
- package/dist/hitl/localAllowRedemption.d.ts +67 -0
- package/dist/hitl/localAllowRedemption.js +256 -0
- package/dist/hitl/parkResolver.js +7 -4
- package/dist/hitl/persistedRulesWire.d.ts +16 -0
- package/dist/hitl/persistedRulesWire.js +70 -0
- package/dist/hitl/planReviewWire.d.ts +3 -0
- package/dist/hitl/planReviewWire.js +23 -6
- package/dist/hitl/policyDenyCause.d.ts +1 -1
- package/dist/hitl/policyDenyCause.js +1 -1
- package/dist/hitl/resumeRunningCard.d.ts +1 -0
- package/dist/hitl/resumeRunningCard.js +1 -0
- package/dist/hitl/screenCardCarriers.d.ts +15 -0
- package/dist/hitl/screenCardCarriers.js +41 -0
- package/dist/hitl/screenDecisionCards.d.ts +98 -0
- package/dist/hitl/screenDecisionCards.js +362 -0
- package/dist/hitl/standingRows.d.ts +3 -1
- package/dist/hitl/standingRows.js +18 -0
- package/dist/hitl/toolApprovalWire.d.ts +30 -8
- package/dist/hitl/toolApprovalWire.js +264 -121
- package/dist/hooksWireCaps.js +3 -3
- package/dist/hostSettingNoticeArm.d.ts +3 -0
- package/dist/hostSettingNoticeArm.js +12 -0
- package/dist/hostTurnSettings.d.ts +3 -0
- package/dist/hostTurnSettings.js +38 -5
- package/dist/humanTurnRead.js +11 -1
- package/dist/i18n/messages.en.d.ts +129 -0
- package/dist/i18n/messages.en.js +135 -7
- package/dist/i18n/messages.zh.js +142 -6
- package/dist/i18n/translate.d.ts +3 -0
- package/dist/i18n/translate.js +78 -10
- package/dist/i18n/zh.d.ts +1 -0
- package/dist/i18n/zh.js +1 -0
- package/dist/index.d.ts +43 -17
- package/dist/index.js +30 -12
- package/dist/interactiveHalt.js +2 -1
- package/dist/interactiveInterrupt.js +20 -2
- package/dist/liveModelCatalog.js +2 -2
- package/dist/localEngineSeatReading.d.ts +2 -2
- package/dist/localEngineSeatReading.js +5 -3
- package/dist/localEngineSpawn.d.ts +0 -7
- package/dist/localEngineSpawn.js +0 -92
- package/dist/localEngineStep.d.ts +2 -2
- package/dist/localEngineStep.js +2 -0
- package/dist/mcpReconnect.js +3 -3
- package/dist/mcpWireCaps.js +1 -1
- package/dist/model/engineModelWords.d.ts +3 -0
- package/dist/model/engineModelWords.js +13 -0
- package/dist/model/subagentModelWord.d.ts +21 -2
- package/dist/model/subagentModelWord.js +100 -29
- package/dist/modelCapabilityProbe.js +3 -40
- package/dist/notifications.d.ts +19 -0
- package/dist/notifications.js +114 -12
- package/dist/ownKey.d.ts +6 -0
- package/dist/ownKey.js +31 -0
- package/dist/pendingUserRow.d.ts +4 -0
- package/dist/pendingUserRow.js +16 -0
- package/dist/permissionsDeclaration.d.ts +1 -0
- package/dist/permissionsDeclaration.js +11 -1
- package/dist/planReviewCardFacts.d.ts +3 -0
- package/dist/planReviewCardFacts.js +52 -2
- package/dist/pluginHooksWire.d.ts +9 -1
- package/dist/pluginHooksWire.js +40 -6
- package/dist/printToolResultFrame.js +4 -2
- package/dist/promptBlockedCopy.js +11 -6
- package/dist/promptSuggestionBatch.d.ts +7 -0
- package/dist/promptSuggestionBatch.js +9 -0
- package/dist/promptSuggestionsTail.d.ts +39 -0
- package/dist/promptSuggestionsTail.js +116 -0
- package/dist/request/taskRequest.js +10 -6
- package/dist/requestCoordinate.d.ts +1 -0
- package/dist/requestCoordinate.js +13 -0
- package/dist/rewindArchiveReading.js +4 -4
- package/dist/runCancelContext.d.ts +2 -1
- package/dist/runCancelContext.js +1 -0
- package/dist/runPermissionModeSwitch.js +2 -2
- package/dist/scenarioRefusal.js +11 -13
- package/dist/scenarioSkills.js +11 -6
- package/dist/seam.d.ts +16 -2
- package/dist/seam.js +10 -0
- package/dist/seatContract.d.ts +109 -9
- package/dist/seatContract.js +218 -15
- package/dist/sessionCostReading.d.ts +70 -0
- package/dist/sessionCostReading.js +150 -0
- package/dist/sessionHooksRoster.d.ts +4 -0
- package/dist/sessionHooksRoster.js +158 -0
- package/dist/sessionMemoryStatus.js +2 -2
- package/dist/sessionReadRedaction.d.ts +7 -0
- package/dist/sessionReadRedaction.js +26 -0
- package/dist/sessionSyncFileHistory.js +8 -9
- package/dist/settingsHooksSalvage.js +2 -1
- package/dist/sseIdleTriage.js +2 -1
- package/dist/storeDurabilityWord.d.ts +15 -0
- package/dist/storeDurabilityWord.js +18 -0
- package/dist/storePostureCapability.d.ts +7 -3
- package/dist/storePostureCapability.js +69 -39
- package/dist/subagent/engineSubagentResume.js +2 -2
- package/dist/subagent/engineSubagentTail.js +2 -1
- package/dist/subagentContentStore.d.ts +11 -0
- package/dist/subagentContentStore.js +28 -2
- package/dist/toolResult.d.ts +1 -0
- package/dist/toolResult.js +2 -0
- package/dist/userAttachmentEnvelope.d.ts +2 -1
- package/dist/userAttachmentEnvelope.js +10 -0
- package/dist/wireErrorTriage.js +2 -1
- package/dist/wireFailureShape.d.ts +4 -0
- package/dist/wireFailureShape.js +141 -19
- package/dist/wireRefusalCopy.d.ts +6 -5
- package/dist/wireRefusalCopy.js +49 -92
- package/docs/INTEGRATION-CLIENTS.md +2118 -17
- package/package.json +6 -2
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -89,6 +89,8 @@ function policyDenyHeadFor(cause) {
|
|
|
89
89
|
return renderMessage(msg('approval.policyDeny.denyRule', { tail }));
|
|
90
90
|
case 'engine_floor':
|
|
91
91
|
return renderMessage(msg('approval.policyDeny.engineFloor', { tail }));
|
|
92
|
+
case 'hook':
|
|
93
|
+
return renderMessage(msg('approval.policyDeny.hook'));
|
|
92
94
|
case 'no_approval_ui':
|
|
93
95
|
case undefined:
|
|
94
96
|
return HITL_POLICY_DENY_MESSAGE;
|
|
@@ -117,20 +119,6 @@ function denyStampedEnd(ev, attribution) {
|
|
|
117
119
|
export const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool use]';
|
|
118
120
|
const RUN_CANCELLED_ERROR_CODE = RUN_CANCELLED_CODE;
|
|
119
121
|
export const ENGINE_ABORT_TOOL_RESULT = 'Operation aborted';
|
|
120
|
-
const bridgeRunHandleFallbacks = new WeakMap();
|
|
121
|
-
export function setBridgeRunHandleFallback(deps, read) {
|
|
122
|
-
bridgeRunHandleFallbacks.set(deps, read);
|
|
123
|
-
}
|
|
124
|
-
export function bridgeRunHandleFallbackOf(deps) {
|
|
125
|
-
try {
|
|
126
|
-
const read = bridgeRunHandleFallbacks.get(deps);
|
|
127
|
-
const v = read?.();
|
|
128
|
-
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
122
|
function isAskToolLoose(name) {
|
|
135
123
|
return typeof name === 'string' && name.replace(/[\s_-]+/g, '').toLowerCase() === 'askuserquestion';
|
|
136
124
|
}
|
|
@@ -371,7 +359,12 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
371
359
|
const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
|
|
372
360
|
const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
|
|
373
361
|
const runTaskId = ctx.taskId.current;
|
|
374
|
-
const
|
|
362
|
+
const streamGeneration = ctx.streamGeneration;
|
|
363
|
+
const runFacts = {
|
|
364
|
+
...(typeof runTaskId === 'string' && runTaskId !== '' ? { taskId: runTaskId } : {}),
|
|
365
|
+
...(streamGeneration !== undefined ? { sourceGeneration: streamGeneration } : {}),
|
|
366
|
+
};
|
|
367
|
+
const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane, Object.keys(runFacts).length > 0 ? runFacts : undefined);
|
|
375
368
|
const hold = liveCardHoldOf(liveFrameCallKey(ev.approvalId));
|
|
376
369
|
const handled = launched.then((outcome) => settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId), (e) => {
|
|
377
370
|
hostLog('debug', `liveHitlAskWire: tool_approval card for ${ev.approvalId} failed: ${String(e).slice(0, 160)}`);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export declare const PLAN_REVIEW_QUESTION_ID_PREFIX = "plan-review:";
|
|
2
2
|
export declare const REOPEN_ID_TAIL = "#reopen-";
|
|
3
|
+
export declare const SCREEN_CARD_ORIGINS: readonly ["live-approval", "park-approval", "park-question", "plan-review", "host-reopen-question", "host-choice", "local-permission"];
|
|
4
|
+
export type ScreenCardOrigin = (typeof SCREEN_CARD_ORIGINS)[number];
|
|
3
5
|
export declare function approvalCallKey(gatedCallId: string | undefined, taskId: string): string;
|
|
4
6
|
export declare function askGateQuestionId(callKey: string): string;
|
|
5
7
|
export declare function planReviewQuestionId(taskId: string): string;
|
|
6
8
|
export declare function freshPlanReviewQuestionId(taskId: string, suffix: string): string;
|
|
9
|
+
export declare function hostReopenQuestionId(rowTaskId: string, suffix: string): string;
|
|
10
|
+
export declare function hostRunChoiceQuestionId(taskId: string, suffix: string): string;
|
|
11
|
+
export declare function questionCardOriginOf(questionId: {} | null | undefined): Extract<ScreenCardOrigin, 'park-question' | 'plan-review' | 'host-reopen-question' | 'host-choice'> | undefined;
|
|
7
12
|
export declare function liveFrameCallKey(approvalId: string): string;
|
|
8
13
|
export declare function taskIdFromPlanReviewQuestionId(questionId: string): string | undefined;
|
|
9
14
|
export declare function armedKeyFromQuestionId(questionId: string): string;
|
|
@@ -2,6 +2,9 @@ const HITL_ASK_QUESTION_ID_PREFIX = 'hitl-ask:';
|
|
|
2
2
|
export const PLAN_REVIEW_QUESTION_ID_PREFIX = 'plan-review:';
|
|
3
3
|
const HITL_FRAME_CALL_KEY_PREFIX = 'hitl-frame:';
|
|
4
4
|
export const REOPEN_ID_TAIL = '#reopen-';
|
|
5
|
+
const HOST_REOPEN_QUESTION_ID_PREFIX = 'ask-park:';
|
|
6
|
+
const HOST_RUN_CHOICE_QUESTION_ID_PREFIX = 'active-run:';
|
|
7
|
+
export const SCREEN_CARD_ORIGINS = Object.freeze(['live-approval', 'park-approval', 'park-question', 'plan-review', 'host-reopen-question', 'host-choice', 'local-permission']);
|
|
5
8
|
export function approvalCallKey(gatedCallId, taskId) {
|
|
6
9
|
return gatedCallId ?? taskId;
|
|
7
10
|
}
|
|
@@ -14,6 +17,26 @@ export function planReviewQuestionId(taskId) {
|
|
|
14
17
|
export function freshPlanReviewQuestionId(taskId, suffix) {
|
|
15
18
|
return `${planReviewQuestionId(taskId)}${REOPEN_ID_TAIL}${suffix}`;
|
|
16
19
|
}
|
|
20
|
+
export function hostReopenQuestionId(rowTaskId, suffix) {
|
|
21
|
+
return `${HOST_REOPEN_QUESTION_ID_PREFIX}${rowTaskId}${REOPEN_ID_TAIL}${suffix}`;
|
|
22
|
+
}
|
|
23
|
+
export function hostRunChoiceQuestionId(taskId, suffix) {
|
|
24
|
+
return `${HOST_RUN_CHOICE_QUESTION_ID_PREFIX}${taskId}${REOPEN_ID_TAIL}${suffix}`;
|
|
25
|
+
}
|
|
26
|
+
export function questionCardOriginOf(questionId) {
|
|
27
|
+
if (typeof questionId !== 'string')
|
|
28
|
+
return undefined;
|
|
29
|
+
const has = (prefix) => questionId.startsWith(prefix) && questionId.length > prefix.length;
|
|
30
|
+
if (has(HITL_ASK_QUESTION_ID_PREFIX))
|
|
31
|
+
return 'park-question';
|
|
32
|
+
if (has(PLAN_REVIEW_QUESTION_ID_PREFIX))
|
|
33
|
+
return 'plan-review';
|
|
34
|
+
if (has(HOST_REOPEN_QUESTION_ID_PREFIX))
|
|
35
|
+
return 'host-reopen-question';
|
|
36
|
+
if (has(HOST_RUN_CHOICE_QUESTION_ID_PREFIX))
|
|
37
|
+
return 'host-choice';
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
17
40
|
export function liveFrameCallKey(approvalId) {
|
|
18
41
|
return `${HITL_FRAME_CALL_KEY_PREFIX}${approvalId}`;
|
|
19
42
|
}
|
|
@@ -138,6 +138,12 @@ export type FindPendingOutcome = {
|
|
|
138
138
|
readonly kind: 'ok';
|
|
139
139
|
}>;
|
|
140
140
|
};
|
|
141
|
+
export interface PendingRowCallIdentity {
|
|
142
|
+
readonly taskId?: string | null;
|
|
143
|
+
readonly toolCallId?: string | null;
|
|
144
|
+
readonly boundCallId?: string | null;
|
|
145
|
+
}
|
|
146
|
+
export declare function pendingRowAnswersCall(row: PendingRowCallIdentity, taskId: string, callId: string): boolean;
|
|
141
147
|
export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
|
|
142
148
|
signal?: AbortSignal;
|
|
143
149
|
}, callId?: string, identityAccepts?: (row: PendingCheckpoint) => boolean): Promise<FindPendingOutcome>;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -8,6 +8,7 @@ import { guardedRead, ownDataField, readOwnDataValue } from '../ownKey.js';
|
|
|
8
8
|
import { PROTOCOL_ERROR_NAME, isSlotCarrier, protocolErrorNameReading, readBlockedBy, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
|
|
9
9
|
import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_INVALID_OUTCOME, RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
|
|
10
10
|
import { suspendedReopenOf } from './suspendedReopen.js';
|
|
11
|
+
import { decisionConflictOf } from './decisionConflict.js';
|
|
11
12
|
import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
|
|
12
13
|
import { askParkBlockGeneration, noteAskParkDecideSettled } from './askParkAutoReopenBlock.js';
|
|
13
14
|
import { checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
|
|
@@ -259,6 +260,9 @@ function notifyDecideObserver(observer, ev) {
|
|
|
259
260
|
invokeHostCallbackIsolated(`hitlBridge: onDecideOperation(${ev.phase})`, () => observer(ev));
|
|
260
261
|
}
|
|
261
262
|
const ANY_TOOL_FAMILY = () => true;
|
|
263
|
+
export function pendingRowAnswersCall(row, taskId, callId) {
|
|
264
|
+
return row.taskId === taskId && (row.boundCallId === callId || row.toolCallId === callId);
|
|
265
|
+
}
|
|
262
266
|
export async function findPendingForTask(client, taskId, matches, opts, callId, identityAccepts = (r) => matches(typeof r.toolName === 'string' ? r.toolName : undefined)) {
|
|
263
267
|
let rows;
|
|
264
268
|
try {
|
|
@@ -275,10 +279,10 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
275
279
|
});
|
|
276
280
|
const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
|
|
277
281
|
const identityRow = callId !== undefined
|
|
278
|
-
? decidable.find((r) => r
|
|
282
|
+
? decidable.find((r) => pendingRowAnswersCall(r, taskId, callId))
|
|
279
283
|
: undefined;
|
|
280
284
|
const identityForeign = callId !== undefined && identityRow === undefined
|
|
281
|
-
? readings.find((x) => x.r
|
|
285
|
+
? readings.find((x) => pendingRowAnswersCall(x.r, taskId, callId) && x.foreign !== null)
|
|
282
286
|
: undefined;
|
|
283
287
|
if (identityForeign !== undefined) {
|
|
284
288
|
const identityBar = identityForeign.foreign === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(identityForeign.r) : undefined;
|
|
@@ -602,8 +606,10 @@ function bindingMismatchReading(e) {
|
|
|
602
606
|
const codeReclassified = code.state === 'code' && code.code === CHECKPOINT_INVALID_OUTCOME;
|
|
603
607
|
const family = (code.state === 'code' && code.code === APPROVAL_BINDING_MISMATCH) ||
|
|
604
608
|
(!codeReclassified && nameReading.state === 'named' && nameReading.name === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
|
|
605
|
-
if (family)
|
|
606
|
-
|
|
609
|
+
if (family) {
|
|
610
|
+
const captured = code.state === 'code' ? { state: 'read', wire: code.code } : code.state === 'absent' ? { state: 'read' } : { state: 'unreadable' };
|
|
611
|
+
return decisionConflictOf(e, captured, nameReading) === 'row_integrity' ? 'not' : 'mismatch';
|
|
612
|
+
}
|
|
607
613
|
if (code.state === 'unreadable')
|
|
608
614
|
return 'unreadable';
|
|
609
615
|
return 'not';
|
|
@@ -22,7 +22,8 @@ export declare function hitlHostSurfaceMissesFor(sessionKey: string): number;
|
|
|
22
22
|
export declare function hitlHostSurfaceFor(sessionKey: string): HitlHostSurface | null;
|
|
23
23
|
export declare function _resetHitlHostSurfaceForTest(): void;
|
|
24
24
|
export declare function surfaceForCurrentSession(): HitlHostSurface | null;
|
|
25
|
-
export declare const CANCEL_DENY_BUDGET_MS =
|
|
25
|
+
export declare const CANCEL_DENY_BUDGET_MS = 8000;
|
|
26
|
+
export declare function interruptDenyLifetime(): AbortSignal;
|
|
26
27
|
export declare const INTERRUPT_DENY_REASON = "Interrupted by user";
|
|
27
28
|
export declare const CANCEL_DENY_WARN_TEXT = "could not cancel the pending question \u2014 the session may stay locked; the run may need engine-side recovery";
|
|
28
29
|
export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow was not remembered by the worker \u2014 you will be asked again for this tool";
|
|
@@ -4,6 +4,7 @@ import { unrefTimer } from '../unrefTimer.js';
|
|
|
4
4
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
5
5
|
import { editedApprovalRefusedLine } from './editedApprovalAcceptance.js';
|
|
6
6
|
import { decisionRefusalSaysAskGone } from './decisionRefusal.js';
|
|
7
|
+
import { deliveryTiming, RETRY_BUDGET_MS } from './approvalDelivery.js';
|
|
7
8
|
const hostSurfaceByKey = createSessionSlot();
|
|
8
9
|
const hostSurfaceMissesByKey = new Map();
|
|
9
10
|
export function installHitlHostSurface(surface) {
|
|
@@ -39,7 +40,10 @@ function surfaceForSession(sessionKey) {
|
|
|
39
40
|
}
|
|
40
41
|
return hostSurface;
|
|
41
42
|
}
|
|
42
|
-
export const CANCEL_DENY_BUDGET_MS =
|
|
43
|
+
export const CANCEL_DENY_BUDGET_MS = RETRY_BUDGET_MS;
|
|
44
|
+
export function interruptDenyLifetime() {
|
|
45
|
+
return AbortSignal.timeout(Math.max(1, deliveryTiming().budgetMs));
|
|
46
|
+
}
|
|
43
47
|
export const INTERRUPT_DENY_REASON = 'Interrupted by user';
|
|
44
48
|
export const CANCEL_DENY_WARN_TEXT = 'could not cancel the pending question — the session may stay locked; the run may need engine-side recovery';
|
|
45
49
|
const CANCEL_DENY_WARN_KEY = 'hitl-cancel-deny-warn';
|
|
@@ -140,8 +144,9 @@ export function surfaceRuleArmRejected(why) {
|
|
|
140
144
|
}
|
|
141
145
|
export function observeCancelByDeny(settle, taskId) {
|
|
142
146
|
let timer;
|
|
147
|
+
const budgetMs = deliveryTiming().budgetMs;
|
|
143
148
|
const budget = new Promise((_, reject) => {
|
|
144
|
-
timer = setTimeout(() => reject(new Error(`cancel-by-deny did not settle within ${
|
|
149
|
+
timer = setTimeout(() => reject(new Error(`cancel-by-deny did not settle within ${budgetMs}ms`)), budgetMs);
|
|
145
150
|
unrefTimer(timer);
|
|
146
151
|
});
|
|
147
152
|
void Promise.race([settle, budget])
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EngineCapState } from '../engineCapsCache.js';
|
|
2
|
+
import type { HostSettingsLayer, WorkspaceTrustState } from '../hostSettingsSource.js';
|
|
3
|
+
import { type LocalAllowRuleDeps } from './localAllowRule.js';
|
|
4
|
+
import { type ListAllPersistedRulesOutcome, type RulesFacade } from './persistedRulesWire.js';
|
|
5
|
+
export declare const LOCAL_ALLOW_NOT_REDEEMABLE_REASONS: readonly ["managed_only", "tighten_only", "no_scope_root", "no_consent", "road_closed"];
|
|
6
|
+
export type LocalAllowNotRedeemableReason = (typeof LOCAL_ALLOW_NOT_REDEEMABLE_REASONS)[number];
|
|
7
|
+
export declare const LOCAL_ALLOW_UNKNOWN_CAUSES: readonly ["input_unreadable", "policy_unreadable", "consent_unreadable", "road_unknown"];
|
|
8
|
+
export type LocalAllowUnknownCause = (typeof LOCAL_ALLOW_UNKNOWN_CAUSES)[number];
|
|
9
|
+
export type LocalAllowRedeemability = {
|
|
10
|
+
readonly kind: 'redeemable';
|
|
11
|
+
readonly via: 'layer' | 'consent';
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'not_redeemable';
|
|
14
|
+
readonly reason: LocalAllowNotRedeemableReason;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'unknown';
|
|
17
|
+
readonly cause: LocalAllowUnknownCause;
|
|
18
|
+
};
|
|
19
|
+
export interface LocalAllowOfferedFacts {
|
|
20
|
+
readonly managedRulesOnly: boolean | 'unreadable';
|
|
21
|
+
readonly scopeRoot: string | undefined;
|
|
22
|
+
readonly road: EngineCapState;
|
|
23
|
+
}
|
|
24
|
+
export type LocalAllowRedeemabilityInput = ({
|
|
25
|
+
readonly subject: 'offered';
|
|
26
|
+
} & LocalAllowOfferedFacts) | {
|
|
27
|
+
readonly subject: 'declared';
|
|
28
|
+
readonly source: HostSettingsLayer;
|
|
29
|
+
readonly workspace: WorkspaceTrustState | 'unreadable';
|
|
30
|
+
readonly managedRulesOnly: boolean | 'unreadable';
|
|
31
|
+
readonly rule: string;
|
|
32
|
+
readonly scopeRoot: string | undefined;
|
|
33
|
+
readonly store: ListAllPersistedRulesOutcome;
|
|
34
|
+
};
|
|
35
|
+
export declare function localAllowRedeemability(input: LocalAllowRedeemabilityInput): LocalAllowRedeemability;
|
|
36
|
+
export declare const LOCAL_ALLOW_REDEMPTION_REFUSALS: readonly ["managed_only", "no_scope_root", "rule_shape", "request_refused", "preview_mismatch", "engine_refused", "ticket_dead"];
|
|
37
|
+
export type LocalAllowRedemptionRefusal = (typeof LOCAL_ALLOW_REDEMPTION_REFUSALS)[number];
|
|
38
|
+
export declare const LOCAL_ALLOW_REDEMPTION_UNAVAILABLE_CAUSES: readonly ["road_closed", "road_unknown", "policy_unreadable", "input_unreadable", "prepare_failed"];
|
|
39
|
+
export type LocalAllowRedemptionUnavailableCause = (typeof LOCAL_ALLOW_REDEMPTION_UNAVAILABLE_CAUSES)[number];
|
|
40
|
+
export declare const LOCAL_ALLOW_REDEMPTION_UNKNOWN_CAUSES: readonly ["redeem_unsettled", "receipt_unreadable"];
|
|
41
|
+
export type LocalAllowRedemptionUnknownCause = (typeof LOCAL_ALLOW_REDEMPTION_UNKNOWN_CAUSES)[number];
|
|
42
|
+
export type LocalAllowRedemptionReceipt = {
|
|
43
|
+
readonly kind: 'applied';
|
|
44
|
+
readonly outcome: 'persisted' | 'already_live';
|
|
45
|
+
readonly rule: string;
|
|
46
|
+
readonly scope: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: 'refused';
|
|
49
|
+
readonly reason: LocalAllowRedemptionRefusal;
|
|
50
|
+
readonly detail?: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly kind: 'unavailable';
|
|
53
|
+
readonly cause: LocalAllowRedemptionUnavailableCause;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: 'unknown';
|
|
56
|
+
readonly cause: LocalAllowRedemptionUnknownCause;
|
|
57
|
+
};
|
|
58
|
+
export type LocalAllowRedemptionFacade = Pick<RulesFacade, 'ccImportPrepare' | 'ccImportRedeem'>;
|
|
59
|
+
export interface ReadOnlyRuleOfferRedemption extends LocalAllowOfferedFacts {
|
|
60
|
+
readonly rule: string;
|
|
61
|
+
readonly expectedToolName?: string;
|
|
62
|
+
readonly settingsPath: string;
|
|
63
|
+
readonly deps: LocalAllowRuleDeps;
|
|
64
|
+
}
|
|
65
|
+
export declare function redeemReadOnlyRuleOffer(facade: LocalAllowRedemptionFacade, input: ReadOnlyRuleOfferRedemption, opts?: {
|
|
66
|
+
readonly signal?: AbortSignal;
|
|
67
|
+
}): Promise<LocalAllowRedemptionReceipt>;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { displayUntrusted } from '../displayUntrusted.js';
|
|
2
|
+
import { readOwnDataValue } from '../ownKey.js';
|
|
3
|
+
import { layerAdmissionWord } from '../settingsLayerAdmission.js';
|
|
4
|
+
import { parseLocalAllowRule } from './localAllowRule.js';
|
|
5
|
+
import { classifyRulesFailure, classifySkippedReason, readCcImportRedeemCounts, } from './persistedRulesWire.js';
|
|
6
|
+
export const LOCAL_ALLOW_NOT_REDEEMABLE_REASONS = Object.freeze(['managed_only', 'tighten_only', 'no_scope_root', 'no_consent', 'road_closed']);
|
|
7
|
+
export const LOCAL_ALLOW_UNKNOWN_CAUSES = Object.freeze(['input_unreadable', 'policy_unreadable', 'consent_unreadable', 'road_unknown']);
|
|
8
|
+
function verdict(v) {
|
|
9
|
+
return Object.freeze({ ...v });
|
|
10
|
+
}
|
|
11
|
+
const notRedeemable = (reason) => verdict({ kind: 'not_redeemable', reason });
|
|
12
|
+
const unknownBecause = (cause) => verdict({ kind: 'unknown', cause });
|
|
13
|
+
const PROJECT_SCOPE_PREFIX = 'project:';
|
|
14
|
+
const ALLOW_BEHAVIOR = 'allow';
|
|
15
|
+
const MANAGED_LAYER = 'policySettings';
|
|
16
|
+
const DETAIL_SHOWN_MAX = 400;
|
|
17
|
+
function policyStep(v) {
|
|
18
|
+
if (v === true)
|
|
19
|
+
return notRedeemable('managed_only');
|
|
20
|
+
if (v === false)
|
|
21
|
+
return undefined;
|
|
22
|
+
return unknownBecause('policy_unreadable');
|
|
23
|
+
}
|
|
24
|
+
function scopeRootOf(v) {
|
|
25
|
+
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
26
|
+
}
|
|
27
|
+
function storeRowConsents(row, rule, scopeRoot) {
|
|
28
|
+
if (typeof row !== 'object' || row === null)
|
|
29
|
+
return false;
|
|
30
|
+
const behavior = readOwnDataValue(row, 'behavior');
|
|
31
|
+
const text = readOwnDataValue(row, 'rule');
|
|
32
|
+
const scope = readOwnDataValue(row, 'scope');
|
|
33
|
+
return behavior === ALLOW_BEHAVIOR && text === rule && (scope === 'global' || scope === PROJECT_SCOPE_PREFIX + scopeRoot);
|
|
34
|
+
}
|
|
35
|
+
function consentRecorded(store, rule, scopeRoot) {
|
|
36
|
+
if (typeof store !== 'object' || store === null)
|
|
37
|
+
return undefined;
|
|
38
|
+
const ok = readOwnDataValue(store, 'ok');
|
|
39
|
+
if (ok !== true)
|
|
40
|
+
return undefined;
|
|
41
|
+
const rows = readOwnDataValue(store, 'rules');
|
|
42
|
+
if (!Array.isArray(rows))
|
|
43
|
+
return undefined;
|
|
44
|
+
for (let i = 0; i < rows.length; i++) {
|
|
45
|
+
if (storeRowConsents(readOwnDataValue(rows, String(i)), rule, scopeRoot))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function roadStep(road) {
|
|
51
|
+
if (road === 'true')
|
|
52
|
+
return verdict({ kind: 'redeemable', via: 'consent' });
|
|
53
|
+
if (road === 'false' || road === 'absent')
|
|
54
|
+
return notRedeemable('road_closed');
|
|
55
|
+
return unknownBecause('road_unknown');
|
|
56
|
+
}
|
|
57
|
+
export function localAllowRedeemability(input) {
|
|
58
|
+
try {
|
|
59
|
+
if (typeof input !== 'object' || input === null)
|
|
60
|
+
return unknownBecause('input_unreadable');
|
|
61
|
+
const subject = readOwnDataValue(input, 'subject');
|
|
62
|
+
if (subject !== 'offered' && subject !== 'declared')
|
|
63
|
+
return unknownBecause('input_unreadable');
|
|
64
|
+
const source = readOwnDataValue(input, 'source');
|
|
65
|
+
if (!(subject === 'declared' && source === MANAGED_LAYER)) {
|
|
66
|
+
const policy = policyStep(readOwnDataValue(input, 'managedRulesOnly'));
|
|
67
|
+
if (policy !== undefined)
|
|
68
|
+
return policy;
|
|
69
|
+
}
|
|
70
|
+
const scopeRoot = scopeRootOf(readOwnDataValue(input, 'scopeRoot'));
|
|
71
|
+
if (subject === 'offered') {
|
|
72
|
+
if (scopeRoot === undefined)
|
|
73
|
+
return notRedeemable('no_scope_root');
|
|
74
|
+
return roadStep(readOwnDataValue(input, 'road'));
|
|
75
|
+
}
|
|
76
|
+
const workspace = readOwnDataValue(input, 'workspace');
|
|
77
|
+
let word;
|
|
78
|
+
try {
|
|
79
|
+
word = layerAdmissionWord(source, workspace, 'permissions', ALLOW_BEHAVIOR);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return unknownBecause('input_unreadable');
|
|
83
|
+
}
|
|
84
|
+
if (word === 'admitted')
|
|
85
|
+
return verdict({ kind: 'redeemable', via: 'layer' });
|
|
86
|
+
if (word === 'tighten_only')
|
|
87
|
+
return notRedeemable('tighten_only');
|
|
88
|
+
if (scopeRoot === undefined)
|
|
89
|
+
return notRedeemable('no_scope_root');
|
|
90
|
+
const rule = readOwnDataValue(input, 'rule');
|
|
91
|
+
if (typeof rule !== 'string' || rule === '')
|
|
92
|
+
return unknownBecause('input_unreadable');
|
|
93
|
+
const consent = consentRecorded(readOwnDataValue(input, 'store'), rule, scopeRoot);
|
|
94
|
+
if (consent === undefined)
|
|
95
|
+
return unknownBecause('consent_unreadable');
|
|
96
|
+
return consent ? verdict({ kind: 'redeemable', via: 'consent' }) : notRedeemable('no_consent');
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return unknownBecause('input_unreadable');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export const LOCAL_ALLOW_REDEMPTION_REFUSALS = Object.freeze([
|
|
103
|
+
'managed_only',
|
|
104
|
+
'no_scope_root',
|
|
105
|
+
'rule_shape',
|
|
106
|
+
'request_refused',
|
|
107
|
+
'preview_mismatch',
|
|
108
|
+
'engine_refused',
|
|
109
|
+
'ticket_dead',
|
|
110
|
+
]);
|
|
111
|
+
export const LOCAL_ALLOW_REDEMPTION_UNAVAILABLE_CAUSES = Object.freeze(['road_closed', 'road_unknown', 'policy_unreadable', 'input_unreadable', 'prepare_failed']);
|
|
112
|
+
export const LOCAL_ALLOW_REDEMPTION_UNKNOWN_CAUSES = Object.freeze(['redeem_unsettled', 'receipt_unreadable']);
|
|
113
|
+
function singleRuleLayerContent(canonical) {
|
|
114
|
+
return JSON.stringify({ permissions: { allow: [canonical] } });
|
|
115
|
+
}
|
|
116
|
+
function receipt(r) {
|
|
117
|
+
return Object.freeze({ ...r });
|
|
118
|
+
}
|
|
119
|
+
function detailOf(text) {
|
|
120
|
+
if (typeof text !== 'string')
|
|
121
|
+
return {};
|
|
122
|
+
const shown = displayUntrusted(text, { max: DETAIL_SHOWN_MAX });
|
|
123
|
+
return shown.trim() !== '' ? { detail: shown } : {};
|
|
124
|
+
}
|
|
125
|
+
const JUDGED_RECEIPT = {
|
|
126
|
+
managed_only: { kind: 'refused', reason: 'managed_only' },
|
|
127
|
+
no_scope_root: { kind: 'refused', reason: 'no_scope_root' },
|
|
128
|
+
road_closed: { kind: 'unavailable', cause: 'road_closed' },
|
|
129
|
+
road_unknown: { kind: 'unavailable', cause: 'road_unknown' },
|
|
130
|
+
policy_unreadable: { kind: 'unavailable', cause: 'policy_unreadable' },
|
|
131
|
+
input_unreadable: { kind: 'unavailable', cause: 'input_unreadable' },
|
|
132
|
+
tighten_only: { kind: 'unavailable', cause: 'input_unreadable' },
|
|
133
|
+
no_consent: { kind: 'unavailable', cause: 'input_unreadable' },
|
|
134
|
+
consent_unreadable: { kind: 'unavailable', cause: 'input_unreadable' },
|
|
135
|
+
};
|
|
136
|
+
function previewSingle(prep, scopeRoot) {
|
|
137
|
+
const preview = typeof prep === 'object' && prep !== null ? readOwnDataValue(prep, 'preview') : undefined;
|
|
138
|
+
if (typeof preview !== 'object' || preview === null)
|
|
139
|
+
return { mismatch: true };
|
|
140
|
+
const skipped = readOwnDataValue(preview, 'skipped');
|
|
141
|
+
if (Array.isArray(skipped) && skipped.length > 0) {
|
|
142
|
+
const first = readOwnDataValue(skipped, '0');
|
|
143
|
+
const reason = typeof first === 'object' && first !== null ? readOwnDataValue(first, 'reason') : undefined;
|
|
144
|
+
return { mismatch: true, skipped: typeof reason === 'string' ? classifySkippedReason(reason).text : '' };
|
|
145
|
+
}
|
|
146
|
+
if (!Array.isArray(skipped))
|
|
147
|
+
return { mismatch: true };
|
|
148
|
+
const candidates = readOwnDataValue(preview, 'candidates');
|
|
149
|
+
if (!Array.isArray(candidates) || candidates.length !== 1)
|
|
150
|
+
return { mismatch: true };
|
|
151
|
+
const c = readOwnDataValue(candidates, '0');
|
|
152
|
+
if (typeof c !== 'object' || c === null)
|
|
153
|
+
return { mismatch: true };
|
|
154
|
+
const behavior = readOwnDataValue(c, 'behavior');
|
|
155
|
+
const rule = readOwnDataValue(c, 'rule');
|
|
156
|
+
const scope = readOwnDataValue(c, 'scope');
|
|
157
|
+
const kind = typeof scope === 'object' && scope !== null ? readOwnDataValue(scope, 'kind') : undefined;
|
|
158
|
+
const root = typeof scope === 'object' && scope !== null ? readOwnDataValue(scope, 'root') : undefined;
|
|
159
|
+
if (behavior !== ALLOW_BEHAVIOR || typeof rule !== 'string' || kind !== 'project' || root !== scopeRoot)
|
|
160
|
+
return { mismatch: true };
|
|
161
|
+
return { rule };
|
|
162
|
+
}
|
|
163
|
+
export async function redeemReadOnlyRuleOffer(facade, input, opts) {
|
|
164
|
+
let canonical;
|
|
165
|
+
let offered;
|
|
166
|
+
let scopeRoot;
|
|
167
|
+
let settingsPath;
|
|
168
|
+
try {
|
|
169
|
+
if (typeof input !== 'object' || input === null)
|
|
170
|
+
return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
|
|
171
|
+
const judged = localAllowRedeemability({
|
|
172
|
+
subject: 'offered',
|
|
173
|
+
managedRulesOnly: readOwnDataValue(input, 'managedRulesOnly'),
|
|
174
|
+
scopeRoot: readOwnDataValue(input, 'scopeRoot'),
|
|
175
|
+
road: readOwnDataValue(input, 'road'),
|
|
176
|
+
});
|
|
177
|
+
if (judged.kind !== 'redeemable')
|
|
178
|
+
return receipt(JUDGED_RECEIPT[judged.kind === 'not_redeemable' ? judged.reason : judged.cause]);
|
|
179
|
+
scopeRoot = readOwnDataValue(input, 'scopeRoot');
|
|
180
|
+
const path = readOwnDataValue(input, 'settingsPath');
|
|
181
|
+
if (typeof path !== 'string' || path === '')
|
|
182
|
+
return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
|
|
183
|
+
settingsPath = path;
|
|
184
|
+
const rule = readOwnDataValue(input, 'rule');
|
|
185
|
+
const expected = readOwnDataValue(input, 'expectedToolName');
|
|
186
|
+
const deps = readOwnDataValue(input, 'deps');
|
|
187
|
+
if (typeof rule !== 'string' || deps === undefined || (expected !== undefined && typeof expected !== 'string'))
|
|
188
|
+
return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
|
|
189
|
+
const parsed = parseLocalAllowRule(rule, expected, deps);
|
|
190
|
+
if (!parsed.ok)
|
|
191
|
+
return receipt({ kind: 'refused', reason: 'rule_shape', ...detailOf(parsed.error) });
|
|
192
|
+
canonical = parsed.canonical;
|
|
193
|
+
offered = rule;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
|
|
197
|
+
}
|
|
198
|
+
const signal = opts?.signal;
|
|
199
|
+
let prep;
|
|
200
|
+
try {
|
|
201
|
+
prep = await facade.ccImportPrepare([{ layer: 'localSettings', path: settingsPath, root: scopeRoot, content: singleRuleLayerContent(canonical) }], signal !== undefined ? { signal } : undefined);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
const f = classifyRulesFailure(e);
|
|
205
|
+
if (f.kind === 'lane-unavailable' || f.kind === 'route-missing')
|
|
206
|
+
return receipt({ kind: 'unavailable', cause: 'road_closed' });
|
|
207
|
+
if (f.kind === 'body-shape' || f.kind === 'field-refused' || f.kind === 'too-many-candidates' || f.kind === 'forbidden' || f.kind === 'operators-unconfigured') {
|
|
208
|
+
return receipt({ kind: 'refused', reason: 'request_refused' });
|
|
209
|
+
}
|
|
210
|
+
return receipt({ kind: 'unavailable', cause: 'prepare_failed' });
|
|
211
|
+
}
|
|
212
|
+
let ticket;
|
|
213
|
+
let storedRule;
|
|
214
|
+
try {
|
|
215
|
+
const single = previewSingle(prep, scopeRoot);
|
|
216
|
+
if ('mismatch' in single) {
|
|
217
|
+
return receipt(single.skipped !== undefined ? { kind: 'refused', reason: 'engine_refused', ...detailOf(single.skipped) } : { kind: 'refused', reason: 'preview_mismatch' });
|
|
218
|
+
}
|
|
219
|
+
if (single.rule !== canonical && single.rule !== offered)
|
|
220
|
+
return receipt({ kind: 'refused', reason: 'preview_mismatch' });
|
|
221
|
+
storedRule = single.rule;
|
|
222
|
+
const t = readOwnDataValue(prep, 'ticket');
|
|
223
|
+
if (typeof t !== 'string' || t === '')
|
|
224
|
+
return receipt({ kind: 'refused', reason: 'preview_mismatch' });
|
|
225
|
+
ticket = t;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return receipt({ kind: 'refused', reason: 'preview_mismatch' });
|
|
229
|
+
}
|
|
230
|
+
let body;
|
|
231
|
+
try {
|
|
232
|
+
body = await facade.ccImportRedeem(ticket, signal !== undefined ? { signal } : undefined);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
const f = classifyRulesFailure(e);
|
|
236
|
+
if (f.kind === 'ticket-dead')
|
|
237
|
+
return receipt({ kind: 'refused', reason: 'ticket_dead' });
|
|
238
|
+
if (f.kind === 'lane-unavailable')
|
|
239
|
+
return receipt({ kind: 'unavailable', cause: 'road_closed' });
|
|
240
|
+
return receipt({ kind: 'unknown', cause: 'redeem_unsettled' });
|
|
241
|
+
}
|
|
242
|
+
const counts = readCcImportRedeemCounts(body);
|
|
243
|
+
if (counts === null)
|
|
244
|
+
return receipt({ kind: 'unknown', cause: 'receipt_unreadable' });
|
|
245
|
+
const refusedRow = counts.refused?.[0];
|
|
246
|
+
if (refusedRow !== undefined)
|
|
247
|
+
return receipt({ kind: 'refused', reason: 'engine_refused', ...detailOf(refusedRow.detail) });
|
|
248
|
+
if (counts.indeterminate.length > 0)
|
|
249
|
+
return receipt({ kind: 'unknown', cause: 'redeem_unsettled' });
|
|
250
|
+
const scope = PROJECT_SCOPE_PREFIX + scopeRoot;
|
|
251
|
+
if (counts.persisted === 1 && counts.deduped === 0)
|
|
252
|
+
return receipt({ kind: 'applied', outcome: 'persisted', rule: storedRule, scope });
|
|
253
|
+
if (counts.deduped === 1 && counts.persisted === 0)
|
|
254
|
+
return receipt({ kind: 'applied', outcome: 'already_live', rule: storedRule, scope });
|
|
255
|
+
return receipt({ kind: 'unknown', cause: 'receipt_unreadable' });
|
|
256
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { putOwnKey, readOwnDataValue } from '../ownKey.js';
|
|
1
|
+
import { guardedRead, putOwnKey, readOwnDataValue } from '../ownKey.js';
|
|
2
2
|
import { HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, } from './hitlBridge.js';
|
|
3
3
|
import { approvalResolutionDetail, approvalResolutionStatusTag, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
|
|
4
4
|
import { readDecisionRefusalAsk } from './decisionRefusal.js';
|
|
5
5
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
6
6
|
import { hostLog } from '../host.js';
|
|
7
7
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
8
|
-
import { INTERRUPT_DENY_REASON, observeCancelByDeny } from './hitlHostSurface.js';
|
|
8
|
+
import { INTERRUPT_DENY_REASON, interruptDenyLifetime, observeCancelByDeny } from './hitlHostSurface.js';
|
|
9
9
|
import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
|
|
10
10
|
import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
|
|
11
11
|
import { inheritFrameBinding } from '../frameBinding.js';
|
|
12
12
|
import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
|
|
13
|
+
import { parkQuestionFrameBits, sourceGenerationNow } from './screenCardCarriers.js';
|
|
13
14
|
import { usableAskQuestions } from './parkOwnership.js';
|
|
14
15
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
15
16
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
@@ -65,6 +66,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
65
66
|
if (!hasQuestionOverlay()) {
|
|
66
67
|
return { kind: 'not_sent', lane: 'ask', cause: 'card_unavailable', detail: NO_QUESTION_OVERLAY_REASON };
|
|
67
68
|
}
|
|
69
|
+
const sourceGeneration = sourceGenerationNow(deps);
|
|
68
70
|
const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
|
|
69
71
|
if (!found.ok)
|
|
70
72
|
return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
|
|
@@ -113,7 +115,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
113
115
|
}
|
|
114
116
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
115
117
|
onPresented?.();
|
|
116
|
-
publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}) });
|
|
118
|
+
publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}), ...parkQuestionFrameBits(taskId, gatedCallId, sourceGeneration) });
|
|
117
119
|
});
|
|
118
120
|
if (withdrawn !== undefined) {
|
|
119
121
|
hostLog('debug', `liveHitlAskWire: question-withdrawn-by-host card=${questionId} verdict=${withdrawn.rowVerdict.kind}`);
|
|
@@ -121,7 +123,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
121
123
|
}
|
|
122
124
|
const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
|
|
123
125
|
if (answer === null) {
|
|
124
|
-
observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId,
|
|
126
|
+
observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, { signal: interruptDenyLifetime() }, pending), taskId);
|
|
125
127
|
return { kind: 'unsettled', ...at, cause: 'interrupted' };
|
|
126
128
|
}
|
|
127
129
|
let declinedReceipt;
|
|
@@ -194,6 +196,7 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
|
|
|
194
196
|
...(onPresented ? { onPresented } : {}),
|
|
195
197
|
...(deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : {}),
|
|
196
198
|
...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
|
|
199
|
+
...((read) => (typeof read === 'function' ? { engineGeneration: () => read.call(deps) } : {}))(guardedRead(() => deps.engineGeneration)),
|
|
197
200
|
}, taskId, argsByCallOf(led), ctx.signal, gatedCallId, park.toolName);
|
|
198
201
|
if (outcome.kind === 'decided' && outcome.decision === 'deny' && outcome.gatedCallId) {
|
|
199
202
|
led.markDenied(outcome.gatedCallId, denyAttributionOf(outcome.deny));
|
|
@@ -44,6 +44,11 @@ export interface CcImportRedeemCounts {
|
|
|
44
44
|
}
|
|
45
45
|
export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'refused' | 'indeterminate';
|
|
46
46
|
export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
|
|
47
|
+
export interface CcImportRefusedReading {
|
|
48
|
+
readonly count: number;
|
|
49
|
+
readonly rows: readonly CcImportRedeemRefused[];
|
|
50
|
+
}
|
|
51
|
+
export declare function refusedRowOf(counts: CcImportRedeemCounts | null | undefined): CcImportRefusedReading | null;
|
|
47
52
|
export type RulesFailure = {
|
|
48
53
|
kind: 'lane-unavailable';
|
|
49
54
|
message: string;
|
|
@@ -189,6 +194,17 @@ export declare function classifySkippedReason(reason: string): {
|
|
|
189
194
|
code?: string;
|
|
190
195
|
text: string;
|
|
191
196
|
};
|
|
197
|
+
export interface CcImportSkippedGroup {
|
|
198
|
+
readonly entries: readonly string[];
|
|
199
|
+
readonly reason: string;
|
|
200
|
+
readonly code?: string;
|
|
201
|
+
}
|
|
202
|
+
export interface CcImportSkippedReading {
|
|
203
|
+
readonly shape: 'absent' | 'malformed' | 'read';
|
|
204
|
+
readonly groups: readonly CcImportSkippedGroup[];
|
|
205
|
+
readonly unreadable: number;
|
|
206
|
+
}
|
|
207
|
+
export declare function ccImportSkippedRowsOf(skipped: {} | null | undefined): CcImportSkippedReading;
|
|
192
208
|
export type RulePersistOutcome = {
|
|
193
209
|
state: 'unknown';
|
|
194
210
|
} | {
|