@sema-agent/core 7.33.8 → 7.35.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 (228) hide show
  1. package/CHANGELOG.md +462 -3
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/errors.d.ts +23 -1
  11. package/dist/brain/errors.js +19 -2
  12. package/dist/brain/open-responses.js +1 -0
  13. package/dist/brain/openai.js +1 -0
  14. package/dist/brain/status-sink.d.ts +7 -1
  15. package/dist/brain/stream-engine.d.ts +3 -0
  16. package/dist/brain/stream-engine.js +2 -1
  17. package/dist/brain/stream-shared.d.ts +3 -1
  18. package/dist/brain/stream-shared.js +16 -4
  19. package/dist/core/a2a.d.ts +3 -1
  20. package/dist/core/a2a.js +1 -1
  21. package/dist/core/ask-origin.d.ts +379 -154
  22. package/dist/core/ask-origin.js +224 -45
  23. package/dist/core/ask-question.js +2 -1
  24. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  25. package/dist/core/ask-unresolvable-notice.js +4 -3
  26. package/dist/core/auto-mode-arming.d.ts +17 -4
  27. package/dist/core/auto-mode-arming.js +31 -6
  28. package/dist/core/auto-mode-defaults.d.ts +16 -7
  29. package/dist/core/auto-mode-defaults.js +2 -1
  30. package/dist/core/auto-mode-directive.d.ts +18 -30
  31. package/dist/core/auto-mode-directive.js +9 -18
  32. package/dist/core/auto-mode-prompt.d.ts +62 -1
  33. package/dist/core/auto-mode-prompt.js +42 -4
  34. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  35. package/dist/core/auto-mode-rebuild.js +1 -0
  36. package/dist/core/auto-mode.d.ts +32 -3
  37. package/dist/core/auto-mode.js +80 -41
  38. package/dist/core/checkpoint-store.d.ts +54 -35
  39. package/dist/core/checkpoint-store.js +58 -3
  40. package/dist/core/child-clamp-seats.js +1 -0
  41. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  42. package/dist/core/classifier-unavailable-arm.js +17 -0
  43. package/dist/core/effective-path-target.d.ts +12 -0
  44. package/dist/core/effective-path-target.js +18 -7
  45. package/dist/core/engine-notice.d.ts +53 -2
  46. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  47. package/dist/core/fs-write-gate-policy.js +4 -1
  48. package/dist/core/gate-fold.js +19 -3
  49. package/dist/core/gate-lanes.js +116 -50
  50. package/dist/core/gate-outcome.d.ts +40 -7
  51. package/dist/core/gate-outcome.js +26 -4
  52. package/dist/core/governance-codes.d.ts +18 -2
  53. package/dist/core/governance-codes.js +23 -1
  54. package/dist/core/hooks.d.ts +53 -29
  55. package/dist/core/hooks.js +1 -3
  56. package/dist/core/mcp.js +1 -1
  57. package/dist/core/message-utils.d.ts +2 -1
  58. package/dist/core/message-utils.js +2 -1
  59. package/dist/core/mode-policy.d.ts +185 -0
  60. package/dist/core/mode-policy.js +184 -0
  61. package/dist/core/oracle-isolation.d.ts +3 -3
  62. package/dist/core/permission-rule-model.d.ts +20 -3
  63. package/dist/core/permission-rule-model.js +40 -3
  64. package/dist/core/permission-rule-provider.d.ts +22 -11
  65. package/dist/core/permission-rule-provider.js +17 -7
  66. package/dist/core/permission-rules.d.ts +23 -1
  67. package/dist/core/permission-rules.js +56 -9
  68. package/dist/core/persisted-rule-arms.d.ts +14 -10
  69. package/dist/core/persisted-rule-arms.js +13 -6
  70. package/dist/core/present-plan-tool.d.ts +1 -1
  71. package/dist/core/present-plan-tool.js +3 -3
  72. package/dist/core/read-only-shell.d.ts +19 -19
  73. package/dist/core/read-only-shell.js +38 -12
  74. package/dist/core/retired-keys.d.ts +9 -3
  75. package/dist/core/retired-keys.js +5 -1
  76. package/dist/core/runner/active-skill-scope.js +5 -4
  77. package/dist/core/runner/assemble-result.js +2 -2
  78. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  79. package/dist/core/runner/classifier-ladder.js +29 -0
  80. package/dist/core/runner/contracts.d.ts +62 -38
  81. package/dist/core/runner/denial-limit-arms.d.ts +35 -7
  82. package/dist/core/runner/denial-limit-arms.js +15 -9
  83. package/dist/core/runner/env-capability-census.js +1 -1
  84. package/dist/core/runner/file-history-request.d.ts +26 -0
  85. package/dist/core/runner/file-history-request.js +90 -0
  86. package/dist/core/runner/gate-exit.d.ts +30 -4
  87. package/dist/core/runner/gate-exit.js +28 -7
  88. package/dist/core/runner/permission-rule-lanes.d.ts +11 -6
  89. package/dist/core/runner/permission-rule-lanes.js +55 -48
  90. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  91. package/dist/core/runner/persisted-path-rows.js +89 -0
  92. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  93. package/dist/core/runner/prepare-artifact.js +9 -3
  94. package/dist/core/runner/prepare-ask-lane.d.ts +6 -4
  95. package/dist/core/runner/prepare-ask-lane.js +3 -3
  96. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  97. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  98. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  99. package/dist/core/runner/prepare-file-history.js +4 -90
  100. package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
  101. package/dist/core/runner/prepare-gate-stations.js +46 -29
  102. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  103. package/dist/core/runner/prepare-hands-readface.js +64 -53
  104. package/dist/core/runner/prepare-inherited-gate.d.ts +30 -23
  105. package/dist/core/runner/prepare-inherited-gate.js +45 -25
  106. package/dist/core/runner/prepare-park-ask.d.ts +6 -4
  107. package/dist/core/runner/prepare-park-ask.js +7 -5
  108. package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
  109. package/dist/core/runner/prepare-policy-chain.js +203 -180
  110. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  111. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  112. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  113. package/dist/core/runner/prepare-run-refs.js +9 -4
  114. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  115. package/dist/core/runner/prepare-safety-scan.js +19 -3
  116. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  117. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  118. package/dist/core/runner/prepare-task.js +56 -24
  119. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  120. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  121. package/dist/core/runner/read-root-grant.d.ts +9 -0
  122. package/dist/core/runner/read-root-grant.js +12 -0
  123. package/dist/core/runner/retired-runner-deps.js +1 -0
  124. package/dist/core/runner/run-brain-sinks.js +3 -1
  125. package/dist/core/runner/run-identity-wiring.js +1 -1
  126. package/dist/core/runner/run-leg.js +29 -18
  127. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  128. package/dist/core/runner/run-notification-lane.js +3 -1
  129. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  130. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  131. package/dist/core/runner/run-turn-boundary.js +1 -1
  132. package/dist/core/runner/runtask.d.ts +1 -0
  133. package/dist/core/runner/runtask.js +8 -11
  134. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  135. package/dist/core/runner/session-mode-seats.js +73 -0
  136. package/dist/core/runner/session-rule-policy.js +3 -2
  137. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  138. package/dist/core/runner/stream-mode-verb.js +19 -0
  139. package/dist/core/runner/turn-attachments.d.ts +3 -3
  140. package/dist/core/runner/turn-attachments.js +1 -1
  141. package/dist/core/runner/write-protection-station.d.ts +7 -1
  142. package/dist/core/runner/write-protection-station.js +6 -3
  143. package/dist/core/runner-deps.d.ts +31 -34
  144. package/dist/core/runtime-caps.d.ts +12 -3
  145. package/dist/core/safety-axis-vocab.d.ts +2 -2
  146. package/dist/core/safety-axis-vocab.js +1 -1
  147. package/dist/core/secret-env.d.ts +30 -58
  148. package/dist/core/secret-env.js +149 -586
  149. package/dist/core/sensitive-path-policy.d.ts +4 -2
  150. package/dist/core/sensitive-path-policy.js +41 -31
  151. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  152. package/dist/core/task-event.d.ts +1 -1
  153. package/dist/core/task-result.d.ts +1 -1
  154. package/dist/core/task-spec.d.ts +41 -89
  155. package/dist/core/task-stream.d.ts +16 -0
  156. package/dist/core/terminal-cause.js +1 -0
  157. package/dist/core/tighten-task-spec.d.ts +2 -2
  158. package/dist/core/tighten-task-spec.js +12 -14
  159. package/dist/core/tool-catalog-entries.js +4 -3
  160. package/dist/core/tool-policy.d.ts +84 -30
  161. package/dist/core/tool-policy.js +76 -16
  162. package/dist/core/tool-registry.d.ts +38 -0
  163. package/dist/core/tool-registry.js +25 -1
  164. package/dist/core/tool-roster.d.ts +17 -2
  165. package/dist/core/tool-roster.js +11 -1
  166. package/dist/core/tool-spec.d.ts +8 -9
  167. package/dist/core/trace.d.ts +18 -6
  168. package/dist/core/types.d.ts +2 -1
  169. package/dist/core/wiring-manifest.d.ts +31 -5
  170. package/dist/core/wiring-manifest.js +28 -1
  171. package/dist/core/write-protect.d.ts +11 -1
  172. package/dist/core/write-protect.js +26 -15
  173. package/dist/engine/execution-env/node-execution-env.d.ts +6 -1
  174. package/dist/engine/execution-env/node-execution-env.js +16 -3
  175. package/dist/engine/harness/agent-harness.js +2 -1
  176. package/dist/engine/harness/types.d.ts +6 -7
  177. package/dist/engine/llm/types.d.ts +2 -4
  178. package/dist/engine/loop/types.d.ts +7 -0
  179. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  180. package/dist/index.d.ts +18 -8
  181. package/dist/index.js +15 -6
  182. package/dist/orchestration/workflow-governance.js +1 -1
  183. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  184. package/dist/orchestration/workflow-size-guideline.js +2 -2
  185. package/dist/stores/file/index.d.ts +5 -3
  186. package/dist/stores/file/session-store.d.ts +8 -2
  187. package/dist/stores/file/session-store.js +5 -5
  188. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  189. package/dist/tools/artifact/artifact-tool.js +16 -1
  190. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  191. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  192. package/dist/tools/fs/containment-face.d.ts +9 -3
  193. package/dist/tools/fs/containment-face.js +2 -0
  194. package/dist/tools/fs/file-read-check.d.ts +50 -0
  195. package/dist/tools/fs/file-read-check.js +79 -0
  196. package/dist/tools/fs/fs-bash.d.ts +3 -3
  197. package/dist/tools/fs/fs-read.d.ts +1 -0
  198. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  199. package/dist/tools/fs/fs-search-tools.js +4 -4
  200. package/dist/tools/fs/fs-shared.js +20 -6
  201. package/dist/tools/fs/fs-write.d.ts +4 -3
  202. package/dist/tools/fs/fs-write.js +9 -9
  203. package/dist/tools/fs/index.d.ts +39 -3
  204. package/dist/tools/fs/index.js +38 -15
  205. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  206. package/dist/tools/fs/read-deny-home.js +225 -0
  207. package/dist/tools/fs/read-deny.d.ts +176 -34
  208. package/dist/tools/fs/read-deny.js +268 -61
  209. package/dist/tools/fs/safety.d.ts +6 -1
  210. package/dist/tools/fs/safety.js +18 -5
  211. package/dist/tools/fs/search.d.ts +3 -0
  212. package/dist/tools/fs/search.js +47 -9
  213. package/dist/tools/fs/shell-read-check.d.ts +52 -13
  214. package/dist/tools/fs/shell-read-check.js +199 -20
  215. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  216. package/dist/tools/fs/shell-read-establish.js +134 -21
  217. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  218. package/dist/tools/fs/shell-read-identity.js +21 -0
  219. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  220. package/dist/tools/fs/shell-read-judge.js +43 -18
  221. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  222. package/dist/tools/fs/shell-read-plan.js +26 -4
  223. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  224. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  225. package/package.json +1 -1
  226. package/test/export-surface.snapshot.json +117 -11
  227. package/wire-changes/core@7.34.0.json +2621 -0
  228. package/wire-changes/core@7.35.0.json +584 -0
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ export { StdioLspTransport } from "./engine/lsp/stdio-lsp-transport.js";
29
29
  export { encodeFrame, createFrameDecoder } from "./engine/lsp/frame-decoder.js";
