@sema-agent/core 5.18.0 → 5.19.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 +97 -0
- package/dist/core/checkpoint-store.d.ts +3 -2
- package/dist/core/fs-write-gate-policy.js +1 -1
- package/dist/core/hooks.d.ts +3 -1
- package/dist/core/hooks.js +36 -0
- package/dist/core/permission-rule-consent.d.ts +8 -1
- package/dist/core/permission-rule-consent.js +21 -10
- package/dist/core/runner/active-skill-scope.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +4 -0
- package/dist/core/runner/prepare-task.js +171 -121
- package/dist/core/runner/runtask.d.ts +3 -1
- package/dist/core/runner/runtask.js +35 -7
- package/dist/core/runner/session-rule-policy.js +1 -1
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/tool-policy.d.ts +6 -0
- package/dist/core/tool-policy.js +75 -24
- package/dist/core/types.d.ts +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/orchestration/run-spec.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +5 -2
- package/dist/tools/fs/bash-readonly-classifier.js +129 -17
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
|
|
|
20
20
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
21
21
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
22
22
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
23
|
-
import { askApproverIdentity, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
23
|
+
import { askApproverIdentity, combinePolicies, isApprovalSettledBy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
24
24
|
const PERSISTED_RULE_TOOL = "Bash";
|
|
25
25
|
import { findAdmittingRule, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
26
26
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
@@ -28,7 +28,7 @@ import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentList
|
|
|
28
28
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
29
29
|
import { emitTrace } from "../trace.js";
|
|
30
30
|
import { createSessionRulePolicy } from "./session-rule-policy.js";
|
|
31
|
-
import { cloneObserverInput, createHookEnvCapabilities, formatHookFeedback, runToolGate } from "../hooks.js";
|
|
31
|
+
import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, runToolGate } from "../hooks.js";
|
|
32
32
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
33
33
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
34
34
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
@@ -1057,6 +1057,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1057
1057
|
const ownOrgVerdictRef = { current: undefined };
|
|
1058
1058
|
const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
|
|
1059
1059
|
const frozenOnAsk = spec.onAsk ?? deps.onAsk;
|
|
1060
|
+
const hookEnvSource = (ownedEnv ?? deps.executionEnv) != null ? executionEnv : undefined;
|
|
1061
|
+
const notifyOwnHookCrash = (err) => {
|
|
1062
|
+
try {
|
|
1063
|
+
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1060
1068
|
const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
|
|
1061
1069
|
const inheritedGateForChildren = () => {
|
|
1062
1070
|
const ancestorRules = [
|
|
@@ -1066,8 +1074,25 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1066
1074
|
const ownCallerPolicy = lockedPreflight.toolPolicy;
|
|
1067
1075
|
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1068
1076
|
(spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
|
|
1077
|
+
const ownPreToolUse = hooks?.preToolUse;
|
|
1078
|
+
const hookConstraint = ownPreToolUse !== undefined &&
|
|
1079
|
+
!(inheritedParentConstraints ?? []).some((pc) => pc.preToolUse === ownPreToolUse &&
|
|
1080
|
+
askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
|
|
1081
|
+
(pc.durableMandate === true) === durableMandate &&
|
|
1082
|
+
pc.hookEnv === hookEnvSource)
|
|
1083
|
+
? [
|
|
1084
|
+
{
|
|
1085
|
+
policy: createPreToolUseConstraintPolicy(ownPreToolUse, hookEnvFace, notifyOwnHookCrash),
|
|
1086
|
+
preToolUse: ownPreToolUse,
|
|
1087
|
+
...(hookEnvSource !== undefined ? { hookEnv: hookEnvSource } : {}),
|
|
1088
|
+
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
1089
|
+
...(durableMandate ? { durableMandate: true } : {}),
|
|
1090
|
+
},
|
|
1091
|
+
]
|
|
1092
|
+
: [];
|
|
1069
1093
|
const parentConstraints = [
|
|
1070
1094
|
...(inheritedParentConstraints ?? []),
|
|
1095
|
+
...hookConstraint,
|
|
1071
1096
|
...(ownCallerPolicy !== undefined
|
|
1072
1097
|
? [
|
|
1073
1098
|
{
|
|
@@ -2920,6 +2945,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2920
2945
|
auditPolicyNames(layer);
|
|
2921
2946
|
const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
|
|
2922
2947
|
const basePolicyForResumeEdit = lockedPreflight.toolPolicy;
|
|
2948
|
+
const hooks = spec.hooks ?? deps.hooks;
|
|
2923
2949
|
const sameInstanceAncestorCount = policy === undefined ? 0 : (inheritedParentConstraints ?? []).reduce((n, pc) => (pc.policy === policy ? n + 1 : n), 0);
|
|
2924
2950
|
const sharedFirstDecision = new Map();
|
|
2925
2951
|
const SHARED_FIRST_DECISION_CAP = 256;
|
|
@@ -3067,125 +3093,133 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3067
3093
|
editArgs = rr.updatedInput;
|
|
3068
3094
|
}
|
|
3069
3095
|
};
|
|
3070
|
-
const parentConstraintWrappers = (inheritedParentConstraints ?? []).map((pc) =>
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
return first;
|
|
3087
|
-
}
|
|
3088
|
-
if (pc.durableMandate === true) {
|
|
3089
|
-
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
3090
|
-
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
3091
|
-
markInheritedUnavailable(creq.toolCallId)) {
|
|
3092
|
-
return first;
|
|
3096
|
+
const parentConstraintWrappers = (inheritedParentConstraints ?? []).map((pc) => pc.preToolUse !== undefined &&
|
|
3097
|
+
pc.preToolUse === hooks?.preToolUse &&
|
|
3098
|
+
pc.durableMandate !== true &&
|
|
3099
|
+
askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
|
|
3100
|
+
pc.hookEnv === hookEnvSource
|
|
3101
|
+
? { check: () => ({ action: "allow" }) }
|
|
3102
|
+
: policy !== undefined && pc.policy === policy
|
|
3103
|
+
? {
|
|
3104
|
+
check: async (creq, csignal) => {
|
|
3105
|
+
const first = sharedFirstDecision.get(creq.toolCallId);
|
|
3106
|
+
if (first === undefined) {
|
|
3107
|
+
return {
|
|
3108
|
+
action: "deny",
|
|
3109
|
+
message: `inherited parent policy could not be arbitrated for "${creq.toolName}" ` +
|
|
3110
|
+
`(shared-instance first decision unavailable); denied fail-closed`,
|
|
3111
|
+
};
|
|
3093
3112
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
}
|
|
3100
|
-
const presentedArgs = creq.args;
|
|
3101
|
-
const askT0 = now();
|
|
3102
|
-
const resolved = await resolveAsk({
|
|
3103
|
-
toolName: creq.toolName,
|
|
3104
|
-
toolCallId: creq.toolCallId,
|
|
3105
|
-
args: presentedArgs,
|
|
3106
|
-
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
3107
|
-
message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
3108
|
-
...askSourceIdentity(),
|
|
3109
|
-
...riskAxesOf(creq.toolName),
|
|
3110
|
-
...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
3111
|
-
}, pc.onAsk, csignal ?? abortController.signal);
|
|
3112
|
-
const askWaitMs = Math.max(0, now() - askT0);
|
|
3113
|
-
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
3114
|
-
if (markInheritedUnavailable(creq.toolCallId))
|
|
3113
|
+
if (first.action === "deny")
|
|
3114
|
+
return first;
|
|
3115
|
+
if (first.action === "allow")
|
|
3116
|
+
return { action: "allow" };
|
|
3117
|
+
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
|
|
3115
3118
|
return first;
|
|
3116
|
-
return {
|
|
3117
|
-
action: "deny",
|
|
3118
|
-
message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
|
|
3119
|
-
};
|
|
3120
|
-
}
|
|
3121
|
-
if (resolved.action !== "allow")
|
|
3122
|
-
return resolved;
|
|
3123
|
-
if (resolved.updatedInput !== undefined) {
|
|
3124
|
-
return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal);
|
|
3125
|
-
}
|
|
3126
|
-
recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
|
|
3127
|
-
return { action: "allow" };
|
|
3128
|
-
},
|
|
3129
|
-
}
|
|
3130
|
-
: {
|
|
3131
|
-
check: async (creq, csignal) => {
|
|
3132
|
-
let decision;
|
|
3133
|
-
try {
|
|
3134
|
-
decision = await pc.policy.check(creq, csignal ?? abortController.signal);
|
|
3135
|
-
}
|
|
3136
|
-
catch (err) {
|
|
3137
|
-
return {
|
|
3138
|
-
action: "deny",
|
|
3139
|
-
message: `inherited parent policy errored for "${creq.toolName}": ${err instanceof Error ? err.message : String(err)}`,
|
|
3140
|
-
};
|
|
3141
|
-
}
|
|
3142
|
-
if (decision.action !== "ask")
|
|
3143
|
-
return decision;
|
|
3144
|
-
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
|
|
3145
|
-
return decision;
|
|
3146
|
-
}
|
|
3147
|
-
if (pc.durableMandate === true) {
|
|
3148
|
-
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
3149
|
-
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
3150
|
-
markInheritedUnavailable(creq.toolCallId)) {
|
|
3151
|
-
return decision;
|
|
3152
3119
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3120
|
+
if (pc.durableMandate === true) {
|
|
3121
|
+
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
3122
|
+
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
3123
|
+
markInheritedUnavailable(creq.toolCallId)) {
|
|
3124
|
+
return first;
|
|
3125
|
+
}
|
|
3126
|
+
return {
|
|
3127
|
+
action: "deny",
|
|
3128
|
+
message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
|
|
3129
|
+
`ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
const presentedArgs = creq.args;
|
|
3133
|
+
const askT0 = now();
|
|
3134
|
+
const resolved = await resolveAsk({
|
|
3135
|
+
toolName: creq.toolName,
|
|
3136
|
+
toolCallId: creq.toolCallId,
|
|
3137
|
+
args: presentedArgs,
|
|
3138
|
+
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
3139
|
+
message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
3140
|
+
...askSourceIdentity(),
|
|
3141
|
+
...riskAxesOf(creq.toolName),
|
|
3142
|
+
...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
3143
|
+
}, pc.onAsk, csignal ?? abortController.signal);
|
|
3144
|
+
const askWaitMs = Math.max(0, now() - askT0);
|
|
3145
|
+
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
3146
|
+
if (markInheritedUnavailable(creq.toolCallId))
|
|
3147
|
+
return first;
|
|
3148
|
+
return {
|
|
3149
|
+
action: "deny",
|
|
3150
|
+
message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
if (resolved.action !== "allow")
|
|
3154
|
+
return resolved;
|
|
3155
|
+
if (resolved.updatedInput !== undefined) {
|
|
3156
|
+
return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal);
|
|
3157
|
+
}
|
|
3158
|
+
recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
|
|
3159
|
+
return { action: "allow" };
|
|
3160
|
+
},
|
|
3161
|
+
}
|
|
3162
|
+
: {
|
|
3163
|
+
check: async (creq, csignal) => {
|
|
3164
|
+
let decision;
|
|
3165
|
+
try {
|
|
3166
|
+
decision = await pc.policy.check(creq, csignal ?? abortController.signal);
|
|
3167
|
+
}
|
|
3168
|
+
catch (err) {
|
|
3169
|
+
return {
|
|
3170
|
+
action: "deny",
|
|
3171
|
+
message: `inherited parent policy errored for "${creq.toolName}": ${err instanceof Error ? err.message : String(err)}`,
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
if (decision.action !== "ask")
|
|
3174
3175
|
return decision;
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3176
|
+
if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME && (pc.durableMandate !== true || markInheritedUnavailable(creq.toolCallId))) {
|
|
3177
|
+
return decision;
|
|
3178
|
+
}
|
|
3179
|
+
if (pc.durableMandate === true) {
|
|
3180
|
+
if (resolveCheckpointStore(spec, deps) !== undefined &&
|
|
3181
|
+
(spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
|
|
3182
|
+
markInheritedUnavailable(creq.toolCallId)) {
|
|
3183
|
+
return decision;
|
|
3184
|
+
}
|
|
3185
|
+
return {
|
|
3186
|
+
action: "deny",
|
|
3187
|
+
message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
|
|
3188
|
+
`ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
|
|
3192
|
+
const askT0 = now();
|
|
3193
|
+
const resolved = await resolveAsk({
|
|
3194
|
+
toolName: creq.toolName,
|
|
3195
|
+
toolCallId: creq.toolCallId,
|
|
3196
|
+
args: presentedArgs,
|
|
3197
|
+
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
3198
|
+
message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
3199
|
+
...askSourceIdentity(),
|
|
3200
|
+
...riskAxesOf(creq.toolName),
|
|
3201
|
+
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
3202
|
+
}, pc.onAsk, csignal ?? abortController.signal);
|
|
3203
|
+
const askWaitMs = Math.max(0, now() - askT0);
|
|
3204
|
+
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
3205
|
+
if (markInheritedUnavailable(creq.toolCallId))
|
|
3206
|
+
return decision;
|
|
3207
|
+
return {
|
|
3208
|
+
action: "deny",
|
|
3209
|
+
message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
if (resolved.action !== "allow")
|
|
3213
|
+
return resolved;
|
|
3214
|
+
if (resolved.updatedInput !== undefined) {
|
|
3215
|
+
return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, csignal);
|
|
3216
|
+
}
|
|
3217
|
+
if (pc.preToolUse === undefined) {
|
|
3218
|
+
recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs);
|
|
3219
|
+
}
|
|
3220
|
+
return decision.updatedInput !== undefined ? { ...resolved, updatedInput: decision.updatedInput } : resolved;
|
|
3221
|
+
},
|
|
3222
|
+
});
|
|
3189
3223
|
const rewriteCapableLayers = policy !== undefined || parentConstraintWrappers.length > 0;
|
|
3190
3224
|
const rewriteEmitterIndex = new Map();
|
|
3191
3225
|
const rewriteCapableChain = [
|
|
@@ -3269,7 +3303,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3269
3303
|
},
|
|
3270
3304
|
}
|
|
3271
3305
|
: foldedPolicy;
|
|
3272
|
-
const hooks = spec.hooks ?? deps.hooks;
|
|
3273
3306
|
const onAsk = spec.onAsk ?? deps.onAsk;
|
|
3274
3307
|
const handWriteTools = handsEnabled && spec.handsReadOnly !== true
|
|
3275
3308
|
? Object.keys(HAND_TOOL_EFFECTS).filter((name) => {
|
|
@@ -3304,6 +3337,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3304
3337
|
}
|
|
3305
3338
|
const preToolContexts = new Map();
|
|
3306
3339
|
const blockedToolCalls = new Set();
|
|
3340
|
+
const approvalSettledBy = new Map();
|
|
3307
3341
|
const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
|
|
3308
3342
|
const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
|
|
3309
3343
|
const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
|
|
@@ -3390,7 +3424,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3390
3424
|
const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
|
|
3391
3425
|
if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
|
|
3392
3426
|
const adjudicate = effectivePolicy
|
|
3393
|
-
? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot }, abortController.signal)), abortController.signal, () => ({
|
|
3427
|
+
? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, abortController.signal)), abortController.signal, () => ({
|
|
3394
3428
|
action: "deny",
|
|
3395
3429
|
message: "policy check aborted (task timed out or cancelled)",
|
|
3396
3430
|
}))
|
|
@@ -3917,7 +3951,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3917
3951
|
if (!presented.ok) {
|
|
3918
3952
|
throw new ParkRefusal(`the stored form of "${req.toolName}"'s arguments could not be presented for re-adjudication (${describeThrown(presented.cause)})`, { cause: presented.cause });
|
|
3919
3953
|
}
|
|
3920
|
-
const reprojected = refuseOutOfContractDecision(await basePolicyForResumeEdit.check({
|
|
3954
|
+
const reprojected = refuseOutOfContractDecision(await basePolicyForResumeEdit.check({
|
|
3955
|
+
toolName: req.toolName,
|
|
3956
|
+
args: presented.value,
|
|
3957
|
+
toolCallId: req.toolCallId,
|
|
3958
|
+
...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}),
|
|
3959
|
+
}, abortController.signal));
|
|
3921
3960
|
const demanded = reprojected.updatedInput !== undefined ? tryCloneArgs(reprojected.updatedInput) : undefined;
|
|
3922
3961
|
const rewroteTheFiledValue = reprojected.updatedInput !== undefined &&
|
|
3923
3962
|
!(demanded?.ok === true &&
|
|
@@ -4156,6 +4195,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4156
4195
|
if (blockedTracked && (result.block || result.suspend)) {
|
|
4157
4196
|
blockedToolCalls.add(e.toolCallId);
|
|
4158
4197
|
}
|
|
4198
|
+
const reported = result.settledBy;
|
|
4199
|
+
if (reported !== undefined) {
|
|
4200
|
+
const settling = result.block === true;
|
|
4201
|
+
if (!isApprovalSettledBy(reported) || (!settling && reported !== "human")) {
|
|
4202
|
+
deps.onError?.(new Error(`a tool-gate settlement reported settledBy "${String(reported)}" on ${settling ? "a blocked" : "an executing"} call — ` +
|
|
4203
|
+
`it is one of "human" / "timeout" / "aborted", and only "human" can be the source of a call that runs; the frame carries no source`), { phase: "config", sessionId });
|
|
4204
|
+
}
|
|
4205
|
+
else {
|
|
4206
|
+
approvalSettledBy.set(e.toolCallId, reported);
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4159
4209
|
return result.block
|
|
4160
4210
|
? { block: true, reason: result.reason }
|
|
4161
4211
|
: result.updatedInput !== undefined
|
|
@@ -4444,7 +4494,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4444
4494
|
: undefined;
|
|
4445
4495
|
overheadState.promptChars = systemPrompt.length;
|
|
4446
4496
|
const preparedHolder = {};
|
|
4447
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4497
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettledBy, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4448
4498
|
const prepared = buildPrepared();
|
|
4449
4499
|
preparedHolder.current = prepared;
|
|
4450
4500
|
return prepared;
|
|
@@ -5,6 +5,7 @@ import { type TaskOutcome } from "../task-outcome.js";
|
|
|
5
5
|
import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
|
|
6
6
|
import type { SessionStore } from "../session.js";
|
|
7
7
|
import { type RecoveredOrphan } from "../session-reconcile.js";
|
|
8
|
+
import { type ApprovalSettledBy } from "../tool-policy.js";
|
|
8
9
|
import type { AgentDefinition, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
|
|
9
10
|
export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
|
|
10
11
|
interface ResumeRun {
|
|
@@ -17,12 +18,13 @@ interface ResumeRun {
|
|
|
17
18
|
onEnvRestoreFailed?: (reason: ReopenReason) => Promise<void>;
|
|
18
19
|
decisionDelivered?: boolean;
|
|
19
20
|
}
|
|
20
|
-
declare function toolEndBodyFrom(result: unknown, isError: boolean): {
|
|
21
|
+
declare function toolEndBodyFrom(result: unknown, isError: boolean, settledBy?: ApprovalSettledBy): {
|
|
21
22
|
output?: unknown;
|
|
22
23
|
truncated?: boolean;
|
|
23
24
|
totalChars?: number;
|
|
24
25
|
structured?: unknown;
|
|
25
26
|
errorCode?: string;
|
|
27
|
+
settledBy?: ApprovalSettledBy;
|
|
26
28
|
};
|
|
27
29
|
export declare function reconciledToolEndBody(orphan: Pick<RecoveredOrphan, "text" | "errorKind">): ReturnType<typeof toolEndBodyFrom>;
|
|
28
30
|
export declare const GOVERNANCE_READ_STALLED: unique symbol;
|
|
@@ -38,7 +38,7 @@ import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "../unt
|
|
|
38
38
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
39
39
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
40
40
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
41
|
-
import { refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.js";
|
|
41
|
+
import { isApprovalSettledBy, refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.js";
|
|
42
42
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
43
43
|
import { discloseDroppedPending, isDelegatedAgentTerminal, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
44
44
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
@@ -126,7 +126,7 @@ function resumeDecisionWasNegative(resume) {
|
|
|
126
126
|
}
|
|
127
127
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
128
128
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
129
|
-
function toolEndBodyFrom(result, isError) {
|
|
129
|
+
function toolEndBodyFrom(result, isError, settledBy) {
|
|
130
130
|
const o = toolOutputFrom(result);
|
|
131
131
|
const st = structuredFrom(result);
|
|
132
132
|
const code = isError ? result?.details?.code : undefined;
|
|
@@ -134,6 +134,7 @@ function toolEndBodyFrom(result, isError) {
|
|
|
134
134
|
...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
|
|
135
135
|
...(st !== undefined ? { structured: st } : {}),
|
|
136
136
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
137
|
+
...(settledBy !== undefined ? { settledBy } : {}),
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
140
|
export function reconciledToolEndBody(orphan) {
|
|
@@ -1202,13 +1203,16 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1202
1203
|
prepared.lspDiagnostics.nudge(p);
|
|
1203
1204
|
}
|
|
1204
1205
|
}
|
|
1206
|
+
const settledBy = prepared.approvalSettledBy.get(event.toolCallId);
|
|
1207
|
+
if (settledBy !== undefined)
|
|
1208
|
+
prepared.approvalSettledBy.delete(event.toolCallId);
|
|
1205
1209
|
pushContent({
|
|
1206
1210
|
type: "tool_end",
|
|
1207
1211
|
toolCallId: event.toolCallId,
|
|
1208
1212
|
toolName: event.toolName,
|
|
1209
1213
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1210
1214
|
isError: event.isError,
|
|
1211
|
-
...toolEndBodyFrom(event.result, event.isError),
|
|
1215
|
+
...toolEndBodyFrom(event.result, event.isError, settledBy),
|
|
1212
1216
|
...ident(),
|
|
1213
1217
|
});
|
|
1214
1218
|
announceWorkspaceMove();
|
|
@@ -3617,6 +3621,14 @@ export class Runner {
|
|
|
3617
3621
|
if (decision !== "allow" && decision !== "deny") {
|
|
3618
3622
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the policy_ask domain — a decide is exactly "allow" or "deny"; refusing pre-CAS, the checkpoint stays pending`);
|
|
3619
3623
|
}
|
|
3624
|
+
const settledBy = decide.settledBy;
|
|
3625
|
+
if (settledBy !== undefined && !isApprovalSettledBy(settledBy)) {
|
|
3626
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume settledBy "${describeSuppliedValue(settledBy)}" is outside the settlement vocabulary — it is exactly "human", "timeout" or "aborted" (or omitted); refusing pre-CAS, the checkpoint stays pending`, { field: "settledBy" });
|
|
3627
|
+
}
|
|
3628
|
+
if (settledBy !== undefined && settledBy !== "human" && decision === "allow") {
|
|
3629
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" with settledBy "${settledBy}" — a non-human settlement is a fail-closed end (nobody answered), so it cannot be the source of an approval that EXECUTES; ` +
|
|
3630
|
+
"supply \"human\", or omit the field if the allow came from configuration rather than a person; refusing pre-CAS, the checkpoint stays pending", { field: "settledBy" });
|
|
3631
|
+
}
|
|
3620
3632
|
const reason = decide.reason;
|
|
3621
3633
|
assertOutcomeText(reason, "reason");
|
|
3622
3634
|
plainPolicyOutcome = {
|
|
@@ -3627,6 +3639,7 @@ export class Runner {
|
|
|
3627
3639
|
...(updatedInput !== undefined ? { updatedInput } : {}),
|
|
3628
3640
|
...(reason !== undefined ? { reason } : {}),
|
|
3629
3641
|
...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
|
|
3642
|
+
...(settledBy !== undefined ? { settledBy } : {}),
|
|
3630
3643
|
};
|
|
3631
3644
|
if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
|
|
3632
3645
|
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
|
|
@@ -3823,9 +3836,14 @@ export class Runner {
|
|
|
3823
3836
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
|
|
3824
3837
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
3825
3838
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
3826
|
-
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError) });
|
|
3839
|
+
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, outcome.settledBy) });
|
|
3827
3840
|
if (outcome.decision === "deny") {
|
|
3828
|
-
const
|
|
3841
|
+
const defaultDenial = outcome.settledBy === "timeout"
|
|
3842
|
+
? `No one answered the approval request for the pending tool call "${pendingAction.toolName}" — the approval window elapsed with no answer, so it was not executed.`
|
|
3843
|
+
: outcome.settledBy === "aborted"
|
|
3844
|
+
? `The approval for the pending tool call "${pendingAction.toolName}" ended without anyone deciding it (it was cancelled or could not be delivered), so it was not executed.`
|
|
3845
|
+
: `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
3846
|
+
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : defaultDenial;
|
|
3829
3847
|
emitEnd(true, { content: formatHookFeedback(reason) });
|
|
3830
3848
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
|
|
3831
3849
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
@@ -3835,7 +3853,12 @@ export class Runner {
|
|
|
3835
3853
|
throw new CheckpointError("checkpoint.invalid_outcome", `pending action reached the resolver with decision "${String(outcome.decision)}" — only "allow" executes and only "deny" injects a denial; refusing to execute`);
|
|
3836
3854
|
}
|
|
3837
3855
|
if (outcome.decision === "allow" && outcome.updatedInput !== undefined && prepared.basePolicyForResumeEdit) {
|
|
3838
|
-
const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check({
|
|
3856
|
+
const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check({
|
|
3857
|
+
toolName: pendingAction.toolName,
|
|
3858
|
+
args: resolvedArgs,
|
|
3859
|
+
toolCallId: pendingAction.toolCallId,
|
|
3860
|
+
...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
|
|
3861
|
+
}, prepared.abortController.signal));
|
|
3839
3862
|
if (rechecked.action === "deny") {
|
|
3840
3863
|
const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is denied by the deployment's tool policy and was not executed${rechecked.message ? `: ${rechecked.message}` : ""}.`);
|
|
3841
3864
|
emitEnd(true, { content: editedDenial });
|
|
@@ -3845,7 +3868,12 @@ export class Runner {
|
|
|
3845
3868
|
}
|
|
3846
3869
|
}
|
|
3847
3870
|
if (prepared.denyNarrowingPolicy) {
|
|
3848
|
-
const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check({
|
|
3871
|
+
const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check({
|
|
3872
|
+
toolName: pendingAction.toolName,
|
|
3873
|
+
args: resolvedArgs,
|
|
3874
|
+
toolCallId: pendingAction.toolCallId,
|
|
3875
|
+
...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
|
|
3876
|
+
}, prepared.abortController.signal));
|
|
3849
3877
|
if (narrowed.action === "deny") {
|
|
3850
3878
|
const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`);
|
|
3851
3879
|
emitEnd(true, { content: narrowedDenial });
|
|
@@ -58,7 +58,7 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
58
58
|
if (typeof path !== "string" || path.length === 0) {
|
|
59
59
|
return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
|
|
60
60
|
}
|
|
61
|
-
const canon = await canonicalizeTarget(env, path, signal, rootPath);
|
|
61
|
+
const canon = await canonicalizeTarget(env, path, signal, req.cwd ?? rootPath);
|
|
62
62
|
if (!canon.ok) {
|
|
63
63
|
return deny(`write to "${path}" denied: its real target could not be resolved against the session-rule allowDirs`);
|
|
64
64
|
}
|
|
@@ -64,7 +64,7 @@ export function createSensitivePathPolicy(opts) {
|
|
|
64
64
|
const path = writeTargetPath(canonical, req.args);
|
|
65
65
|
if (typeof path !== "string" || path.length === 0)
|
|
66
66
|
return { action: "allow" };
|
|
67
|
-
const canon = await canonicalizeTarget(opts.env, path, signal, opts.rootPath);
|
|
67
|
+
const canon = await canonicalizeTarget(opts.env, path, signal, req.cwd ?? opts.rootPath);
|
|
68
68
|
if (!canon.ok) {
|
|
69
69
|
if (canon.unresolvedSymlink) {
|
|
70
70
|
return {
|
|
@@ -2,6 +2,7 @@ export interface ToolCallRequest {
|
|
|
2
2
|
toolName: string;
|
|
3
3
|
args: unknown;
|
|
4
4
|
toolCallId: string;
|
|
5
|
+
cwd?: string;
|
|
5
6
|
budget?: {
|
|
6
7
|
resourceRemainingMicroUsd?: number;
|
|
7
8
|
resourceSpentMicroUsd: number;
|
|
@@ -9,11 +10,15 @@ export interface ToolCallRequest {
|
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
export type DecisionReason = "rule" | "mode" | "hook" | "safety" | "classifier" | "persisted_rule";
|
|
13
|
+
export type ApprovalSettledBy = "human" | "timeout" | "aborted";
|
|
14
|
+
export declare const APPROVAL_SETTLED_BY_VALUES: readonly ApprovalSettledBy[];
|
|
15
|
+
export declare function isApprovalSettledBy(v: unknown): v is ApprovalSettledBy;
|
|
12
16
|
export type PermissionResult = {
|
|
13
17
|
action: "allow";
|
|
14
18
|
updatedInput?: unknown;
|
|
15
19
|
message?: string;
|
|
16
20
|
decisionReason?: DecisionReason;
|
|
21
|
+
settledBy?: Extract<ApprovalSettledBy, "human">;
|
|
17
22
|
} | {
|
|
18
23
|
action: "ask";
|
|
19
24
|
updatedInput?: unknown;
|
|
@@ -25,6 +30,7 @@ export type PermissionResult = {
|
|
|
25
30
|
updatedInput?: unknown;
|
|
26
31
|
message?: string;
|
|
27
32
|
decisionReason?: DecisionReason;
|
|
33
|
+
settledBy?: ApprovalSettledBy;
|
|
28
34
|
};
|
|
29
35
|
export declare function decisionText(d: PermissionResult): string | undefined;
|
|
30
36
|
export interface ToolPolicy {
|