@sema-agent/core 7.44.0 → 7.45.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/dist/agents/subagent.js +33 -14
  3. package/dist/agents/tool-filter.d.ts +65 -4
  4. package/dist/agents/tool-filter.js +127 -4
  5. package/dist/core/agent-definition.d.ts +2 -2
  6. package/dist/core/allow-entry.d.ts +25 -1
  7. package/dist/core/allow-entry.js +29 -4
  8. package/dist/core/ask-origin.d.ts +23 -4
  9. package/dist/core/ask-origin.js +10 -4
  10. package/dist/core/checkpoint-store.d.ts +2 -2
  11. package/dist/core/decision-receipt.js +1 -1
  12. package/dist/core/effective-path-target.d.ts +7 -6
  13. package/dist/core/effective-path-target.js +3 -7
  14. package/dist/core/engine-notice.d.ts +20 -3
  15. package/dist/core/engine-notice.js +12 -6
  16. package/dist/core/gate-lanes.js +2 -0
  17. package/dist/core/gate-outcome.d.ts +5 -0
  18. package/dist/core/gate-outcome.js +2 -0
  19. package/dist/core/hooks.d.ts +2 -0
  20. package/dist/core/memory-engine/content-origin.d.ts +0 -34
  21. package/dist/core/memory-engine/content-origin.js +9 -4
  22. package/dist/core/memory-engine/layout.d.ts +5 -1
  23. package/dist/core/memory-engine/layout.js +1 -0
  24. package/dist/core/permission-rules.d.ts +4 -0
  25. package/dist/core/permission-rules.js +5 -2
  26. package/dist/core/real-approval.d.ts +9 -6
  27. package/dist/core/real-approval.js +5 -5
  28. package/dist/core/runner/child-tree.d.ts +13 -0
  29. package/dist/core/runner/child-tree.js +3 -0
  30. package/dist/core/runner/contracts.d.ts +8 -2
  31. package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
  32. package/dist/core/runner/prepare-ask-lane.js +2 -2
  33. package/dist/core/runner/prepare-config-doors.js +3 -1
  34. package/dist/core/runner/prepare-execution-env.js +2 -1
  35. package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
  36. package/dist/core/runner/prepare-gate-stations.js +2 -1
  37. package/dist/core/runner/prepare-listings.d.ts +4 -4
  38. package/dist/core/runner/prepare-listings.js +2 -22
  39. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  40. package/dist/core/runner/prepare-park-ask.js +2 -2
  41. package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
  42. package/dist/core/runner/prepare-policy-chain.js +4 -2
  43. package/dist/core/runner/prepare-task.js +10 -6
  44. package/dist/core/runner/resume-policy-outcome.js +2 -1
  45. package/dist/core/runner/run-leg.js +1 -1
  46. package/dist/core/runner/run-notification-lane.js +2 -2
  47. package/dist/core/runner/runtask.d.ts +2 -0
  48. package/dist/core/runner/runtask.js +2 -0
  49. package/dist/core/runner/stream-steer-verb.js +2 -2
  50. package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
  51. package/dist/core/runner/sub-agent-deny-lists.js +121 -0
  52. package/dist/core/runner/synthetic-tools.d.ts +1 -1
  53. package/dist/core/runner/synthetic-tools.js +3 -2
  54. package/dist/core/session-policy-store.d.ts +6 -2
  55. package/dist/core/session-policy-store.js +2 -1
  56. package/dist/core/skill-grant.d.ts +10 -2
  57. package/dist/core/skill-grant.js +20 -4
  58. package/dist/core/task-notification.d.ts +12 -1
  59. package/dist/core/task-notification.js +13 -1
  60. package/dist/core/task-spec.d.ts +5 -3
  61. package/dist/core/tool-policy.d.ts +15 -4
  62. package/dist/core/tool-policy.js +9 -13
  63. package/dist/core/tool-spec.d.ts +20 -2
  64. package/dist/core/types.d.ts +1 -1
  65. package/dist/core/types.js +1 -1
  66. package/dist/index.d.ts +5 -3
  67. package/dist/index.js +5 -3
  68. package/dist/orchestration/workflow.js +2 -1
  69. package/dist/tools/fs/removal-targets.d.ts +82 -0
  70. package/dist/tools/fs/removal-targets.js +236 -0
  71. package/dist/tools/fs/shell-read-write-targets.d.ts +27 -0
  72. package/dist/tools/fs/shell-read-write-targets.js +1 -1
  73. package/package.json +1 -1
  74. package/test/export-surface.snapshot.json +33 -1
  75. package/wire-changes/core@7.45.0.json +1084 -0
@@ -21,6 +21,7 @@ import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermission
21
21
  import { protocolOf } from "./protocol-table.js";