30
30
  export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, MODEL_ROLES, } from "./core/roles.js";
31
31
  export { selectModel, selectModelOrThrow } from "./core/select-model.js";
32
- export { BrainError, classifyHttp } from "./brain/errors.js";
32
+ export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE } from "./brain/errors.js";
33
33
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
34
34
  export { computeCostMicroUsd, isModelPriced, modelCostToPricing, } from "./core/pricing.js";
35
35
  export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf } from "./core/runner/usage-accounting.js";
@@ -48,6 +48,12 @@ export { probeMcpServers } from "./core/mcp-probe.js";
48
48
  export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classifyMcpFailure } from "./core/mcp-failure.js";
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
+ export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
52
+ export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
53
+ export { TERMINAL_CAUSE_IS_REPLAYABLE } from "./core/terminal-cause.js";
54
+ export { RESERVED_AGENT_NAMES } from "./agents/subagent.js";
55
+ export { WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS } from "./orchestration/workflow-size-guideline.js";
56
+ export { ENGINE_AUTHORITY_ENVELOPE_TAGS } from "./core/untrusted-text.js";
51
57
  export { materializeA2aTools, A2aRpcError } from "./core/a2a.js";
52
58
  export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf } from "./core/protocol-table.js";
53
59
  export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, } from "./core/session-policy-store.js";
