@sema-agent/core 5.17.0-pre.0 → 5.17.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 +107 -1
- package/dist/agents/subagent.d.ts +8 -0
- package/dist/agents/subagent.js +69 -2
- package/dist/core/ask-question.js +6 -1
- package/dist/core/canonical-json.js +176 -14
- package/dist/core/checkpoint-store.d.ts +14 -0
- package/dist/core/checkpoint-store.js +73 -0
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +24 -6
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +15 -3
- package/dist/core/runner/prepare-task.d.ts +1 -0
- package/dist/core/runner/prepare-task.js +78 -22
- package/dist/core/runner/runtask.js +3 -1
- package/dist/core/runner/turn-attachments.d.ts +2 -1
- package/dist/core/runner/turn-attachments.js +9 -6
- package/dist/core/session-reconcile.js +19 -1
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/tool-policy.d.ts +9 -0
- package/dist/core/tool-policy.js +28 -8
- package/dist/core/types.d.ts +1 -0
- package/dist/core/wiring-manifest.js +2 -2
- package/dist/engine/llm/validation.js +121 -5
- package/dist/engine/loop/agent-loop.d.ts +2 -0
- package/dist/engine/loop/agent-loop.js +17 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +1 -1
- package/dist/stores/file/checkpoint-store.d.ts +1 -0
- package/dist/stores/file/checkpoint-store.js +1 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +11 -10
- package/dist/tools/fs/fs-bash.js +6 -6
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +4 -3
- package/dist/tools/fs/fs-shared.d.ts +3 -0
- package/dist/tools/fs/fs-shared.js +8 -1
- package/dist/tools/fs/fs-write.js +8 -8
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/fs/safety.d.ts +1 -0
- package/dist/tools/fs/safety.js +9 -2
- package/package.json +1 -1
package/dist/core/mcp.js
CHANGED
|
@@ -69,6 +69,18 @@ export function gateMcpOutput(content, limitTokens = mcpMaxOutputTokens()) {
|
|
|
69
69
|
}
|
|
70
70
|
const MCP_ERROR_HEAD_CHARS = 8_000;
|
|
71
71
|
const MCP_ERROR_TAIL_CHARS = 2_000;
|
|
72
|
+
const STRUCTURED_CONTENT_DEDUP_MIN_CHARS = 32;
|
|
73
|
+
export function structuredContentErrorLine(structuredContent, collectedText) {
|
|
74
|
+
if (structuredContent === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const json = JSON.stringify(structuredContent);
|
|
77
|
+
if (json === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const distinctive = json.length >= STRUCTURED_CONTENT_DEDUP_MIN_CHARS && (json.startsWith("{") || json.startsWith("["));
|
|
80
|
+
if (distinctive && truncateMcpErrorText(collectedText.trim()).includes(json))
|
|
81
|
+
return undefined;
|
|
82
|
+
return `[structuredContent] ${truncateMcpErrorText(json)}`;
|
|
83
|
+
}
|
|
72
84
|
export function truncateMcpErrorText(s) {
|
|
73
85
|
const max = MCP_ERROR_HEAD_CHARS + MCP_ERROR_TAIL_CHARS;
|
|
74
86
|
if (s.length <= max)
|
|
@@ -1198,9 +1210,9 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
|
|
|
1198
1210
|
else
|
|
1199
1211
|
parts.push(`[${block.type} block omitted from error result]`);
|
|
1200
1212
|
}
|
|
1201
|
-
const
|
|
1202
|
-
if (
|
|
1203
|
-
parts.push(
|
|
1213
|
+
const errScLine = structuredContentErrorLine(res.structuredContent, parts.join("\n"));
|
|
1214
|
+
if (errScLine !== undefined)
|
|
1215
|
+
parts.push(errScLine);
|
|
1204
1216
|
const body = truncateMcpErrorText(parts.join("\n").trim());
|
|
1205
1217
|
const msg = body
|
|
1206
1218
|
? `MCP tool ${inlineUntrusted(remoteName)} reported an error. The server's error content follows as external/untrusted data:\n${delimitUntrusted(`${spec.name} tool error`, body)}`
|
|
@@ -127,6 +127,7 @@ export interface Prepared {
|
|
|
127
127
|
activeTools: Set<string>;
|
|
128
128
|
deferredToolNames?: ReadonlySet<string>;
|
|
129
129
|
toolMaterializeStatic: boolean;
|
|
130
|
+
deferDirectCall: boolean;
|
|
130
131
|
staticFaceFor?: (name: string) => boolean;
|
|
131
132
|
memoryEngineSession?: {
|
|
132
133
|
engine: MemoryEngine;
|
|
@@ -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, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
23
|
+
import { askApproverIdentity, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
24
24
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
25
25
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
26
26
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -77,7 +77,7 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
|
|
|
77
77
|
import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
|
|
78
78
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
79
79
|
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
80
|
-
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, resolveCheckpointStore, } from "../checkpoint-store.js";
|
|
80
|
+
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
|
|
81
81
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
82
82
|
import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
83
83
|
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
@@ -168,6 +168,9 @@ export function resolveEnvLifetimeExpiry(env, observedAt) {
|
|
|
168
168
|
return { unanchored: true };
|
|
169
169
|
return { expiresAt: anchor + lifetimeMs };
|
|
170
170
|
}
|
|
171
|
+
function isLiveApproverSeat(onAsk) {
|
|
172
|
+
return typeof onAsk === "function";
|
|
173
|
+
}
|
|
171
174
|
const DEFAULT_UNATTENDED_APPROVAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
172
175
|
function sanitizedTtlMs(ttlMs) {
|
|
173
176
|
if (ttlMs === undefined)
|
|
@@ -182,6 +185,12 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
|
182
185
|
export function checkpointScopeOf(spec) {
|
|
183
186
|
return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
|
|
184
187
|
}
|
|
188
|
+
class ParkRefusal extends Error {
|
|
189
|
+
constructor(message, options) {
|
|
190
|
+
super(message, options);
|
|
191
|
+
this.name = "ParkRefusal";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
185
194
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
186
195
|
export function isFableFamilyModelId(id) {
|
|
187
196
|
const tail = id.toLowerCase().split("/").pop() ?? "";
|
|
@@ -1050,7 +1059,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1050
1059
|
...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
|
|
1051
1060
|
];
|
|
1052
1061
|
const ownCallerPolicy = lockedPreflight.toolPolicy;
|
|
1053
|
-
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1062
|
+
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1063
|
+
(spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
|
|
1054
1064
|
const parentConstraints = [
|
|
1055
1065
|
...(inheritedParentConstraints ?? []),
|
|
1056
1066
|
...(ownCallerPolicy !== undefined
|
|
@@ -1595,6 +1605,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1595
1605
|
...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
|
|
1596
1606
|
...(deps.hands?.readImageDownsampler !== undefined ? { readImageDownsampler: deps.hands.readImageDownsampler } : {}),
|
|
1597
1607
|
...(deps.hands?.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: deps.hands.autoBackgroundOnTimeout } : {}),
|
|
1608
|
+
...(deps.hands?.readCyberReminder !== undefined ? { readCyberReminder: deps.hands.readCyberReminder } : {}),
|
|
1598
1609
|
beforeWrite: async (w) => {
|
|
1599
1610
|
const deploymentGate = deps.hands?.beforeWrite;
|
|
1600
1611
|
if (deploymentGate) {
|
|
@@ -2358,6 +2369,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2358
2369
|
.map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
|
|
2359
2370
|
let toolsDeltaRef;
|
|
2360
2371
|
let toolMaterializeStatic = false;
|
|
2372
|
+
let deferDirectCall = false;
|
|
2361
2373
|
const staticFaceForRef = {};
|
|
2362
2374
|
if (deferred.size > 0 || failedMcpServers.length > 0) {
|
|
2363
2375
|
toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
|
|
@@ -2380,6 +2392,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2380
2392
|
const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
|
|
2381
2393
|
const materializeStatic = requestedStrategy === "static" && !laneDegrade;
|
|
2382
2394
|
toolMaterializeStatic = materializeStatic;
|
|
2395
|
+
deferDirectCall = spec.deferSelfResolve !== false;
|
|
2383
2396
|
promptManifest.toolDisclosure = {
|
|
2384
2397
|
deferredTools: deferred.size,
|
|
2385
2398
|
strategy: materializeStatic ? "static" : "swap",
|
|
@@ -3403,15 +3416,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3403
3416
|
});
|
|
3404
3417
|
const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
|
|
3405
3418
|
if (!checkpointStore)
|
|
3406
|
-
return false;
|
|
3419
|
+
return { ok: false };
|
|
3407
3420
|
if (memoryEngineSession)
|
|
3408
3421
|
await memoryEngineSession.harvest("checkpoint");
|
|
3409
3422
|
try {
|
|
3410
3423
|
await checkpointStore.put(token, cp);
|
|
3411
|
-
return true;
|
|
3424
|
+
return { ok: true };
|
|
3412
3425
|
}
|
|
3413
3426
|
catch (putErr) {
|
|
3414
3427
|
deps.onError?.(putErr, { phase: "config", sessionId });
|
|
3428
|
+
const reason = "the approval checkpoint could not be persisted (the store rejected the write; the deployment's error face carries the store's own message)";
|
|
3415
3429
|
if (remoteEnv !== undefined && remoteHandle?.snapshotId !== undefined) {
|
|
3416
3430
|
const { outcome: back, attempts: backAttempts } = await restoreWorkspaceWithRetry(remoteEnv, remoteHandle.snapshotId, {
|
|
3417
3431
|
abortSignal: abortController.signal,
|
|
@@ -3419,7 +3433,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3419
3433
|
if (back.ok) {
|
|
3420
3434
|
const init = await remoteEnv.postResumeInit();
|
|
3421
3435
|
if (init.ok)
|
|
3422
|
-
return false;
|
|
3436
|
+
return { ok: false, reason };
|
|
3423
3437
|
remoteEnvFailures.push(remoteEnvFailureNote("postResumeInit", init.error, 1));
|
|
3424
3438
|
deps.onError?.(init.error, { phase: "config", sessionId });
|
|
3425
3439
|
}
|
|
@@ -3429,9 +3443,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3429
3443
|
}
|
|
3430
3444
|
abortController.abort();
|
|
3431
3445
|
void harness.abort();
|
|
3432
|
-
return false;
|
|
3446
|
+
return { ok: false, reason };
|
|
3433
3447
|
}
|
|
3434
|
-
return false;
|
|
3448
|
+
return { ok: false, reason };
|
|
3435
3449
|
}
|
|
3436
3450
|
};
|
|
3437
3451
|
const publishCommittedSuspend = (token, gate, scope, remoteHandle) => {
|
|
@@ -3519,7 +3533,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3519
3533
|
resourceLedger: resourceLedgerOut,
|
|
3520
3534
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3521
3535
|
};
|
|
3522
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3536
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3523
3537
|
return false;
|
|
3524
3538
|
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3525
3539
|
try {
|
|
@@ -3604,7 +3618,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3604
3618
|
...(spec.principal ? { principal: spec.principal } : {}),
|
|
3605
3619
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3606
3620
|
};
|
|
3607
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3621
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3608
3622
|
return false;
|
|
3609
3623
|
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3610
3624
|
try {
|
|
@@ -3700,7 +3714,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3700
3714
|
};
|
|
3701
3715
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
3702
3716
|
? async (req, postHookArgs, safety, liveFaceUnavailable) => {
|
|
3703
|
-
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) :
|
|
3717
|
+
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
3704
3718
|
if (syncFirstEligible &&
|
|
3705
3719
|
runtimeCaps?.forceDurableGate !== true &&
|
|
3706
3720
|
liveFaceUnavailable !== true &&
|
|
@@ -3713,13 +3727,50 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3713
3727
|
let remoteHandle;
|
|
3714
3728
|
const remoteEnv = suspendableEnv;
|
|
3715
3729
|
try {
|
|
3730
|
+
const parked = tryCloneArgs(postHookArgs);
|
|
3731
|
+
if (!parked.ok) {
|
|
3732
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be captured onto an approval checkpoint (unclonable value: ${parked.reason}) — ` +
|
|
3733
|
+
`a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: parked.cause });
|
|
3734
|
+
}
|
|
3735
|
+
let backendFidelity;
|
|
3736
|
+
try {
|
|
3737
|
+
backendFidelity = resolveDeclaredFidelity(checkpointStore, "checkpointStore");
|
|
3738
|
+
}
|
|
3739
|
+
catch (err) {
|
|
3740
|
+
throw new ParkRefusal(`the checkpoint backend declares an unrecognized encoding fidelity, so the width of the snapshot this row may hold is unknown (${describeThrown(err)}) — ` +
|
|
3741
|
+
`refusing to park rather than guess which values would survive it`, { cause: err });
|
|
3742
|
+
}
|
|
3743
|
+
const captured = encodeAtFidelity(backendFidelity, parked.value);
|
|
3744
|
+
if (!captured.ok) {
|
|
3745
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be encoded by this checkpoint backend (${describeThrown(captured.cause)}) — ` +
|
|
3746
|
+
`a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: captured.cause });
|
|
3747
|
+
}
|
|
3748
|
+
const parkedArgs = captured.value;
|
|
3749
|
+
const projectionMovedArgs = backendFidelity !== "structured-clone" && !samePlainValue(parked.value, parkedArgs);
|
|
3750
|
+
if (projectionMovedArgs && basePolicyForResumeEdit !== undefined) {
|
|
3751
|
+
const presented = encodeAtFidelity(backendFidelity, parkedArgs);
|
|
3752
|
+
if (!presented.ok) {
|
|
3753
|
+
throw new ParkRefusal(`the stored form of "${req.toolName}"'s arguments could not be presented for re-adjudication (${describeThrown(presented.cause)})`, { cause: presented.cause });
|
|
3754
|
+
}
|
|
3755
|
+
const reprojected = refuseOutOfContractDecision(await basePolicyForResumeEdit.check({ toolName: req.toolName, args: presented.value, toolCallId: req.toolCallId }, abortController.signal));
|
|
3756
|
+
const demanded = reprojected.updatedInput !== undefined ? tryCloneArgs(reprojected.updatedInput) : undefined;
|
|
3757
|
+
const rewroteTheFiledValue = reprojected.updatedInput !== undefined &&
|
|
3758
|
+
!(demanded?.ok === true &&
|
|
3759
|
+
samePlainValue(demanded.value, parkedArgs) &&
|
|
3760
|
+
samePlainValue(reprojected.updatedInput, parkedArgs));
|
|
3761
|
+
if (reprojected.action === "deny" || rewroteTheFiledValue) {
|
|
3762
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" change shape when stored by this checkpoint backend, and the deployment's tool policy does not accept the stored shape` +
|
|
3763
|
+
`${reprojected.action === "deny" && reprojected.message ? `: ${reprojected.message}` : ""} — ` +
|
|
3764
|
+
`refusing to file a row whose executable form nothing adjudicated`);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3716
3767
|
if (offloadStore && isVolatileOffloadStore(offloadStore) && !offloadStore.isEmpty()) {
|
|
3717
|
-
throw new
|
|
3768
|
+
throw new ParkRefusal("durable suspend requires a durable toolResultStore (the default InMemoryToolResultStore " +
|
|
3718
3769
|
"holds offloaded results that are lost across a cross-replica resume); inject " +
|
|
3719
3770
|
"RunnerDeps.toolResultStore or disable offload.");
|
|
3720
3771
|
}
|
|
3721
3772
|
if (ownedEnv !== undefined && remoteEnv === undefined && parkOnlyRemoteEnv === undefined) {
|
|
3722
|
-
throw new
|
|
3773
|
+
throw new ParkRefusal(incompleteSuspendAdapter !== undefined
|
|
3723
3774
|
? `durable suspend is not supported with this per-task executionEnvFactory env: it declares capabilities.suspendable but its adapter does not implement ${incompleteSuspendAdapter.join(" or ")}, so a snapshot taken now could never be restored. Implement the full RemoteExecutionEnv restore surface, or declare capabilities.suspendable:false if the workspace is externally durable (the park-only lane).`
|
|
3724
3775
|
: "durable suspend is not supported with a non-remote per-task executionEnvFactory env: the " +
|
|
3725
3776
|
"minted env is destroyed on suspend, so a resumed file/shell tool would act on a fresh " +
|
|
@@ -3728,7 +3779,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3728
3779
|
}
|
|
3729
3780
|
const leafId = await session.getLeafId();
|
|
3730
3781
|
if (!leafId) {
|
|
3731
|
-
throw new
|
|
3782
|
+
throw new ParkRefusal("cannot suspend: session has no committed leaf to resume from");
|
|
3732
3783
|
}
|
|
3733
3784
|
const { messages } = await session.buildContext();
|
|
3734
3785
|
const { batchToolCallIds, completedCallIds } = batchContextAt(messages, req.toolCallId);
|
|
@@ -3755,7 +3806,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3755
3806
|
token = mintCheckpointToken();
|
|
3756
3807
|
const riskDescriptor = buildRiskDescriptor({
|
|
3757
3808
|
toolName: req.toolName,
|
|
3758
|
-
args:
|
|
3809
|
+
args: parkedArgs,
|
|
3759
3810
|
safety,
|
|
3760
3811
|
shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
|
|
3761
3812
|
...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
|
|
@@ -3798,12 +3849,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3798
3849
|
kind: "tool_approval",
|
|
3799
3850
|
toolCallId: req.toolCallId,
|
|
3800
3851
|
toolName: req.toolName,
|
|
3801
|
-
args:
|
|
3852
|
+
args: parkedArgs,
|
|
3802
3853
|
...(() => {
|
|
3803
|
-
const preview = approvalPreviewOf(req.toolName,
|
|
3854
|
+
const preview = approvalPreviewOf(req.toolName, parkedArgs);
|
|
3804
3855
|
return preview !== undefined ? { preview } : {};
|
|
3805
3856
|
})(),
|
|
3806
|
-
boundInputHash: boundInputHashOf(
|
|
3857
|
+
boundInputHash: boundInputHashOf(parkedArgs),
|
|
3807
3858
|
batchToolCallIds,
|
|
3808
3859
|
completedCallIds,
|
|
3809
3860
|
},
|
|
@@ -3828,10 +3879,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3828
3879
|
}
|
|
3829
3880
|
catch (err) {
|
|
3830
3881
|
deps.onError?.(err, { phase: "config", sessionId });
|
|
3831
|
-
return
|
|
3882
|
+
return {
|
|
3883
|
+
parkFailed: err instanceof ParkRefusal
|
|
3884
|
+
? err.message
|
|
3885
|
+
: "the durable approval park could not be prepared (the deployment's error face carries the exception)",
|
|
3886
|
+
};
|
|
3832
3887
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3888
|
+
const committed = await commitSuspendSaga(token, cp, remoteEnv, remoteHandle);
|
|
3889
|
+
if (!committed.ok) {
|
|
3890
|
+
return committed.reason !== undefined ? { parkFailed: committed.reason } : undefined;
|
|
3835
3891
|
}
|
|
3836
3892
|
publishCommittedSuspend(token, gate, cp.scope, remoteHandle);
|
|
3837
3893
|
try {
|
|
@@ -4206,7 +4262,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4206
4262
|
: undefined;
|
|
4207
4263
|
overheadState.promptChars = systemPrompt.length;
|
|
4208
4264
|
const preparedHolder = {};
|
|
4209
|
-
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, ...(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 } : {}) });
|
|
4265
|
+
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 } : {}) });
|
|
4210
4266
|
const prepared = buildPrepared();
|
|
4211
4267
|
preparedHolder.current = prepared;
|
|
4212
4268
|
return prepared;
|
|
@@ -577,7 +577,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
577
577
|
skillsListing: rs.attach.skillsListingOn,
|
|
578
578
|
mcpInstructions: mcpInstructionsOn,
|
|
579
579
|
},
|
|
580
|
-
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
580
|
+
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
581
|
+
? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
|
|
582
|
+
: {}),
|
|
581
583
|
...(changed !== undefined ? { changedFiles: changed } : {}),
|
|
582
584
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
583
585
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
@@ -94,6 +94,7 @@ export interface AttachmentInputs {
|
|
|
94
94
|
mcpInstructions?: boolean;
|
|
95
95
|
};
|
|
96
96
|
undiscoveredTools?: readonly string[];
|
|
97
|
+
deferDirectCall?: boolean;
|
|
97
98
|
changedFiles?: ReadonlyArray<{
|
|
98
99
|
path: string;
|
|
99
100
|
mtimeMs: number;
|
|
@@ -118,7 +119,7 @@ export interface AttachmentInputs {
|
|
|
118
119
|
mcpDroppedTools?: ReadonlyArray<McpDroppedTool>;
|
|
119
120
|
}
|
|
120
121
|
export declare function collectDueAttachments(state: AttachmentState, inp: AttachmentInputs): readonly TurnAttachment[];
|
|
121
|
-
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[]): string;
|
|
122
|
+
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[], directCallEnabled?: boolean): string;
|
|
122
123
|
export declare function renderDateChange(newDate: string): string;
|
|
123
124
|
export declare function collectDateChange(state: DateChangeState, today: string): TurnAttachment | undefined;
|
|
124
125
|
export declare function collectInstructionsChange(state: InstructionsChangeState, probed: ReadonlyArray<{
|
|
@@ -145,7 +145,7 @@ export function collectDueAttachments(state, inp) {
|
|
|
145
145
|
const sinceUse = state.toolSearchLastUseTurn === undefined ? Number.POSITIVE_INFINITY : cadenceNow - state.toolSearchLastUseTurn;
|
|
146
146
|
if (sinceUse >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS && cadenceNow - state.lastToolSearchReminderTurn >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS) {
|
|
147
147
|
state.lastToolSearchReminderTurn = cadenceNow;
|
|
148
|
-
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools) });
|
|
148
|
+
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools, inp.deferDirectCall !== false) });
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
if (inp.config.planModeReminder && inp.planActive) {
|
|
@@ -248,14 +248,17 @@ function renderTaskReminder(state) {
|
|
|
248
248
|
}
|
|
249
249
|
return message;
|
|
250
250
|
}
|
|
251
|
-
export function renderToolSearchUsageReminder(undiscovered) {
|
|
251
|
+
export function renderToolSearchUsageReminder(undiscovered, directCallEnabled = true) {
|
|
252
252
|
const shown = undiscovered.slice(0, TOOL_SEARCH_REMINDER_CONFIG.MAX_NAMES);
|
|
253
253
|
const more = undiscovered.length - shown.length;
|
|
254
254
|
const names = `${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
255
255
|
return (`Some available tools' schemas are not loaded in this conversation yet: ${names}. Before concluding a ` +
|
|
256
256
|
`capability is missing or building a workaround, use ${TOOL_SEARCH_TOOL_NAME} to find and load relevant tools — ` +
|
|
257
|
-
`keywords to search, or query "select:<name>[,<name>...]" for specific tools.
|
|
258
|
-
|
|
257
|
+
`keywords to search, or query "select:<name>[,<name>...]" for specific tools. ` +
|
|
258
|
+
(directCallEnabled
|
|
259
|
+
? `You do not have these tools' parameters, so activate one rather than guessing its arguments. `
|
|
260
|
+
: `Calling a tool before its schema is loaded will fail. `) +
|
|
261
|
+
`This is just a gentle reminder - ignore if not applicable to the current work.`);
|
|
259
262
|
}
|
|
260
263
|
export function renderDateChange(newDate) {
|
|
261
264
|
return `The date has changed. Today's date is now ${newDate}. DO NOT mention this to the user explicitly because they are already aware.`;
|
|
@@ -400,8 +403,8 @@ export function renderToolsDelta(input) {
|
|
|
400
403
|
if (added.length > 0) {
|
|
401
404
|
const swapped = input.staticFace === true ? (input.swappedUnderStatic ?? []).filter((n) => added.includes(n)) : [];
|
|
402
405
|
blocks.push((input.staticFace === true
|
|
403
|
-
? "The following deferred tools are now active — call them directly.
|
|
404
|
-
|
|
406
|
+
? "The following deferred tools are now active — call them directly. The tools list keeps compact " +
|
|
407
|
+
'placeholder entries, so re-run ToolSearch ("select:<name>") if you need a parameter schema:\n'
|
|
405
408
|
: "The following deferred tools are now available. Their full schemas are loaded — call them " +
|
|
406
409
|
"directly like any other tool:\n") +
|
|
407
410
|
added.map((n) => `- ${n}`).join("\n") +
|
|
@@ -10,6 +10,20 @@ const INTERRUPTED_IDEMPOTENT = "[INTERRUPTED] The previous run ended before this
|
|
|
10
10
|
"what you intended. If you decide not to re-issue it, verify the current state first.";
|
|
11
11
|
const INTERRUPTED_NEVER_STARTED = "[INTERRUPTED] The run was aborted before this tool call started. It was never executed and had " +
|
|
12
12
|
"no side effects — it is safe to re-issue this call if you still need it.";
|
|
13
|
+
const INTERRUPTED_REPEAT_UNKNOWN = "[INTERRUPTED] Same as an earlier interrupted call in this batch: no result was recorded and the outcome is " +
|
|
14
|
+
"UNKNOWN — verify before relying on it.";
|
|
15
|
+
const INTERRUPTED_REPEAT_SAFE = "[INTERRUPTED] Same as an earlier interrupted call in this batch: this tool is read-only/idempotent, so just " +
|
|
16
|
+
"call it again if you still need the result.";
|
|
17
|
+
const INTERRUPTED_REPEAT_IDEMPOTENT = "[INTERRUPTED] Same as an earlier interrupted call in this batch: it is safe to REPLAY — re-issue it, or verify " +
|
|
18
|
+
"the current state if you decide not to.";
|
|
19
|
+
const INTERRUPTED_REPEAT_NEVER_STARTED = "[INTERRUPTED] Same as an earlier interrupted call in this batch: it never started and had no side effects — " +
|
|
20
|
+
"safe to re-issue.";
|
|
21
|
+
const REPEAT_TEXT = {
|
|
22
|
+
never_started: INTERRUPTED_REPEAT_NEVER_STARTED,
|
|
23
|
+
read: INTERRUPTED_REPEAT_SAFE,
|
|
24
|
+
idempotent: INTERRUPTED_REPEAT_IDEMPOTENT,
|
|
25
|
+
unknown: INTERRUPTED_REPEAT_UNKNOWN,
|
|
26
|
+
};
|
|
13
27
|
export function findOrphanToolCalls(messages, suspendedBatch) {
|
|
14
28
|
const resolvedAt = new Map();
|
|
15
29
|
const callSitesAt = new Map();
|
|
@@ -70,11 +84,15 @@ export async function reconcileInterruptedSession(session, toolEffects, suspende
|
|
|
70
84
|
const { messages } = await session.buildContext();
|
|
71
85
|
const orphans = findOrphanToolCalls(messages, suspendedBatch).filter((o) => o.kind !== "result");
|
|
72
86
|
const recovered = [];
|
|
87
|
+
const statedInFull = new Set();
|
|
73
88
|
for (const orphan of orphans) {
|
|
74
89
|
const effect = toolEffects?.get(orphan.toolName) ?? "write";
|
|
75
90
|
const effectText = effect === "read" ? INTERRUPTED_SAFE : effect === "idempotent" ? INTERRUPTED_IDEMPOTENT : INTERRUPTED_UNKNOWN;
|
|
76
91
|
const neverStarted = startedToolCallIds !== undefined && !startedToolCallIds.has(orphan.toolCallId);
|
|
77
|
-
const
|
|
92
|
+
const fullText = neverStarted ? INTERRUPTED_NEVER_STARTED : effectText;
|
|
93
|
+
const cls = neverStarted ? "never_started" : effect === "read" ? "read" : effect === "idempotent" ? "idempotent" : "unknown";
|
|
94
|
+
const text = statedInFull.has(cls) ? REPEAT_TEXT[cls] : fullText;
|
|
95
|
+
statedInFull.add(cls);
|
|
78
96
|
const errorKind = neverStarted ? "interrupted_never_started" : "interrupted_outcome_unknown";
|
|
79
97
|
const entryId = await session.appendMessage({
|
|
80
98
|
role: "toolResult",
|
|
@@ -982,6 +982,7 @@ export async function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
982
982
|
handle.errorRetryAfterMs = undefined;
|
|
983
983
|
handle.resultIsPartial = undefined;
|
|
984
984
|
handle.stopSource = undefined;
|
|
985
|
+
handle.stoppedBy = undefined;
|
|
985
986
|
handle.completionId = undefined;
|
|
986
987
|
handle.terminalNotified = undefined;
|
|
987
988
|
handle.reviveCycle = (handle.reviveCycle ?? 0) + 1;
|
|
@@ -100,6 +100,15 @@ export type AskOutcome = boolean | "unavailable" | {
|
|
|
100
100
|
};
|
|
101
101
|
export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDelegationProvenance): OnAsk;
|
|
102
102
|
export declare function askApproverIdentity(onAsk: unknown): unknown;
|
|
103
|
+
export declare function tryCloneArgs<T>(v: T): {
|
|
104
|
+
ok: true;
|
|
105
|
+
value: T;
|
|
106
|
+
} | {
|
|
107
|
+
ok: false;
|
|
108
|
+
reason: string;
|
|
109
|
+
cause?: unknown;
|
|
110
|
+
};
|
|
111
|
+
export declare function describeThrown(err: unknown): string;
|
|
103
112
|
export type ResolvedAsk = PermissionResult & {
|
|
104
113
|
approverUnavailable?: true;
|
|
105
114
|
presentedInput?: unknown;
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { isAbsolute, join, normalize as normalizePath, sep } from "node:path";
|
|
3
3
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
4
4
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
5
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
5
6
|
import { writeTargetPath } from "../tools/fs/safety.js";
|
|
6
7
|
export function decisionText(d) {
|
|
7
8
|
return d.message;
|
|
@@ -90,7 +91,7 @@ export function createApprovalPolicy(opts) {
|
|
|
90
91
|
catch (err) {
|
|
91
92
|
return {
|
|
92
93
|
action: "deny",
|
|
93
|
-
message: `approval errored for "${req.toolName}": ${
|
|
94
|
+
message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
const okRaw = ok;
|
|
@@ -528,17 +529,36 @@ export function withDelegationProvenance(onAsk, delegation) {
|
|
|
528
529
|
export function askApproverIdentity(onAsk) {
|
|
529
530
|
return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
|
|
530
531
|
}
|
|
531
|
-
function tryCloneArgs(v) {
|
|
532
|
+
export function tryCloneArgs(v) {
|
|
532
533
|
try {
|
|
533
534
|
const value = structuredClone(v);
|
|
534
535
|
if (containsSharedMemory(value))
|
|
535
|
-
return { ok: false };
|
|
536
|
+
return { ok: false, reason: "the value carries shared memory (SharedArrayBuffer), which a clone still aliases" };
|
|
536
537
|
return { ok: true, value };
|
|
537
538
|
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
return { ok: false, reason: describeThrown(err), cause: err };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
export function describeThrown(err) {
|
|
544
|
+
try {
|
|
545
|
+
if (typeof err === "string")
|
|
546
|
+
return containThrownText(err);
|
|
547
|
+
if (err instanceof Error) {
|
|
548
|
+
const message = err.message;
|
|
549
|
+
if (typeof message === "string" && message !== "")
|
|
550
|
+
return containThrownText(message);
|
|
551
|
+
}
|
|
552
|
+
return `a non-Error value was thrown (${err === null ? "null" : typeof err})`;
|
|
553
|
+
}
|
|
538
554
|
catch {
|
|
539
|
-
return
|
|
555
|
+
return "the value could not be described";
|
|
540
556
|
}
|
|
541
557
|
}
|
|
558
|
+
const THROWN_TEXT_MAX = 300;
|
|
559
|
+
function containThrownText(text) {
|
|
560
|
+
return inlineUntrusted(text, THROWN_TEXT_MAX);
|
|
561
|
+
}
|
|
542
562
|
function containsSharedMemory(v, seen = new Set()) {
|
|
543
563
|
if (typeof SharedArrayBuffer !== "undefined" && v instanceof SharedArrayBuffer)
|
|
544
564
|
return true;
|
|
@@ -593,7 +613,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
593
613
|
if (!presented.ok) {
|
|
594
614
|
return {
|
|
595
615
|
action: "deny",
|
|
596
|
-
message: `approval for "${req.toolName}" could not present the args safely (unclonable value) — denied fail-closed`,
|
|
616
|
+
message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${presented.reason}) — denied fail-closed`,
|
|
597
617
|
decisionReason: "mode",
|
|
598
618
|
};
|
|
599
619
|
}
|
|
@@ -603,7 +623,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
603
623
|
if (!approverView.ok) {
|
|
604
624
|
return {
|
|
605
625
|
action: "deny",
|
|
606
|
-
message: `approval for "${req.toolName}" could not present the args safely (unclonable value) — denied fail-closed`,
|
|
626
|
+
message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${approverView.reason}) — denied fail-closed`,
|
|
607
627
|
decisionReason: "mode",
|
|
608
628
|
};
|
|
609
629
|
}
|
|
@@ -612,7 +632,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
612
632
|
catch (err) {
|
|
613
633
|
return {
|
|
614
634
|
action: "deny",
|
|
615
|
-
message: `approval errored for "${req.toolName}": ${
|
|
635
|
+
message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
|
|
616
636
|
decisionReason: "mode",
|
|
617
637
|
};
|
|
618
638
|
}
|
|
@@ -635,7 +655,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
635
655
|
if (!edit.ok) {
|
|
636
656
|
return {
|
|
637
657
|
action: "deny",
|
|
638
|
-
message: `the approved edit for "${req.toolName}" is not safely clonable — denied fail-closed`,
|
|
658
|
+
message: `the approved edit for "${req.toolName}" is not safely clonable (${edit.reason}) — denied fail-closed`,
|
|
639
659
|
decisionReason: "mode",
|
|
640
660
|
};
|
|
641
661
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -174,6 +174,7 @@ export interface HandsBandOptions {
|
|
|
174
174
|
readImageDownsampler?: ((input: Buffer, mimeType: string) => Promise<import("./mcp.js").DownsampledImage | undefined>) | false;
|
|
175
175
|
beforeWrite?: BeforeWriteHook;
|
|
176
176
|
autoBackgroundOnTimeout?: boolean;
|
|
177
|
+
readCyberReminder?: boolean;
|
|
177
178
|
}
|
|
178
179
|
export interface AgentDefinition {
|
|
179
180
|
name: string;
|
|
@@ -19,9 +19,9 @@ export function deriveAskEffective(form, parkEffective) {
|
|
|
19
19
|
case "callback":
|
|
20
20
|
return "human_reachable";
|
|
21
21
|
case "allow":
|
|
22
|
-
return "auto_allow";
|
|
22
|
+
return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_allow";
|
|
23
23
|
case "deny":
|
|
24
|
-
return "auto_deny";
|
|
24
|
+
return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_deny";
|
|
25
25
|
case "absent":
|
|
26
26
|
return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_deny";
|
|
27
27
|
default: {
|