22
22
  import { isAbsoluteForFamily, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
23
23
  import { ASSIGNMENT_PREFIX, writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
24
+ import { removalReadingOf } from "../tools/fs/removal-targets.js";
24
25
  const nameListHolds = (set, req) => heldBy("policy.nameLists", set, req);
25
26
  const shellToolsListHolds = (set, req) => heldBy("policy.shellToolsList", set, req);
26
27
  export function advertisedWritableRootsSeat(seat) {
@@ -920,28 +921,23 @@ function hasRecursiveForce(argvTail) {
920
921
  export function createUnverifiableDeletePolicy(opts) {
921
922
  const safeVars = new Set([...UNVERIFIABLE_DELETE_SAFE_VARS, ...(opts?.safeVars ?? [])]);
922
923
  const shellTools = canonicalToolNameSet(opts?.tools);
924
+ const unverifiable = (command, finding = findUnverifiableRecursiveDelete(command, safeVars)) => (finding === undefined ? ALLOW : removalAsk("unverifiable_delete", finding));
923
925
  return brandPolicyAskClass({
924
- check(req) {
926
+ check(req, signal) {
925
927
  if (!shellToolsListHolds(shellTools, req))
926
928
  return ALLOW;
927
929
  const command = req.args?.command;
928
930
  if (typeof command !== "string")
929
931
  return ALLOW;
930
- const finding = findUnverifiableRecursiveDelete(command, safeVars);
931
- if (finding === undefined)
932
- return ALLOW;
933
- return {
934
- action: "ask",
935
- decisionReason: "safety",
936
- requiresRealApproval: true,
937
- guard: "unverifiable_delete",
938
- message: `Unverifiable recursive delete (fail-closed unless cleared): ${finding}. ` +
939
- `Re-run the delete with the resolved literal path written into the command itself ` +
940
- `(or assign the variable in the same command, e.g. \`DIR=/exact/path; rm -rf "$DIR"\`) so the target can be verified.`,
941
- };
932
+ const removal = removalReadingOf(command, req, opts?.removalSeat);
933
+ return removal === undefined ? unverifiable(command) : removal.judge(signal).then((found) => (found === undefined ? unverifiable(command) : removalAsk(found.guard, found.text)));
942
934
  },
943
935
  }, "external_authority");
944
936
  }
937
+ function removalAsk(guard, text) {
938
+ const message = guard === "dangerous_delete" ? `Dangerous removal: ${text}. No permission mode, allow rule or classifier clears this — only a person confirming this call does.` : `Unverifiable recursive delete (fail-closed unless cleared): ${text}. Re-run the delete with the resolved literal path written into the command itself (or assign the variable a literal path in the same command, e.g. \`DIR=/exact/path; rm -rf "$DIR"\`) so the target can be verified.`;
939
+ return { action: "ask", decisionReason: "safety", requiresRealApproval: true, guard, message };
940
+ }
945
941
  function pathSegments(p) {
946
942
  return p.split(/[\\/]/).filter(Boolean);
947
943
  }
@@ -175,18 +175,36 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
175
175
  * offered pool a child of that type actually mounts. Filled by `createSubagentTool` beside
176
176
  * `agentListing`; a type with neither list gets the whole offered pool.
177
177
  *
178
- * Read by ONE consumer: the memory content-origin face, which classifies a delegation RESULT by the
178
+ * Read by the memory content-origin face, which classifies a delegation RESULT by the
179
179
  * child's tool face. A child that can reach a network or protocol tool returns content from outside
180
180
  * the trust boundary just as surely as calling that tool here would, and the parent's transcript is
181
181
  * where it lands. Static by construction — the child's face is known from its definition, so no
182
- * runtime tracking of what the child actually did is involved (that is the v2 ticket).
182
+ * runtime tracking of what the child actually did is involved (that is the v2 ticket). `authored`
183
+ * marks the entries minted from a definition a caller wrote on a tool a deployment mounted (not the
184
+ * engine's built-ins, the general-purpose alias, fork, or the engine's nested copy of the tool for a
185
+ * delegated child); prepare's deny-name check judges only those.
183
186
  */
184
187
  agentToolFaces?: ReadonlyArray<{
185
188
  name: string;
186
189
  allowTools?: readonly string[];
187
190
  denyTools?: readonly string[];
188
191
  canRedelegate?: boolean;
192
+ authored?: true;
189
193
  }>;
194
+ /**
195
+ * The tool-level `denyTools` list of the delegation tool (`SubagentToolOptions.denyTools`, applied when no agent definition is
196
+ * selected), exactly as written, filled by `createSubagentTool` beside {@link agentToolFaces}; absent when none was written
197
+ * and on the engine's nested copy for a delegated child. Read by prepare's deny-name check, which judges it with
198
+ * the `authored` entries of the faces.
199
+ */
200
+ agentToolLevelDenyTools?: readonly string[];
201
+ /**
202
+ * The tool-level `allowTools` list of the delegation tool (`SubagentToolOptions.allowTools`, applied when no agent definition is
203
+ * selected), as the tool copied it, filled by `createSubagentTool` beside {@link agentToolLevelDenyTools}; absent when none was
204
+ * written and on the engine's nested copy for a delegated child. Read by prepare's allowed-list judgment: a value that is not a
205
+ * list of strings is refused before the run.
206
+ */
207
+ agentToolLevelAllowTools?: readonly string[];
190
208
  /**
191
209
  * design/178 §3 (ruled 2026-08-08) — the POOL a delegated child draws from, as the delegation tool
192
210
  * captured it. Read together with {@link agentToolFaces} by the memory content-origin face: the face
@@ -30,5 +30,5 @@ export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegatio
30
30
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
31
31
  export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
32
32
  export type { EngineNotice, OwedUserInputAccount, UserInputUndrainedCause } from "./engine-notice.js";
33
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES } from "./engine-notice.js";
33
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./engine-notice.js";
34
34
  export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
@@ -1,4 +1,4 @@
1
1
  export { MODEL_ROLES } from "./model-seat.js";
2
2
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
3
3
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
4
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES } from "./engine-notice.js";
4
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./engine-notice.js";
package/dist/index.d.ts CHANGED
@@ -63,7 +63,7 @@ export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classify
63
63
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice, type McpInjectionDropReason, type McpInjectionDropFacts } from "./core/mcp-injection-drop.js";
64
64
  export { askUnresolvableNotice, type AskUnresolvableFacts } from "./core/ask-unresolvable-notice.js";
65
65
  export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
66
- export { USER_INPUT_UNDRAINED_CAUSES, type UserInputUndrainedCause } from "./core/engine-notice.js";
66
+ export { USER_INPUT_UNDRAINED_CAUSES, type UserInputUndrainedCause, USER_INPUT_UNDRAINED_FAMILIES } from "./core/engine-notice.js";
67
67
  export { STEER_REPLAY_FACETS, type SteerReplayFacet } from "./core/steer-replay.js";
68
68
  export { TOOL_TURN_INCOMPLETE_CAUSES, type ToolTurnIncompleteCause } from "./internal/llm.js";
69
69
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
@@ -146,7 +146,7 @@ export { judgeRuleToolName, RULE_TOOL_NAME_VERDICTS, type RuleToolNameVerdict }
146
146
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
147
147
  export { renderTaskNotificationXml, RESUPPLIED_FRAME_PREAMBLE, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, type NotificationInjectionOutcome, // the injector's outcome words (`parked_durably` = the session's durable box took the frame)
148
148
  NOTIFICATION_INJECTION_OUTCOMES, // …and their value half + guard, so a host wiring its own uplink validates a reported outcome without duplicating the vocabulary
149
- isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, steerPriorityOf, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, HANDBACK_STATES, type HandbackState, COMPACTION_KINDS, type CompactionKind, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
149
+ isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, steerPriorityOf, injectionLaneOf, type UserInputLane, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, HANDBACK_STATES, type HandbackState, COMPACTION_KINDS, type CompactionKind, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
150
150
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, type ToolFamily, type ToolMountTag, type ToolApprovalCard, type ToolPathAccess, type ToolPathBase, type ToolPathAbsence, type ToolPathTarget, type ToolCallPathTarget, type ToolRosterPathTarget, pathTargetSlotOnly, type ToolRuleFace, type ToolRenderHints, type ToolFaceDeclaration, type ToolCatalogEntry, } from "./core/tool-face.js";
151
151
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
152
152
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, type RosterSource, type ToolIdentity, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, type RosterBoundMembers, type RosterBoundCode, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
@@ -207,7 +207,8 @@ export { ALLOW_RULE_MEMBERS, type AllowRuleMember, type AllowRuleShadow } from "
207
207
  export { mintNamespacePrefix } from "./core/protocol-naming.js";
208
208
  export { readDenyClassifierRules, renderClassifierDenyRules, type DerivedDenyRule } from "./core/auto-mode-prompt.js";
209
209
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource } from "./core/runtime-caps.js";
210
- export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_PARTIES, type SettlementParty, isSettlementParty, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, type ClassifierDenyCause, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, type GateDisposition, type GateOutcome, type ModeRelease, type ModeRefusal, screenGateOutcome } from "./core/gate-outcome.js";
210
+ export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_PARTIES, type SettlementParty, isSettlementParty, HOST_DECIDERS, type HostDecider, isHostDecider, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, type ClassifierDenyCause, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, type GateDisposition, type GateOutcome, type ModeRelease, type ModeRefusal, screenGateOutcome } from "./core/gate-outcome.js";
211
+ export { policyCannotAnswer, type HeldQuestion } from "./core/real-approval.js";
211
212
  export { type AskCarry } from "./core/hooks.js";
212
213
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps, type SwappableDepSeat, type SwappableDeps } from "./core/swappable-deps.js";
213
214
  export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, classifierParseFailureDenyMessage, AUTO_MODE_PARSE_FAILURES, isAutoModeParseFailure, type AutoModeParseFailure, type AutoModeUnavailableCause, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, type AutoModeClassifierFallback, type AutoModeFallbackCause, type AutoModeClassifierRound, type AutoModeLadderRung, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
@@ -253,6 +254,7 @@ export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreTo
253
254
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE, type HookFamily, type WiringManifestHookEntry } from "./core/hook-seats.js";
254
255
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, MEMORY_PROJECT_KEY_ABSENT_REASONS, type MemoryProjectKeyAbsentReason, type MemoryProjectKey, } from "./core/memory.js";
255
256
  export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, writableRootOf, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, type OriginClearanceRow, type OriginClearanceEvent, type OriginClearanceShadow, committedDistilledOf, distilledEquals, type MemoryEntryDistilled, type MemoryEntryDistilledInput, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, deriveShadowedSet, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, type SessionMemoryStatus, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, type CleanArmLeakFinding, type CleanArmLeakVerdict, type MemoryDistillerPurityContract, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationRunStopReason, type RunMemoryConsolidationOptions, isInstructionEntry, type MemoryEntryOrigin, type MemoryOriginCause, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, type SessionCaptureOptOutRecord, type SessionCaptureOptOutMarkOutcome, type SessionCaptureRecordStore, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, type MemoryGetDetails, type MemoryIndexDetails, type MemoryIndexRow, type CleanMemoryIndexRow, type ExposedMemoryIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type MemoryScopeEnumeration, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
257
+ export { STORE_FORM_REFUSAL_CODES, TARGET_OUTSIDE_STORE, SCOPE_HOME_NOT_DIRECTORY, SCOPE_HOME_FORMS, type StoreFormRefusalCode, type StoreFormRefusalDetail, type StoreFormRefusal, type ScopeHomeForm } from "./core/memory-engine/layout.js";
256
258
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
257
259
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
258
260
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classify
49
49
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice } from "./core/mcp-injection-drop.js";
50
50
  export { askUnresolvableNotice } from "./core/ask-unresolvable-notice.js";
51
51
  export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
52
- export { USER_INPUT_UNDRAINED_CAUSES } from "./core/engine-notice.js";
52
+ export { USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./core/engine-notice.js";
53
53
  export { STEER_REPLAY_FACETS } from "./core/steer-replay.js";
54
54
  export { TOOL_TURN_INCOMPLETE_CAUSES } from "./internal/llm.js";
55
55
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
@@ -118,7 +118,7 @@ export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./co
118
118
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
119
119
  export { judgeRuleToolName, RULE_TOOL_NAME_VERDICTS } from "./core/tool-name-aliases.js";
120
120
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
121
- export { renderTaskNotificationXml, RESUPPLIED_FRAME_PREAMBLE, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, steerPriorityOf, AGENT_MESSAGE_TAG, renderAgentMessageFrame, HANDBACK_STATES, COMPACTION_KINDS, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
121
+ export { renderTaskNotificationXml, RESUPPLIED_FRAME_PREAMBLE, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, steerPriorityOf, injectionLaneOf, AGENT_MESSAGE_TAG, renderAgentMessageFrame, HANDBACK_STATES, COMPACTION_KINDS, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
122
122
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, pathTargetSlotOnly, } from "./core/tool-face.js";
123
123
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
124
124
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
@@ -169,7 +169,8 @@ export { ALLOW_RULE_MEMBERS } from "./core/allow-rule-shadow.js";
169
169
  export { mintNamespacePrefix } from "./core/protocol-naming.js";
170
170
  export { readDenyClassifierRules, renderClassifierDenyRules } from "./core/auto-mode-prompt.js";
171
171
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-caps.js";
172
- export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_PARTIES, isSettlementParty, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, screenGateOutcome } from "./core/gate-outcome.js";
172
+ export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_PARTIES, isSettlementParty, HOST_DECIDERS, isHostDecider, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, screenGateOutcome } from "./core/gate-outcome.js";
173
+ export { policyCannotAnswer } from "./core/real-approval.js";
173
174
  export {} from "./core/hooks.js";
174
175
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
175
176
  export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, classifierParseFailureDenyMessage, AUTO_MODE_PARSE_FAILURES, isAutoModeParseFailure, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
@@ -199,6 +200,7 @@ export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreTo
199
200
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE } from "./core/hook-seats.js";
200
201
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, MEMORY_PROJECT_KEY_ABSENT_REASONS, } from "./core/memory.js";
201
202
  export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, writableRootOf, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, committedDistilledOf, distilledEquals, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, deriveShadowedSet, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, isInstructionEntry, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, erasureSelectHash, computeMemoryBundleHash, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
203
+ export { STORE_FORM_REFUSAL_CODES, TARGET_OUTSIDE_STORE, SCOPE_HOME_NOT_DIRECTORY, SCOPE_HOME_FORMS } from "./core/memory-engine/layout.js";
202
204
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, } from "./core/shared-memory/types.js";
203
205
  export { sharedMemoryStoreContract, } from "./core/shared-memory/contract.js";
204
206
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
@@ -19,6 +19,7 @@ import { boundInputHashOf } from "../core/canonical-json.js";
19
19
  import { boundedRedactedSummary } from "../core/untrusted-egress.js";
20
20
  import { delimitUntrusted } from "../core/untrusted-text.js";
21
21
  import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
22
+ import { deniedToolNamesOf } from "../core/runner/sub-agent-deny-lists.js";
22
23
  import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
23
24
  import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentParkedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
24
25
  import { WorkflowScriptError } from "./workflow-meta.js";
@@ -118,7 +119,7 @@ export function applyWorkflowAgentType(spec, agentType, registry) {
118
119
  if (out.memoryPersistenceCapable === undefined && def.memoryPersistenceCapable !== undefined)
119
120
  out.memoryPersistenceCapable = def.memoryPersistenceCapable;
120
121
  if (def.allowTools !== undefined || def.denyTools !== undefined) {
121
- const perType = createAllowDenyPolicy({ ...(def.allowTools ? { allow: def.allowTools } : {}), ...(def.denyTools ? { deny: def.denyTools } : {}), scope: "narrowing" });
122
+ const perType = createAllowDenyPolicy({ ...(def.allowTools ? { allow: def.allowTools } : {}), ...(def.denyTools ? { deny: deniedToolNamesOf(`workflow agentType "${def.name}"`, def.denyTools) } : {}), scope: "narrowing" });
122
123
  out.toolPolicy = out.toolPolicy ? combinePolicies(out.toolPolicy, perType) : perType;
123
124
  }
124
125
  return out;
@@ -0,0 +1,82 @@
1
+ import { type EstablishedPlan } from "./shell-read-establish.js";
2
+ import type { ExecutionEnv } from "../../internal/harness-types.js";
3
+ import type { ToolCallRequest } from "../../core/tool-policy.js";
4
+ /** The REMOVAL programs — the reference implementation's dangerous-removal arm reads `rm` and `rmdir`, with or without a
5
+ * recursive option (removing an empty home or the task root is still removing it). */
6
+ export declare const SHELL_REMOVAL_COMMANDS: readonly ["rm", "rmdir"];
7
+ export type ShellRemovalCommand = (typeof SHELL_REMOVAL_COMMANDS)[number];
8
+ /** What makes a removal target dangerous, in the order a card names it. `runtime_script`: a shell's script text is built
9
+ * only when the command runs, and what it may run includes a removal nobody can read before it does. */
10
+ export type DangerousRemovalKind = "filesystem_root" | "home" | "workspace" | "top_level" | "runtime_script";
11
+ /** The protected locations a removal is judged against. `homes`: the environment's declared home and every other spelling
12
+ * of it (its canonical one); `workspace`: the task root, the working directory, the additional working directories. */
13
+ export interface RemovalSeat {
14
+ readonly homes: readonly string[];
15
+ readonly workspace: readonly string[];
16
+ }
17
+ /** One dangerous removal a command makes: the program (the removal program, or the shell a runtime-built script is handed
18
+ * to), the operand as written, the target as established (the script word as written, for `runtime_script`). */
19
+ export interface DangerousRemoval {
20
+ readonly kind: DangerousRemovalKind;
21
+ readonly program: string;
22
+ readonly operand: string;
23
+ readonly target: string;
24
+ }
25
+ /**
26
+ * The ONE dangerous-removal predicate: is removing this absolute path removing a protected location? The reference
27
+ * implementation's set (cli 2.1.291 `dme` / `xW`) — the filesystem root (a drive root, a UNC share root), a home directory
28
+ * (every spelling the seat holds), a workspace directory or any of its ANCESTORS (the task root, the working directory, an
29
+ * additional working directory — whether the home lies inside the task root or the task root inside the home), a top-level
30
+ * directory (one segment below the root). `undefined` for a relative path or any other target.
31
+ */
32
+ export declare function dangerousRemovalOf(path: string, seat: RemovalSeat): DangerousRemovalKind | undefined;
33
+ /** What one removal finding asks: a protected location (`dangerous_delete`), or a target the command builds from a value
34
+ * of its own that the guard cannot read (`unverifiable_delete`). */
35
+ export type RemovalFinding = (DangerousRemoval & {
36
+ readonly guard: "dangerous_delete";
37
+ }) | {
38
+ readonly guard: "unverifiable_delete";
39
+ readonly program: string;
40
+ readonly operand: string;
41
+ readonly name: string;
42
+ };
43
+ /**
44
+ * The removal findings of an established plan — ONE walk, nested plans included (a `bash -c` / `sh -c` text, a substitution
45
+ * body), two words. For every `rm` / `rmdir` segment, each of its OPERANDS (every write target that is not one of its
46
+ * option words, read off its argv with {@link REMOVAL_GRAMMAR}) as the plan established it on the WRITE face (the entry the
47
+ * program unlinks, and the deny-floor shadow of a home word the plan did not place) is judged by {@link dangerousRemovalOf};
48
+ * an operand the plan could not place that expands a name the command itself binds is judged in every spelling those
49
+ * bindings give it ({@link boundSpellingsOf}), and one with a binding this reading cannot read is an unverifiable target (a
50
+ * recursive force `rm`). A shell handed a script built only when the command runs is a dangerous removal of its own
51
+ * ({@link runtimeScriptOf}). The read-face copy of an operand is not judged: it follows a link the removal does not follow.
52
+ */
53
+ export declare function removalFindingsOf(established: EstablishedPlan, seat: RemovalSeat): RemovalFinding[];
54
+ /** Does any segment of this plan (nested plans included) run a removal program, or hand a shell a script built when it
55
+ * runs? The guard establishes nothing for a command that does neither. */
56
+ export declare function planRunsRemoval(plan: EstablishedPlan["plan"]): boolean;
57
+ /** What the removal guard asks for one command: the guard word and its sentence's account — the first protected location
58
+ * (the program, the location, the target), else the first bound value the guard cannot read; `undefined` when neither. */
59
+ export interface RemovalVerdict {
60
+ readonly guard: RemovalFinding["guard"];
61
+ readonly text: string;
62
+ }
63
+ /**
64
+ * The seat the dangerous-removal arm of the removal guard (`createUnverifiableDeletePolicy`, tool-policy.ts) reads, beside the request's own `root` /
65
+ * `cwd` (the task root and the live working directory): the execution environment (its declared home — the `HOME` the
66
+ * shell child receives, the same seat the read station folds `~` / `$HOME` with — and its resolver, so a target that
67
+ * resolves through a link is judged where it lands), and the leg's additional working directories.
68
+ */
69
+ export interface RemovalGuardSeat {
70
+ readonly env?: ExecutionEnv;
71
+ readonly additionalRoots?: () => readonly string[];
72
+ }
73
+ /**
74
+ * The dangerous-removal READING of one command — undefined when no segment of it (nested shell text included) runs a
75
+ * removal program, so a command that removes nothing costs the gate no suspension. Otherwise `judge` establishes the plan
76
+ * the read station plans (the same planner, the same write-target reading) lexically against the request's `cwd` and the
77
+ * seat's home, and — where an environment is seated — through its resolver too, and returns what either establishment
78
+ * finds ({@link removalFindingsOf}: the protected-location word first, then the unverifiable one).
79
+ */
80
+ export declare function removalReadingOf(command: string, req: Pick<ToolCallRequest, "cwd" | "root">, seat: RemovalGuardSeat | undefined): {
81
+ judge(signal?: AbortSignal): Promise<RemovalVerdict | undefined>;
82
+ } | undefined;
@@ -0,0 +1,236 @@
1
+ import { planShellReads } from "./shell-read-plan.js";
2
+ import { createShellReadEstablisher, establishShellReadsLexically } from "./shell-read-establish.js";
3
+ import { isAbsolutePathForm, isWinFormPath, normalizeAbsPathLexically } from "./safety.js";
4
+ import { reentryText } from "./shell-reentry-text.js";
5
+ import { bashClassifyDefaultAllow, SHELL_REENTRY_PROGRAM_RE } from "./bash-readonly-classifier.js";
6
+ import { programArgs, readArgv } from "./shell-read-write-targets.js";
7
+ import { inlineUntrusted } from "../../core/untrusted-text.js";
8
+ export const SHELL_REMOVAL_COMMANDS = Object.freeze(["rm", "rmdir"]);
9
+ const REMOVAL_COMMAND_SET = new Set(SHELL_REMOVAL_COMMANDS);
10
+ const REMOVAL_GRAMMAR = { required: "", optional: "", longRequired: [] };
11
+ function removalKey(path) {
12
+ let k = normalizeAbsPathLexically(path.normalize("NFC")).replace(/\\/g, "/").toLowerCase();
13
+ k = k.replace(/(?:\/+\*+)+\/*$/, "");
14
+ const drive = driveOf(path, k);
15
+ let rest = k.slice(drive.length);
16
+ if (rest === "")
17
+ rest = "/";
18
+ if (rest.length > 1)
19
+ rest = rest.replace(/\/+$/, "");
20
+ return drive + rest.replace(/^\/private\/(etc|var|tmp|home)(?=\/|$)/, "/$1");
21
+ }
22
+ function driveOf(path, key) {
23
+ return isWinFormPath(path) && key.length >= 2 && key[1] === ":" ? key.slice(0, 2) : "";
24
+ }
25
+ export function dangerousRemovalOf(path, seat) {
26
+ if (!isAbsolutePathForm(path))
27
+ return undefined;
28
+ const k = removalKey(path);
29
+ const rest = k.slice(driveOf(path, k).length);
30
+ if (rest === "/" || /^\/\/[^/]+\/[^/]+$/.test(k))
31
+ return "filesystem_root";
32
+ if (seat.homes.some((h) => isAbsolutePathForm(h) && removalKey(h) === k))
33
+ return "home";
34
+ const prefix = k.endsWith("/") ? k : `${k}/`;
35
+ if (seat.workspace.some((w) => isAbsolutePathForm(w) && (removalKey(w) === k || removalKey(w).startsWith(prefix))))
36
+ return "workspace";
37
+ if (/^\/[^/]+$/.test(rest))
38
+ return "top_level";
39
+ return undefined;
40
+ }
41
+ function tildeNamesAHome(spelled) {
42
+ const m = /^~([^/]*)(\/.*)?$/.exec(spelled);
43
+ if (m === null)
44
+ return false;
45
+ const key = removalKey(`/~home${m[2] ?? ""}`);
46
+ return key === "/~home" || !key.startsWith("/~home/");
47
+ }
48
+ function removalKindOf(read, seat) {
49
+ const floors = read.established === "unestablishable" && read.candidate.bases === "unmodelled" ? read.spellings : [];
50
+ for (const view of [read.target, read.origin, ...floors]) {
51
+ const kind = view === undefined ? undefined : dangerousRemovalOf(view, seat);
52
+ if (kind !== undefined)
53
+ return kind;
54
+ }
55
+ return read.established === "unestablishable" && tildeNamesAHome(read.candidate.spelled) ? "home" : undefined;
56
+ }
57
+ function literalOf(pieces) {
58
+ return pieces.every((q) => q.literal !== undefined) ? pieces.map((q) => q.literal).join("") : undefined;
59
+ }
60
+ const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
61
+ const NAME_BINDERS = new Set(["unset", "local", "declare", "typeset", "readonly", "export", "read"]);
62
+ function bindingsOf(plan) {
63
+ const out = new Map();
64
+ const bind = (name, value) => {
65
+ out.set(name, [...(out.get(name) ?? []), value]);
66
+ };
67
+ for (const segment of plan.segments) {
68
+ for (const w of segment.argv)
69
+ if (w.assignment !== undefined)
70
+ bind(w.assignment.name, w.assignment.settlesWholeName ? literalOf(w.assignment.value.pieces) : undefined);
71
+ const [head, name] = segment.argv;
72
+ if ((head?.folded === "for" || head?.folded === "select") && name !== undefined && NAME.test(name.folded)) {
73
+ const at = segment.argv.findIndex((w, i) => i > 1 && w.folded === "in");
74
+ const words = at < 0 ? [undefined] : segment.argv.slice(at + 1).map((w) => literalOf(w.pieces));
75
+ for (const v of words.length > 0 ? words : [undefined])
76
+ bind(name.folded, v);
77
+ }
78
+ if (head !== undefined && NAME_BINDERS.has(head.folded))
79
+ for (const w of segment.argv.slice(1))
80
+ if (w.assignment === undefined && NAME.test(w.folded))
81
+ bind(w.folded, undefined);
82
+ }
83
+ return out;
84
+ }
85
+ const BOUND_SPELLINGS_MAX = 64;
86
+ function boundSpellingsOf(word, bindings) {
87
+ const named = word.pieces.filter((q) => q.literal === undefined);
88
+ if (named.length === 0 || !named.some((q) => q.expansion?.kind === "parameter" && q.expansion.name !== undefined && bindings.has(q.expansion.name)))
89
+ return undefined;
90
+ let spellings = [""];
91
+ for (const q of word.pieces) {
92
+ if (q.literal !== undefined) {
93
+ spellings = spellings.map((sp) => sp + q.literal);
94
+ continue;
95
+ }
96
+ const name = q.expansion?.kind === "parameter" && (q.expansion.body === undefined || q.expansion.body === q.expansion.name) ? q.expansion.name : undefined;
97
+ const values = name !== undefined ? bindings.get(name) : undefined;
98
+ if (name === undefined || values === undefined)
99
+ return { unreadable: q.expansion?.name ?? word.raw };
100
+ if (values.some((v) => v === undefined || (q.quotedBy === undefined && /\s/.test(v))))
101
+ return { unreadable: name };
102
+ spellings = spellings.flatMap((sp) => values.map((v) => sp + v));
103
+ if (spellings.length > BOUND_SPELLINGS_MAX)
104
+ return { unreadable: name };
105
+ }
106
+ return { spellings };
107
+ }
108
+ function recursiveForce(args) {
109
+ const { options } = readArgv(args, REMOVAL_GRAMMAR);
110
+ const has = (long, short) => options.some((o) => (o.long ? long.startsWith(o.name) : short.test(o.name)));
111
+ return has("recursive", /^[rR]$/) && has("force", /^f$/);
112
+ }
113
+ export function removalFindingsOf(established, seat) {
114
+ const out = [];
115
+ const bindings = bindingsOf(established.plan);
116
+ for (const segment of established.plan.segments) {
117
+ const program = segment.verb?.basename;
118
+ if (program !== undefined && REMOVAL_COMMAND_SET.has(program)) {
119
+ const args = programArgs(segment);
120
+ const operands = new Set(readArgv(args, REMOVAL_GRAMMAR).operands);
121
+ const options = new Set(args.filter((w) => !operands.has(w)).map((w) => w.raw));
122
+ for (const read of established.reads.get(segment) ?? []) {
123
+ if (read.candidate.face !== "write" || options.has(read.candidate.raw))
124
+ continue;
125
+ const kind = removalKindOf(read, seat);
126
+ if (kind !== undefined) {
127
+ out.push({ guard: "dangerous_delete", kind, program, operand: read.candidate.raw, target: read.target ?? read.candidate.floorSpelled ?? read.candidate.spelled });
128
+ continue;
129
+ }
130
+ if (read.established !== "unestablishable")
131
+ continue;
132
+ const word = segment.argv.find((w) => w.raw === read.candidate.raw);
133
+ const bound = word === undefined ? undefined : boundSpellingsOf(word, bindings);
134
+ if (bound === undefined)
135
+ continue;
136
+ if ("unreadable" in bound) {
137
+ if (program === "rm" && recursiveForce(args))
138
+ out.push({ guard: "unverifiable_delete", program, operand: read.candidate.raw, name: bound.unreadable });
139
+ continue;
140
+ }
141
+ const bases = segment.bases === "unmodelled" ? (read.candidate.floorBases ?? []) : segment.bases;
142
+ for (const spelled of bound.spellings) {
143
+ const views = isAbsolutePathForm(spelled) ? [spelled] : bases.map((base) => `${base}/${spelled}`);
144
+ for (const view of views) {
145
+ const hit = dangerousRemovalOf(view, seat);
146
+ if (hit === undefined)
147
+ continue;
148
+ out.push({ guard: "dangerous_delete", kind: hit, program, operand: read.candidate.raw, target: normalizeAbsPathLexically(view) });
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ const script = runtimeScriptOf(segment);
155
+ if (script !== undefined && program !== undefined)
156
+ out.push({ guard: "dangerous_delete", kind: "runtime_script", program, operand: script, target: script });
157
+ for (const nested of segment.nested) {
158
+ const sub = established.nested.get(nested);
159
+ if (sub !== undefined)
160
+ out.push(...removalFindingsOf(sub, seat));
161
+ }
162
+ }
163
+ return out;
164
+ }
165
+ function runtimeScriptOf(segment) {
166
+ const program = segment.verb;
167
+ if (segment.reentry !== "shell" || program === undefined || !SHELL_REENTRY_PROGRAM_RE.test(program.basename))
168
+ return undefined;
169
+ const at = segment.argv.findIndex((w) => w.folded === program.name);
170
+ if (at < 0)
171
+ return undefined;
172
+ const MARK = "\u0000";
173
+ const words = segment.argv.slice(at + 1).map((w) => literalOf(w.pieces) ?? `${MARK}${w.raw}`);
174
+ const text = reentryText(program.basename, words);
175
+ return text === undefined || !text.includes(MARK) ? undefined : text.split(MARK).join("");
176
+ }
177
+ export function planRunsRemoval(plan) {
178
+ return plan.segments.some((s) => (s.verb !== undefined && REMOVAL_COMMAND_SET.has(s.verb.basename)) || runtimeScriptOf(s) !== undefined || s.nested.some(planRunsRemoval));
179
+ }
180
+ const DANGEROUS_REMOVAL_SENTENCE = {
181
+ filesystem_root: (p, t) => `\`${p}\` would remove the filesystem root (${t})`,
182
+ home: (p, t) => `\`${p}\` would remove a home directory (${t})`,
183
+ workspace: (p, t) => `\`${p}\` would remove a workspace directory — the task root, the working directory or an additional working directory — or one of its ancestors (${t})`,
184
+ top_level: (p, t) => `\`${p}\` would remove a top-level system directory (${t})`,
185
+ runtime_script: (p, t) => `\`${p}\` runs a script built only when the command runs (${t}), and it may remove the filesystem root, a home directory or the task's directories before anyone can read it`,
186
+ };
187
+ function verdictOf(findings) {
188
+ for (const f of findings)
189
+ if (f.guard === "dangerous_delete")
190
+ return { guard: f.guard, text: DANGEROUS_REMOVAL_SENTENCE[f.kind](f.program, inlineUntrusted(f.target, 200)) };
191
+ for (const f of findings)
192
+ if (f.guard === "unverifiable_delete")
193
+ return { guard: f.guard, text: `\`${f.program}\` recursive+force target ${inlineUntrusted(f.operand, 200)} takes ${f.name}'s value, which this command sets in a form the guard cannot read (an expansion, an unset or bare declaration, a \`read\`, a loop over unread words) — the deleted path cannot be verified from this command` };
194
+ return undefined;
195
+ }
196
+ const REMOVAL_RESOLVE_TIMEOUT_MS = 10_000;
197
+ export function removalReadingOf(command, req, seat) {
198
+ const homeDir = seat?.env?.homeDir;
199
+ const cwd = req.cwd ?? req.root ?? "";
200
+ const roots = req.root !== undefined ? [req.root] : cwd !== "" ? [cwd] : [];
201
+ const boundary = { roots, ...(cwd !== "" ? { cwd } : {}), ...(homeDir !== undefined ? { homeDir } : {}) };
202
+ const plan = planShellReads(command, { readers: new Set(bashClassifyDefaultAllow()), cwd, roots, ...(homeDir !== undefined ? { homeDir } : {}) });
203
+ if (!planRunsRemoval(plan))
204
+ return undefined;
205
+ return {
206
+ async judge(signal) {
207
+ const env = seat?.env;
208
+ const resolved = env !== undefined && homeDir !== undefined ? await resolvedOrUndefined(env.canonicalPath(homeDir, signal), signal) : undefined;
209
+ const removalSeat = {
210
+ homes: [...(homeDir !== undefined ? [homeDir] : []), ...(resolved !== undefined && resolved.ok ? [resolved.value] : [])],
211
+ workspace: [...(req.root !== undefined ? [req.root] : []), ...(req.cwd !== undefined ? [req.cwd] : []), ...(seat?.additionalRoots?.() ?? [])],
212
+ };
213
+ const lexical = removalFindingsOf(establishShellReadsLexically(plan, boundary), removalSeat);
214
+ if (lexical.some((f) => f.guard === "dangerous_delete") || env === undefined || roots.length === 0)
215
+ return verdictOf(lexical);
216
+ const established = await resolvedOrUndefined(createShellReadEstablisher(env, roots, { rootsCanonical: true }).establish(plan, boundary, signal), signal);
217
+ return verdictOf(established === undefined ? lexical : [...removalFindingsOf(established, removalSeat), ...lexical]);
218
+ },
219
+ };
220
+ }
221
+ async function resolvedOrUndefined(leg, signal) {
222
+ let timer;
223
+ try {
224
+ return await Promise.race([
225
+ leg.catch(() => undefined),
226
+ new Promise((resolve) => {
227
+ timer = setTimeout(() => resolve(undefined), REMOVAL_RESOLVE_TIMEOUT_MS);
228
+ signal?.addEventListener("abort", () => resolve(undefined), { once: true });
229
+ }),
230
+ ]);
231
+ }
232
+ finally {
233
+ if (timer !== undefined)
234
+ clearTimeout(timer);
235
+ }
236
+ }
@@ -27,6 +27,32 @@ export interface ArgWord {
27
27
  }
28
28
  /** The words a segment hands its program — the argv after the program word (a launcher's own words are not the program's). */
29
29
  export declare function programArgs(segment: ShellReadSegment): ArgWord[];
30
+ /**
31
+ * One program's option GRAMMAR, as its getopt reads it: the short letters taking a REQUIRED value (the rest of the word,
32
+ * or the next word), those taking an OPTIONAL one (the rest of the word only), and the long names taking a required value
33
+ * (any unambiguous prefix; `=` or the next word) — absent for a program with no long options (a `--name` word is then an
34
+ * error the program stops on, and names nothing here). The GNU and BSD programs of one name read the same argv
35
+ * differently (`sed -l` takes a value in GNU and none in BSD; `cp -S` a suffix in GNU and none in BSD), so every question
36
+ * below reads the argv under BOTH grammars and takes the union of what they name.
37
+ */
38
+ export interface OptionGrammar {
39
+ readonly required: string;
40
+ readonly optional: string;
41
+ readonly longRequired?: readonly string[];
42
+ }
43
+ /** One option read off an argv: a short letter or a long name as written (a prefix), and its value when it has one. */
44
+ interface OptionRead {
45
+ readonly name: string;
46
+ readonly long: boolean;
47
+ readonly value?: ArgWord;
48
+ }
49
+ /** An argv read under one grammar: its options in order, and its operands (every word after `--` included). Options are
50
+ * read anywhere among the operands, as GNU getopt permutes them — for a BSD getopt, which stops at the first operand,
51
+ * that is a superset of its options (the tightening direction). */
52
+ export declare function readArgv(words: readonly ArgWord[], grammar: OptionGrammar): {
53
+ readonly options: readonly OptionRead[];
54
+ readonly operands: readonly ArgWord[];
55
+ };
30
56
  /** What a `sed` invocation's option words say about writing, under either grammar ({@link SED_GRAMMARS}): whether it edits
31
57
  * in place (`-i` / `-I` / `--in-place`), the backup suffixes it is given (their values; an empty value keeps no backup),
32
58
  * and whether GNU `--follow-symlinks` is on. */
@@ -71,3 +97,4 @@ export interface JudgedCandidate {
71
97
  * lists it among the reads), plus its WRITE targets for the write face ({@link writeTargetsOf}), plus the deny-floor
72
98
  * shadow of every unplaced home word ({@link denyFloorShadowsOf}) on both faces. */
73
99
  export declare function judgedCandidatesOf(segment: ShellReadSegment): readonly JudgedCandidate[];
100
+ export {};
@@ -21,7 +21,7 @@ const COPY_GRAMMARS = [
21
21
  { required: "St", optional: "", longRequired: ["target-directory", "suffix", "no-preserve", "sparse"] },
22
22
  { required: "", optional: "" },
23
23
  ];
24
- function readArgv(words, grammar) {
24
+ export function readArgv(words, grammar) {
25
25
  const options = [];
26
26
  const operands = [];
27
27
  let endOfOptions = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.44.0",
3
+ "version": "7.45.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",