@@ -58,7 +64,7 @@ export { LOCKED_KEY_REGISTRY, resolveLockedKeys } from "./core/locked-config.js"
58
64
  export { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies, } from "./core/compliance.js";
59
65
  export { admitMemoryScopes, foldAdmissionFreeze, } from "./core/memory-admission.js";
60
66
  export { assertRetentionCapability, RETENTION_CASCADE_DOMAINS, RETENTION_CASCADE_EXEMPT, } from "./core/retention.js";
61
- export { GOVERNANCE_CODES, governanceRetryClass } from "./core/governance-codes.js";
67
+ export { GOVERNANCE_CODES, governanceRetryClass, TOOL_DECLARATION_CODES } from "./core/governance-codes.js";
62
68
  export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES } from "./core/governance-codes.js";
63
69
  export { TtlSessionStore } from "./core/session-store.js";
64
70
  export { reconcileInterruptedSession, findOrphanToolCalls, } from "./core/session-reconcile.js";
@@ -82,7 +88,7 @@ export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
82
88
  export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
83
89
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
84
90
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
85
- export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, } from "./tools/fs/index.js";
91
+ export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, READ_DENY_HOME_WORD, readDenyPatternRun, readDenyHomesOf, compileWriteDeny, DENY_FACES, } from "./tools/fs/index.js";
86
92
  export { deploymentFaceClampNotice, resolveContainmentFace, } from "./tools/fs/index.js";
87
93
  export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, WRITE_PROTECTION_TARGET_VIEWS, } from "./core/write-protect.js";
