@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/dist/agents/subagent.js +24 -0
  3. package/dist/core/auto-compaction.d.ts +6 -0
  4. package/dist/core/auto-compaction.js +15 -1
  5. package/dist/core/checkpoint-store.d.ts +4 -2
  6. package/dist/core/governance-codes.js +1 -0
  7. package/dist/core/hooks.d.ts +9 -0
  8. package/dist/core/hooks.js +21 -0
  9. package/dist/core/mcp.js +3 -0
  10. package/dist/core/memory-engine/content-origin.d.ts +27 -0
  11. package/dist/core/memory-engine/content-origin.js +38 -0
  12. package/dist/core/memory-engine/engine.d.ts +12 -2
  13. package/dist/core/memory-engine/engine.js +172 -12
  14. package/dist/core/memory-engine/file-backend.d.ts +4 -0
  15. package/dist/core/memory-engine/file-backend.js +25 -3
  16. package/dist/core/memory-engine/index.d.ts +2 -1
  17. package/dist/core/memory-engine/index.js +2 -1
  18. package/dist/core/memory-engine/layout.d.ts +16 -0
  19. package/dist/core/memory-engine/layout.js +90 -2
  20. package/dist/core/memory-engine/sync-client.d.ts +1 -0
  21. package/dist/core/memory-engine/sync-client.js +23 -5
  22. package/dist/core/memory-engine/tools.d.ts +55 -0
  23. package/dist/core/memory-engine/tools.js +307 -0
  24. package/dist/core/memory-engine/types.d.ts +1 -1
  25. package/dist/core/memory.d.ts +4 -0
  26. package/dist/core/memory.js +15 -2
  27. package/dist/core/permission-rule-consent.d.ts +138 -0
  28. package/dist/core/permission-rule-consent.js +318 -0
  29. package/dist/core/permission-rule-model.d.ts +66 -0
  30. package/dist/core/permission-rule-model.js +135 -0
  31. package/dist/core/permission-rule-store.d.ts +89 -0
  32. package/dist/core/permission-rule-store.js +145 -0
  33. package/dist/core/permission-rules.d.ts +3 -2
  34. package/dist/core/permission-rules.js +9 -4
  35. package/dist/core/runner/prepare-memory.d.ts +3 -1
  36. package/dist/core/runner/prepare-memory.js +54 -14
  37. package/dist/core/runner/prepare-task.d.ts +12 -0
  38. package/dist/core/runner/prepare-task.js +206 -12
  39. package/dist/core/runner/runtask.d.ts +3 -1
  40. package/dist/core/runner/runtask.js +47 -5
  41. package/dist/core/runner/tool-output-projection.js +1 -1
  42. package/dist/core/tool-policy.d.ts +13 -1
  43. package/dist/core/tool-policy.js +93 -12
  44. package/dist/core/tools.js +1 -0
  45. package/dist/core/trace.d.ts +20 -0
  46. package/dist/core/types.d.ts +15 -0
  47. package/dist/core/wiring-manifest.d.ts +5 -1
  48. package/dist/core/wiring-manifest.js +2 -0
  49. package/dist/index.d.ts +7 -3
  50. package/dist/index.js +6 -2
  51. package/dist/stores/file/permission-rule-store.d.ts +32 -0
  52. package/dist/stores/file/permission-rule-store.js +213 -0
  53. package/dist/tools/fs/fs-bash.js +12 -5
  54. package/dist/tools/fs/fs-shared.d.ts +12 -0
  55. package/dist/tools/fs/fs-shared.js +65 -1
  56. package/dist/tools/web.js +2 -0
  57. 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 { memoryEngineSession, memoryBlock: memoryBlockFromEngine, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, } = await prepareMemory({
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 (sharedMemoryPairMounted && sharedMemoryPair.length > 0) {
2222
- const withoutPair = tools.filter((t) => !sharedMemoryPair.some((n) => n === t.name));
2223
- const d1 = classifyDeferredOverFace(tools, sharedMemoryPair);
2224
- const d0 = classifyDeferredOverFace(withoutPair, []);
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 (sharedMemoryPair.some((n) => n === tools[i].name))
2259
+ if (builtinDeferPairNames.some((n) => n === tools[i].name))
2230
2260
  tools.splice(i, 1);
2231
2261
  }
2232
- sharedMemoryPairMounted = false;
2233
- deps.onError?.(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
2234
- `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" });
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 reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
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;