@sema-agent/core 5.17.0-pre.0 → 5.18.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 +186 -1
- package/dist/agents/subagent.d.ts +8 -0
- package/dist/agents/subagent.js +93 -2
- package/dist/core/ask-question.js +6 -1
- package/dist/core/auto-compaction.d.ts +6 -0
- package/dist/core/auto-compaction.js +15 -1
- package/dist/core/canonical-json.js +176 -14
- package/dist/core/checkpoint-store.d.ts +15 -0
- package/dist/core/checkpoint-store.js +73 -0
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +12 -1
- package/dist/core/hooks.js +41 -6
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +18 -3
- 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 +131 -0
- package/dist/core/permission-rule-consent.js +307 -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 +270 -32
- package/dist/core/runner/runtask.js +27 -1
- package/dist/core/runner/tool-output-projection.js +1 -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 +17 -1
- package/dist/core/tool-policy.js +64 -8
- 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 +4 -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 +7 -2
- package/dist/index.js +6 -1
- 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/stores/file/permission-rule-store.d.ts +32 -0
- package/dist/stores/file/permission-rule-store.js +213 -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 +17 -10
- 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 +15 -0
- package/dist/tools/fs/fs-shared.js +73 -2
- 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/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, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
23
|
+
import { askApproverIdentity, combinePolicies, 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";
|
|
@@ -77,7 +82,7 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
|
|
|
77
82
|
import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
|
|
78
83
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
79
84
|
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";
|
|
85
|
+
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
86
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
82
87
|
import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
83
88
|
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
@@ -168,6 +173,9 @@ export function resolveEnvLifetimeExpiry(env, observedAt) {
|
|
|
168
173
|
return { unanchored: true };
|
|
169
174
|
return { expiresAt: anchor + lifetimeMs };
|
|
170
175
|
}
|
|
176
|
+
function isLiveApproverSeat(onAsk) {
|
|
177
|
+
return typeof onAsk === "function";
|
|
178
|
+
}
|
|
171
179
|
const DEFAULT_UNATTENDED_APPROVAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
172
180
|
function sanitizedTtlMs(ttlMs) {
|
|
173
181
|
if (ttlMs === undefined)
|
|
@@ -182,6 +190,12 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
|
182
190
|
export function checkpointScopeOf(spec) {
|
|
183
191
|
return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
|
|
184
192
|
}
|
|
193
|
+
class ParkRefusal extends Error {
|
|
194
|
+
constructor(message, options) {
|
|
195
|
+
super(message, options);
|
|
196
|
+
this.name = "ParkRefusal";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
185
199
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
186
200
|
export function isFableFamilyModelId(id) {
|
|
187
201
|
const tail = id.toLowerCase().split("/").pop() ?? "";
|
|
@@ -1050,7 +1064,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1050
1064
|
...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
|
|
1051
1065
|
];
|
|
1052
1066
|
const ownCallerPolicy = lockedPreflight.toolPolicy;
|
|
1053
|
-
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1067
|
+
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1068
|
+
(spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
|
|
1054
1069
|
const parentConstraints = [
|
|
1055
1070
|
...(inheritedParentConstraints ?? []),
|
|
1056
1071
|
...(ownCallerPolicy !== undefined
|
|
@@ -1379,6 +1394,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1379
1394
|
}
|
|
1380
1395
|
tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
|
|
1381
1396
|
const rebuildHarnessToolsRef = {};
|
|
1397
|
+
const contentOriginWrapRef = {};
|
|
1382
1398
|
const toolCallGateArmedRef = { armed: false };
|
|
1383
1399
|
if (lockedPreflight.mcp?.length) {
|
|
1384
1400
|
tools.push({
|
|
@@ -1595,6 +1611,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1595
1611
|
...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
|
|
1596
1612
|
...(deps.hands?.readImageDownsampler !== undefined ? { readImageDownsampler: deps.hands.readImageDownsampler } : {}),
|
|
1597
1613
|
...(deps.hands?.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: deps.hands.autoBackgroundOnTimeout } : {}),
|
|
1614
|
+
...(deps.hands?.readCyberReminder !== undefined ? { readCyberReminder: deps.hands.readCyberReminder } : {}),
|
|
1598
1615
|
beforeWrite: async (w) => {
|
|
1599
1616
|
const deploymentGate = deps.hands?.beforeWrite;
|
|
1600
1617
|
if (deploymentGate) {
|
|
@@ -1857,13 +1874,23 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1857
1874
|
.catch(() => undefined);
|
|
1858
1875
|
}
|
|
1859
1876
|
: undefined;
|
|
1860
|
-
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({
|
|
1861
1887
|
spec,
|
|
1862
1888
|
deps,
|
|
1863
1889
|
sessionId,
|
|
1864
1890
|
taskRootPath,
|
|
1865
1891
|
memoryWriteGateRef,
|
|
1866
1892
|
writeToolsMounted: tools.some((t) => t.name === "Write") && !(toolFaceSnapshot.exclude?.includes("Write") ?? false),
|
|
1893
|
+
memorySearchToolsPlanned,
|
|
1867
1894
|
admissionCtx: {
|
|
1868
1895
|
orgMemoryDenied: complianceDenies.has("org_memory_mount"),
|
|
1869
1896
|
complianceDegraded,
|
|
@@ -2001,6 +2028,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2001
2028
|
sharedMemoryPairMounted = true;
|
|
2002
2029
|
}
|
|
2003
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
|
+
}
|
|
2004
2043
|
const skillsListing = skillSpecs.length > 0
|
|
2005
2044
|
? {
|
|
2006
2045
|
entries: skillSpecs.map((s) => ({
|
|
@@ -2205,22 +2244,39 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2205
2244
|
}
|
|
2206
2245
|
return deferredSet;
|
|
2207
2246
|
};
|
|
2208
|
-
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];
|
|
2209
2250
|
let deferred;
|
|
2210
|
-
if (
|
|
2211
|
-
const
|
|
2212
|
-
const d1 = classifyDeferredOverFace(tools,
|
|
2213
|
-
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, []);
|
|
2214
2255
|
const soleCause = d0.size === 0 && d1.size > 0;
|
|
2215
2256
|
const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
|
|
2216
2257
|
if (soleCause && supportNameTaken) {
|
|
2217
2258
|
for (let i = tools.length - 1; i >= 0; i--) {
|
|
2218
|
-
if (
|
|
2259
|
+
if (builtinDeferPairNames.some((n) => n === tools[i].name))
|
|
2219
2260
|
tools.splice(i, 1);
|
|
2220
2261
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
`
|
|
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
|
+
}
|
|
2224
2280
|
deferred = d0;
|
|
2225
2281
|
}
|
|
2226
2282
|
else {
|
|
@@ -2230,6 +2286,78 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2230
2286
|
else {
|
|
2231
2287
|
deferred = classifyDeferredOverFace(tools, []);
|
|
2232
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
|
+
}
|
|
2233
2361
|
let centerAdoption;
|
|
2234
2362
|
let centerAdoptionFresh = false;
|
|
2235
2363
|
{
|
|
@@ -2358,6 +2486,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2358
2486
|
.map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
|
|
2359
2487
|
let toolsDeltaRef;
|
|
2360
2488
|
let toolMaterializeStatic = false;
|
|
2489
|
+
let deferDirectCall = false;
|
|
2361
2490
|
const staticFaceForRef = {};
|
|
2362
2491
|
if (deferred.size > 0 || failedMcpServers.length > 0) {
|
|
2363
2492
|
toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
|
|
@@ -2380,6 +2509,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2380
2509
|
const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
|
|
2381
2510
|
const materializeStatic = requestedStrategy === "static" && !laneDegrade;
|
|
2382
2511
|
toolMaterializeStatic = materializeStatic;
|
|
2512
|
+
deferDirectCall = spec.deferSelfResolve !== false;
|
|
2383
2513
|
promptManifest.toolDisclosure = {
|
|
2384
2514
|
deferredTools: deferred.size,
|
|
2385
2515
|
strategy: materializeStatic ? "static" : "swap",
|
|
@@ -2472,6 +2602,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2472
2602
|
const withdrawnTools = new Set();
|
|
2473
2603
|
const deltaRef = toolsDeltaRef;
|
|
2474
2604
|
const rematerialize = async (active) => {
|
|
2605
|
+
contentOriginWrapRef.current?.();
|
|
2475
2606
|
const list = buildToolList(active);
|
|
2476
2607
|
await harnessRef.current.setTools(list, list.map((t) => t.name));
|
|
2477
2608
|
if (fpRef.current)
|
|
@@ -2515,6 +2646,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2515
2646
|
}
|
|
2516
2647
|
else {
|
|
2517
2648
|
rebuildHarnessToolsRef.current = async () => {
|
|
2649
|
+
contentOriginWrapRef.current?.();
|
|
2518
2650
|
const list = [...tools];
|
|
2519
2651
|
await harnessRef.current.setTools(list, list.map((t) => t.name));
|
|
2520
2652
|
if (fpRef.current)
|
|
@@ -2851,6 +2983,47 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2851
2983
|
return {};
|
|
2852
2984
|
return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
|
|
2853
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
|
+
};
|
|
2854
3027
|
const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal) => {
|
|
2855
3028
|
let editArgs = edit;
|
|
2856
3029
|
for (let round = 0;; round++) {
|
|
@@ -2881,6 +3054,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2881
3054
|
toolName: creq.toolName,
|
|
2882
3055
|
toolCallId: creq.toolCallId,
|
|
2883
3056
|
args: editArgs,
|
|
3057
|
+
...ruleSuggestionsOf(creq.toolName, editArgs),
|
|
2884
3058
|
message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2885
3059
|
...askSourceIdentity(),
|
|
2886
3060
|
...riskAxesOf(creq.toolName),
|
|
@@ -2929,6 +3103,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2929
3103
|
toolName: creq.toolName,
|
|
2930
3104
|
toolCallId: creq.toolCallId,
|
|
2931
3105
|
args: presentedArgs,
|
|
3106
|
+
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
2932
3107
|
message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2933
3108
|
...askSourceIdentity(),
|
|
2934
3109
|
...riskAxesOf(creq.toolName),
|
|
@@ -2987,6 +3162,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2987
3162
|
toolName: creq.toolName,
|
|
2988
3163
|
toolCallId: creq.toolCallId,
|
|
2989
3164
|
args: presentedArgs,
|
|
3165
|
+
...ruleSuggestionsOf(creq.toolName, presentedArgs),
|
|
2990
3166
|
message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2991
3167
|
...askSourceIdentity(),
|
|
2992
3168
|
...riskAxesOf(creq.toolName),
|
|
@@ -3202,6 +3378,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3202
3378
|
...(checkpointStore !== undefined ? { checkpointDurability: resolveDeclaredDurability(checkpointStore, "checkpointStore") } : {}),
|
|
3203
3379
|
sessionDurability: resolveDeclaredDurability(sessions, "sessionStore"),
|
|
3204
3380
|
backgroundAgentStoreWired: deps.backgroundAgentStore !== undefined,
|
|
3381
|
+
permissionRuleStoreWired: deps.permissionRuleStore !== undefined,
|
|
3205
3382
|
hostChildEventSinkWired: deps.onBackgroundChildEvent !== undefined,
|
|
3206
3383
|
lockedConfigWired: deps.lockedConfig !== undefined,
|
|
3207
3384
|
complianceWired: deps.compliancePostureResolver !== undefined,
|
|
@@ -3290,6 +3467,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3290
3467
|
const preview = approvalPreviewOf(req.toolName, req.args);
|
|
3291
3468
|
return preview !== undefined ? { preview } : {};
|
|
3292
3469
|
})(),
|
|
3470
|
+
...ruleSuggestionsOf(req.toolName, req.args),
|
|
3293
3471
|
message: decision.message ?? `approval required for "${req.toolName}"`,
|
|
3294
3472
|
...askSourceIdentity(),
|
|
3295
3473
|
...riskAxesOf(req.toolName),
|
|
@@ -3403,15 +3581,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3403
3581
|
});
|
|
3404
3582
|
const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
|
|
3405
3583
|
if (!checkpointStore)
|
|
3406
|
-
return false;
|
|
3584
|
+
return { ok: false };
|
|
3407
3585
|
if (memoryEngineSession)
|
|
3408
3586
|
await memoryEngineSession.harvest("checkpoint");
|
|
3409
3587
|
try {
|
|
3410
3588
|
await checkpointStore.put(token, cp);
|
|
3411
|
-
return true;
|
|
3589
|
+
return { ok: true };
|
|
3412
3590
|
}
|
|
3413
3591
|
catch (putErr) {
|
|
3414
3592
|
deps.onError?.(putErr, { phase: "config", sessionId });
|
|
3593
|
+
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
3594
|
if (remoteEnv !== undefined && remoteHandle?.snapshotId !== undefined) {
|
|
3416
3595
|
const { outcome: back, attempts: backAttempts } = await restoreWorkspaceWithRetry(remoteEnv, remoteHandle.snapshotId, {
|
|
3417
3596
|
abortSignal: abortController.signal,
|
|
@@ -3419,7 +3598,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3419
3598
|
if (back.ok) {
|
|
3420
3599
|
const init = await remoteEnv.postResumeInit();
|
|
3421
3600
|
if (init.ok)
|
|
3422
|
-
return false;
|
|
3601
|
+
return { ok: false, reason };
|
|
3423
3602
|
remoteEnvFailures.push(remoteEnvFailureNote("postResumeInit", init.error, 1));
|
|
3424
3603
|
deps.onError?.(init.error, { phase: "config", sessionId });
|
|
3425
3604
|
}
|
|
@@ -3429,9 +3608,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3429
3608
|
}
|
|
3430
3609
|
abortController.abort();
|
|
3431
3610
|
void harness.abort();
|
|
3432
|
-
return false;
|
|
3611
|
+
return { ok: false, reason };
|
|
3433
3612
|
}
|
|
3434
|
-
return false;
|
|
3613
|
+
return { ok: false, reason };
|
|
3435
3614
|
}
|
|
3436
3615
|
};
|
|
3437
3616
|
const publishCommittedSuspend = (token, gate, scope, remoteHandle) => {
|
|
@@ -3519,7 +3698,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3519
3698
|
resourceLedger: resourceLedgerOut,
|
|
3520
3699
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3521
3700
|
};
|
|
3522
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3701
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3523
3702
|
return false;
|
|
3524
3703
|
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3525
3704
|
try {
|
|
@@ -3604,7 +3783,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3604
3783
|
...(spec.principal ? { principal: spec.principal } : {}),
|
|
3605
3784
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3606
3785
|
};
|
|
3607
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3786
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3608
3787
|
return false;
|
|
3609
3788
|
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3610
3789
|
try {
|
|
@@ -3700,7 +3879,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3700
3879
|
};
|
|
3701
3880
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
3702
3881
|
? async (req, postHookArgs, safety, liveFaceUnavailable) => {
|
|
3703
|
-
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) :
|
|
3882
|
+
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
3704
3883
|
if (syncFirstEligible &&
|
|
3705
3884
|
runtimeCaps?.forceDurableGate !== true &&
|
|
3706
3885
|
liveFaceUnavailable !== true &&
|
|
@@ -3713,13 +3892,50 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3713
3892
|
let remoteHandle;
|
|
3714
3893
|
const remoteEnv = suspendableEnv;
|
|
3715
3894
|
try {
|
|
3895
|
+
const parked = tryCloneArgs(postHookArgs);
|
|
3896
|
+
if (!parked.ok) {
|
|
3897
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be captured onto an approval checkpoint (unclonable value: ${parked.reason}) — ` +
|
|
3898
|
+
`a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: parked.cause });
|
|
3899
|
+
}
|
|
3900
|
+
let backendFidelity;
|
|
3901
|
+
try {
|
|
3902
|
+
backendFidelity = resolveDeclaredFidelity(checkpointStore, "checkpointStore");
|
|
3903
|
+
}
|
|
3904
|
+
catch (err) {
|
|
3905
|
+
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)}) — ` +
|
|
3906
|
+
`refusing to park rather than guess which values would survive it`, { cause: err });
|
|
3907
|
+
}
|
|
3908
|
+
const captured = encodeAtFidelity(backendFidelity, parked.value);
|
|
3909
|
+
if (!captured.ok) {
|
|
3910
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be encoded by this checkpoint backend (${describeThrown(captured.cause)}) — ` +
|
|
3911
|
+
`a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: captured.cause });
|
|
3912
|
+
}
|
|
3913
|
+
const parkedArgs = captured.value;
|
|
3914
|
+
const projectionMovedArgs = backendFidelity !== "structured-clone" && !samePlainValue(parked.value, parkedArgs);
|
|
3915
|
+
if (projectionMovedArgs && basePolicyForResumeEdit !== undefined) {
|
|
3916
|
+
const presented = encodeAtFidelity(backendFidelity, parkedArgs);
|
|
3917
|
+
if (!presented.ok) {
|
|
3918
|
+
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
|
+
}
|
|
3920
|
+
const reprojected = refuseOutOfContractDecision(await basePolicyForResumeEdit.check({ toolName: req.toolName, args: presented.value, toolCallId: req.toolCallId }, abortController.signal));
|
|
3921
|
+
const demanded = reprojected.updatedInput !== undefined ? tryCloneArgs(reprojected.updatedInput) : undefined;
|
|
3922
|
+
const rewroteTheFiledValue = reprojected.updatedInput !== undefined &&
|
|
3923
|
+
!(demanded?.ok === true &&
|
|
3924
|
+
samePlainValue(demanded.value, parkedArgs) &&
|
|
3925
|
+
samePlainValue(reprojected.updatedInput, parkedArgs));
|
|
3926
|
+
if (reprojected.action === "deny" || rewroteTheFiledValue) {
|
|
3927
|
+
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` +
|
|
3928
|
+
`${reprojected.action === "deny" && reprojected.message ? `: ${reprojected.message}` : ""} — ` +
|
|
3929
|
+
`refusing to file a row whose executable form nothing adjudicated`);
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3716
3932
|
if (offloadStore && isVolatileOffloadStore(offloadStore) && !offloadStore.isEmpty()) {
|
|
3717
|
-
throw new
|
|
3933
|
+
throw new ParkRefusal("durable suspend requires a durable toolResultStore (the default InMemoryToolResultStore " +
|
|
3718
3934
|
"holds offloaded results that are lost across a cross-replica resume); inject " +
|
|
3719
3935
|
"RunnerDeps.toolResultStore or disable offload.");
|
|
3720
3936
|
}
|
|
3721
3937
|
if (ownedEnv !== undefined && remoteEnv === undefined && parkOnlyRemoteEnv === undefined) {
|
|
3722
|
-
throw new
|
|
3938
|
+
throw new ParkRefusal(incompleteSuspendAdapter !== undefined
|
|
3723
3939
|
? `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
3940
|
: "durable suspend is not supported with a non-remote per-task executionEnvFactory env: the " +
|
|
3725
3941
|
"minted env is destroyed on suspend, so a resumed file/shell tool would act on a fresh " +
|
|
@@ -3728,7 +3944,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3728
3944
|
}
|
|
3729
3945
|
const leafId = await session.getLeafId();
|
|
3730
3946
|
if (!leafId) {
|
|
3731
|
-
throw new
|
|
3947
|
+
throw new ParkRefusal("cannot suspend: session has no committed leaf to resume from");
|
|
3732
3948
|
}
|
|
3733
3949
|
const { messages } = await session.buildContext();
|
|
3734
3950
|
const { batchToolCallIds, completedCallIds } = batchContextAt(messages, req.toolCallId);
|
|
@@ -3755,7 +3971,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3755
3971
|
token = mintCheckpointToken();
|
|
3756
3972
|
const riskDescriptor = buildRiskDescriptor({
|
|
3757
3973
|
toolName: req.toolName,
|
|
3758
|
-
args:
|
|
3974
|
+
args: parkedArgs,
|
|
3759
3975
|
safety,
|
|
3760
3976
|
shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
|
|
3761
3977
|
...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
|
|
@@ -3798,12 +4014,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3798
4014
|
kind: "tool_approval",
|
|
3799
4015
|
toolCallId: req.toolCallId,
|
|
3800
4016
|
toolName: req.toolName,
|
|
3801
|
-
args:
|
|
4017
|
+
args: parkedArgs,
|
|
3802
4018
|
...(() => {
|
|
3803
|
-
const preview = approvalPreviewOf(req.toolName,
|
|
4019
|
+
const preview = approvalPreviewOf(req.toolName, parkedArgs);
|
|
3804
4020
|
return preview !== undefined ? { preview } : {};
|
|
3805
4021
|
})(),
|
|
3806
|
-
|
|
4022
|
+
...ruleSuggestionsOf(req.toolName, parkedArgs),
|
|
4023
|
+
boundInputHash: boundInputHashOf(parkedArgs),
|
|
3807
4024
|
batchToolCallIds,
|
|
3808
4025
|
completedCallIds,
|
|
3809
4026
|
},
|
|
@@ -3828,10 +4045,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3828
4045
|
}
|
|
3829
4046
|
catch (err) {
|
|
3830
4047
|
deps.onError?.(err, { phase: "config", sessionId });
|
|
3831
|
-
return
|
|
4048
|
+
return {
|
|
4049
|
+
parkFailed: err instanceof ParkRefusal
|
|
4050
|
+
? err.message
|
|
4051
|
+
: "the durable approval park could not be prepared (the deployment's error face carries the exception)",
|
|
4052
|
+
};
|
|
3832
4053
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
4054
|
+
const committed = await commitSuspendSaga(token, cp, remoteEnv, remoteHandle);
|
|
4055
|
+
if (!committed.ok) {
|
|
4056
|
+
return committed.reason !== undefined ? { parkFailed: committed.reason } : undefined;
|
|
3835
4057
|
}
|
|
3836
4058
|
publishCommittedSuspend(token, gate, cp.scope, remoteHandle);
|
|
3837
4059
|
try {
|
|
@@ -3905,6 +4127,22 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3905
4127
|
onHookError: notifyHookError,
|
|
3906
4128
|
shellGated: (e.toolName === "Bash" && shellGatedBash) || (e.toolName === "Monitor" && shellGatedMonitor),
|
|
3907
4129
|
...(autoModeDecider ? { autoMode: { decider: autoModeDecider } } : {}),
|
|
4130
|
+
...(permissionRuleLane
|
|
4131
|
+
? {
|
|
4132
|
+
persistedRules: {
|
|
4133
|
+
admits: permissionRuleLane.admits,
|
|
4134
|
+
onResolved: (info) => emitTrace(deps.tracer, () => ({
|
|
4135
|
+
kind: "permission.persisted_rule_allowed",
|
|
4136
|
+
version: 1,
|
|
4137
|
+
taskId: spec.taskId ?? sessionId,
|
|
4138
|
+
toolName: info.toolName,
|
|
4139
|
+
toolCallId: info.toolCallId,
|
|
4140
|
+
rule: info.rule,
|
|
4141
|
+
ts: Date.now(),
|
|
4142
|
+
})),
|
|
4143
|
+
},
|
|
4144
|
+
}
|
|
4145
|
+
: {}),
|
|
3908
4146
|
isMarkedUnresolvable: (toolCallId) => inheritedUnavailableAsks.has(toolCallId),
|
|
3909
4147
|
});
|
|
3910
4148
|
}
|
|
@@ -4206,7 +4444,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4206
4444
|
: undefined;
|
|
4207
4445
|
overheadState.promptChars = systemPrompt.length;
|
|
4208
4446
|
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 } : {}) });
|
|
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 } : {}) });
|
|
4210
4448
|
const prepared = buildPrepared();
|
|
4211
4449
|
preparedHolder.current = prepared;
|
|
4212
4450
|
return prepared;
|
|
@@ -328,6 +328,24 @@ function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
|
328
328
|
e.retryAfterMs = retryAfterMs;
|
|
329
329
|
return e;
|
|
330
330
|
}
|
|
331
|
+
const disclosedUnevaluableWindow = new WeakSet();
|
|
332
|
+
function discloseUnevaluableWindow(u, prepared, tracer, taskId, onError) {
|
|
333
|
+
if (disclosedUnevaluableWindow.has(prepared))
|
|
334
|
+
return;
|
|
335
|
+
disclosedUnevaluableWindow.add(prepared);
|
|
336
|
+
emitTrace(tracer, () => ({ kind: "compaction.unevaluable", version: 1, taskId, estTokens: u.estTokens, ts: Date.now() }));
|
|
337
|
+
const modelClause = u.modelId !== undefined ? ` "${u.modelId}"` : "";
|
|
338
|
+
const cause = u.windowValue !== undefined
|
|
339
|
+
? `its \`${u.windowField}\` is ${u.windowValue}, which is not a positive number of tokens — that field takes precedence over the ` +
|
|
340
|
+
`others, so fixing or removing it is what restores the threshold`
|
|
341
|
+
: `it declares no context window at all (\`autoCompactTokens\` / \`contextTokens\` / \`contextWindow\` are all absent) — declare one to restore the threshold`;
|
|
342
|
+
try {
|
|
343
|
+
onError?.(new Error(`auto-compaction cannot run for model${modelClause}: ${cause}. Until then there is no threshold to compare against, so the ` +
|
|
344
|
+
`context will grow until the provider refuses the request. The conversation is already about ${u.estTokens} estimated tokens.`), { phase: "config", sessionId: prepared.sessionId });
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
331
349
|
function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
332
350
|
const { spec, queue, manualCompactRef, todoToolMounted, taskToolsMounted, walltimeMonotonicDeadline, timeout, ident, postToolBatchHook, batchArgs, compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact, runnerHooks } = deps;
|
|
333
351
|
let finalVerifySeenAtLastBoundary = 0;
|
|
@@ -577,7 +595,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
577
595
|
skillsListing: rs.attach.skillsListingOn,
|
|
578
596
|
mcpInstructions: mcpInstructionsOn,
|
|
579
597
|
},
|
|
580
|
-
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
598
|
+
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
599
|
+
? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
|
|
600
|
+
: {}),
|
|
581
601
|
...(changed !== undefined ? { changedFiles: changed } : {}),
|
|
582
602
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
583
603
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
@@ -851,6 +871,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
851
871
|
if (comp.contextUsage) {
|
|
852
872
|
queue.push({ type: "context_usage", ...comp.contextUsage, ...ident() });
|
|
853
873
|
}
|
|
874
|
+
if (comp.unevaluableWindow) {
|
|
875
|
+
discloseUnevaluableWindow(comp.unevaluableWindow, prepared, rs.telemetry.tracer, rs.telemetry.taskId, runnerHooks.onError);
|
|
876
|
+
}
|
|
854
877
|
if (comp.suppressedByFloor) {
|
|
855
878
|
const s = comp.suppressedByFloor;
|
|
856
879
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
@@ -3967,6 +3990,9 @@ export class Runner {
|
|
|
3967
3990
|
...this.compactionHookOptions(spec, prepared.sessionId, "auto"),
|
|
3968
3991
|
});
|
|
3969
3992
|
this.recordCompactionReuse(prepared, finishComp);
|
|
3993
|
+
if (finishComp.unevaluableWindow) {
|
|
3994
|
+
discloseUnevaluableWindow(finishComp.unevaluableWindow, prepared, spec.tracer ?? this.deps.tracer, spec.taskId ?? prepared.sessionId, this.deps.onError);
|
|
3995
|
+
}
|
|
3970
3996
|
comp = finishComp;
|
|
3971
3997
|
}
|
|
3972
3998
|
catch (err) {
|