88
94
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, } from "./core/tool-result-store.js";
@@ -111,7 +117,7 @@ export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
111
117
  export { renderTaskNotificationXml, RESUPPLIED_FRAME_PREAMBLE, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, HANDBACK_STATES, COMPACTION_KINDS, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
112
118
  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";
113
119
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
114
- export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
120
+ export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
115
121
  export {} from "./core/tool-policy.js";
116
122
  export { effectivePathTargetOf, pathTargetBaseOf } from "./core/effective-path-target.js";
117
123
  export { pathFamilyOf, isAbsoluteForFamily, pathIdentityOf, win32NamespaceScreen, WIN32_NAMESPACE_REFUSALS } from "./tools/fs/safety.js";
@@ -148,9 +154,12 @@ export { hasScheduler, isValidCronExpr, SchedulerError } from "./core/scheduler.
148
154
  export { createSchedulerTools, SCHEDULE_WAKEUP_TOOL_NAME, AUTONOMOUS_LOOP_SENTINEL, AUTONOMOUS_LOOP_DYNAMIC_SENTINEL, } from "./tools/scheduler-tools.js";
149
155
  export { resolveAutonomousLoopPrompt, AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT, } from "./tools/loop-tick.js";
150
156
  export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.js";
151
- export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, decisionText, resolveAsk, toolPolicyNameSets, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
157
+ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
152
158
  export {} from "./core/read-root-grant-screen.js";
153
- export { ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL, RULE_STORE_UNREADABLE_KINDS, isRuleStoreUnreadable, PERSISTED_RULE_MANDATES, isPersistedRuleMandate } from "./core/ask-origin.js";
159
+ export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate } from "./core/ask-origin.js";
160
+ export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, RELEASABLE_ASK_CLASSES, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
161
+ export { modePolicyOf, PERMISSION_MODE_FOLD_REASONS, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
162
+ export { readDenyClassifierRules, renderClassifierDenyRules } from "./core/auto-mode-prompt.js";
154
163
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-caps.js";
155
164
  export { SETTLEMENT_KINDS, isSettlementKind, 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";
156
165
  export {} from "./core/hooks.js";
@@ -298,7 +298,7 @@ function pickWhitelist(scriptSpec) {
298
298
  if (!Array.isArray(readDenyPatternsRaw)) {
299
299
  throw new WorkflowScriptError('agent(spec): `readDenyPatterns` must be an array of deny entries (a "/"-separated segment run such as ".ssh", or { pattern, caseSensitive? })');
300
300
  }
301
- const entries = mapUntrustedArray(readDenyPatternsRaw, "agent(spec): `readDenyPatterns`", MAX_SCRIPT_DENY_ENTRIES, "Every entry is judged against every path the child reads, for the child's whole run; the deployment's own entries, and whichever built-in tiers the deployment switched on (none by default), are in force on top of these.", pickReadDenyEntry);
301
+ const entries = mapUntrustedArray(readDenyPatternsRaw, "agent(spec): `readDenyPatterns`", MAX_SCRIPT_DENY_ENTRIES, "Every entry is judged against every path the child reads, for the child's whole run; the deployment's own entries, and whichever built-in tiers the deployment has on (by default the credentials and home agent-config tiers), are in force on top of these.", pickReadDenyEntry);
302
302
  try {
303
303
  compileReadDeny(entries, "agent(spec).readDenyPatterns");
304
304
  }
@@ -52,7 +52,7 @@ parentReadFace?: () => TaskSpec["readFace"],
52
52
  * stricter-wins into every spawned child on its own seat (the two seats never read each other). */
53
53
  parentWriteFace?: () => TaskSpec["writeFace"],
54
54
  /** Twin of the above for the deny-set additions (the extra entries the host's own deployment/task
55
- * layers stacked on; the built-in rows the DEPLOYMENT switched on — none by default — apply beside them). */
55
+ * layers stacked on; the built-in rows the DEPLOYMENT's tier selection activates apply beside them). */
56
56
  parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"],
57
57
  /** #345 ③ — the HOST task's write-hands clamp is ON (`spec.handsReadOnly === true`). Injected
58
58
  * tighten-only into every spawned child, like the read-face clamp above; see the injection
@@ -1,10 +1,10 @@
1
1
  export const WORKFLOW_SIZE_GUIDELINES = ["unrestricted", "small", "medium", "large"];
2
2
  export const WORKFLOW_SIZE_GUIDELINE_DEFAULT = "medium";
3
- export const WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS = {
3
+ export const WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS = Object.freeze({
4
4
  small: 5,
5
5
  medium: 15,
6
6
  large: 50,
7
- };
7
+ });
8
8
  export function normalizeWorkflowSizeGuideline(value) {
9
9
  return WORKFLOW_SIZE_GUIDELINES.includes(value) ? value : undefined;
10
10
  }
@@ -96,8 +96,8 @@ export interface FileStorageBackendOptions {
96
96
  * grows the seat later is wired here in the same constructor.
97
97
  *
98
98
  * The payload is the UNION shape ({@link FileStorageCorruptReadInfo}): `path` + `reason` always,
99
- * with the session-policy face's `sessionId`/`principal` present only when the reading store keys by
100
- * them. One sink for one failure grammar — a host does not register three.
99
+ * `sessionId` when the disclosure is about one session (the session store and the session-policy face),
100
+ * `principal` when the read was keyed by one. One sink for one failure grammar — a host does not register three.
101
101
  */
102
102
  onCorruptRead?: (info: FileStorageCorruptReadInfo) => void;
103
103
  }
@@ -110,7 +110,9 @@ export interface FileStorageBackendOptions {
110
110
  * that replay's count (`"replay"`) from a disclosure about a file or directory as a whole (`"file"`).
111
111
  */
112
112
  export type FileStorageCorruptReadInfo = CorruptReadInfo & {
113
- /** Present only when the reading store keys by session (the session-policy face). */
113
+ /** Present when the disclosure is about ONE session: the session-policy face's rows, and the session store's
114
+ * log of that session (its skipped records and tally, or the log skipped from a listing). Absent on a
115
+ * disclosure about a directory as a whole or about a store not keyed by session. */
114
116
  sessionId?: string;
115
117
  /** Present only when the read was keyed by a principal. */
116
118
  principal?: string;
@@ -14,8 +14,14 @@ export interface FileSessionRepoOptions {
14
14
  * Never fires on plain ENOENT / `not_found` — that is honest absence, and the torn TAIL stays silent
15
15
  * too (dying mid-write is the append log's documented crash-recovery shape, not corruption).
16
16
  * Swallow-guarded: a throwing sink cannot re-introduce the fault the fail-open avoids.
17
+ *
18
+ * `sessionId` rides every disclosure about ONE session — its log's skipped records and tally, and its log
19
+ * skipped from a listing — naming the session the repo was reading (on a listing, the id its file name
20
+ * spells); a disclosure about the sessions directory itself carries none.
17
21
  */
18
- onCorruptRead?: (info: CorruptReadInfo) => void;
22
+ onCorruptRead?: (info: CorruptReadInfo & {
23
+ sessionId?: string;
24
+ }) => void;
19
25
  }
20
26
  export declare class FileSessionRepo implements SessionRepo {
21
27
  private readonly dir;
@@ -26,7 +32,7 @@ export declare class FileSessionRepo implements SessionRepo {
26
32
  * check on the way out possible without pinning a storage the table itself only holds weakly. */
27
33
  private readonly joined;
28
34
  constructor(root: string, opts?: FileSessionRepoOptions);
29
- /** The one delivery point for {@link FileSessionRepoOptions.onCorruptRead}. */
35
+ /** The one delivery point for {@link FileSessionRepoOptions.onCorruptRead}'s whole-file level; `sessionId` when the file is one session's log. */
30
36
  private disclose;
31
37
  private pathFor;
32
38
  /**
@@ -74,8 +74,8 @@ export class FileSessionRepo {
74
74
  ensureDir(this.tmpDir);
75
75
  this.onCorruptRead = guardCorruptSink(opts?.onCorruptRead);
76
76
  }
77
- disclose(path, reason) {
78
- this.onCorruptRead?.({ kind: "file", path, reason });
77
+ disclose(path, reason, sessionId) {
78
+ this.onCorruptRead?.({ kind: "file", path, reason, ...(sessionId !== undefined ? { sessionId } : {}) });
79
79
  }
80
80
  pathFor(id) {
81
81
  return join(this.dir, `${sanitizePathComponent(id)}${SUFFIX}`);
@@ -89,7 +89,7 @@ export class FileSessionRepo {
89
89
  const lines = readJsonlRecords(path, (info) => {
90
90
  if (info.kind === "replay")
91
91
  tally = info;
92
- this.onCorruptRead?.(info);
92
+ this.onCorruptRead?.({ ...info, sessionId: id });
93
93
  }, SESSION_LINE_JUDGE);
94
94
  let meta;
95
95
  const entries = [];
@@ -174,7 +174,7 @@ export class FileSessionRepo {
174
174
  }
175
175
  catch (err) {
176
176
  if (!(err instanceof SessionError && err.code === "not_found")) {
177
- this.disclose(this.pathFor(id), `session log unreadable (${thrownText(err)}) — skipped from the listing`);
177
+ this.disclose(this.pathFor(id), `session log unreadable (${thrownText(err)}) — skipped from the listing`, id);
178
178
  }
179
179
  }
180
180
  }
@@ -203,7 +203,7 @@ export class FileSessionRepo {
203
203
  }
204
204
  catch (err) {
205
205
  if (!(err instanceof SessionError && err.code === "not_found")) {
206
- this.disclose(this.pathFor(id), `session log unreadable (${thrownText(err)}) — skipped from the placed listing`);
206
+ this.disclose(this.pathFor(id), `session log unreadable (${thrownText(err)}) — skipped from the placed listing`, id);
207
207
  }
208
208
  continue;
209
209
  }
@@ -1,3 +1,12 @@
1
+ /**
2
+ * The `Artifact` tool: one tool, `action`-multiplexed, assembled from the host's capability declaration.
3
+ * The action enum, the parameter set and the description are all functions of `capabilities` — the
4
+ * implementation behind the seam is never inspected. Session state (the version tracked per URL and the
5
+ * local-path → URL binding that makes "same file path ⇒ same page" hold) lives in this factory's closure,
6
+ * fresh per run, never checkpointed.
7
+ */
8
+ import { type PermissionMode } from "../../core/ask-origin.js";
9
+ import { type EngineNotice } from "../../core/engine-notice.js";
1
10
  import { type ArtifactHost, type ArtifactHostCapabilities } from "../../core/artifact-host.js";
2
11
  import { type OnAsk } from "../../core/tool-policy.js";
3
12
  import type { ToolSpec } from "../../core/types.js";
@@ -42,6 +51,14 @@ export interface ArtifactToolOptions {
42
51
  * = the run has no ask face, and a shared read is then REFUSED fail-closed (`artifact.refused`, content withheld) —
43
52
  * never silently delivered. */
44
53
  onAsk?: OnAsk;
54
+ /** The run's effective permission mode, read at each shared-read consent (a steer moves it). The consent ask is
55
+ * arbitrated as the gate's asks are (class `artifact_shared_read`): `bypassPermissions` RELEASES the read — delivered,
56
+ * announced once per run through `onNotice`, and NOT remembered as an approval (a mode release is not a person's
57
+ * nod; a later `default` leg asks) — `dontAsk` refuses it, every other mode asks the approver. Absent ⇒ no arbitration
58
+ * (the approver decides, or the read is withheld with none). */
59
+ permissionMode?: () => PermissionMode;
60
+ /** The notice sink the release account rides (`permission.ask_released_by_mode`, once per run for this class). */
61
+ onNotice?: (notice: EngineNotice) => void;
45
62
  }
46
63
  /** Supporting files per publish (upstream cap); enforced in the schema AND in the body, for callers that bypass the schema. */
47
64
  export declare const MAX_FILES_PER_PUBLISH = 255;
@@ -1,3 +1,5 @@
1
+ import { arbitrateByMode, classifyAsk } from "../../core/ask-origin.js";
2
+ import { deliverEngineNotice } from "../../core/engine-notice.js";
1
3
  import { createHash } from "node:crypto";
2
4
  import { Type } from "typebox";
3
5
  import { ARTIFACT_ACTION_CONCURRENCY_SAFE, ARTIFACT_COMMIT_THROW_OUTCOME, ARTIFACT_FAILURE_RESULT_CODE, ARTIFACT_HOST_ERROR_CODE, ARTIFACT_LIST_SCOPES, ARTIFACT_PLANE_CONTENT_ORIGIN, ARTIFACT_PUBLISH_CONTEXTS, } from "../../core/artifact-host.js";
@@ -128,7 +130,8 @@ function buildSchema(caps) {
128
130
  const invalid = (text) => errorResult(`Error (Artifact): ${text}`, { type: "artifact", invalidArguments: true });
129
131
  const typed = (code, text, extra = {}) => errorResult(`Error (Artifact): ${text}`, { type: "artifact", code: ARTIFACT_FAILURE_RESULT_CODE[code], ...extra });
130
132
  export function createArtifactTool(opts) {
131
- const { host, capabilities: caps, runId, taskId, sessionId, agentName, readLocalFile, onAsk } = opts;
133
+ const { host, capabilities: caps, runId, taskId, sessionId, agentName, readLocalFile, onAsk, permissionMode, onNotice } = opts;
134
+ let releaseAnnounced = false;
132
135
  const versions = new Map();
133
136
  const bindings = new Map();
134
137
  const occurrences = new Map();
@@ -316,6 +319,18 @@ export function createArtifactTool(opts) {
316
319
  const sharedReadConsent = (url, ref, ctx) => {
317
320
  if (sharedReadApproved.has(url))
318
321
  return Promise.resolve({ ok: true });
322
+ if (permissionMode !== undefined) {
323
+ const verdict = arbitrateByMode(permissionMode(), classifyAsk({ action: "ask", requiresRealApproval: true, guard: "artifact_shared_read" }, { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} }));
324
+ if (verdict.kind === "released") {
325
+ if (!releaseAnnounced && onNotice !== undefined) {
326
+ releaseAnnounced = true;
327
+ deliverEngineNotice(onNotice, { code: "permission.ask_released_by_mode", message: `the ${verdict.mode} permission mode released a shared-artifact read (${inlineUntrusted(url, 300)}) without asking anyone; further shared reads of this run under the mode are released the same way`, detail: { mode: verdict.mode, askClass: verdict.askClass, count: 1, firstToolCallId: ctx.toolCallId, ...(sessionId !== undefined ? { sessionId } : {}), runId } });
328
+ }
329
+ return Promise.resolve({ ok: true });
330
+ }
331
+ if (verdict.kind === "denied_by_mode")
332
+ return Promise.resolve({ ok: false, reason: `the ${verdict.mode} permission mode refuses to read another person's artifact without approval` });
333
+ }
319
334
  const pending = consentInFlight.get(url);
320
335
  if (pending !== undefined)
321
336
  return pending;
@@ -15,7 +15,7 @@ import { type LexedCommand, type LexedSegment, type LexedWord, type ShellLex, ty
15
15
  * hostname), and `file` (`-C -m` → compiles/writes a magic file) were REMOVED — each is read-only by NAME
16
16
  * but state-MUTATING with args, which an argv[0]-only filter cannot tell apart. Leaving them in defeated
17
17
  * both the `effect:read` truthfulness here and the classifier's irreversibility promise (a `date -s` would
18
- * auto-allow an irreversible clock jump under `shellGate:"classify"`).
18
+ * auto-allow an irreversible clock jump on the full shell face).
19
19
  *
20
20
  * Rows 3-4 are the CC-anchored expansion — the members of CC's safe-command
21
21
  * set (pretty223.js:420513-420565 `e6s`; identical in the 220 corpus; the 220→245 campaign diff records
@@ -223,7 +223,7 @@ export declare function splitShellCompoundSegments(source: string, options?: {
223
223
  reject: string;
224
224
  };
225
225
  /**
226
- * Compound read-only classification for the `bash` CLASSIFY face (shellGate:"classify").
226
+ * Compound read-only classification for the `bash` full-shell face (the shape tier).
227
227
  * Splits a command on the connector set `;` `&&` `||` `|` and passes every segment through
228
228
  * {@link coarseReadonlyCheck} (the single {@link parseLeadingCommandName} parser + bare-name allowlist).
229
229
  * All segments green ⇒ read-only. Returns a reason when rejected, else undefined.
@@ -304,8 +304,11 @@ export interface BashReadonlyRootBoundary {
304
304
  * EVERY read face wires this one seat, the `bash_readonly` shell included, at BOTH of its judging
305
305
  * moments — roots answer WHERE a command may read and the allowlist WHICH verb runs, neither answers
306
306
  * WHICH FILES are off the table. What this seat is and is not is stated once, at
307
- * `shell.read_boundary.friction_floor`. */
308
- denyMatch?: (resolvedPath: string) => string | null;
307
+ * `shell.read_boundary.friction_floor`. `walked` — the path is a member a traversal enumerated, not a
308
+ * spelling of the command ({@link import("./read-deny.js").ReadDenyMatcher.matchPath}). */
309
+ denyMatch?: (resolvedPath: string, opts?: {
310
+ readonly walked?: boolean;
311
+ }) => string | null;
309
312
  /** What the wired {@link denyMatch} judge would apply — the ACTIVE deny patterns. Read for ONE question:
310
313
  * is there anything for a traversal to defeat? EMPTY ⇒ the judge refuses nothing, so a walk under it
311
314
  * bypasses nothing and {@link CompoundReadonlyVerdict.recursiveReadPaths} is not minted. ABSENT ⇒ the
@@ -328,7 +331,7 @@ export interface BashReadonlyRootBoundary {
328
331
  */
329
332
  export interface CompoundReadonlyVerdict {
330
333
  /** Demotion reason (the same string {@link classifyCompoundReadonly} returns), or undefined: the
331
- * command's SHAPE is not one the classify doctrine auto-allows. WHERE the command reads is not this
334
+ * command's SHAPE is not one the shape tier auto-allows. WHERE the command reads is not this
332
335
  * verdict's business — that is the gate's shell read station (`shell-read-check.ts`). */
333
336
  reason?: string;
334
337
  }
@@ -350,6 +353,24 @@ export declare function formatOutOfRootReadApprovalOption(directory: string): st
350
353
  * operand scan below, which is safe for an unknown command because a bare word can only ever name
351
354
  * something under the working directory. */
352
355
  export declare const NO_PATH_OPERAND_COMMANDS: ReadonlySet<string>;
356
+ /** The no-path-operand programs that EVALUATE a word (`test -v 'a[$(…)]'` runs the substitution in the subscript): a word
357
+ * the shell or that evaluation rewrites is handed over as any program's is; only their settled words name nothing. */
358
+ export declare const NO_PATH_OPERAND_EVALUATORS: ReadonlySet<string>;
359
+ /** The builtins whose `NAME=value` arguments are assignments bash performs (and exports, for `export`), never paths. */
360
+ export declare const DECLARATION_BUILTINS: ReadonlySet<string>;
361
+ /** The shell BUILTINS that open no file, start no program and evaluate nothing: an external program's loader reads its
362
+ * environment (`LD_PRELOAD` for `sleep`), `printf` reads `TZ` for `%(…)T` and assigns through `-v`, `test -v` and an
363
+ * array subscript evaluate arithmetic (which runs a `$(…)` inside it). A declaration builtin counts only bare — no option
364
+ * word (`-i` evaluates the value) — and every inert segment's words carry no `[` `]` `$` or backquote. */
365
+ export declare const INERT_BUILTINS: ReadonlySet<string>;
366
+ /** The indices of the SETTLED words a {@link MESSAGE_SLOTS} row reads as message text under the subcommand's grammar:
367
+ * the value of `-m` (attached, or the next word) or of `--message` (`=`, or the next word; an unambiguous prefix of it
368
+ * counts). A word the shell rewrites is never claimed, and neither is any word another option consumes — those words
369
+ * stay the verb-blind walk's. */
370
+ export declare function messageSlotWords(program: string, words: readonly {
371
+ readonly folded: string;
372
+ readonly expands: unknown;
373
+ }[], programIndex: number): Set<number>;
353
374
  /** True for a token that can denote a filesystem location OUTSIDE the working directory. A bare word
354
375
  * (`foo`, `a.txt`, a grep pattern, an awk program) cannot: with no separator and no `..` it always
355
376
  * resolves under the cwd, which the caller's cwd governance already covers.
@@ -934,8 +955,8 @@ export declare const POLL_LOOP_MAX_BEATS = 120;
934
955
  export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
935
956
  /**
936
957
  * The BOUNDED READ-ONLY POLL LOOP arm of the classify face. The compound classifier above rejects any
937
- * control structure (`for` is not an allowlisted name), which makes `shellGate:"classify"` behave like
938
- * `"always"` for a monitoring consumer whose whole idiom is `for i in $(seq 1 8); do tail -n 5 x.log;
958
+ * control structure (`for` is not an allowlisted name), which makes the shape tier behave like
959
+ * the `"always"` floor for a monitoring consumer whose whole idiom is `for i in $(seq 1 8); do tail -n 5 x.log;
939
960
  * sleep 2; done`. This arm accepts EXACTLY that grammar and nothing else. Returns the rejection
940
961
  * reason, or undefined when the command matches the grammar (⇒ safe to treat as reversible).
941
962
  *
@@ -162,7 +162,95 @@ export function formatOutOfRootReadApprovalOption(directory) {
162
162
  export const NO_PATH_OPERAND_COMMANDS = new Set([
163
163
  "pwd", "echo", "printf", "whoami", "uname", "which", "tr", "basename", "dirname",
164
164
  "cal", "uptime", "id", "groups", "nproc", "locale", "free", "true", "false", "seq", "expr", "type", "sleep",
165
+ "test", "[",
165
166
  ]);
167
+ export const NO_PATH_OPERAND_EVALUATORS = new Set(["test", "["]);
168
+ export const DECLARATION_BUILTINS = new Set(["export", "declare", "typeset", "local", "readonly"]);
169
+ export const INERT_BUILTINS = new Set(["echo", "true", "false", "pwd", ...DECLARATION_BUILTINS]);
170
+ const MESSAGE_SLOTS = {
171
+ git: {
172
+ globalPlain: ["-p", "--paginate", "-P", "--no-pager", "--bare", "--no-replace-objects", "--literal-pathspecs", "--glob-pathspecs", "--noglob-pathspecs", "--icase-pathspecs", "--no-optional-locks"],
173
+ globalValue: ["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env", "--super-prefix"],
174
+ subcommands: {
175
+ commit: { plain: "apsnvqeioz", optional: "Su", value: "mFCct", longValue: ["message", "file", "template", "pathspec-from-file", "author", "date", "cleanup", "fixup", "squash", "trailer", "reuse-message", "reedit-message"] },
176
+ tag: { plain: "asfdvle", optional: "n", value: "mFu", longValue: ["message", "file", "local-user", "cleanup", "trailer", "sort", "format", "contains", "no-contains", "points-at", "merged", "no-merged"] },
177
+ merge: { plain: "nqve", optional: "S", value: "mFsX", longValue: ["message", "file", "strategy", "strategy-option", "into-name", "cleanup"] },
178
+ stash: { plain: "pkuaqS", optional: "", value: "m", longValue: ["message", "pathspec-from-file"] },
179
+ },
180
+ },
181
+ };
182
+ export function messageSlotWords(program, words, programIndex) {
183
+ const out = new Set();
184
+ const row = MESSAGE_SLOTS[program];
185
+ if (row === undefined)
186
+ return out;
187
+ let j = programIndex + 1;
188
+ for (; j < words.length; j++) {
189
+ const w = words[j].folded;
190
+ if (!w.startsWith("-"))
191
+ break;
192
+ if (row.globalValue.includes(w))
193
+ j++;
194
+ else if (!row.globalPlain.includes(w) && !row.globalValue.some((o) => o.startsWith("--") && w.startsWith(`${o}=`)))
195
+ return out;
196
+ }
197
+ const sub = row.subcommands[words[j]?.folded ?? ""];
198
+ if (sub === undefined)
199
+ return out;
200
+ const settled = (k) => words[k] !== undefined && words[k].expands === false;
201
+ let pending;
202
+ for (let k = j + 1; k < words.length; k++) {
203
+ const w = words[k].folded;
204
+ if (pending !== undefined) {
205
+ if (pending === "message" && settled(k))
206
+ out.add(k);
207
+ pending = undefined;
208
+ continue;
209
+ }
210
+ if (w === "--")
211
+ break;
212
+ if (!settled(k)) {
213
+ pending = "other";
214
+ continue;
215
+ }
216
+ if (!w.startsWith("-") || w === "-")
217
+ continue;
218
+ if (w.startsWith("--")) {
219
+ const eq = w.indexOf("=");
220
+ const name = w.slice(2, eq < 0 ? undefined : eq);
221
+ const takers = sub.longValue.filter((n) => n.startsWith(name));
222
+ const message = name.length > 0 && takers.length > 0 && takers.every((n) => n === "message") || name === "message";
223
+ if (eq >= 0) {
224
+ if (message)
225
+ out.add(k);
226
+ continue;
227
+ }
228
+ if (takers.length > 0)
229
+ pending = message ? "message" : "other";
230
+ continue;
231
+ }
232
+ const letters = w.slice(1);
233
+ for (let i = 0; i < letters.length; i++) {
234
+ const c = letters[i];
235
+ if (sub.plain.includes(c))
236
+ continue;
237
+ if (sub.optional.includes(c))
238
+ break;
239
+ if (sub.value.includes(c)) {
240
+ if (i < letters.length - 1) {
241
+ if (c === "m")
242
+ out.add(k);
243
+ }
244
+ else
245
+ pending = c === "m" ? "message" : "other";
246
+ break;
247
+ }
248
+ pending = "other";
249
+ break;
250
+ }
251
+ }
252
+ return out;
253
+ }
166
254
  const XARGS_TARGETS_ACCEPTED_UNJUDGED = NO_PATH_OPERAND_COMMANDS;
167
255
  export function isPathShapedToken(tok) {
168
256
  return tok.includes("/") || tok.startsWith("~") || tok === "." || tok === "..";
@@ -18,8 +18,8 @@ export declare function containmentFaceSeatName(kind: ContainmentFaceKind, layer
18
18
  * screen the seats on runs that never mount hands. The resolver itself also runs on every hands-less
19
19
  * leg; this screen still fires first, so a garbage value refuses before any leg-shape branching. */
20
20
  export declare function assertContainmentFaceValue(v: unknown, kind: ContainmentFaceKind, layer: ContainmentFaceLayer): ContainmentFace | undefined;
21
- /** Inputs to {@link resolveContainmentFace} — all structural/declaration facts, never permission modes
22
- * (a deliberate axis separation: this shape carries facts, not verdicts). */
21
+ /** Inputs to {@link resolveContainmentFace} — structural/declaration facts; a permission mode enters only as
22
+ * the face its table row states ({@link modeFace}), never as a mode word. */
23
23
  export interface ContainmentFaceInputs {
24
24
  /** Which face is being resolved. Selects the seat spellings and the mint codes; the ORDER below is
25
25
  * one body, identical for both. */
@@ -41,8 +41,13 @@ export interface ContainmentFaceInputs {
41
41
  * tool-band assembly from spec-time facts (env kind / handsReadOnly / Bash exclusion — every
42
42
  * exclusion source is spec-time-frozen, so this is always computable). When false, the roots fence
43
43
  * is the ONLY boundary this face has, so no IMPLICIT default may open it; an EXPLICIT "open" on
44
- * either seat still wins (row 4 of the order is a default, not a clamp). */
44
+ * either seat or the mode's face still wins (row 4 of the order is a default, not a clamp). */
45
45
  fullShellReachable: boolean;
46
+ /** The face the leg's permission MODE states (`ModePolicy.writeFace`: `bypassPermissions` ⇒ "open"; absent ⇒
47
+ * the mode states nothing). Row 3′: read after the explicit seats and before the defaults — a seat's word (the
48
+ * deployment's ceiling, a task's tighten) outranks it, and neither the read-only mount nor org governance
49
+ * consults it. */
50
+ modeFace?: ContainmentFace;
46
51
  /** Observation hook for the ONE silent branch of the order: row 1's deployment-seat clamp
47
52
  * (readOnly mount + deps "open" + NO task seat). Fired exactly when the deps declaration was the
48
53
  * deciding seat and the mount overrode it — not when a task-level "roots" pinned the same value
@@ -63,6 +68,7 @@ export interface ContainmentFaceInputs {
63
68
  * 2. org-governed: the task seat's "open" refuses (task layer only tightens); deps "open" wins;
64
69
  * otherwise ROOTS.
65
70
  * 3. explicit seat: spec ?? deps.
71
+ * 3′. the mode's face ({@link ContainmentFaceInputs.modeFace}).
66
72
  * 4. bash-less mount: ROOTS (the fence is the only boundary there — "the fence guards nothing bash
67
73
  * reaches anyway" does not hold, so no implicit default may open it).
68
74
  * 5. default: ROOTS — the engine never opens implicitly.
@@ -67,6 +67,8 @@ export function resolveContainmentFace(i) {
67
67
  return spec;
68
68
  if (deps !== undefined)
69
69
  return deps;
70
+ if (i.modeFace !== undefined)
71
+ return i.modeFace;
70
72
  if (!i.fullShellReachable)
71
73
  return "roots";
72
74
  return "roots";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The READ station of the file tools (Read / Grep / Glob) — the read face's twin of the shell read station, one gate
3
+ * input shape ({@link ShellReadCheck}) so the gate, the approved-edit re-judge and the resumed leg's belt read both:
4
+ * before approval, where the call's declared path target lands is resolved exactly as the tool will resolve it
5
+ * (`resolveKey` over the same spelling, base, roots, deny table and containment face), and a target OUTSIDE the read
6
+ * roots is an ORDINARY question — a person answers it, a persisted `Read(//dir/**)` row or the auto-mode classifier
7
+ * may, a permission mode arbitrates it — with the directory whose grant clears it on the card. The deny table and
8
+ * every other refusal stay the tool's own: this station judges containment only, after the deny judgment.
9
+ *
10
+ * A call whose out-of-root read the gate put to a question is ADMITTED for that one target when the gate applies the
11
+ * verdict ({@link ShellReadVerdict.admit}): the tool reads it once ({@link FileReadStation.admitted}), on the canonical
12
+ * key the station judged — a target that resolves elsewhere by then is refused by the tool's own fence, and a verdict
13
+ * the gate never received (its wait elapsed, the judgment threw) admits nothing.
14
+ */
15
+ import type { ExecutionEnv } from "../../internal/harness-types.js";
16
+ import type { ContainmentFace } from "./containment-face.js";
17
+ import { type ReadDenyMatcher } from "./read-deny.js";
18
+ import type { ShellReadCheck } from "./shell-read-check.js";
19
+ export interface FileReadStation {
20
+ readonly check: ShellReadCheck;
21
+ /** The station's membership: the tool names whose declared read target it judges. */
22
+ readonly tools: ReadonlySet<string>;
23
+ /** Did the gate put THIS call's read of `canonicalKey` outside the roots to a question it then let through? Consumed
24
+ * on a match: the admission covers one read of one target. */
25
+ readonly admitted: (toolCallId: string | undefined, canonicalKey: string) => boolean;
26
+ }
27
+ export interface FileReadStationOptions {
28
+ readonly env: ExecutionEnv;
29
+ readonly rootCanonical: string;
30
+ /** The extra read roots, read per call (a grant widens the live set). */
31
+ readonly roots: () => readonly string[];
32
+ readonly readDeny?: ReadDenyMatcher;
33
+ readonly readFace?: ContainmentFace;
34
+ readonly homeDir?: string;
35
+ /** The file tools this leg mounts (their catalog path target is the one judged). */
36
+ readonly tools: ReadonlySet<string>;
37
+ /** A target no answer may admit (the store-isolation rule's judgment, `rootAdmittingArtifactStore`): it is left to
38
+ * the tool's own refusal, no question is raised for it. */
39
+ readonly excluded?: (canonicalKey: string) => boolean;
40
+ /** A target the tools' own fence admits without the roots (the engine's background output file,
41
+ * `backgroundOutputReadExemption`): no question is put for it. */
42
+ readonly exempt?: (canonicalKey: string) => boolean;
43
+ }
44
+ export declare function createFileReadStation(opts: FileReadStationOptions): FileReadStation;
45
+ /** One gate input over the leg's read stations: each call is judged by the station whose membership holds the first of
46
+ * its names any station holds (the spelling first). */
47
+ export declare function composeReadChecks(stations: ReadonlyArray<{
48
+ readonly check: ShellReadCheck;
49
+ readonly tools: ReadonlySet<string>;
50
+ }>): ShellReadCheck | undefined;
@@ -0,0 +1,79 @@
1
+ import { effectivePathSpellingOf } from "../../core/effective-path-target.js";
2
+ import { firstNamed, pathTargetOf, ruleNamesOf } from "../../core/tool-registry.js";
3
+ import { fitMiddle, READ_DENY_SENTENCE_PART_MAX } from "./read-deny.js";
4
+ import { resolveKey, violationDetails } from "./safety.js";
5
+ import { grantOfferFor, parentDirLexically } from "./shell-read-judge.js";
6
+ const ADMISSION_MAX = 1024;
7
+ const VOUCH = { disposition: "vouch", finding: "none" };
8
+ export function createFileReadStation(opts) {
9
+ const admissions = new Map();
10
+ const remember = (toolCallId, key) => {
11
+ if (admissions.size >= ADMISSION_MAX) {
12
+ const oldest = admissions.keys().next().value;
13
+ if (oldest !== undefined)
14
+ admissions.delete(oldest);
15
+ }
16
+ admissions.set(toolCallId, key);
17
+ };
18
+ const check = {
19
+ judge(call, args, cwd, signal, callOpts) {
20
+ const member = ruleNamesOf(call).find((n) => opts.tools.has(n));
21
+ if (member === undefined)
22
+ return null;
23
+ const toolName = call.toolName;
24
+ const selection = effectivePathSpellingOf({ toolName, args }, pathTargetOf(member), { root: opts.rootCanonical, cwd });
25
+ if (selection === undefined || "refused" in selection || selection.spelled === undefined)
26
+ return null;
27
+ const spelled = selection.spelled;
28
+ return (async () => {
29
+ const extra = opts.roots();
30
+ const r = await resolveKey(opts.env, opts.rootCanonical, spelled, signal, selection.base, extra, undefined, opts.readDeny, opts.readFace);
31
+ if (r.ok || r.violation.code !== "path_not_in_root" || r.violation.target === undefined || opts.excluded?.(r.violation.target) === true || opts.exempt?.(r.violation.target) === true)
32
+ return VOUCH;
33
+ const target = r.violation.target;
34
+ const toolCallId = callOpts.toolCallId;
35
+ const roots = [opts.rootCanonical, ...extra];
36
+ const details = violationDetails(r.violation);
37
+ const allowed = fitMiddle(roots.join(", "), READ_DENY_SENTENCE_PART_MAX);
38
+ if (callOpts.seat === "rejudge") {
39
+ return { disposition: "refuse", finding: "outside_roots", sentence: `Error (${toolName}): the approved edit reads "${fitMiddle(target, READ_DENY_SENTENCE_PART_MAX)}", outside the directories allowed for this session (${allowed}) — re-submit it as a fresh call and answer its card.`, details };
40
+ }
41
+ const info = pathTargetOf(member)?.absent === "base" ? await opts.env.fileInfo(target, signal) : undefined;
42
+ const readsDirectory = info?.ok === true && info.value.kind === "directory";
43
+ const offer = grantOfferFor(readsDirectory ? target : parentDirLexically(target), target, { roots, ...(opts.homeDir !== undefined ? { homeDir: opts.homeDir } : {}) });
44
+ return {
45
+ disposition: "ordinary",
46
+ finding: "outside_roots",
47
+ sentence: `reads outside the allowed directories for this session: "${fitMiddle(target, READ_DENY_SENTENCE_PART_MAX)}" is not under ${allowed}`,
48
+ details,
49
+ ...(offer !== undefined ? { readRootCandidate: { dir: offer.dir, ...(offer.covers !== undefined ? { covers: offer.covers } : {}), clearsThisAsk: true } } : {}),
50
+ ...(toolCallId !== undefined ? { admit: () => remember(toolCallId, target) } : {}),
51
+ };
52
+ })();
53
+ },
54
+ unanswered: () => VOUCH,
55
+ };
56
+ const admitted = (toolCallId, key) => {
57
+ if (toolCallId === undefined || admissions.get(toolCallId) !== key)
58
+ return false;
59
+ admissions.delete(toolCallId);
60
+ return true;
61
+ };
62
+ return { check, tools: opts.tools, admitted };
63
+ }
64
+ export function composeReadChecks(stations) {
65
+ if (stations.length === 0)
66
+ return undefined;
67
+ if (stations.length === 1)
68
+ return stations[0].check;
69
+ const byName = new Map();
70
+ for (const s of stations)
71
+ for (const n of s.tools)
72
+ if (!byName.has(n))
73
+ byName.set(n, s);
74
+ const stationOf = (call) => firstNamed(byName, ruleNamesOf(call))?.value;
75
+ return {
76
+ judge: (call, args, cwd, signal, callOpts) => stationOf(call)?.check.judge(call, args, cwd, signal, callOpts) ?? null,
77
+ unanswered: (call, cause) => (stationOf(call) ?? stations[0]).check.unanswered(call, cause),
78
+ };
79
+ }