@sema-agent/core 5.17.0 → 5.18.1
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 +121 -0
- package/dist/agents/subagent.js +24 -0
- package/dist/core/auto-compaction.d.ts +6 -0
- package/dist/core/auto-compaction.js +15 -1
- package/dist/core/checkpoint-store.d.ts +4 -2
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +9 -0
- package/dist/core/hooks.js +21 -0
- package/dist/core/mcp.js +3 -0
- package/dist/core/memory-engine/content-origin.d.ts +27 -0
- package/dist/core/memory-engine/content-origin.js +38 -0
- package/dist/core/memory-engine/engine.d.ts +12 -2
- package/dist/core/memory-engine/engine.js +172 -12
- package/dist/core/memory-engine/file-backend.d.ts +4 -0
- package/dist/core/memory-engine/file-backend.js +25 -3
- package/dist/core/memory-engine/index.d.ts +2 -1
- package/dist/core/memory-engine/index.js +2 -1
- package/dist/core/memory-engine/layout.d.ts +16 -0
- package/dist/core/memory-engine/layout.js +90 -2
- package/dist/core/memory-engine/sync-client.d.ts +1 -0
- package/dist/core/memory-engine/sync-client.js +23 -5
- package/dist/core/memory-engine/tools.d.ts +55 -0
- package/dist/core/memory-engine/tools.js +307 -0
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/memory.d.ts +4 -0
- package/dist/core/memory.js +15 -2
- package/dist/core/permission-rule-consent.d.ts +138 -0
- package/dist/core/permission-rule-consent.js +318 -0
- package/dist/core/permission-rule-model.d.ts +66 -0
- package/dist/core/permission-rule-model.js +135 -0
- package/dist/core/permission-rule-store.d.ts +89 -0
- package/dist/core/permission-rule-store.js +145 -0
- package/dist/core/permission-rules.d.ts +3 -2
- package/dist/core/permission-rules.js +9 -4
- package/dist/core/runner/prepare-memory.d.ts +3 -1
- package/dist/core/runner/prepare-memory.js +54 -14
- package/dist/core/runner/prepare-task.d.ts +12 -0
- package/dist/core/runner/prepare-task.js +206 -12
- package/dist/core/runner/runtask.d.ts +3 -1
- package/dist/core/runner/runtask.js +47 -5
- package/dist/core/runner/tool-output-projection.js +1 -1
- package/dist/core/tool-policy.d.ts +13 -1
- package/dist/core/tool-policy.js +93 -12
- package/dist/core/tools.js +1 -0
- package/dist/core/trace.d.ts +20 -0
- package/dist/core/types.d.ts +15 -0
- package/dist/core/wiring-manifest.d.ts +5 -1
- package/dist/core/wiring-manifest.js +2 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/stores/file/permission-rule-store.d.ts +32 -0
- package/dist/stores/file/permission-rule-store.js +213 -0
- package/dist/tools/fs/fs-bash.js +12 -5
- package/dist/tools/fs/fs-shared.d.ts +12 -0
- package/dist/tools/fs/fs-shared.js +65 -1
- package/dist/tools/web.js +2 -0
- package/package.json +1 -1
|
@@ -20,7 +20,9 @@ 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
|
+
const PERSISTED_RULE_TOOL = "Bash";
|
|
25
|
+
import { findAdmittingRule, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
24
26
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
25
27
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
26
28
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -41,6 +43,9 @@ import { compileOutputSchema } from "./strict-output-schema.js";
|
|
|
41
43
|
import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, staticSchemaRenderable, } from "./tool-disclosure.js";
|
|
42
44
|
import { createSharedMemoryTools } from "../shared-memory/tools.js";
|
|
43
45
|
import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
|
|
46
|
+
import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
|
|
47
|
+
import { MEMORY_RECALL_DISCIPLINE } from "../memory-engine/engine.js";
|
|
48
|
+
import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
|
|
44
49
|
import { composeMemoryBlock } from "../memory.js";
|
|
45
50
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
46
51
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
|
|
@@ -1389,6 +1394,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1389
1394
|
}
|
|
1390
1395
|
tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
|
|
1391
1396
|
const rebuildHarnessToolsRef = {};
|
|
1397
|
+
const contentOriginWrapRef = {};
|
|
1392
1398
|
const toolCallGateArmedRef = { armed: false };
|
|
1393
1399
|
if (lockedPreflight.mcp?.length) {
|
|
1394
1400
|
tools.push({
|
|
@@ -1868,13 +1874,23 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1868
1874
|
.catch(() => undefined);
|
|
1869
1875
|
}
|
|
1870
1876
|
: undefined;
|
|
1871
|
-
const
|
|
1877
|
+
const memoryPairNameDomain = new Set();
|
|
1878
|
+
for (const t of tools) {
|
|
1879
|
+
memoryPairNameDomain.add(t.name);
|
|
1880
|
+
for (const alias of t.aliases ?? [])
|
|
1881
|
+
memoryPairNameDomain.add(alias);
|
|
1882
|
+
}
|
|
1883
|
+
const memoryPairExcludedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => (toolFaceSnapshot.exclude ?? []).includes(n));
|
|
1884
|
+
const memoryPairOccupiedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => memoryPairNameDomain.has(n));
|
|
1885
|
+
const memorySearchToolsPlanned = memoryPairExcludedName === undefined && memoryPairOccupiedName === undefined;
|
|
1886
|
+
const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, } = await prepareMemory({
|
|
1872
1887
|
spec,
|
|
1873
1888
|
deps,
|
|
1874
1889
|
sessionId,
|
|
1875
1890
|
taskRootPath,
|
|
1876
1891
|
memoryWriteGateRef,
|
|
1877
1892
|
writeToolsMounted: tools.some((t) => t.name === "Write") && !(toolFaceSnapshot.exclude?.includes("Write") ?? false),
|
|
1893
|
+
memorySearchToolsPlanned,
|
|
1878
1894
|
admissionCtx: {
|
|
1879
1895
|
orgMemoryDenied: complianceDenies.has("org_memory_mount"),
|
|
1880
1896
|
complianceDegraded,
|
|
@@ -2012,6 +2028,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2012
2028
|
sharedMemoryPairMounted = true;
|
|
2013
2029
|
}
|
|
2014
2030
|
}
|
|
2031
|
+
let memoryEnginePairMounted = false;
|
|
2032
|
+
if (memoryTools !== undefined && memoryTools.length > 0) {
|
|
2033
|
+
tools.push(...memoryTools.map((s) => defineTool(s)));
|
|
2034
|
+
memoryEnginePairMounted = true;
|
|
2035
|
+
}
|
|
2036
|
+
else if (memoryEngineSession !== undefined && !memorySearchToolsPlanned) {
|
|
2037
|
+
deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: ` +
|
|
2038
|
+
(memoryPairExcludedName !== undefined
|
|
2039
|
+
? `excludeTools removes "${memoryPairExcludedName}"`
|
|
2040
|
+
: `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`) +
|
|
2041
|
+
" — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
|
|
2042
|
+
}
|
|
2015
2043
|
const skillsListing = skillSpecs.length > 0
|
|
2016
2044
|
? {
|
|
2017
2045
|
entries: skillSpecs.map((s) => ({
|
|
@@ -2216,22 +2244,39 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2216
2244
|
}
|
|
2217
2245
|
return deferredSet;
|
|
2218
2246
|
};
|
|
2219
|
-
const sharedMemoryPair = SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n));
|
|
2247
|
+
const sharedMemoryPair = sharedMemoryPairMounted ? SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
|
|
2248
|
+
const memoryEnginePair = memoryEnginePairMounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
|
|
2249
|
+
const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
|
|
2220
2250
|
let deferred;
|
|
2221
|
-
if (
|
|
2222
|
-
const
|
|
2223
|
-
const d1 = classifyDeferredOverFace(tools,
|
|
2224
|
-
const d0 = classifyDeferredOverFace(
|
|
2251
|
+
if (builtinDeferPairNames.length > 0) {
|
|
2252
|
+
const withoutPairs = tools.filter((t) => !builtinDeferPairNames.some((n) => n === t.name));
|
|
2253
|
+
const d1 = classifyDeferredOverFace(tools, builtinDeferPairNames);
|
|
2254
|
+
const d0 = classifyDeferredOverFace(withoutPairs, []);
|
|
2225
2255
|
const soleCause = d0.size === 0 && d1.size > 0;
|
|
2226
2256
|
const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
|
|
2227
2257
|
if (soleCause && supportNameTaken) {
|
|
2228
2258
|
for (let i = tools.length - 1; i >= 0; i--) {
|
|
2229
|
-
if (
|
|
2259
|
+
if (builtinDeferPairNames.some((n) => n === tools[i].name))
|
|
2230
2260
|
tools.splice(i, 1);
|
|
2231
2261
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
`
|
|
2262
|
+
if (sharedMemoryPair.length > 0) {
|
|
2263
|
+
sharedMemoryPairMounted = false;
|
|
2264
|
+
deps.onError?.(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
|
|
2265
|
+
`the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
|
|
2266
|
+
}
|
|
2267
|
+
if (memoryEnginePair.length > 0) {
|
|
2268
|
+
memoryEnginePairMounted = false;
|
|
2269
|
+
if (memoryBlock !== undefined) {
|
|
2270
|
+
if (memoryBlock === MEMORY_RECALL_DISCIPLINE)
|
|
2271
|
+
memoryBlock = undefined;
|
|
2272
|
+
else if (memoryBlock.includes(`\n\n${MEMORY_RECALL_DISCIPLINE}`))
|
|
2273
|
+
memoryBlock = memoryBlock.replace(`\n\n${MEMORY_RECALL_DISCIPLINE}`, "");
|
|
2274
|
+
else if (memoryBlock.startsWith(`${MEMORY_RECALL_DISCIPLINE}\n\n`))
|
|
2275
|
+
memoryBlock = memoryBlock.slice(MEMORY_RECALL_DISCIPLINE.length + 2);
|
|
2276
|
+
}
|
|
2277
|
+
deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
|
|
2278
|
+
`the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
|
|
2279
|
+
}
|
|
2235
2280
|
deferred = d0;
|
|
2236
2281
|
}
|
|
2237
2282
|
else {
|
|
@@ -2241,6 +2286,78 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2241
2286
|
else {
|
|
2242
2287
|
deferred = classifyDeferredOverFace(tools, []);
|
|
2243
2288
|
}
|
|
2289
|
+
if (memoryEngineSession !== undefined) {
|
|
2290
|
+
const { pollution, contentSafety } = memoryEngineSession;
|
|
2291
|
+
const callerToolNames = new Set((spec.tools ?? []).map((t) => t.name));
|
|
2292
|
+
const contentOriginWrapped = new WeakSet();
|
|
2293
|
+
const classOf = (t) => classifyToolContentOrigin({
|
|
2294
|
+
declared: t.contentOrigin,
|
|
2295
|
+
isProtocolTool: protocolOf(t.name) !== undefined,
|
|
2296
|
+
isCallerTool: callerToolNames.has(t.name),
|
|
2297
|
+
trusted: contentSafety.trustedTools.has(t.name),
|
|
2298
|
+
});
|
|
2299
|
+
const delegationByName = new Map();
|
|
2300
|
+
for (const t of spec.tools ?? []) {
|
|
2301
|
+
const withFaces = t;
|
|
2302
|
+
if (withFaces.agentToolFaces === undefined && t.agentListing === undefined)
|
|
2303
|
+
continue;
|
|
2304
|
+
delegationByName.set(t.name, { faces: withFaces.agentToolFaces, pool: withFaces.agentToolPool });
|
|
2305
|
+
}
|
|
2306
|
+
const poolToolPollutes = (t) => contentOriginPollutes(classifyToolContentOrigin({
|
|
2307
|
+
declared: t.contentOrigin,
|
|
2308
|
+
isProtocolTool: protocolOf(t.name) !== undefined,
|
|
2309
|
+
isCallerTool: true,
|
|
2310
|
+
trusted: contentSafety.trustedTools.has(t.name),
|
|
2311
|
+
}), contentSafety.execIsExternalContent);
|
|
2312
|
+
contentOriginWrapRef.current = () => {
|
|
2313
|
+
for (let i = 0; i < tools.length; i++) {
|
|
2314
|
+
const t = tools[i];
|
|
2315
|
+
if (contentOriginWrapped.has(t))
|
|
2316
|
+
continue;
|
|
2317
|
+
const origin = classOf(t);
|
|
2318
|
+
const delegation = delegationByName.get(t.name);
|
|
2319
|
+
const isDelegation = delegation !== undefined;
|
|
2320
|
+
const pollutes = contentOriginPollutes(origin, contentSafety.execIsExternalContent);
|
|
2321
|
+
if (!pollutes && !isDelegation) {
|
|
2322
|
+
contentOriginWrapped.add(t);
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
const inner = t.execute.bind(t);
|
|
2326
|
+
const mark = (reason) => {
|
|
2327
|
+
try {
|
|
2328
|
+
pollution.markPolluted(reason);
|
|
2329
|
+
}
|
|
2330
|
+
catch {
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
const wrapped = {
|
|
2334
|
+
...t,
|
|
2335
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
2336
|
+
if (pollutes)
|
|
2337
|
+
mark(`tool "${t.name}" (${origin} content class) was invoked in this session`);
|
|
2338
|
+
if (!isDelegation)
|
|
2339
|
+
return inner(toolCallId, params, signal, onUpdate);
|
|
2340
|
+
const requested = params?.subagent_type;
|
|
2341
|
+
const external = delegationCallIsExternal({
|
|
2342
|
+
requestedType: typeof requested === "string" ? requested : undefined,
|
|
2343
|
+
faces: delegation.faces,
|
|
2344
|
+
pool: delegation.pool,
|
|
2345
|
+
isPolluting: poolToolPollutes,
|
|
2346
|
+
});
|
|
2347
|
+
const result = await inner(toolCallId, params, signal, onUpdate);
|
|
2348
|
+
if (external) {
|
|
2349
|
+
mark(`tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
|
|
2350
|
+
(typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)"));
|
|
2351
|
+
}
|
|
2352
|
+
return result;
|
|
2353
|
+
},
|
|
2354
|
+
};
|
|
2355
|
+
contentOriginWrapped.add(wrapped);
|
|
2356
|
+
tools[i] = wrapped;
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
contentOriginWrapRef.current();
|
|
2360
|
+
}
|
|
2244
2361
|
let centerAdoption;
|
|
2245
2362
|
let centerAdoptionFresh = false;
|
|
2246
2363
|
{
|
|
@@ -2485,6 +2602,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2485
2602
|
const withdrawnTools = new Set();
|
|
2486
2603
|
const deltaRef = toolsDeltaRef;
|
|
2487
2604
|
const rematerialize = async (active) => {
|
|
2605
|
+
contentOriginWrapRef.current?.();
|
|
2488
2606
|
const list = buildToolList(active);
|
|
2489
2607
|
await harnessRef.current.setTools(list, list.map((t) => t.name));
|
|
2490
2608
|
if (fpRef.current)
|
|
@@ -2528,6 +2646,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2528
2646
|
}
|
|
2529
2647
|
else {
|
|
2530
2648
|
rebuildHarnessToolsRef.current = async () => {
|
|
2649
|
+
contentOriginWrapRef.current?.();
|
|
2531
2650
|
const list = [...tools];
|
|
2532
2651
|
await harnessRef.current.setTools(list, list.map((t) => t.name));
|
|
2533
2652
|
if (fpRef.current)
|
|
@@ -2864,6 +2983,47 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2864
2983
|
return {};
|
|
2865
2984
|
return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
|
|
2866
2985
|
};
|
|
2986
|
+
const permissionRuleLane = (() => {
|
|
2987
|
+
const provider = deps.permissionRuleStore;
|
|
2988
|
+
if (provider === undefined)
|
|
2989
|
+
return undefined;
|
|
2990
|
+
const root = taskRootPath;
|
|
2991
|
+
return {
|
|
2992
|
+
admits: async (req) => {
|
|
2993
|
+
if (spec.principal === undefined || spec.principal === "")
|
|
2994
|
+
return undefined;
|
|
2995
|
+
if (req.toolName !== PERSISTED_RULE_TOOL)
|
|
2996
|
+
return undefined;
|
|
2997
|
+
const command = req.args?.command;
|
|
2998
|
+
if (typeof command !== "string")
|
|
2999
|
+
return undefined;
|
|
3000
|
+
let listed;
|
|
3001
|
+
try {
|
|
3002
|
+
listed = await provider.forPrincipal(spec.principal).list();
|
|
3003
|
+
}
|
|
3004
|
+
catch (err) {
|
|
3005
|
+
emitTrace(deps.tracer, () => ({
|
|
3006
|
+
kind: "permission.rule_store_unreadable",
|
|
3007
|
+
version: 1,
|
|
3008
|
+
taskId: spec.taskId ?? sessionId,
|
|
3009
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3010
|
+
ts: Date.now(),
|
|
3011
|
+
}));
|
|
3012
|
+
return undefined;
|
|
3013
|
+
}
|
|
3014
|
+
return findAdmittingRule(listed.rules, { tool: req.toolName, command, cwd: root })?.rule;
|
|
3015
|
+
},
|
|
3016
|
+
};
|
|
3017
|
+
})();
|
|
3018
|
+
const ruleSuggestionsOf = (toolName, args) => {
|
|
3019
|
+
if (permissionRuleLane === undefined || toolName !== PERSISTED_RULE_TOOL)
|
|
3020
|
+
return {};
|
|
3021
|
+
const command = args?.command;
|
|
3022
|
+
if (typeof command !== "string")
|
|
3023
|
+
return {};
|
|
3024
|
+
const suggestions = suggestRulesForCommand(command);
|
|
3025
|
+
return suggestions.length > 0 ? { ruleSuggestions: suggestions } : {};
|
|
3026
|
+
};
|
|
2867
3027
|
const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal) => {
|
|
2868
3028
|
let editArgs = edit;
|
|
2869
3029
|
for (let round = 0;; round++) {
|
|
@@ -2894,6 +3054,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2894
3054
|
toolName: creq.toolName,
|
|
2895
3055
|
toolCallId: creq.toolCallId,
|
|
2896
3056
|
args: editArgs,
|
|
3057
|
+
...ruleSuggestionsOf(creq.toolName, editArgs),
|
|
2897
3058
|
message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2898
3059
|
...askSourceIdentity(),
|
|
2899
3060
|
...riskAxesOf(creq.toolName),
|
|
@@ -2942,6 +3103,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2942
3103
|
toolName: creq.toolName,
|
|
2943
3104
|
toolCallId: creq.toolCallId,
|
|
2944
3105
|
args: presentedArgs,
|
|
3106
|
+
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
2945
3107
|
message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2946
3108
|
...askSourceIdentity(),
|
|
2947
3109
|
...riskAxesOf(creq.toolName),
|
|
@@ -3000,6 +3162,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3000
3162
|
toolName: creq.toolName,
|
|
3001
3163
|
toolCallId: creq.toolCallId,
|
|
3002
3164
|
args: presentedArgs,
|
|
3165
|
+
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
3003
3166
|
message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
3004
3167
|
...askSourceIdentity(),
|
|
3005
3168
|
...riskAxesOf(creq.toolName),
|
|
@@ -3141,6 +3304,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3141
3304
|
}
|
|
3142
3305
|
const preToolContexts = new Map();
|
|
3143
3306
|
const blockedToolCalls = new Set();
|
|
3307
|
+
const approvalSettledBy = new Map();
|
|
3144
3308
|
const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
|
|
3145
3309
|
const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
|
|
3146
3310
|
const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
|
|
@@ -3215,6 +3379,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3215
3379
|
...(checkpointStore !== undefined ? { checkpointDurability: resolveDeclaredDurability(checkpointStore, "checkpointStore") } : {}),
|
|
3216
3380
|
sessionDurability: resolveDeclaredDurability(sessions, "sessionStore"),
|
|
3217
3381
|
backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
|
|
3382
|
+
permissionRuleStoreWired: deps.permissionRuleStore !== undefined,
|
|
3218
3383
|
hostChildEventSinkWired: deps.onBackgroundChildEvent !== undefined,
|
|
3219
3384
|
lockedConfigWired: deps.lockedConfig !== undefined,
|
|
3220
3385
|
complianceWired: deps.compliancePostureResolver !== undefined,
|
|
@@ -3303,6 +3468,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3303
3468
|
const preview = approvalPreviewOf(req.toolName, req.args);
|
|
3304
3469
|
return preview !== undefined ? { preview } : {};
|
|
3305
3470
|
})(),
|
|
3471
|
+
...ruleSuggestionsOf(req.toolName, req.args),
|
|
3306
3472
|
message: decision.message ?? `approval required for "${req.toolName}"`,
|
|
3307
3473
|
...askSourceIdentity(),
|
|
3308
3474
|
...riskAxesOf(req.toolName),
|
|
@@ -3854,6 +4020,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3854
4020
|
const preview = approvalPreviewOf(req.toolName, parkedArgs);
|
|
3855
4021
|
return preview !== undefined ? { preview } : {};
|
|
3856
4022
|
})(),
|
|
4023
|
+
...ruleSuggestionsOf(req.toolName, parkedArgs),
|
|
3857
4024
|
boundInputHash: boundInputHashOf(parkedArgs),
|
|
3858
4025
|
batchToolCallIds,
|
|
3859
4026
|
completedCallIds,
|
|
@@ -3961,6 +4128,22 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3961
4128
|
onHookError: notifyHookError,
|
|
3962
4129
|
shellGated: (e.toolName === "Bash" && shellGatedBash) || (e.toolName === "Monitor" && shellGatedMonitor),
|
|
3963
4130
|
...(autoModeDecider ? { autoMode: { decider: autoModeDecider } } : {}),
|
|
4131
|
+
...(permissionRuleLane
|
|
4132
|
+
? {
|
|
4133
|
+
persistedRules: {
|
|
4134
|
+
admits: permissionRuleLane.admits,
|
|
4135
|
+
onResolved: (info) => emitTrace(deps.tracer, () => ({
|
|
4136
|
+
kind: "permission.persisted_rule_allowed",
|
|
4137
|
+
version: 1,
|
|
4138
|
+
taskId: spec.taskId ?? sessionId,
|
|
4139
|
+
toolName: info.toolName,
|
|
4140
|
+
toolCallId: info.toolCallId,
|
|
4141
|
+
rule: info.rule,
|
|
4142
|
+
ts: Date.now(),
|
|
4143
|
+
})),
|
|
4144
|
+
},
|
|
4145
|
+
}
|
|
4146
|
+
: {}),
|
|
3964
4147
|
isMarkedUnresolvable: (toolCallId) => inheritedUnavailableAsks.has(toolCallId),
|
|
3965
4148
|
});
|
|
3966
4149
|
}
|
|
@@ -3974,6 +4157,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3974
4157
|
if (blockedTracked && (result.block || result.suspend)) {
|
|
3975
4158
|
blockedToolCalls.add(e.toolCallId);
|
|
3976
4159
|
}
|
|
4160
|
+
const reported = result.settledBy;
|
|
4161
|
+
if (reported !== undefined) {
|
|
4162
|
+
const settling = result.block === true;
|
|
4163
|
+
if (!isApprovalSettledBy(reported) || (!settling && reported !== "human")) {
|
|
4164
|
+
deps.onError?.(new Error(`a tool-gate settlement reported settledBy "${String(reported)}" on ${settling ? "a blocked" : "an executing"} call — ` +
|
|
4165
|
+
`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 });
|
|
4166
|
+
}
|
|
4167
|
+
else {
|
|
4168
|
+
approvalSettledBy.set(e.toolCallId, reported);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
3977
4171
|
return result.block
|
|
3978
4172
|
? { block: true, reason: result.reason }
|
|
3979
4173
|
: result.updatedInput !== undefined
|
|
@@ -4262,7 +4456,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4262
4456
|
: undefined;
|
|
4263
4457
|
overheadState.promptChars = systemPrompt.length;
|
|
4264
4458
|
const preparedHolder = {};
|
|
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 } : {}) });
|
|
4459
|
+
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 } : {}) });
|
|
4266
4460
|
const prepared = buildPrepared();
|
|
4267
4461
|
preparedHolder.current = prepared;
|
|
4268
4462
|
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) {
|
|
@@ -328,6 +329,24 @@ function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
|
328
329
|
e.retryAfterMs = retryAfterMs;
|
|
329
330
|
return e;
|
|
330
331
|
}
|
|
332
|
+
const disclosedUnevaluableWindow = new WeakSet();
|
|
333
|
+
function discloseUnevaluableWindow(u, prepared, tracer, taskId, onError) {
|
|
334
|
+
if (disclosedUnevaluableWindow.has(prepared))
|
|
335
|
+
return;
|
|
336
|
+
disclosedUnevaluableWindow.add(prepared);
|
|
337
|
+
emitTrace(tracer, () => ({ kind: "compaction.unevaluable", version: 1, taskId, estTokens: u.estTokens, ts: Date.now() }));
|
|
338
|
+
const modelClause = u.modelId !== undefined ? ` "${u.modelId}"` : "";
|
|
339
|
+
const cause = u.windowValue !== undefined
|
|
340
|
+
? `its \`${u.windowField}\` is ${u.windowValue}, which is not a positive number of tokens — that field takes precedence over the ` +
|
|
341
|
+
`others, so fixing or removing it is what restores the threshold`
|
|
342
|
+
: `it declares no context window at all (\`autoCompactTokens\` / \`contextTokens\` / \`contextWindow\` are all absent) — declare one to restore the threshold`;
|
|
343
|
+
try {
|
|
344
|
+
onError?.(new Error(`auto-compaction cannot run for model${modelClause}: ${cause}. Until then there is no threshold to compare against, so the ` +
|
|
345
|
+
`context will grow until the provider refuses the request. The conversation is already about ${u.estTokens} estimated tokens.`), { phase: "config", sessionId: prepared.sessionId });
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
331
350
|
function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
332
351
|
const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
|
|
333
352
|
let finalVerifySeenAtLastBoundary = 0;
|
|
@@ -853,6 +872,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
853
872
|
if (comp.contextUsage) {
|
|
854
873
|
queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
|
|
855
874
|
}
|
|
875
|
+
if (comp.unevaluableWindow) {
|
|
876
|
+
discloseUnevaluableWindow(comp.unevaluableWindow, prepared, rs.telemetry.tracer, rs.telemetry.taskId, runnerHooks.onError);
|
|
877
|
+
}
|
|
856
878
|
if (comp.suppressedByFloor) {
|
|
857
879
|
const s = comp.suppressedByFloor;
|
|
858
880
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
@@ -1181,13 +1203,16 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1181
1203
|
prepared.lspDiagnostics.nudge(p);
|
|
1182
1204
|
}
|
|
1183
1205
|
}
|
|
1206
|
+
const settledBy = prepared.approvalSettledBy.get(event.toolCallId);
|
|
1207
|
+
if (settledBy !== undefined)
|
|
1208
|
+
prepared.approvalSettledBy.delete(event.toolCallId);
|
|
1184
1209
|
pushContent({
|
|
1185
1210
|
type: "tool_end",
|
|
1186
1211
|
toolCallId: event.toolCallId,
|
|
1187
1212
|
toolName: event.toolName,
|
|
1188
1213
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1189
1214
|
isError: event.isError,
|
|
1190
|
-
...toolEndBodyFrom(event.result, event.isError),
|
|
1215
|
+
...toolEndBodyFrom(event.result, event.isError, settledBy),
|
|
1191
1216
|
...ident(),
|
|
1192
1217
|
});
|
|
1193
1218
|
announceWorkspaceMove();
|
|
@@ -3596,6 +3621,14 @@ export class Runner {
|
|
|
3596
3621
|
if (decision !== "allow" && decision !== "deny") {
|
|
3597
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`);
|
|
3598
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
|
+
}
|
|
3599
3632
|
const reason = decide.reason;
|
|
3600
3633
|
assertOutcomeText(reason, "reason");
|
|
3601
3634
|
plainPolicyOutcome = {
|
|
@@ -3606,6 +3639,7 @@ export class Runner {
|
|
|
3606
3639
|
...(updatedInput !== undefined ? { updatedInput } : {}),
|
|
3607
3640
|
...(reason !== undefined ? { reason } : {}),
|
|
3608
3641
|
...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
|
|
3642
|
+
...(settledBy !== undefined ? { settledBy } : {}),
|
|
3609
3643
|
};
|
|
3610
3644
|
if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
|
|
3611
3645
|
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
|
|
@@ -3802,9 +3836,14 @@ export class Runner {
|
|
|
3802
3836
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
|
|
3803
3837
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
3804
3838
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
3805
|
-
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) });
|
|
3806
3840
|
if (outcome.decision === "deny") {
|
|
3807
|
-
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;
|
|
3808
3847
|
emitEnd(true, { content: formatHookFeedback(reason) });
|
|
3809
3848
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
|
|
3810
3849
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
@@ -3969,6 +4008,9 @@ export class Runner {
|
|
|
3969
4008
|
...this.compactionHookOptions(spec, prepared.sessionId, "auto"),
|
|
3970
4009
|
});
|
|
3971
4010
|
this.recordCompactionReuse(prepared, finishComp);
|
|
4011
|
+
if (finishComp.unevaluableWindow) {
|
|
4012
|
+
discloseUnevaluableWindow(finishComp.unevaluableWindow, prepared, spec.tracer ?? this.deps.tracer, spec.taskId ?? prepared.sessionId, this.deps.onError);
|
|
4013
|
+
}
|
|
3972
4014
|
comp = finishComp;
|
|
3973
4015
|
}
|
|
3974
4016
|
catch (err) {
|
|
@@ -62,7 +62,7 @@ const CC_DETAIL_TYPES = new Set([
|
|
|
62
62
|
"agent", "task", "task-list", "task-output", "workflow-run",
|
|
63
63
|
"web-fetch", "web-search", "todo", "cron-create", "cron-delete", "cron-list", "image",
|
|
64
64
|
"task-stop", "tool-search", "repo-map", "fork", "enter-plan-mode", "exit-plan-mode",
|
|
65
|
-
"monitor-start", "path_not_in_root", "readonly_out_of_root",
|
|
65
|
+
"monitor-start", "path_not_in_root", "readonly_out_of_root", "bash_invalid_timeout",
|
|
66
66
|
"report-findings", "schedule-wakeup", "send-message", "agent-transcript", "a2a", "document",
|
|
67
67
|
]);
|
|
68
68
|
export const structuredFrom = (result) => {
|
|
@@ -8,12 +8,16 @@ export interface ToolCallRequest {
|
|
|
8
8
|
suspendCount: number;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export type DecisionReason = "rule" | "mode" | "hook" | "safety" | "classifier";
|
|
11
|
+
export type DecisionReason = "rule" | "mode" | "hook" | "safety" | "classifier" | "persisted_rule";
|
|
12
|
+
export type ApprovalSettledBy = "human" | "timeout" | "aborted";
|
|
13
|
+
export declare const APPROVAL_SETTLED_BY_VALUES: readonly ApprovalSettledBy[];
|
|
14
|
+
export declare function isApprovalSettledBy(v: unknown): v is ApprovalSettledBy;
|
|
12
15
|
export type PermissionResult = {
|
|
13
16
|
action: "allow";
|
|
14
17
|
updatedInput?: unknown;
|
|
15
18
|
message?: string;
|
|
16
19
|
decisionReason?: DecisionReason;
|
|
20
|
+
settledBy?: Extract<ApprovalSettledBy, "human">;
|
|
17
21
|
} | {
|
|
18
22
|
action: "ask";
|
|
19
23
|
updatedInput?: unknown;
|
|
@@ -25,6 +29,7 @@ export type PermissionResult = {
|
|
|
25
29
|
updatedInput?: unknown;
|
|
26
30
|
message?: string;
|
|
27
31
|
decisionReason?: DecisionReason;
|
|
32
|
+
settledBy?: ApprovalSettledBy;
|
|
28
33
|
};
|
|
29
34
|
export declare function decisionText(d: PermissionResult): string | undefined;
|
|
30
35
|
export interface ToolPolicy {
|
|
@@ -43,6 +48,12 @@ export declare function toolPolicyNameSets(p: ToolPolicy | undefined): readonly
|
|
|
43
48
|
export declare function createAllowDenyPolicy(opts: {
|
|
44
49
|
allow?: string[];
|
|
45
50
|
deny?: string[];
|
|
51
|
+
onInvalidName?: "throw" | "skip";
|
|
52
|
+
onInvalidNameIssue?: (issue: {
|
|
53
|
+
entry: string;
|
|
54
|
+
list: "allow" | "deny";
|
|
55
|
+
message: string;
|
|
56
|
+
}) => void;
|
|
46
57
|
}): NamedToolPolicy;
|
|
47
58
|
export declare function createApprovalPolicy(opts: {
|
|
48
59
|
requireApproval: string[];
|
|
@@ -81,6 +92,7 @@ export interface AskRequest {
|
|
|
81
92
|
args: unknown;
|
|
82
93
|
readonly preview?: unknown;
|
|
83
94
|
readonly boundInputHash?: string;
|
|
95
|
+
readonly ruleSuggestions?: readonly import("./permission-rule-model.js").RuleSuggestion[];
|
|
84
96
|
message: string;
|
|
85
97
|
readonly principal?: string;
|
|
86
98
|
readonly sourceTaskId?: string;
|