@sema-agent/client-core 0.109.0 → 0.110.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 +173 -0
- package/README.md +46 -22
- package/dist/adapt/arms.js +20 -7
- package/dist/adapter/downstream/eventToSdkMessage.js +11 -6
- package/dist/declared/supportFloorDeclared.js +1 -1
- package/dist/displayUntrusted.js +132 -14
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +3 -0
- package/dist/engineHealthFacts.d.ts +9 -0
- package/dist/engineHealthFacts.js +21 -1
- package/dist/engineInlineTaskStats.d.ts +4 -3
- package/dist/engineInlineTaskStats.js +18 -2
- package/dist/engineNoticeCodes.d.ts +10 -0
- package/dist/engineNoticeCodes.js +33 -1
- package/dist/engineRepoint.d.ts +140 -0
- package/dist/engineRepoint.js +285 -0
- package/dist/engineRowRef.js +2 -0
- package/dist/engineSessionMessages.d.ts +2 -1
- package/dist/engineSessionMessages.js +5 -0
- package/dist/errorSupplementRoles.js +2 -1
- package/dist/fleet/fleetProjection.js +3 -2
- package/dist/generated/engineFactTables.d.ts +2 -0
- package/dist/generated/engineFactTables.js +12 -0
- package/dist/generated/sdkFactTables.d.ts +1 -0
- package/dist/generated/sdkFactTables.js +4 -0
- package/dist/hitl/approvalAskers.d.ts +6 -0
- package/dist/hitl/approvalAskers.js +30 -0
- package/dist/hitl/approvalCardReasons.js +6 -3
- package/dist/hitl/approvalRequestFrameReading.d.ts +7 -0
- package/dist/hitl/approvalRequestFrameReading.js +11 -0
- package/dist/hitl/approvalResolution.d.ts +8 -1
- package/dist/hitl/approvalResolution.js +4 -0
- package/dist/hitl/askGateWire.js +0 -2
- package/dist/hitl/automaticAnswerVerdict.js +2 -2
- package/dist/hitl/decisionConflict.js +6 -3
- package/dist/hitl/decisionRefusal.js +1 -0
- package/dist/hitl/decisionSend.d.ts +3 -0
- package/dist/hitl/decisionSend.js +29 -11
- package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
- package/dist/hitl/editedApprovalAcceptance.js +26 -0
- package/dist/hitl/frameRouter.js +4 -18
- package/dist/hitl/gateLedger.d.ts +0 -4
- package/dist/hitl/gateLedger.js +0 -19
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +4 -4
- package/dist/hitl/hitlHostSurface.d.ts +1 -0
- package/dist/hitl/hitlHostSurface.js +7 -2
- package/dist/hitl/hostWithdrawal.d.ts +15 -0
- package/dist/hitl/hostWithdrawal.js +38 -0
- package/dist/hitl/parkResolver.js +25 -9
- package/dist/hitl/persistedRulesWire.js +2 -2
- package/dist/hitl/planReviewWire.js +14 -4
- package/dist/hitl/sessionPolicyWire.js +6 -4
- package/dist/hitl/standingRows.d.ts +1 -0
- package/dist/hitl/standingRows.js +15 -1
- package/dist/hitl/toolApprovalWire.d.ts +35 -8
- package/dist/hitl/toolApprovalWire.js +235 -33
- package/dist/hooksWireCaps.js +31 -1
- package/dist/hostTurnSettings.d.ts +10 -4
- package/dist/hostTurnSettings.js +112 -5
- package/dist/i18n/messages.en.d.ts +83 -0
- package/dist/i18n/messages.en.js +86 -2
- package/dist/i18n/messages.zh.js +96 -2
- package/dist/index.d.ts +21 -7
- package/dist/index.js +14 -4
- package/dist/inheritEnvCapability.js +2 -0
- package/dist/inputAuthor.d.ts +16 -0
- package/dist/inputAuthor.js +44 -0
- package/dist/interruptRefusalNote.d.ts +2 -0
- package/dist/interruptRefusalNote.js +17 -0
- package/dist/liveModelCatalog.d.ts +2 -0
- package/dist/liveModelCatalog.js +21 -7
- package/dist/liveQuestionStore.d.ts +4 -1
- package/dist/liveQuestionStore.js +12 -3
- package/dist/mcpEngineLeg.d.ts +3 -0
- package/dist/mcpEngineLeg.js +38 -1
- package/dist/mcpPanel.js +2 -0
- package/dist/mcpReconnect.d.ts +9 -1
- package/dist/mcpReconnect.js +121 -16
- package/dist/modelCapabilityProbe.js +4 -3
- package/dist/modsFace.d.ts +8 -1
- package/dist/modsFace.js +27 -0
- package/dist/notifications.d.ts +17 -6
- package/dist/notifications.js +67 -19
- package/dist/ownKey.d.ts +0 -5
- package/dist/ownKey.js +0 -7
- package/dist/peerFrames.d.ts +40 -2
- package/dist/peerFrames.js +158 -3
- package/dist/pluginHooksWire.d.ts +2 -1
- package/dist/pluginHooksWire.js +56 -1
- package/dist/replayBody.js +10 -2
- package/dist/request/taskRequest.d.ts +5 -0
- package/dist/request/taskRequest.js +138 -21
- package/dist/resumeRefusalCopy.d.ts +1 -3
- package/dist/resumeRefusalCopy.js +2 -1
- package/dist/retryStatus.d.ts +2 -2
- package/dist/retryStatus.js +2 -0
- package/dist/seam.d.ts +12 -1
- package/dist/seam.js +7 -1
- package/dist/seatContract.d.ts +86 -9
- package/dist/seatContract.js +196 -7
- package/dist/sessionBackground.js +2 -1
- package/dist/sessionBackgroundHandoff.js +2 -1
- package/dist/sessionEngine.d.ts +2 -0
- package/dist/sessionEngine.js +111 -0
- package/dist/sessionMemoryStatus.d.ts +2 -1
- package/dist/sessionSyncFileHistory.d.ts +4 -0
- package/dist/sessionSyncFileHistory.js +28 -0
- package/dist/sseIdleTriage.d.ts +13 -1
- package/dist/sseIdleTriage.js +24 -6
- package/dist/subagent/engineSubagentTail.js +6 -0
- package/dist/terminalStatuses.d.ts +1 -0
- package/dist/terminalStatuses.js +4 -1
- package/dist/turnFailurePresentation.js +9 -2
- package/dist/typePins.d.ts +11 -0
- package/dist/usageAnalytics.d.ts +17 -0
- package/dist/usageAnalytics.js +44 -2
- package/dist/wireFailureShape.d.ts +2 -5
- package/dist/wireFailureShape.js +10 -20
- package/dist/wireRefusalCopy.d.ts +10 -0
- package/dist/wireRefusalCopy.js +76 -0
- package/docs/INTEGRATION-CLIENTS.md +2126 -20
- package/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
|
|
2
2
|
import { displayUntrusted } from '../displayUntrusted.js';
|
|
3
|
+
import { guardedRead, readOwnDataValue } from '../ownKey.js';
|
|
3
4
|
export const APPROVAL_REQUEST_FRAME_UNREADABLE_WHYS = Object.freeze(['version_unknown', 'kind_unknown', 'unreadable_fields']);
|
|
4
5
|
const KNOWN_FRAME_TYPE = 'approval_request';
|
|
5
6
|
const KNOWN_SCHEMA_VERSION = 1;
|
|
@@ -60,6 +61,16 @@ export function approvalRequestFrameReadingOf(frame) {
|
|
|
60
61
|
export function approvalUnreadableWhyWordOf(v) {
|
|
61
62
|
return APPROVAL_REQUEST_FRAME_UNREADABLE_WHYS.find((w) => w === v);
|
|
62
63
|
}
|
|
64
|
+
export function cardPayloadReadingOf(card) {
|
|
65
|
+
if (typeof card !== 'object' || card === null)
|
|
66
|
+
return Object.freeze({ payloadUnreadable: false });
|
|
67
|
+
const flag = guardedRead(() => readOwnDataValue(card, 'cardPayloadUnreadable'));
|
|
68
|
+
if (flag !== true)
|
|
69
|
+
return Object.freeze({ payloadUnreadable: false });
|
|
70
|
+
const word = guardedRead(() => readOwnDataValue(card, 'cardPayloadUnreadableWhy'));
|
|
71
|
+
const why = typeof word === 'string' ? approvalUnreadableWhyWordOf(word) : undefined;
|
|
72
|
+
return why === undefined ? Object.freeze({ payloadUnreadable: true }) : Object.freeze({ payloadUnreadable: true, why });
|
|
73
|
+
}
|
|
63
74
|
export function approvalFrameUnreadableWhyOf(reading) {
|
|
64
75
|
try {
|
|
65
76
|
if (typeof reading !== 'object' || reading === null)
|
|
@@ -8,7 +8,7 @@ import { type DeliveredReading, type LadderSafetyStop } from './attributionLadde
|
|
|
8
8
|
import { type CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
|
|
9
9
|
export declare const APPROVAL_RESOLUTION_LANES: readonly ["respond", "decide", "ask"];
|
|
10
10
|
export type ApprovalResolutionLane = (typeof APPROVAL_RESOLUTION_LANES)[number];
|
|
11
|
-
export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "wrong_gate", "fetch_failed", "no_input", "card_unavailable", "safety_stop", "row_unresumable"];
|
|
11
|
+
export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "wrong_gate", "fetch_failed", "no_input", "card_unavailable", "safety_stop", "row_unresumable", "withdrawn"];
|
|
12
12
|
export type ApprovalNotSentCause = (typeof APPROVAL_NOT_SENT_CAUSES)[number];
|
|
13
13
|
export declare const APPROVAL_UNSETTLED_CAUSES: readonly ["engine_refused", "engine_error", "no_answer", "transport_exhausted", "parking_in_progress", "interrupted", "orchestration", "unreadable"];
|
|
14
14
|
export type ApprovalUnsettledCause = (typeof APPROVAL_UNSETTLED_CAUSES)[number];
|
|
@@ -18,6 +18,11 @@ export interface ApprovalDenyFacts {
|
|
|
18
18
|
readonly policyCause?: PolicyDenyCause;
|
|
19
19
|
readonly attributionKey?: 'dropped' | 'renamed';
|
|
20
20
|
}
|
|
21
|
+
export interface ToolPermissionSettledElsewhere {
|
|
22
|
+
readonly firstDecision: 'allow' | 'deny';
|
|
23
|
+
readonly contrary: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function toolPermissionSettledElsewhereOf(first: 'allow' | 'deny', sent: ToolApprovalRespondDecision | 'approve'): ToolPermissionSettledElsewhere;
|
|
21
26
|
interface ApprovalResolutionBase {
|
|
22
27
|
readonly lane: ApprovalResolutionLane;
|
|
23
28
|
readonly gatedCallId?: string;
|
|
@@ -30,6 +35,7 @@ export interface ApprovalResolutionDecided extends ApprovalResolutionBase {
|
|
|
30
35
|
readonly receipt?: DecideReceiptView;
|
|
31
36
|
readonly ack?: ToolApprovalRespondAckView;
|
|
32
37
|
readonly answered?: AskAnsweredOutput;
|
|
38
|
+
readonly settledElsewhere?: ToolPermissionSettledElsewhere;
|
|
33
39
|
}
|
|
34
40
|
export interface ApprovalResolutionNotSent extends ApprovalResolutionBase {
|
|
35
41
|
readonly kind: 'not_sent';
|
|
@@ -38,6 +44,7 @@ export interface ApprovalResolutionNotSent extends ApprovalResolutionBase {
|
|
|
38
44
|
readonly rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
39
45
|
readonly kind: 'ok';
|
|
40
46
|
}>;
|
|
47
|
+
readonly toldBy?: 'host';
|
|
41
48
|
readonly detail?: string;
|
|
42
49
|
}
|
|
43
50
|
export interface ApprovalResolutionUnsettled extends ApprovalResolutionBase {
|
|
@@ -16,6 +16,7 @@ export const APPROVAL_NOT_SENT_CAUSES = Object.freeze([
|
|
|
16
16
|
'card_unavailable',
|
|
17
17
|
'safety_stop',
|
|
18
18
|
'row_unresumable',
|
|
19
|
+
'withdrawn',
|
|
19
20
|
]);
|
|
20
21
|
export const APPROVAL_UNSETTLED_CAUSES = Object.freeze([
|
|
21
22
|
'engine_refused',
|
|
@@ -27,6 +28,9 @@ export const APPROVAL_UNSETTLED_CAUSES = Object.freeze([
|
|
|
27
28
|
'orchestration',
|
|
28
29
|
'unreadable',
|
|
29
30
|
]);
|
|
31
|
+
export function toolPermissionSettledElsewhereOf(first, sent) {
|
|
32
|
+
return Object.freeze({ firstDecision: first, contrary: (first === 'deny') !== (sent === 'deny') });
|
|
33
|
+
}
|
|
30
34
|
export function isApprovalDecided(r) {
|
|
31
35
|
return typeof r === 'object' && r !== null && r.kind === 'decided';
|
|
32
36
|
}
|
package/dist/hitl/askGateWire.js
CHANGED
|
@@ -91,7 +91,6 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
|
|
|
91
91
|
await abortableSleep(reopenAfterMs, opts?.signal ?? new AbortController().signal);
|
|
92
92
|
if (!opts?.signal?.aborted) {
|
|
93
93
|
const seq = led.lastSeq();
|
|
94
|
-
led.beginStreamSegment();
|
|
95
94
|
stream = deps.runsEvents(taskId.current, {
|
|
96
95
|
...(seq !== undefined ? { lastEventId: seq } : {}),
|
|
97
96
|
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
@@ -170,7 +169,6 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
|
|
|
170
169
|
led.forgetRunScopedState();
|
|
171
170
|
}
|
|
172
171
|
const seq = handoff !== undefined ? undefined : led.lastSeq();
|
|
173
|
-
led.beginStreamSegment();
|
|
174
172
|
stream = deps.runsEvents(taskId.current, {
|
|
175
173
|
...(seq !== undefined ? { lastEventId: seq } : {}),
|
|
176
174
|
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
@@ -61,13 +61,13 @@ export function foldModeArbitrationRow(classes, askFace) {
|
|
|
61
61
|
return ORDINARY_ROW;
|
|
62
62
|
return Object.freeze({ bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor });
|
|
63
63
|
}
|
|
64
|
-
const
|
|
64
|
+
const HOOK_BLOCKING_CLASSES = new Set([...CORE_REAL_APPROVAL_ASK_CLASSES, ORIGIN_IMPLIES_CLASS.content_question]);
|
|
65
65
|
function classBlocks(lane, c, face) {
|
|
66
66
|
if (lane === 'mode_posture')
|
|
67
67
|
return bypassImmuneOn(c, face);
|
|
68
68
|
if (lane === 'standing_rule')
|
|
69
69
|
return !TABLE[c].ruleMayClear;
|
|
70
|
-
return
|
|
70
|
+
return HOOK_BLOCKING_CLASSES.has(c);
|
|
71
71
|
}
|
|
72
72
|
function mustAsk(road, extra) {
|
|
73
73
|
return Object.freeze({ kind: 'must_ask', road, ...(extra?.askClass !== undefined ? { askClass: extra.askClass } : {}), ...(extra?.origin !== undefined ? { origin: extra.origin } : {}) });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_UNSUPPORTED_VERSION, RESUME_OUTCOME_INVALID } from '../engineErrorCodes.js';
|
|
1
|
+
import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_INVALID_OUTCOME, CHECKPOINT_UNSUPPORTED_VERSION, RESUME_OUTCOME_INVALID } from '../engineErrorCodes.js';
|
|
2
2
|
import { msg, renderMessage } from '../i18n/translate.js';
|
|
3
3
|
import { ownDataField } from '../ownKey.js';
|
|
4
4
|
import { SDK_DECIDE_BINDING_KEYS } from '../generated/sdkFactTables.js';
|
|
@@ -28,16 +28,19 @@ export function decisionConflictOf(err, captured, protocolNameRead) {
|
|
|
28
28
|
return undefined;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
const protocolName = namedProtocolOf(protocolNameRead ?? protocolErrorNameReading(err
|
|
31
|
+
const protocolName = namedProtocolOf(protocolNameRead ?? protocolErrorNameReading(err));
|
|
32
32
|
if (code === CHECKPOINT_UNSUPPORTED_VERSION)
|
|
33
33
|
return 'format_version';
|
|
34
34
|
const bindingFamily = code === APPROVAL_BINDING_MISMATCH || (code === undefined && protocolName === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
|
|
35
|
-
|
|
35
|
+
const rowOwnFamily = code === CHECKPOINT_INVALID_OUTCOME || (code === undefined && protocolName === PROTOCOL_ERROR_NAME.CheckpointInvalidOutcomeError);
|
|
36
|
+
if (!bindingFamily && !rowOwnFamily && code !== RESUME_OUTCOME_INVALID)
|
|
36
37
|
return undefined;
|
|
37
38
|
const slots = readRefusalSlots(err, REFUSAL_WORD_KEYS, extra.bag);
|
|
38
39
|
const field = slots.field.word;
|
|
39
40
|
if (code === RESUME_OUTCOME_INVALID)
|
|
40
41
|
return field === ANSWER_FIELD ? 'answer_invalid' : field === GRANT_DIGEST_FIELD ? 'grant_changed' : undefined;
|
|
42
|
+
if (rowOwnFamily)
|
|
43
|
+
return field === undefined && !slots.field.present && slots.reason.word !== undefined ? 'row_integrity' : undefined;
|
|
41
44
|
if (field === undefined) {
|
|
42
45
|
if (slots.field.present)
|
|
43
46
|
return undefined;
|
|
@@ -226,6 +226,7 @@ export function mintDecisionRefusalReading(err, context, attributionCarried, del
|
|
|
226
226
|
const acceptableForm = errorCode === RESUME_CONSTRAINT_REJECTED && obj !== undefined ? isolated(() => readErrorPresent(obj, 'acceptableForm')) : undefined;
|
|
227
227
|
const transient = inRange &&
|
|
228
228
|
conflict !== 'grant_changed' &&
|
|
229
|
+
conflict !== 'row_integrity' &&
|
|
229
230
|
!unrecoverable &&
|
|
230
231
|
(TRANSIENT_STATUSES.has(status) || retriable || (status === 404 && obj !== undefined && isolated(() => notPendingCauseReading(obj)) === 'other_replica'));
|
|
231
232
|
return Object.freeze({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentClient, ElicitResponse } from '@sema-agent/sdk';
|
|
2
2
|
import type { UiLanguage } from '../env/uiLanguage.js';
|
|
3
|
+
import type { ToolPermissionSettledElsewhere } from './approvalResolution.js';
|
|
3
4
|
import { type AskGoneDetail } from '../decideReceipt.js';
|
|
4
5
|
import type { ApprovalDecidedByReading } from '../approvalDecidedByCapability.js';
|
|
5
6
|
import { type DecisionNoteAudit } from './approvalDecisionNoteAudit.js';
|
|
@@ -64,6 +65,7 @@ interface HitlDecisionOutcomeBase {
|
|
|
64
65
|
readonly attempts: number;
|
|
65
66
|
readonly fallback?: 'approval';
|
|
66
67
|
readonly noteDropped?: true;
|
|
68
|
+
readonly allowNoteDropped?: true;
|
|
67
69
|
readonly rememberDropped?: true;
|
|
68
70
|
readonly allowSessionCarried?: true;
|
|
69
71
|
readonly attributionDropped?: true;
|
|
@@ -115,5 +117,6 @@ export interface HitlDecisionSendTiming {
|
|
|
115
117
|
export declare function __setHitlDecisionSendTimingForTests(t?: HitlDecisionSendTiming): void;
|
|
116
118
|
export declare function hitlAskIdempotencyKey(askId: string, verdict: 'approve' | 'deny'): string;
|
|
117
119
|
export declare function sendHitlDecision(request: HitlDecisionSendRequest, deps: HitlDecisionSendDeps): Promise<HitlDecisionSendOutcome>;
|
|
120
|
+
export declare function toolPermissionSettledLine(fact: ToolPermissionSettledElsewhere | null | undefined, lang?: UiLanguage): string | undefined;
|
|
118
121
|
export declare function hitlDecisionOutcomeContent(outcome: HitlDecisionSendOutcome, lang?: UiLanguage): string | undefined;
|
|
119
122
|
export {};
|
|
@@ -14,7 +14,7 @@ import { ASK_DECIDED_CODE, classifyDeliveredDecisionRefusal, decisionRefusalClos
|
|
|
14
14
|
import { sessionAllowEligible } from './sessionAllowRoute.js';
|
|
15
15
|
import { addDecisionOpKeys, closeDecisionOp, decisionLedgerKeysOf, decisionOpsWithKey, openDecisionOp } from './decisionLedger.js';
|
|
16
16
|
import { hostHoldIsActive, takeHostHoldLedgerOp, tryHoldApprovalNow } from './approvalHolds.js';
|
|
17
|
-
import { answerPayloadFor, decisionAttributionKeyOf,
|
|
17
|
+
import { allowNoteReadingOf, answerPayloadFor, decisionAttributionKeyOf, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, replayedFirstDecisionOf, settlementAckOf, } from './toolApprovalWire.js';
|
|
18
18
|
export const HITL_DECISION_FAMILIES = Object.freeze(['approval', 'ask', 'elicitation']);
|
|
19
19
|
export const HITL_DECISION_NOT_APPLIED_CAUSES = Object.freeze(['gone', 'parked', 'parking', 'lane_disabled', 'refused', 'idempotency_crossed']);
|
|
20
20
|
export const HITL_DECISION_UNCONFIRMED_CAUSES = Object.freeze(['transport', 'server_error', 'local_error', 'aborted', 'gone_after_retry']);
|
|
@@ -163,9 +163,10 @@ async function sendApprovalFamily(respond, approvalId, answer, ctx) {
|
|
|
163
163
|
const hint = view !== undefined ? ownReadingSlot(view, 'retryAfterMs') : undefined;
|
|
164
164
|
return { ...base, kind: 'not_applied', cause: 'parking', ...(view !== undefined ? { parking: view, ...(hint !== undefined ? { retryAfterMs: hint } : {}) } : {}) };
|
|
165
165
|
}
|
|
166
|
-
const parsed = readToolApprovalRespondAck(raw);
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
const parsed = settlementAckOf(readToolApprovalRespondAck(raw));
|
|
167
|
+
const replayWord = replayedFirstDecisionOf(parsed);
|
|
168
|
+
if (parsed !== undefined && replayWord !== undefined) {
|
|
169
|
+
const first = parsed.approvalId === approvalId ? verdictOf(replayWord) : undefined;
|
|
169
170
|
return {
|
|
170
171
|
...base,
|
|
171
172
|
kind: 'settled_elsewhere',
|
|
@@ -183,6 +184,7 @@ function bodyFacts(answer, payload, rememberUnsupported) {
|
|
|
183
184
|
return {
|
|
184
185
|
...(reason !== undefined && reason.trim() !== '' && payload.note === undefined ? { noteDropped: true } : {}),
|
|
185
186
|
...(rememberUnsupported && answer.kind === 'allow' && answer.allowSession === true ? { rememberDropped: true } : {}),
|
|
187
|
+
...(answer.kind === 'allow' && allowNoteReadingOf(answer).kind === 'text' ? { allowNoteDropped: true } : {}),
|
|
186
188
|
};
|
|
187
189
|
}
|
|
188
190
|
function answerGateCannotTake(out) {
|
|
@@ -361,8 +363,10 @@ function snapshotAnswer(raw) {
|
|
|
361
363
|
if (kind === 'allow') {
|
|
362
364
|
const allowSession = guardedRead(() => readOwnDataValue(raw, 'allowSession')) === true;
|
|
363
365
|
const edited = guardedRead(() => readOwnDataValue(raw, 'updatedInput'));
|
|
366
|
+
const noteRaw = guardedRead(() => readOwnDataValue(raw, 'note'));
|
|
367
|
+
const note = typeof noteRaw === 'string' && noteRaw.trim() !== '' ? noteRaw : undefined;
|
|
364
368
|
if (edited === undefined)
|
|
365
|
-
return Object.freeze({ kind: 'allow', allowSession });
|
|
369
|
+
return Object.freeze({ kind: 'allow', allowSession, ...(note !== undefined ? { note } : {}) });
|
|
366
370
|
let copy;
|
|
367
371
|
try {
|
|
368
372
|
const text = JSON.stringify(edited);
|
|
@@ -373,7 +377,7 @@ function snapshotAnswer(raw) {
|
|
|
373
377
|
catch {
|
|
374
378
|
return undefined;
|
|
375
379
|
}
|
|
376
|
-
return Object.freeze({ kind: 'allow', allowSession, updatedInput: copy });
|
|
380
|
+
return Object.freeze({ kind: 'allow', allowSession, ...(note !== undefined ? { note } : {}), updatedInput: copy });
|
|
377
381
|
}
|
|
378
382
|
if (kind === 'deny') {
|
|
379
383
|
const reason = guardedRead(() => readOwnDataValue(raw, 'reason'));
|
|
@@ -560,6 +564,24 @@ export async function sendHitlDecision(request, deps) {
|
|
|
560
564
|
ownHold.release(out.kind === 'applied' || out.kind === 'settled_elsewhere' ? 'answered' : undefined);
|
|
561
565
|
return Object.freeze(out);
|
|
562
566
|
}
|
|
567
|
+
function settledElsewhereContraryContent(first, lang) {
|
|
568
|
+
return first === 'deny'
|
|
569
|
+
? renderMessage(msg('approval.decision.settledElsewhereContrary.deniedFirst'), lang)
|
|
570
|
+
: renderMessage(msg('approval.decision.settledElsewhereContrary.approvedFirst'), lang);
|
|
571
|
+
}
|
|
572
|
+
export function toolPermissionSettledLine(fact, lang) {
|
|
573
|
+
try {
|
|
574
|
+
if (typeof fact !== 'object' || fact === null)
|
|
575
|
+
return undefined;
|
|
576
|
+
if (readOwnDataValue(fact, 'contrary') !== true)
|
|
577
|
+
return undefined;
|
|
578
|
+
const first = readOwnDataValue(fact, 'firstDecision');
|
|
579
|
+
return first === 'allow' || first === 'deny' ? settledElsewhereContraryContent(first, lang) : undefined;
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
563
585
|
export function hitlDecisionOutcomeContent(outcome, lang) {
|
|
564
586
|
try {
|
|
565
587
|
if (typeof outcome !== 'object' || outcome === null)
|
|
@@ -569,11 +591,7 @@ export function hitlDecisionOutcomeContent(outcome, lang) {
|
|
|
569
591
|
if (readOwnDataValue(outcome, 'contrary') !== true)
|
|
570
592
|
return undefined;
|
|
571
593
|
const first = readOwnDataValue(outcome, 'firstDecision');
|
|
572
|
-
|
|
573
|
-
return renderMessage(msg('approval.decision.settledElsewhereContrary.deniedFirst'), lang);
|
|
574
|
-
if (first === 'approve')
|
|
575
|
-
return renderMessage(msg('approval.decision.settledElsewhereContrary.approvedFirst'), lang);
|
|
576
|
-
return undefined;
|
|
594
|
+
return first === 'deny' || first === 'approve' ? settledElsewhereContraryContent(first, lang) : undefined;
|
|
577
595
|
}
|
|
578
596
|
if (kind === 'unconfirmed' && readOwnDataValue(outcome, 'cause') === 'gone_after_retry')
|
|
579
597
|
return renderMessage(msg('approval.decision.unconfirmedAfterRetry'), lang);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { UiLanguage } from '../env/uiLanguage.js';
|
|
2
|
+
export declare const EDITED_APPROVAL_STATES: readonly ["accepts", "refuses", "unknown"];
|
|
3
|
+
export type EditedApprovalState = (typeof EDITED_APPROVAL_STATES)[number];
|
|
4
|
+
export declare const EDITED_APPROVAL_BASES: Readonly<{
|
|
5
|
+
accepts: readonly ["args_available"];
|
|
6
|
+
refuses: readonly ["args_unavailable"];
|
|
7
|
+
unknown: readonly ["card_unreadable", "slot_unreadable", "slot_malformed"];
|
|
8
|
+
}>;
|
|
9
|
+
interface EditedApprovalAccepts {
|
|
10
|
+
readonly state: 'accepts';
|
|
11
|
+
readonly basis: (typeof EDITED_APPROVAL_BASES.accepts)[number];
|
|
12
|
+
}
|
|
13
|
+
interface EditedApprovalRefuses {
|
|
14
|
+
readonly state: 'refuses';
|
|
15
|
+
readonly basis: (typeof EDITED_APPROVAL_BASES.refuses)[number];
|
|
16
|
+
readonly askStaysPending: true;
|
|
17
|
+
}
|
|
18
|
+
interface EditedApprovalUnknown {
|
|
19
|
+
readonly state: 'unknown';
|
|
20
|
+
readonly basis: (typeof EDITED_APPROVAL_BASES.unknown)[number];
|
|
21
|
+
}
|
|
22
|
+
export type EditedApprovalAcceptance = EditedApprovalAccepts | EditedApprovalRefuses | EditedApprovalUnknown;
|
|
23
|
+
export declare function editedApprovalAcceptanceOf(card: {} | null | undefined): EditedApprovalAcceptance;
|
|
24
|
+
export declare function editedApprovalRefusedLine(lang?: UiLanguage): string;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
2
|
+
import { slotReadOf } from '../wireFailureShape.js';
|
|
3
|
+
export const EDITED_APPROVAL_STATES = Object.freeze(['accepts', 'refuses', 'unknown']);
|
|
4
|
+
export const EDITED_APPROVAL_BASES = Object.freeze({
|
|
5
|
+
accepts: Object.freeze(['args_available']),
|
|
6
|
+
refuses: Object.freeze(['args_unavailable']),
|
|
7
|
+
unknown: Object.freeze(['card_unreadable', 'slot_unreadable', 'slot_malformed']),
|
|
8
|
+
});
|
|
9
|
+
const ACCEPTS = Object.freeze({ state: 'accepts', basis: 'args_available' });
|
|
10
|
+
const REFUSES = Object.freeze({ state: 'refuses', basis: 'args_unavailable', askStaysPending: true });
|
|
11
|
+
const UNKNOWN_CARD = Object.freeze({ state: 'unknown', basis: 'card_unreadable' });
|
|
12
|
+
const UNKNOWN_SLOT = Object.freeze({ state: 'unknown', basis: 'slot_unreadable' });
|
|
13
|
+
const UNKNOWN_MALFORMED = Object.freeze({ state: 'unknown', basis: 'slot_malformed' });
|
|
14
|
+
export function editedApprovalAcceptanceOf(card) {
|
|
15
|
+
if (typeof card !== 'object' || card === null)
|
|
16
|
+
return UNKNOWN_CARD;
|
|
17
|
+
const read = slotReadOf(card, 'argsUnavailable');
|
|
18
|
+
if (read.presence === 'unreadable')
|
|
19
|
+
return UNKNOWN_SLOT;
|
|
20
|
+
if (read.presence === 'absent' || read.value === undefined)
|
|
21
|
+
return ACCEPTS;
|
|
22
|
+
return read.value === true ? REFUSES : UNKNOWN_MALFORMED;
|
|
23
|
+
}
|
|
24
|
+
export function editedApprovalRefusedLine(lang) {
|
|
25
|
+
return renderMessage(msg('approval.editRefusal.line'), lang);
|
|
26
|
+
}
|
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -10,13 +10,13 @@ import { guardedRead, readOwnDataValue } from '../ownKey.js';
|
|
|
10
10
|
import { parkHeldCallIdOf } from '../engineRowRef.js';
|
|
11
11
|
import { inheritFrameBinding } from '../frameBinding.js';
|
|
12
12
|
import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
|
|
13
|
-
import { liveCardHoldOf,
|
|
13
|
+
import { liveCardHoldOf, settleEngineClosedApproval } from './approvalHolds.js';
|
|
14
14
|
import { approvalCompleteParked } from './approvalCompleteParked.js';
|
|
15
15
|
import { liveFrameCallKey } from './gateIdentity.js';
|
|
16
16
|
import { readPolicyDenyCause } from './policyDenyCause.js';
|
|
17
17
|
import { msg, renderMessage } from '../i18n/translate.js';
|
|
18
18
|
import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
|
|
19
|
-
import {
|
|
19
|
+
import { replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
|
|
20
20
|
import { settleWithinDeadline } from '../abortableSleep.js';
|
|
21
21
|
import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
|
|
22
22
|
import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
|
|
@@ -349,7 +349,7 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
349
349
|
return { kind: 'skip' };
|
|
350
350
|
}
|
|
351
351
|
const settledHere = settledApprovalDisposition(ev);
|
|
352
|
-
if (settledHere !== undefined ||
|
|
352
|
+
if (settledHere !== undefined || hasLedgerPositionKey(ev)) {
|
|
353
353
|
const disposition = settledHere ?? (await judgeLedgerApprovalFrame(ev, ctx));
|
|
354
354
|
if (disposition.kind !== 'live') {
|
|
355
355
|
hostLog('debug', disposition.kind === 'decided' && disposition.settled !== undefined
|
|
@@ -357,8 +357,6 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
357
357
|
: `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
|
|
358
358
|
`${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
|
|
359
359
|
' — not raised as a question, no decision sent');
|
|
360
|
-
if (settledHere === undefined && replayedApprovalClosesCardFrame(disposition))
|
|
361
|
-
noteEngineClosedApproval(ev.approvalId, false);
|
|
362
360
|
if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
|
|
363
361
|
surfaceReplayedApprovalListUnreadable();
|
|
364
362
|
if (deps.onReplayedApproval !== undefined) {
|
|
@@ -416,7 +414,7 @@ async function judgeLedgerApprovalFrame(ev, ctx) {
|
|
|
416
414
|
.finally(release);
|
|
417
415
|
});
|
|
418
416
|
const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
|
|
419
|
-
return replayedApprovalDisposition(ev, {
|
|
417
|
+
return replayedApprovalDisposition(ev, { approvals });
|
|
420
418
|
}
|
|
421
419
|
function routeToolStart(ev, callId, led) {
|
|
422
420
|
led.noteStart(callId, ev);
|
|
@@ -511,17 +509,5 @@ export async function routeFrame(ev, ctx) {
|
|
|
511
509
|
if (ev.type === 'failed') {
|
|
512
510
|
return { kind: 'end', events: [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal }), ev] };
|
|
513
511
|
}
|
|
514
|
-
if (ev.type === 'approval_request') {
|
|
515
|
-
try {
|
|
516
|
-
if (!hasLedgerPositionKey(ev)) {
|
|
517
|
-
const id = ev.approvalId;
|
|
518
|
-
if (typeof id === 'string' && id !== '')
|
|
519
|
-
led.notePreambleApproval(id);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
catch (e) {
|
|
523
|
-
hostLog('debug', `liveHitlAskWire: approval_request frame unreadable — not counted as preamble evidence: ${String(e).slice(0, 120)}`);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
512
|
return { kind: 'yield', events: [ev] };
|
|
527
513
|
}
|
|
@@ -18,14 +18,10 @@ export interface AskAnsweredOutput {
|
|
|
18
18
|
export declare function durableSeqOf(ev: object): string | undefined;
|
|
19
19
|
export declare const BODY_SEQ_OTHER_AXIS_EVENTS: readonly string[];
|
|
20
20
|
export declare function hasLedgerPositionKey(ev: object): boolean;
|
|
21
|
-
export declare const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
|
|
22
21
|
export interface GateLedger {
|
|
23
22
|
noteSeq(ev: AgentEvent): void;
|
|
24
23
|
lastSeq(): string | undefined;
|
|
25
24
|
forgetRunScopedState(): void;
|
|
26
|
-
notePreambleApproval(approvalId: string): void;
|
|
27
|
-
preambleApprovals(): ReadonlySet<string>;
|
|
28
|
-
beginStreamSegment(): void;
|
|
29
25
|
trackLiveCard(phaseOf: () => 'card' | 'respond', handled: Promise<void>): void;
|
|
30
26
|
answeringLiveCards(): Promise<void>[];
|
|
31
27
|
liveCardsSettled(): Promise<void>;
|
package/dist/hitl/gateLedger.js
CHANGED
|
@@ -21,7 +21,6 @@ export function hasLedgerPositionKey(ev) {
|
|
|
21
21
|
const { id, seq } = ev;
|
|
22
22
|
return (id !== undefined && id !== null) || (seq !== undefined && seq !== null);
|
|
23
23
|
}
|
|
24
|
-
export const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
|
|
25
24
|
export const START_ARGS_MAX_ENTRIES = 256;
|
|
26
25
|
function normalizeDenyAttribution(a) {
|
|
27
26
|
if (a === undefined)
|
|
@@ -108,7 +107,6 @@ export function createGateLedger() {
|
|
|
108
107
|
let denyStampNextFsEnd = false;
|
|
109
108
|
let denyStampAttribution;
|
|
110
109
|
let seq;
|
|
111
|
-
const preambleApprovalIds = new Set();
|
|
112
110
|
const liveCards = new Set();
|
|
113
111
|
return {
|
|
114
112
|
noteSeq(ev) {
|
|
@@ -125,23 +123,6 @@ export function createGateLedger() {
|
|
|
125
123
|
startArgsByCall.clear();
|
|
126
124
|
gatedStartCalls.clear();
|
|
127
125
|
},
|
|
128
|
-
notePreambleApproval(approvalId) {
|
|
129
|
-
if (typeof approvalId !== 'string' || approvalId === '')
|
|
130
|
-
return;
|
|
131
|
-
preambleApprovalIds.delete(approvalId);
|
|
132
|
-
preambleApprovalIds.add(approvalId);
|
|
133
|
-
if (preambleApprovalIds.size > PREAMBLE_APPROVALS_MAX_ENTRIES) {
|
|
134
|
-
const oldest = preambleApprovalIds.values().next().value;
|
|
135
|
-
if (oldest !== undefined)
|
|
136
|
-
preambleApprovalIds.delete(oldest);
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
preambleApprovals() {
|
|
140
|
-
return preambleApprovalIds;
|
|
141
|
-
},
|
|
142
|
-
beginStreamSegment() {
|
|
143
|
-
preambleApprovalIds.clear();
|
|
144
|
-
},
|
|
145
126
|
trackLiveCard(phaseOf, handled) {
|
|
146
127
|
const entry = { phaseOf, handled };
|
|
147
128
|
liveCards.add(entry);
|
|
@@ -160,6 +160,7 @@ export declare class HitlBridge {
|
|
|
160
160
|
decision: 'approve';
|
|
161
161
|
updatedInput?: unknown;
|
|
162
162
|
remember?: 'session' | undefined;
|
|
163
|
+
feedback?: string | undefined;
|
|
163
164
|
} | HitlDecideDenyOutcome, toolUseID?: string, opts?: {
|
|
164
165
|
signal?: AbortSignal;
|
|
165
166
|
operation?: DecideOperationSpan;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -385,6 +385,7 @@ export class HitlBridge {
|
|
|
385
385
|
...this.bindingOf(pending),
|
|
386
386
|
...(outcome.updatedInput !== undefined ? { updatedInput: outcome.updatedInput } : {}),
|
|
387
387
|
...(outcome.remember !== undefined ? { remember: outcome.remember } : {}),
|
|
388
|
+
...(outcome.feedback !== undefined ? { feedback: outcome.feedback } : {}),
|
|
388
389
|
}
|
|
389
390
|
: {
|
|
390
391
|
decision: 'deny',
|
|
@@ -598,12 +599,11 @@ function bindingMismatchReading(e) {
|
|
|
598
599
|
return 'not';
|
|
599
600
|
const code = readWireErrorCodeReading(e);
|
|
600
601
|
const nameReading = protocolErrorNameReading(e);
|
|
601
|
-
const
|
|
602
|
-
|
|
602
|
+
const codeReclassified = code.state === 'code' && code.code === CHECKPOINT_INVALID_OUTCOME;
|
|
603
|
+
const family = (code.state === 'code' && code.code === APPROVAL_BINDING_MISMATCH) ||
|
|
604
|
+
(!codeReclassified && nameReading.state === 'named' && nameReading.name === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
|
|
603
605
|
if (family)
|
|
604
606
|
return 'mismatch';
|
|
605
|
-
if (nameReading.state === 'sdk-instance' && code.state !== 'code')
|
|
606
|
-
return 'unreadable';
|
|
607
607
|
if (code.state === 'unreadable')
|
|
608
608
|
return 'unreadable';
|
|
609
609
|
return 'not';
|
|
@@ -28,6 +28,7 @@ export declare const CANCEL_DENY_WARN_TEXT = "could not cancel the pending quest
|
|
|
28
28
|
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";
|
|
29
29
|
export declare const EDIT_NOT_FORWARDED_WARN_TEXT = "your edited tool input was NOT forwarded \u2014 the tool is running with the ORIGINAL input you saw before editing";
|
|
30
30
|
export declare function surfaceRememberNotApplied(): void;
|
|
31
|
+
export declare function surfaceAllowNoteNotCarried(sentence: string): void;
|
|
31
32
|
export declare function surfaceEditNotForwarded(): void;
|
|
32
33
|
export declare const APPROVAL_PARKING_WARN_TEXT = "This approval is being parked; answer it again on the parked card when it appears.";
|
|
33
34
|
export declare function approvalParkingNoticeText(retryAfterMs?: number): string;
|
|
@@ -2,6 +2,7 @@ import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
|
2
2
|
import { hostLog } from '../host.js';
|
|
3
3
|
import { unrefTimer } from '../unrefTimer.js';
|
|
4
4
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
5
|
+
import { editedApprovalRefusedLine } from './editedApprovalAcceptance.js';
|
|
5
6
|
import { decisionRefusalSaysAskGone } from './decisionRefusal.js';
|
|
6
7
|
const hostSurfaceByKey = createSessionSlot();
|
|
7
8
|
const hostSurfaceMissesByKey = new Map();
|
|
@@ -73,6 +74,11 @@ const EDIT_NOT_FORWARDED_TIMEOUT_MS = 20_000;
|
|
|
73
74
|
export function surfaceRememberNotApplied() {
|
|
74
75
|
surfaceSelfClearingWarn(REMEMBER_NOT_APPLIED_KEY, REMEMBER_NOT_APPLIED_WARN_TEXT, REMEMBER_NOT_APPLIED_TIMEOUT_MS);
|
|
75
76
|
}
|
|
77
|
+
const ALLOW_NOTE_NOT_CARRIED_KEY = 'hitl-allow-note-not-carried';
|
|
78
|
+
const ALLOW_NOTE_NOT_CARRIED_TIMEOUT_MS = 10_000;
|
|
79
|
+
export function surfaceAllowNoteNotCarried(sentence) {
|
|
80
|
+
surfaceSelfClearingWarn(ALLOW_NOTE_NOT_CARRIED_KEY, sentence, ALLOW_NOTE_NOT_CARRIED_TIMEOUT_MS);
|
|
81
|
+
}
|
|
76
82
|
export function surfaceEditNotForwarded() {
|
|
77
83
|
surfaceSelfClearingWarn(EDIT_NOT_FORWARDED_KEY, EDIT_NOT_FORWARDED_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
|
|
78
84
|
}
|
|
@@ -100,8 +106,7 @@ const REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS = 10_000;
|
|
|
100
106
|
export function surfaceReplayedApprovalListUnreadable() {
|
|
101
107
|
surfaceSelfClearingWarn(REPLAYED_APPROVAL_LIST_UNREADABLE_KEY, REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT, REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS);
|
|
102
108
|
}
|
|
103
|
-
export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT =
|
|
104
|
-
'The request is still waiting — approve or deny it as it is.';
|
|
109
|
+
export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT = editedApprovalRefusedLine();
|
|
105
110
|
const EDIT_REFUSED_ON_BLIND_ASK_KEY = 'hitl-edit-refused-blind-ask';
|
|
106
111
|
export function surfaceEditRefusedOnBlindAsk() {
|
|
107
112
|
surfaceSelfClearingWarn(EDIT_REFUSED_ON_BLIND_ASK_KEY, EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CheckpointRowBar } from '../checkpointRowVerdict.js';
|
|
2
|
+
export interface HostWithdrawal {
|
|
3
|
+
readonly toldBy: 'host';
|
|
4
|
+
readonly rowVerdict: CheckpointRowBar;
|
|
5
|
+
}
|
|
6
|
+
export declare function hostWithdrawalOf(v: object | null | undefined): HostWithdrawal | undefined;
|
|
7
|
+
export declare const PARK_QUESTION_WITHDRAW_REFUSALS: readonly ["malformed", "no_card", "not_withdrawable"];
|
|
8
|
+
export type ParkQuestionWithdrawRefusal = (typeof PARK_QUESTION_WITHDRAW_REFUSALS)[number];
|
|
9
|
+
export type ParkQuestionWithdrawal = {
|
|
10
|
+
readonly withdrawn: true;
|
|
11
|
+
} | {
|
|
12
|
+
readonly withdrawn: false;
|
|
13
|
+
readonly refusal: ParkQuestionWithdrawRefusal;
|
|
14
|
+
};
|
|
15
|
+
export declare function withdrawParkQuestion(questionId: string, withdrawal: HostWithdrawal): ParkQuestionWithdrawal;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { slotReadOf } from '../wireFailureShape.js';
|
|
2
|
+
import { checkpointRowBarOf } from '../checkpointRowVerdict.js';
|
|
3
|
+
import { takeLocalQuestionWithdrawHook } from '../liveQuestionStore.js';
|
|
4
|
+
import { hostLog } from '../host.js';
|
|
5
|
+
export function hostWithdrawalOf(v) {
|
|
6
|
+
try {
|
|
7
|
+
if (typeof v !== 'object' || v === null)
|
|
8
|
+
return undefined;
|
|
9
|
+
const told = slotReadOf(v, 'toldBy');
|
|
10
|
+
if (told.presence !== 'present' || told.value !== 'host')
|
|
11
|
+
return undefined;
|
|
12
|
+
if (slotReadOf(v, 'rowVerdict').presence !== 'present')
|
|
13
|
+
return undefined;
|
|
14
|
+
const bar = checkpointRowBarOf(v);
|
|
15
|
+
return bar === undefined ? undefined : Object.freeze({ toldBy: 'host', rowVerdict: bar });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const PARK_QUESTION_WITHDRAW_REFUSALS = Object.freeze(['malformed', 'no_card', 'not_withdrawable']);
|
|
22
|
+
const WITHDRAWN = Object.freeze({ withdrawn: true });
|
|
23
|
+
const refused = (refusal) => Object.freeze({ withdrawn: false, refusal });
|
|
24
|
+
export function withdrawParkQuestion(questionId, withdrawal) {
|
|
25
|
+
const told = hostWithdrawalOf(withdrawal);
|
|
26
|
+
if (told === undefined)
|
|
27
|
+
return refused('malformed');
|
|
28
|
+
const taken = takeLocalQuestionWithdrawHook(questionId);
|
|
29
|
+
if (taken === 'no_card' || taken === 'not_withdrawable')
|
|
30
|
+
return refused(taken);
|
|
31
|
+
try {
|
|
32
|
+
taken(told);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
hostLog('debug', `hostWithdrawal: withdraw-hook-threw card=${questionId} thrown=${e instanceof Error ? e.name : typeof e}`);
|
|
36
|
+
}
|
|
37
|
+
return WITHDRAWN;
|
|
38
|
+
}
|