@sema-agent/client-core 0.90.0 → 0.91.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 +193 -0
- package/README.md +24 -18
- package/dist/adapt/arms.js +9 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
- package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
- package/dist/adapter/downstream/wiringManifestView.js +16 -0
- package/dist/adapter/runStream.js +37 -20
- package/dist/askSurvivesStandingAnswer.d.ts +10 -0
- package/dist/askSurvivesStandingAnswer.js +40 -0
- package/dist/attachmentSeatCapability.d.ts +11 -0
- package/dist/attachmentSeatCapability.js +27 -0
- package/dist/attachmentsWireCaps.d.ts +9 -0
- package/dist/attachmentsWireCaps.js +74 -21
- package/dist/compensations.js +10 -14
- package/dist/controlRouter.d.ts +9 -2
- package/dist/controlRouter.js +66 -28
- package/dist/decideFailureNote.js +4 -2
- package/dist/disableSkillsWire.d.ts +1 -0
- package/dist/disableSkillsWire.js +8 -1
- package/dist/displayUntrusted.js +153 -4
- package/dist/engineCapsCache.d.ts +3 -1
- package/dist/engineCapsCache.js +56 -6
- package/dist/engineClosedSets.d.ts +2 -0
- package/dist/engineClosedSets.js +3 -0
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +10 -0
- package/dist/generated/engineFactTables.d.ts +12 -0
- package/dist/generated/engineFactTables.js +47 -0
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +1 -1
- package/dist/hitl/crashConverged.d.ts +7 -0
- package/dist/hitl/crashConverged.js +57 -0
- package/dist/hitl/crashConvergedNotice.d.ts +4 -0
- package/dist/hitl/crashConvergedNotice.js +57 -0
- package/dist/hitl/hitlBridge.d.ts +7 -4
- package/dist/hitl/parkResolver.d.ts +7 -2
- package/dist/hitl/parkResolver.js +19 -7
- package/dist/hitl/planReviewWire.d.ts +1 -1
- package/dist/hitl/planReviewWire.js +18 -4
- package/dist/hitl/sessionPolicyWire.d.ts +5 -0
- package/dist/hitl/sessionPolicyWire.js +63 -0
- package/dist/hitl/suspendedReopen.js +2 -2
- package/dist/hitl/toolApprovalWire.d.ts +4 -1
- package/dist/hitl/toolApprovalWire.js +20 -0
- package/dist/hookMatcherVerdict.d.ts +29 -0
- package/dist/hookMatcherVerdict.js +265 -0
- package/dist/humanTurn.d.ts +91 -0
- package/dist/humanTurn.js +253 -0
- package/dist/humanTurnRead.d.ts +105 -0
- package/dist/humanTurnRead.js +331 -0
- package/dist/index.d.ts +25 -8
- package/dist/index.js +16 -6
- package/dist/inheritEnvWire.js +1 -1
- package/dist/liveModelCatalog.d.ts +2 -0
- package/dist/liveModelCatalog.js +51 -3
- package/dist/permissionRuleIssue.d.ts +1 -1
- package/dist/permissionRuleIssue.js +22 -3
- package/dist/permissionsAllowGrantCapability.d.ts +2 -1
- package/dist/permissionsAllowGrantCapability.js +11 -2
- package/dist/permissionsDeclaration.d.ts +23 -0
- package/dist/permissionsDeclaration.js +133 -53
- package/dist/promptDelivery.d.ts +11 -3
- package/dist/promptDelivery.js +164 -29
- package/dist/providerRejection.d.ts +1 -0
- package/dist/providerRejection.js +20 -0
- package/dist/readDenyTiers.d.ts +10 -0
- package/dist/readDenyTiers.js +29 -0
- package/dist/request/taskRequest.js +2 -2
- package/dist/resumeRefusalCopy.d.ts +11 -0
- package/dist/resumeRefusalCopy.js +61 -4
- package/dist/rewindArchiveCapability.js +4 -4
- package/dist/rewindWireCaps.d.ts +2 -1
- package/dist/rewindWireCaps.js +19 -2
- package/dist/seam.d.ts +2 -0
- package/dist/taskRequestWords.d.ts +7 -1
- package/dist/taskRequestWords.js +18 -8
- package/dist/toolHistoryBadTurn.d.ts +1 -0
- package/dist/toolHistoryBadTurn.js +89 -0
- package/dist/toolHistoryMismatch.d.ts +2 -1
- package/dist/toolHistoryMismatch.js +19 -6
- package/dist/userAttachmentEnvelope.d.ts +61 -0
- package/dist/userAttachmentEnvelope.js +204 -0
- package/dist/wireFailureShape.d.ts +1 -0
- package/dist/wireFailureShape.js +4 -0
- package/dist/wireRefusalCopy.d.ts +6 -0
- package/dist/wireRefusalCopy.js +26 -3
- package/docs/INTEGRATION-CLIENTS.md +875 -14
- package/package.json +2 -2
|
@@ -127,3 +127,60 @@ export function projectCrashConverged(env) {
|
|
|
127
127
|
}
|
|
128
128
|
return { total: resumeSafe.length + needsHuman.length, resumeSafe, needsHuman, dropped };
|
|
129
129
|
}
|
|
130
|
+
const isCount = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
131
|
+
function bucketRowsOf(bucket) {
|
|
132
|
+
if (!Array.isArray(bucket))
|
|
133
|
+
return undefined;
|
|
134
|
+
const len = ownDataValue(bucket, 'length');
|
|
135
|
+
if (!isCount(len) || len > MAX_CRASH_CONVERGED_ROWS)
|
|
136
|
+
return undefined;
|
|
137
|
+
const rows = [];
|
|
138
|
+
for (let i = 0; i < len; i++)
|
|
139
|
+
rows.push(ownDataValue(bucket, String(i)));
|
|
140
|
+
return rows;
|
|
141
|
+
}
|
|
142
|
+
function reasonTextOf(row) {
|
|
143
|
+
try {
|
|
144
|
+
if (typeof row !== 'object' || row === null)
|
|
145
|
+
return undefined;
|
|
146
|
+
const v = ownDataValue(row, 'needsHumanReason');
|
|
147
|
+
return typeof v === 'string' ? v : undefined;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function causeWordsOf(rows) {
|
|
154
|
+
const seen = [];
|
|
155
|
+
try {
|
|
156
|
+
for (const row of rows) {
|
|
157
|
+
if (typeof row !== 'object' || row === null)
|
|
158
|
+
return undefined;
|
|
159
|
+
const c = ownDataValue(row, 'cause');
|
|
160
|
+
if (isCrashConvergedCause(c) && !seen.includes(c))
|
|
161
|
+
seen.push(c);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return seen;
|
|
168
|
+
}
|
|
169
|
+
export function readCrashConvergedNoticeFacts(projection) {
|
|
170
|
+
try {
|
|
171
|
+
if (typeof projection !== 'object' || projection === null)
|
|
172
|
+
return undefined;
|
|
173
|
+
const total = ownDataValue(projection, 'total');
|
|
174
|
+
const dropped = ownDataValue(projection, 'dropped');
|
|
175
|
+
if (!isCount(total) || !isCount(dropped) || total > MAX_CRASH_CONVERGED_ROWS)
|
|
176
|
+
return undefined;
|
|
177
|
+
const safe = bucketRowsOf(ownDataValue(projection, 'resumeSafe'));
|
|
178
|
+
const human = bucketRowsOf(ownDataValue(projection, 'needsHuman'));
|
|
179
|
+
if (safe === undefined || human === undefined || safe.length + human.length !== total)
|
|
180
|
+
return undefined;
|
|
181
|
+
return { total, dropped, needsHumanReasons: human.map(reasonTextOf), causes: causeWordsOf([...safe, ...human]) };
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CrashConvergedProjection } from './crashConverged.js';
|
|
2
|
+
export declare const NEEDS_HUMAN_REASON_ORDER: readonly ["approved_then_interrupted", "pending_unsafe", "contradictory", "unstable_row"];
|
|
3
|
+
export declare function crashConvergedNeedsHumanSentence(reason: string | undefined, n: number, total: number): string | undefined;
|
|
4
|
+
export declare function crashConvergedNotice(projection: CrashConvergedProjection | undefined): string | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readCrashConvergedNoticeFacts } from './crashConverged.js';
|
|
2
|
+
export const NEEDS_HUMAN_REASON_ORDER = Object.freeze(['approved_then_interrupted', 'pending_unsafe', 'contradictory', 'unstable_row']);
|
|
3
|
+
const _orderCoversReasons = true;
|
|
4
|
+
const _orderInsideReasons = true;
|
|
5
|
+
const _orderHasNoDuplicate = true;
|
|
6
|
+
void _orderCoversReasons;
|
|
7
|
+
void _orderInsideReasons;
|
|
8
|
+
void _orderHasNoDuplicate;
|
|
9
|
+
const RECORDED = 'based on what was recorded';
|
|
10
|
+
const NO_AUTO_RERUN = 'sema has not re-run anything';
|
|
11
|
+
const plural = (n, one, many) => (n === 1 ? one : many);
|
|
12
|
+
const SENTENCE = Object.freeze({
|
|
13
|
+
approved_then_interrupted: (n, total) => `${n} of ${total} had already been approved and may have been running when the previous engine run ended, so a side effect may have partly landed`,
|
|
14
|
+
pending_unsafe: (n, total) => `${n} of ${total} had not started, ${RECORDED}, but the engine did not mark ${plural(n, 'it', 'them')} as safe to re-run`,
|
|
15
|
+
contradictory: (n, total) => `${n} of ${total} ${plural(n, 'has a contradictory record', 'have contradictory records')}`,
|
|
16
|
+
unstable_row: (n, total) => `${n} of ${total} ${plural(n, 'has a record that read differently each time it was read', 'have records that read differently each time they were read')}`,
|
|
17
|
+
});
|
|
18
|
+
const fallbackSentence = (n, total) => `${n} of ${total} had already been approved, or cannot be accounted for, so a side effect may have partly landed`;
|
|
19
|
+
const isNeedsHumanReason = (v) => typeof v === 'string' && Object.hasOwn(SENTENCE, v);
|
|
20
|
+
export function crashConvergedNeedsHumanSentence(reason, n, total) {
|
|
21
|
+
if (!isNeedsHumanReason(reason))
|
|
22
|
+
return undefined;
|
|
23
|
+
if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 1)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof total !== 'number' || !Number.isSafeInteger(total) || total < n)
|
|
26
|
+
return undefined;
|
|
27
|
+
return SENTENCE[reason](n, total);
|
|
28
|
+
}
|
|
29
|
+
function needsHumanClauses(reasons, total) {
|
|
30
|
+
const clauses = [];
|
|
31
|
+
for (const reason of NEEDS_HUMAN_REASON_ORDER) {
|
|
32
|
+
const n = reasons.filter((r) => r === reason).length;
|
|
33
|
+
if (n > 0)
|
|
34
|
+
clauses.push(SENTENCE[reason](n, total));
|
|
35
|
+
}
|
|
36
|
+
const fallback = reasons.filter((r) => !isNeedsHumanReason(r)).length;
|
|
37
|
+
if (fallback > 0)
|
|
38
|
+
clauses.push(fallbackSentence(fallback, total));
|
|
39
|
+
return `${clauses.join('; ')} — check ${plural(reasons.length, 'it', 'them')} by hand before re-running`;
|
|
40
|
+
}
|
|
41
|
+
export function crashConvergedNotice(projection) {
|
|
42
|
+
const facts = readCrashConvergedNoticeFacts(projection);
|
|
43
|
+
if (facts === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const { total, dropped, needsHumanReasons, causes } = facts;
|
|
46
|
+
if (total === 0 && dropped === 0)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (total === 0) {
|
|
49
|
+
return `the previous session left ${dropped} crash-converged approval ${plural(dropped, 'record', 'records')} that this version could not read. ${NO_AUTO_RERUN}.`;
|
|
50
|
+
}
|
|
51
|
+
const recordedAs = causes !== undefined && causes.length > 0 ? ` (recorded as ${causes.join(', ')})` : '';
|
|
52
|
+
const head = `the previous engine run ended with ${total} ${plural(total, 'approval', 'approvals')} still waiting${recordedAs}, ` +
|
|
53
|
+
`and the engine converged ${plural(total, 'it', 'them')} to denied`;
|
|
54
|
+
const body = needsHumanReasons.length > 0 ? needsHumanClauses(needsHumanReasons, total) : `${RECORDED}, ${plural(total, 'it', 'they')} can be re-run`;
|
|
55
|
+
const droppedClause = dropped > 0 ? ` (${dropped} further ${plural(dropped, 'record', 'records')} could not be read.)` : '';
|
|
56
|
+
return `${head}; ${body}. ${NO_AUTO_RERUN}.${droppedClause}`;
|
|
57
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentEvent, ApprovalDecision, ApprovalStaleError, PendingCheckpoint, CheckpointGate, PlanReviewRequest, AssistantTaskStatus } from '@sema-agent/sdk';
|
|
2
|
+
import type { PermissionResult } from '@sema-agent/agent-types';
|
|
2
3
|
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
3
4
|
import { type SuspendedReopenReading } from './suspendedReopen.js';
|
|
4
5
|
export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
|
|
@@ -8,9 +9,9 @@ export interface HitlPermissionDecisionLike {
|
|
|
8
9
|
message?: string;
|
|
9
10
|
updatedInput?: unknown;
|
|
10
11
|
}
|
|
11
|
-
export type HitlCanUseToolFn<D extends HitlPermissionDecisionLike = HitlPermissionDecisionLike> = (tool: {
|
|
12
|
+
export type HitlCanUseToolFn<D extends HitlPermissionDecisionLike = HitlPermissionDecisionLike, Ctx = never, Msg = never> = (tool: {
|
|
12
13
|
name: string;
|
|
13
|
-
}, input: Record<string, unknown>, toolUseContext:
|
|
14
|
+
}, input: Record<string, unknown>, toolUseContext: Ctx, assistantMessage: Msg, toolUseID: string, forceDecision?: D) => Promise<D>;
|
|
14
15
|
export interface ApprovalsResourceLike {
|
|
15
16
|
list(opts?: {
|
|
16
17
|
signal?: AbortSignal;
|
|
@@ -147,8 +148,10 @@ export type GatePrompt<D extends HitlPermissionDecisionLike = HitlPermissionDeci
|
|
|
147
148
|
toolUseID: string;
|
|
148
149
|
gate: CheckpointGate | null;
|
|
149
150
|
}) => Promise<D>;
|
|
150
|
-
export declare function makeHitlCanUseTool<D extends HitlPermissionDecisionLike = HitlPermissionDecisionLike>(bridge: HitlBridge, prompt: GatePrompt<D>): HitlCanUseToolFn<D>;
|
|
151
|
-
export interface HitlBackendDenyDecision {
|
|
151
|
+
export declare function makeHitlCanUseTool<D extends HitlPermissionDecisionLike = HitlPermissionDecisionLike, Ctx = unknown, Msg = unknown>(bridge: HitlBridge, prompt: GatePrompt<D>): HitlCanUseToolFn<D, Ctx, Msg>;
|
|
152
|
+
export interface HitlBackendDenyDecision extends Extract<PermissionResult, {
|
|
153
|
+
behavior: 'deny';
|
|
154
|
+
}> {
|
|
152
155
|
behavior: 'deny';
|
|
153
156
|
message: string;
|
|
154
157
|
decisionReason: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
2
2
|
import { type GateCurrentPending, type HitlFailureStage } from './hitlBridge.js';
|
|
3
3
|
import { type QuestionAnswer } from '../liveQuestionStore.js';
|
|
4
|
+
import { type FsApprovalOutcome } from './toolApprovalWire.js';
|
|
4
5
|
import type { AskAnsweredOutput, GateLedger } from './gateLedger.js';
|
|
5
6
|
import { type AskGateWireDeps, type GatePark } from './frameRouter.js';
|
|
6
7
|
import { type DecideReceiptView } from '../decideReceipt.js';
|
|
@@ -29,12 +30,16 @@ export type GateOutcome = {
|
|
|
29
30
|
reason: string;
|
|
30
31
|
code?: GateFailureCode | undefined;
|
|
31
32
|
errorCode?: string | undefined;
|
|
33
|
+
retriable?: true;
|
|
32
34
|
retryExhausted?: true;
|
|
33
35
|
currentPending?: GateCurrentPending | undefined;
|
|
34
36
|
};
|
|
35
37
|
export declare function isAlreadyResolvedGateReason(reason: unknown): boolean;
|
|
36
|
-
export declare function
|
|
37
|
-
|
|
38
|
+
export declare function isAlreadyResolvedFailure(outcome: Extract<GateOutcome | FsApprovalOutcome, {
|
|
39
|
+
kind: 'failed';
|
|
40
|
+
}>): boolean;
|
|
41
|
+
export declare function isGateStandingErrorCode(code: string | null | undefined, retriable?: boolean): boolean;
|
|
42
|
+
export declare function isCodeClassifiedGateFailure(code: string | null | undefined, retriable?: boolean): boolean;
|
|
38
43
|
export declare function toAnsweredOutput(questions: unknown[], answer: QuestionAnswer): AskAnsweredOutput;
|
|
39
44
|
export type ParkResolution = {
|
|
40
45
|
kind: 'reattach';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { putOwnKey } from '../ownKey.js';
|
|
2
2
|
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
|
|
3
|
-
import { PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode } from '../engineErrorCodes.js';
|
|
3
|
+
import { CHECKPOINT_UNSUPPORTED_VERSION, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
4
4
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
5
5
|
import { hostLog } from '../host.js';
|
|
6
6
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
@@ -40,21 +40,31 @@ function alreadyResolvedReasonToken(reason) {
|
|
|
40
40
|
const r = reason.toLowerCase();
|
|
41
41
|
return ALREADY_RESOLVED_REASON_TOKENS.find((t) => r.includes(t));
|
|
42
42
|
}
|
|
43
|
-
function isAlreadyResolvedFailure(outcome) {
|
|
43
|
+
export function isAlreadyResolvedFailure(outcome) {
|
|
44
|
+
try {
|
|
45
|
+
return alreadyResolvedFailureVerdict(outcome);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function alreadyResolvedFailureVerdict(outcome) {
|
|
52
|
+
if (typeof outcome !== 'object' || outcome === null)
|
|
53
|
+
return false;
|
|
44
54
|
const code = 'code' in outcome && isGateFailureCode(outcome.code) ? outcome.code : undefined;
|
|
45
55
|
if (code !== undefined)
|
|
46
56
|
return code === 'no_pending';
|
|
47
|
-
if ('errorCode' in outcome && isCodeClassifiedGateFailure(outcome.errorCode))
|
|
57
|
+
if ('errorCode' in outcome && isCodeClassifiedGateFailure(outcome.errorCode, Object.hasOwn(outcome, 'retriable') && outcome.retriable === true))
|
|
48
58
|
return false;
|
|
49
59
|
if ('errorCode' in outcome && isDecideParkMovedCode(outcome.errorCode))
|
|
50
60
|
return true;
|
|
51
61
|
return isAlreadyResolvedGateReason(outcome.reason);
|
|
52
62
|
}
|
|
53
|
-
export function isGateStandingErrorCode(code) {
|
|
54
|
-
return code === RESUME_POLICY_FOLD_CODE || code === 'corrupt';
|
|
63
|
+
export function isGateStandingErrorCode(code, retriable) {
|
|
64
|
+
return code === RESUME_POLICY_FOLD_CODE || code === 'corrupt' || (code === CHECKPOINT_UNSUPPORTED_VERSION && retriable === true);
|
|
55
65
|
}
|
|
56
|
-
export function isCodeClassifiedGateFailure(code) {
|
|
57
|
-
return isGateStandingErrorCode(code) || code === PARKED_RESUME_STARTUP_FAILED;
|
|
66
|
+
export function isCodeClassifiedGateFailure(code, retriable) {
|
|
67
|
+
return isGateStandingErrorCode(code, retriable) || code === PARKED_RESUME_STARTUP_FAILED;
|
|
58
68
|
}
|
|
59
69
|
export function toAnsweredOutput(questions, answer) {
|
|
60
70
|
const answers = {};
|
|
@@ -153,6 +163,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
153
163
|
const code = e instanceof HitlSafetyError && isGateFailureCode(e.code) ? e.code : undefined;
|
|
154
164
|
const currentPending = readDecideCurrentPending(e);
|
|
155
165
|
const wireCode = readWireErrorCode(e);
|
|
166
|
+
const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
|
|
156
167
|
return {
|
|
157
168
|
kind: 'failed',
|
|
158
169
|
stage: 'decide',
|
|
@@ -162,6 +173,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
162
173
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
163
174
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
164
175
|
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
176
|
+
...(wireRetriable ? { retriable: true } : {}),
|
|
165
177
|
};
|
|
166
178
|
}
|
|
167
179
|
}
|
|
@@ -43,7 +43,7 @@ export interface PlanReviewOutcomeMeta {
|
|
|
43
43
|
decision: 'approve' | 'reject';
|
|
44
44
|
effect: PlanReviewDecisionEffect;
|
|
45
45
|
}
|
|
46
|
-
export declare function planReviewOutcomeTail(taskId: string, decision: 'approve' | 'reject', effect: PlanReviewDecisionEffect): string;
|
|
46
|
+
export declare function planReviewOutcomeTail(taskId: string, decision: 'approve' | 'reject', effect: PlanReviewDecisionEffect, cause?: 'stored_form_unsupported'): string;
|
|
47
47
|
export type PlanReviewWireClient = {
|
|
48
48
|
assistant: Pick<AgentClient['assistant'], 'planReview'>;
|
|
49
49
|
runs: Pick<AgentClient['runs'], 'get'>;
|
|
@@ -6,6 +6,8 @@ import { engineWireTarget } from '../engineWireTarget.js';
|
|
|
6
6
|
import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
7
7
|
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
8
8
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
9
|
+
import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError } from '../resumeRefusalCopy.js';
|
|
10
|
+
import { REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
|
|
9
11
|
import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
|
|
10
12
|
import { engineCapString } from '../engineCapsCache.js';
|
|
11
13
|
import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
|
|
@@ -303,7 +305,10 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
303
305
|
return false;
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
|
-
export function planReviewOutcomeTail(taskId, decision, effect) {
|
|
308
|
+
export function planReviewOutcomeTail(taskId, decision, effect, cause) {
|
|
309
|
+
if (effect === 'still_parked' && cause === 'stored_form_unsupported') {
|
|
310
|
+
return `Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'}, and do not ${decision} it again: deciding again will not take effect while it is stored in this form. Tell the user the way forward this outcome gives — cancel this task's run, then submit the work again as a new task.`;
|
|
311
|
+
}
|
|
307
312
|
switch (effect) {
|
|
308
313
|
case 'took_effect':
|
|
309
314
|
return decision === 'approve'
|
|
@@ -500,6 +505,7 @@ function planReviewClientsFor(resolved) {
|
|
|
500
505
|
async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, gateBefore) {
|
|
501
506
|
let outcome;
|
|
502
507
|
let effect = 'unconfirmed';
|
|
508
|
+
let stillParkedCause;
|
|
503
509
|
let stillParkedProven = false;
|
|
504
510
|
let statusSeen;
|
|
505
511
|
const wire = opts?.wire;
|
|
@@ -543,7 +549,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
543
549
|
}
|
|
544
550
|
catch (e1) {
|
|
545
551
|
const conflict = e1;
|
|
546
|
-
if (mode === 'default' && conflict?.status === 400 && conflict?.errorCode ===
|
|
552
|
+
if (mode === 'default' && conflict?.status === 400 && conflict?.errorCode === REQUEST_FIELD_CONFLICT) {
|
|
547
553
|
hostLog('debug', 'planReviewWire: permissionModeAfter "default" refused as not applicable — re-sending the approval without the key (same meaning)');
|
|
548
554
|
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, reason !== undefined ? { decision, reason } : { decision }));
|
|
549
555
|
}
|
|
@@ -610,7 +616,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
610
616
|
const status = e?.status;
|
|
611
617
|
const refusedCode = e?.errorCode;
|
|
612
618
|
const noAnswer = typeof status === 'number' || typeof e !== 'object' ? null : noAnswerKindOf(e);
|
|
613
|
-
if (status === 400 && refusedCode ===
|
|
619
|
+
if (status === 400 && refusedCode === REQUEST_FIELD_CONFLICT && permissionModeAfter === 'acceptEdits') {
|
|
614
620
|
hostLog('debug', 'planReviewWire: approve + acceptEdits refused (request.field_conflict) — NOT re-sent with a different mode');
|
|
615
621
|
effect = 'still_parked';
|
|
616
622
|
stillParkedProven = true;
|
|
@@ -625,6 +631,14 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
625
631
|
`This ${decision} was not applied: the engine no longer has a pending plan review for this task — an earlier decision most likely already went through (or the review expired). ` +
|
|
626
632
|
'This is NOT a rejection of the plan and NOT a failure of the task; the answer just given simply had nothing left to act on.';
|
|
627
633
|
}
|
|
634
|
+
else if (checkpointUnsupportedVersionFromError(e) !== null) {
|
|
635
|
+
const cu = checkpointUnsupportedVersionFromError(e);
|
|
636
|
+
hostLog('debug', `planReviewWire: ${decision} → 409 checkpoint.unsupported_version (the server cannot continue this parked review in its stored form; still pending)`);
|
|
637
|
+
effect = 'still_parked';
|
|
638
|
+
stillParkedProven = true;
|
|
639
|
+
stillParkedCause = 'stored_form_unsupported';
|
|
640
|
+
outcome = `This ${decision} was not applied. ${checkpointUnsupportedVersionContent(cu, { parkKind: 'plan_review', runId: taskId })}`;
|
|
641
|
+
}
|
|
628
642
|
else if (typeof status === 'number' && corruptStoredRowWhere(e) !== undefined) {
|
|
629
643
|
const where = corruptStoredRowWhere(e);
|
|
630
644
|
hostLog('debug', `planReviewWire: ${decision} → ${String(status)} corrupt (stored resume context unreadable)`);
|
|
@@ -651,7 +665,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
651
665
|
}
|
|
652
666
|
}
|
|
653
667
|
}
|
|
654
|
-
const tail = planReviewOutcomeTail(taskId, decision, effect);
|
|
668
|
+
const tail = planReviewOutcomeTail(taskId, decision, effect, stillParkedCause);
|
|
655
669
|
const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
|
|
656
670
|
const onOutcome = opts?.onOutcome;
|
|
657
671
|
settle(effect === 'not_sent' || (effect === 'still_parked' && stillParkedProven));
|
|
@@ -105,6 +105,11 @@ export declare function tightenSessionPolicy(facade: SessionPolicyFacade, req: S
|
|
|
105
105
|
signal?: AbortSignal;
|
|
106
106
|
}): Promise<SessionPolicyTightenOutcome>;
|
|
107
107
|
export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome): string;
|
|
108
|
+
export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyReadOutcome, {
|
|
109
|
+
kind: 'present';
|
|
110
|
+
}> | {
|
|
111
|
+
kind: 'no_target';
|
|
112
|
+
}): string;
|
|
108
113
|
export interface SessionPolicyRemovedEntry {
|
|
109
114
|
readonly field: 'toolDeny' | 'toolAllow';
|
|
110
115
|
readonly rule: string;
|
|
@@ -344,6 +344,69 @@ export function sessionPolicyTightenNotice(outcome) {
|
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
+
const READ_NOTICE_GENERIC = 'This session’s rule record could not be shown, so what it holds is not known. Try reading it again.';
|
|
348
|
+
const READ_NOTICE_NO_TARGET = 'There is no engine connection for this session, so its rule record cannot be read and what it holds is not known. Look again once it is connected.';
|
|
349
|
+
function readFailureSentence(kind) {
|
|
350
|
+
switch (kind) {
|
|
351
|
+
case 'face-unavailable':
|
|
352
|
+
return 'This deployment does not keep per-session tool rules, so there is no session rule record to read.';
|
|
353
|
+
case 'loosen-forbidden':
|
|
354
|
+
return 'The engine answered this read with a refusal that only applies to relaxing rules, so this session’s rule record could not be read. Try again; if it keeps happening, whoever runs the engine needs to look at it.';
|
|
355
|
+
case 'store-corrupt':
|
|
356
|
+
return 'The engine could not establish which version this session’s rules are at, so it would not hand the record over. This needs an operator to look at the rule store.';
|
|
357
|
+
case 'cas-conflict':
|
|
358
|
+
return 'The engine reported a version clash on this session’s rule record, so it could not be read this time. Try again.';
|
|
359
|
+
case 'session-ownerless':
|
|
360
|
+
return 'This session has no owner, so it has no rule record to read.';
|
|
361
|
+
case 'session-not-found':
|
|
362
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
|
|
363
|
+
case 'request-rejected':
|
|
364
|
+
return 'The engine rejected the request to read this session’s rule record as malformed, so it could not be read. Sending the same request again will not change that; check which session was asked for.';
|
|
365
|
+
case 'legacy-tool-name':
|
|
366
|
+
return 'The engine answered this read with a refusal about a tool name it no longer accepts, so this session’s rule record could not be read. Try again; if it keeps happening, whoever runs the engine needs to look at it.';
|
|
367
|
+
case 'unauthorized':
|
|
368
|
+
return 'This call could not present a verifiable identity, so this session’s rule record could not be read.';
|
|
369
|
+
case 'error':
|
|
370
|
+
return 'The engine refused to hand over this session’s rule record for a reason this version has no wording for, so it could not be read. Try again; if it keeps happening, whoever runs the engine needs to look at it.';
|
|
371
|
+
case 'no-verdict':
|
|
372
|
+
return 'No clear answer came back from the engine when this session’s rule record was read, so what it holds is not known — that is not the same as holding nothing. Try again.';
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function readMalformedSentence(why) {
|
|
376
|
+
switch (why) {
|
|
377
|
+
case 'envelope':
|
|
378
|
+
return 'The engine answered, but not in a form this version can read as a session rule record, so what the record holds is not known. Try again; if it keeps happening, this version and the engine may be out of step.';
|
|
379
|
+
case 'rev':
|
|
380
|
+
return 'The engine’s answer for this session’s rule record carried no usable version, so this version will not show it — a record it cannot place may still hold restrictions that are in effect. Try again.';
|
|
381
|
+
case 'rule_field':
|
|
382
|
+
return 'One of the lists in this session’s rule record came back in a form this version cannot read, so none of the record is shown rather than part of it — a partly shown record could leave out restrictions that are in effect.';
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function ownRead(o, key) {
|
|
386
|
+
const d = Object.getOwnPropertyDescriptor(o, key);
|
|
387
|
+
return d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
388
|
+
}
|
|
389
|
+
export function sessionPolicyReadNotice(outcome) {
|
|
390
|
+
try {
|
|
391
|
+
if (outcome === null || typeof outcome !== 'object')
|
|
392
|
+
return READ_NOTICE_GENERIC;
|
|
393
|
+
const arm = ownRead(outcome, 'kind');
|
|
394
|
+
if (arm === 'failed') {
|
|
395
|
+
const failure = ownRead(outcome, 'failure');
|
|
396
|
+
if (failure === null || typeof failure !== 'object')
|
|
397
|
+
return READ_NOTICE_GENERIC;
|
|
398
|
+
return readFailureSentence(ownRead(failure, 'kind')) ?? READ_NOTICE_GENERIC;
|
|
399
|
+
}
|
|
400
|
+
if (arm === 'malformed')
|
|
401
|
+
return readMalformedSentence(ownRead(outcome, 'why')) ?? READ_NOTICE_GENERIC;
|
|
402
|
+
if (arm === 'no_target')
|
|
403
|
+
return READ_NOTICE_NO_TARGET;
|
|
404
|
+
return READ_NOTICE_GENERIC;
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return READ_NOTICE_GENERIC;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
347
410
|
function withoutRefusedEntries(prior, mounted) {
|
|
348
411
|
const REFUSABLE_NAME_FIELDS = ['toolDeny', 'toolAllow'];
|
|
349
412
|
const isRefusable = (f) => REFUSABLE_NAME_FIELDS.includes(f);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE } from '../engineErrorCodes.js';
|
|
1
|
+
import { RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
2
2
|
const UNSTATED = Object.freeze({ kind: 'unstated' });
|
|
3
3
|
const NOT_REOPENED = Object.freeze({ kind: 'not_reopened' });
|
|
4
4
|
export function suspendedReopenOf(ev) {
|
|
@@ -25,7 +25,7 @@ export function resumeReopenFromError(err) {
|
|
|
25
25
|
const code = err.errorCode;
|
|
26
26
|
if (typeof code !== 'string' || !RESUME_REOPEN_CODES.includes(code))
|
|
27
27
|
return null;
|
|
28
|
-
if (err
|
|
28
|
+
if (!refusalSaysStillPending(err))
|
|
29
29
|
return null;
|
|
30
30
|
const reason = code.slice('resume.'.length);
|
|
31
31
|
return { code, reason, retriable: true };
|
|
@@ -37,6 +37,7 @@ export type FsApprovalOutcome = {
|
|
|
37
37
|
reason: string;
|
|
38
38
|
code?: 'no_pending' | undefined;
|
|
39
39
|
errorCode?: string | undefined;
|
|
40
|
+
retriable?: true;
|
|
40
41
|
safetyCode?: HitlSafetyCode | undefined;
|
|
41
42
|
retryExhausted?: true;
|
|
42
43
|
currentPending?: GateCurrentPending | undefined;
|
|
@@ -131,6 +132,7 @@ export interface ApprovalCardRequest {
|
|
|
131
132
|
hasBidiControls?: true;
|
|
132
133
|
persistedRuleShadowed?: string;
|
|
133
134
|
probeCause?: unknown;
|
|
135
|
+
probeReason?: string;
|
|
134
136
|
ruleEvidence?: unknown;
|
|
135
137
|
requiresRealApproval?: true;
|
|
136
138
|
irreversibleParkGate?: true;
|
|
@@ -180,6 +182,7 @@ export interface ToolApprovalFrame {
|
|
|
180
182
|
ruleStoreUnreadable?: string;
|
|
181
183
|
persistedRuleShadowed?: string;
|
|
182
184
|
probeCause?: unknown;
|
|
185
|
+
probeReason?: string;
|
|
183
186
|
ruleEvidence?: unknown;
|
|
184
187
|
requiresRealApproval?: true;
|
|
185
188
|
mandated?: true;
|
|
@@ -204,7 +207,7 @@ export interface ToolApprovalDelegation {
|
|
|
204
207
|
depth: number;
|
|
205
208
|
agentName?: string;
|
|
206
209
|
}
|
|
207
|
-
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "ruleStoreUnreadable", "readRootCandidate", "mandated", "mandate", "origin", "askClass", "classes", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
|
|
210
|
+
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "ruleStoreUnreadable", "readRootCandidate", "mandated", "mandate", "origin", "askClass", "classes", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "probeReason", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
|
|
208
211
|
export interface ApprovalMandatedFacts {
|
|
209
212
|
readonly mandated?: true;
|
|
210
213
|
readonly ruleOffersAbsence?: string;
|
|
@@ -10,6 +10,7 @@ import { isRuleStoreUnreadableKind } from '../gateVocabulary.js';
|
|
|
10
10
|
import { isApprovalMandateWord } from '../gateVocabulary.js';
|
|
11
11
|
import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
|
|
12
12
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
13
|
+
import { refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
13
14
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
14
15
|
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, ASK_ORIGINS, PERSISTED_RULE_MANDATES, } from '@sema-agent/sdk';
|
|
15
16
|
export function isFsApprovalGate(gate) {
|
|
@@ -190,6 +191,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
190
191
|
const callKey = approvalCallKey(gatedCallId, taskId);
|
|
191
192
|
const ruleOffersReadOnly = readRuleOfferSupply(pending.ruleOffers, pending.ruleSuggestions);
|
|
192
193
|
const durableProbeCause = pending.riskDescriptor?.probeCause;
|
|
194
|
+
const durableProbeReason = readProbeReason(pending.riskDescriptor);
|
|
193
195
|
const durableShadowedRule = pending.riskDescriptor?.shadowedRule;
|
|
194
196
|
const durableRuleOffersAbsence = pending.ruleOffersAbsence;
|
|
195
197
|
const durableRuleStoreUnreadable = readRuleStoreUnreadable(pending.ruleStoreUnreadable);
|
|
@@ -215,6 +217,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
215
217
|
...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
216
218
|
...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
|
|
217
219
|
...(isWireRecordCarrier(durableProbeCause) ? { probeCause: durableProbeCause } : {}),
|
|
220
|
+
...(durableProbeReason !== undefined ? { probeReason: durableProbeReason } : {}),
|
|
218
221
|
...(durableRuleStoreUnreadable !== undefined
|
|
219
222
|
? { ruleStoreUnreadable: durableRuleStoreUnreadable }
|
|
220
223
|
: {}),
|
|
@@ -279,6 +282,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
279
282
|
catch (e) {
|
|
280
283
|
const currentPending = readDecideCurrentPending(e);
|
|
281
284
|
const wireCode = readWireErrorCode(e);
|
|
285
|
+
const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
|
|
282
286
|
const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
|
|
283
287
|
return {
|
|
284
288
|
kind: 'failed',
|
|
@@ -288,6 +292,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
288
292
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
289
293
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
290
294
|
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
295
|
+
...(wireRetriable ? { retriable: true } : {}),
|
|
291
296
|
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
292
297
|
};
|
|
293
298
|
}
|
|
@@ -335,6 +340,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
335
340
|
catch (e) {
|
|
336
341
|
const currentPending = readDecideCurrentPending(e);
|
|
337
342
|
const wireCode = readWireErrorCode(e);
|
|
343
|
+
const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
|
|
338
344
|
const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
|
|
339
345
|
const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
|
|
340
346
|
return {
|
|
@@ -345,6 +351,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
345
351
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
346
352
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
347
353
|
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
354
|
+
...(wireRetriable ? { retriable: true } : {}),
|
|
348
355
|
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
349
356
|
...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
|
|
350
357
|
};
|
|
@@ -393,6 +400,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
393
400
|
'parked',
|
|
394
401
|
'persistedRuleShadowed',
|
|
395
402
|
'probeCause',
|
|
403
|
+
'probeReason',
|
|
396
404
|
'ruleEvidence',
|
|
397
405
|
'requiresRealApproval',
|
|
398
406
|
'expiresInMs',
|
|
@@ -644,6 +652,17 @@ function pathFromGateMessage(message) {
|
|
|
644
652
|
return undefined;
|
|
645
653
|
return body.slice(0, close);
|
|
646
654
|
}
|
|
655
|
+
function readProbeReason(v) {
|
|
656
|
+
try {
|
|
657
|
+
if (typeof v !== 'object' || v === null || !Object.hasOwn(v, 'probeReason'))
|
|
658
|
+
return undefined;
|
|
659
|
+
const raw = v.probeReason;
|
|
660
|
+
return typeof raw === 'string' && raw.trim() !== '' ? raw : undefined;
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
647
666
|
function isNonNegativeFinite(v) {
|
|
648
667
|
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
649
668
|
}
|
|
@@ -887,6 +906,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
887
906
|
? { persistedRuleShadowed: frame.persistedRuleShadowed }
|
|
888
907
|
: {}),
|
|
889
908
|
...(isWireRecordCarrier(frame.probeCause) ? { probeCause: frame.probeCause } : {}),
|
|
909
|
+
...((r) => (r !== undefined ? { probeReason: r } : {}))(readProbeReason(frame)),
|
|
890
910
|
...(isWireRecordCarrier(frame.ruleEvidence) ? { ruleEvidence: frame.ruleEvidence } : {}),
|
|
891
911
|
...(frame.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
892
912
|
...(hasOwnStrictTrue(frame, 'mandated') ? { mandated: true } : {}),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type HookMatcherUnjudgedReason = 'event_out_of_scope' | 'not_a_string' | 'oversize' | 'invalid_pattern' | 'pattern_unsupported' | 'over_budget';
|
|
2
|
+
export type HookMatcherVerdict = {
|
|
3
|
+
readonly kind: 'matchesAll';
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: 'accepted';
|
|
6
|
+
readonly reading: 'names';
|
|
7
|
+
readonly names: readonly string[];
|
|
8
|
+
readonly aliases: readonly {
|
|
9
|
+
readonly alias: string;
|
|
10
|
+
readonly canonical: string;
|
|
11
|
+
}[];
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'accepted';
|
|
14
|
+
readonly reading: 'pattern';
|
|
15
|
+
readonly aliases: readonly {
|
|
16
|
+
readonly alias: string;
|
|
17
|
+
readonly canonical: string;
|
|
18
|
+
}[];
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: 'refused';
|
|
21
|
+
readonly reason: 'alias_without_canonical';
|
|
22
|
+
readonly reading: 'names' | 'pattern';
|
|
23
|
+
readonly alias: string;
|
|
24
|
+
readonly canonical: string;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'unjudged';
|
|
27
|
+
readonly reason: HookMatcherUnjudgedReason;
|
|
28
|
+
};
|
|
29
|
+
export declare function hookMatcherVerdict(event: string, matcher: unknown): HookMatcherVerdict;
|