@sema-agent/core 7.13.0 → 7.15.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 (102) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/agents/subagent.d.ts +4 -9
  3. package/dist/agents/subagent.js +16 -12
  4. package/dist/agents/teacher.js +1 -1
  5. package/dist/brain/reasoning.d.ts +23 -0
  6. package/dist/brain/reasoning.js +35 -4
  7. package/dist/brain/stream-engine.js +35 -17
  8. package/dist/brain/timeout.d.ts +25 -4
  9. package/dist/brain/timeout.js +1 -1
  10. package/dist/core/ask-origin.d.ts +30 -11
  11. package/dist/core/ask-origin.js +10 -5
  12. package/dist/core/auto-mode.d.ts +3 -3
  13. package/dist/core/checkpoint-seat.d.ts +18 -0
  14. package/dist/core/checkpoint-seat.js +6 -0
  15. package/dist/core/checkpoint-store.d.ts +23 -31
  16. package/dist/core/checkpoint-store.js +7 -4
  17. package/dist/core/engine-notice.d.ts +3 -1
  18. package/dist/core/env-knob-announce.d.ts +16 -0
  19. package/dist/core/env-knob-announce.js +14 -0
  20. package/dist/core/gate-fold.js +2 -0
  21. package/dist/core/gate-lanes.js +21 -12
  22. package/dist/core/gate-outcome.d.ts +42 -3
  23. package/dist/core/gate-outcome.js +22 -0
  24. package/dist/core/governance-codes.js +1 -1
  25. package/dist/core/hooks.d.ts +9 -19
  26. package/dist/core/hooks.js +6 -4
  27. package/dist/core/mcp.d.ts +3 -2
  28. package/dist/core/mcp.js +5 -11
  29. package/dist/core/memory-engine/engine.d.ts +2 -2
  30. package/dist/core/runner/advertised-writable-dirs.d.ts +74 -0
  31. package/dist/core/runner/advertised-writable-dirs.js +48 -0
  32. package/dist/core/runner/assemble-result.d.ts +5 -0
  33. package/dist/core/runner/contracts.d.ts +2 -2
  34. package/dist/core/runner/denial-limit-arms.d.ts +7 -9
  35. package/dist/core/runner/denial-limit-arms.js +8 -10
  36. package/dist/core/runner/gate-exit.d.ts +15 -3
  37. package/dist/core/runner/gate-exit.js +6 -4
  38. package/dist/core/runner/permission-rule-lanes.d.ts +3 -1
  39. package/dist/core/runner/permission-rule-lanes.js +1 -1
  40. package/dist/core/runner/prepare-caps-and-workflow.d.ts +34 -4
  41. package/dist/core/runner/prepare-caps-and-workflow.js +47 -12
  42. package/dist/core/runner/prepare-defer-classify.d.ts +1 -1
  43. package/dist/core/runner/prepare-defer-classify.js +12 -10
  44. package/dist/core/runner/prepare-gate-stations.d.ts +4 -5
  45. package/dist/core/runner/prepare-gate-stations.js +2 -2
  46. package/dist/core/runner/prepare-hands-readface.d.ts +18 -12
  47. package/dist/core/runner/prepare-hands-readface.js +32 -43
  48. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
  49. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  50. package/dist/core/runner/prepare-memory.d.ts +31 -34
  51. package/dist/core/runner/prepare-memory.js +73 -51
  52. package/dist/core/runner/prepare-park-ask.d.ts +5 -6
  53. package/dist/core/runner/prepare-park-ask.js +3 -3
  54. package/dist/core/runner/prepare-policy-chain.d.ts +2 -2
  55. package/dist/core/runner/prepare-policy-chain.js +7 -4
  56. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -0
  57. package/dist/core/runner/prepare-prompt-inputs.js +2 -2
  58. package/dist/core/runner/prepare-question-face.d.ts +10 -8
  59. package/dist/core/runner/prepare-question-face.js +1 -3
  60. package/dist/core/runner/prepare-safety-scan.js +1 -1
  61. package/dist/core/runner/prepare-task.js +60 -53
  62. package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
  63. package/dist/core/runner/prepare-wiring-manifest.js +1 -1
  64. package/dist/core/runner/run-compaction-machinery.js +2 -0
  65. package/dist/core/runner/run-harness-handlers.js +3 -1
  66. package/dist/core/runner/stream-settle-backstop.js +1 -1
  67. package/dist/core/runner/tool-defer-gate.d.ts +86 -0
  68. package/dist/core/runner/tool-defer-gate.js +57 -0
  69. package/dist/core/runner/tool-disclosure.d.ts +0 -36
  70. package/dist/core/runner/tool-disclosure.js +0 -43
  71. package/dist/core/runner-deps.d.ts +16 -9
  72. package/dist/core/runtime-caps.d.ts +21 -0
  73. package/dist/core/runtime-caps.js +5 -1
  74. package/dist/core/task-event.d.ts +11 -2
  75. package/dist/core/task-registry-shared.js +8 -6
  76. package/dist/core/task-result.d.ts +15 -0
  77. package/dist/core/task-spec.d.ts +8 -2
  78. package/dist/core/terminal-cause.d.ts +6 -2
  79. package/dist/core/tool-policy.d.ts +34 -28
  80. package/dist/core/tool-policy.js +29 -5
  81. package/dist/core/tool-roster.js +2 -0
  82. package/dist/core/tool-spec.d.ts +20 -16
  83. package/dist/core/types.d.ts +3 -2
  84. package/dist/core/types.js +1 -0
  85. package/dist/core/wiring-manifest.d.ts +16 -9
  86. package/dist/core/wiring-manifest.js +8 -3
  87. package/dist/index.d.ts +7 -6
  88. package/dist/index.js +5 -4
  89. package/dist/orchestration/run-workflow-tool.d.ts +8 -8
  90. package/dist/orchestration/run-workflow-tool.js +1 -1
  91. package/dist/orchestration/workflow-primitives.d.ts +4 -3
  92. package/dist/orchestration/workflow-primitives.js +3 -3
  93. package/dist/orchestration/workflow-types.d.ts +14 -0
  94. package/dist/orchestration/workflow.d.ts +41 -4
  95. package/dist/orchestration/workflow.js +23 -5
  96. package/dist/tools/fs/fs-bash.d.ts +41 -0
  97. package/dist/tools/fs/fs-bash.js +86 -29
  98. package/dist/tools/fs/fs-shared.js +9 -3
  99. package/dist/tools/fs/read-deny.d.ts +24 -8
  100. package/dist/tools/fs/read-deny.js +20 -1
  101. package/package.json +2 -1
  102. package/test/export-surface.snapshot.json +29 -1
package/dist/index.d.ts CHANGED
@@ -93,7 +93,7 @@ export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core
93
93
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
94
94
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
95
95
  export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, CheckResult, } from "./core/property-harness.js";
96
- export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
96
+ export { bashReversibilityProbe, bashReadBoundaryProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
97
97
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
98
98
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
99
99
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
@@ -138,7 +138,7 @@ export { parkedToolIdentity } from "./core/tool-roster.js";
138
138
  export { type ToolCallIdentity } from "./core/fs-write-gate-policy.js";
139
139
  export { type RuleFaceView, catalogRuleFaceView } from "./core/permission-rules.js";
140
140
  export { DURABLE_HISTORY_NAMES } from "./engine/compaction/utils.js";
141
- export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, type SubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, AUTO_MODE_ARM_REASONS, type AutoModeArmReason, type WiringManifest, type WiringManifestMcpEntry, type WiringFacts, type WiringLegKind, type AskSeamForm, type AskEffective, type QuestionChannelState, type SeamProvenance, type ParkLaneReason, type ManifestDurability, type StaticWiringDeps, type StaticWiringSpec, } from "./core/wiring-manifest.js";
141
+ export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, type SubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, AUTO_MODE_ARM_REASONS, type AutoModeArmReason, type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry, type WiringFacts, type WiringLegKind, type AskSeamForm, type AskEffective, type QuestionChannelState, type SeamProvenance, type ParkLaneReason, type ManifestDurability, type StaticWiringDeps, type StaticWiringSpec, } from "./core/wiring-manifest.js";
142
142
  export { probeParkRoundTrip, durableParkGapOf, durableParkGapFor, PARK_SELFCHECK_SCOPE_PREFIX, type ParkSelfCheckResult, type ParkProbeFinding, type ParkProbeFindingCode, } from "./core/park-selfcheck.js";
143
143
  export { type StoreDurability } from "./core/checkpoint-store.js";
144
144
  export { type StoreFidelity } from "./core/checkpoint-store.js";
@@ -164,8 +164,9 @@ export { createSchedulerTools, type SchedulerToolContext, SCHEDULE_WAKEUP_TOOL_N
164
164
  export { resolveAutonomousLoopPrompt, AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT, type AutonomousLoopPromptOptions, } from "./tools/loop-tick.js";
165
165
  export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.js";
166
166
  export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, decisionText, resolveAsk, toolPolicyNameSets, type ToolPolicyNameSets, type NamedToolPolicy, type ToolPolicyProjection, type ToolPolicyProjectionComponent, type ConstraintChainEntry, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, type ToolPolicy, type ToolCallRequest, type PermissionResult, type DecisionReason, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, type OnAsk, type AskOutcome, type ResolvedAsk, type AskRequest, type AskDelegationProvenance, type AskRuleEvidence, type AskEvidenceAbsence, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
167
- export { type AskOrigin, ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL } from "./core/ask-origin.js";
168
- export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, type GateDisposition, type GateOutcome, screenGateOutcome } from "./core/gate-outcome.js";
167
+ export { type AskOrigin, ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL, RULE_STORE_UNREADABLE_KINDS, type RuleStoreUnreadable, isRuleStoreUnreadable } from "./core/ask-origin.js";
168
+ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource } from "./core/runtime-caps.js";
169
+ export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, 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, screenGateOutcome } from "./core/gate-outcome.js";
169
170
  export { type AskCarry } from "./core/hooks.js";
170
171
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps, type SwappableDepSeat, type SwappableDeps } from "./core/swappable-deps.js";
171
172
  export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, type AutoModeUnavailableCause, 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";
@@ -228,7 +229,7 @@ export { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore
228
229
  export { stripEngineMetadata } from "./internal/llm.js";
229
230
  export { EVENT_PROMPT_REGISTRY, eventDefaultOn, type CompiledEventPrompt, type CompiledMessageInjection, type EventDedupe, type EventDefaultPolicy } from "./prompt-assembly/event-registry.js";
230
231
  export type { CompiledSection, ComposedPrompt, PackSectionDeclaration, PromptCacheClass, PromptCarrier, PromptMutability, PromptOwner, PromptPack, PromptRenderCadence, PromptRuntimeFacts, PromptSlot, PromptTrust, SectionRenderInputs, } from "./prompt-assembly/types.js";
231
- export { type ReasoningIntensity, type ReasoningResolution, type ResolvedReasoning, type ReasoningFormat, DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffExpressible, declaredOffSpelling, type OffCapabilityModel, resolveReasoningProfile, type ReasoningTier, type ReasoningProfileFlags, } from "./brain/reasoning.js";
232
+ export { type ReasoningIntensity, type ReasoningResolution, type ResolvedReasoning, type ReasoningFormat, DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, reasoningEffortLevelsOf, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffExpressible, declaredOffSpelling, type OffCapabilityModel, resolveReasoningProfile, type ReasoningTier, type ReasoningProfileFlags, } from "./brain/reasoning.js";
232
233
  export { DESIGN_REVIEW_PROMPTS, CODE_REVIEW_PROMPT, SCENARIO_REGISTRY, runScenario, type ScenarioId, type CodeReviewMode, type ScenarioProfile, type RunScenarioOptions, type RunScenarioResult, } from "./scenarios/scenario-registry.js";
233
234
  export { teacherMode, TEACHER_PROFILE, type TeacherModePair, type TeacherProfile, } from "./scenarios/teacher-quickstart.js";
234
235
  export { loadOrchestrationEnv, DEFAULT_REASONING_INTENSITY, type OrchestrationMode, type OrchestrationEnv, } from "./scenarios/env.js";
@@ -317,7 +318,7 @@ export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS, type RouteAdjudicationVector } f
317
318
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
318
319
  export { createAssistantMessageEventStream } from "./internal/llm.js";
319
320
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
320
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, TrackFileEditHook, TrackEditRequest, TrackEditResult, FileEditedHook, FileEditedNotice, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, ResumePreflightInfo, ResumePreflightVerdict, EngineNotice, RuntimeCaps, BackgroundChildEvent, DelegationLifecycleEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, TerminalCause, PausedCause, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolInputVerdict, ToolInputValidationContext, ReversibilityVerdict, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
321
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, TrackFileEditHook, TrackEditRequest, TrackEditResult, FileEditedHook, FileEditedNotice, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, ResumePreflightInfo, ResumePreflightVerdict, EngineNotice, RuntimeCaps, BackgroundChildEvent, DelegationLifecycleEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, TerminalCause, PausedCause, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, ShellGateDoctrine, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolInputVerdict, ToolInputValidationContext, ReversibilityVerdict, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
321
322
  export { Type } from "typebox";
322
323
  export type { TSchema, Static } from "typebox";
323
324
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";
package/dist/index.js CHANGED
@@ -72,7 +72,7 @@ export { runExecGate } from "./core/exec-gate.js";
72
72
  export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core/untrusted-text.js";
73
73
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
74
74
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
75
- export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
75
+ export { bashReversibilityProbe, bashReadBoundaryProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
76
76
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
77
77
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
78
78
  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";
@@ -138,8 +138,9 @@ export { createSchedulerTools, SCHEDULE_WAKEUP_TOOL_NAME, AUTONOMOUS_LOOP_SENTIN
138
138
  export { resolveAutonomousLoopPrompt, AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT, } from "./tools/loop-tick.js";
139
139
  export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.js";
140
140
  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";
141
- export { ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL } from "./core/ask-origin.js";
142
- export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, screenGateOutcome } from "./core/gate-outcome.js";
141
+ export { ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL, RULE_STORE_UNREADABLE_KINDS, isRuleStoreUnreadable } from "./core/ask-origin.js";
142
+ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-caps.js";
143
+ 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";
143
144
  export {} from "./core/hooks.js";
144
145
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
145
146
  export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
@@ -186,7 +187,7 @@ export { buildTurnPromptSnapshot, loweringRecordFor, LOWERING_VERSION, PREFIX_IN
186
187
  export { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, } from "./prompt-assembly/artifact-store.js";
187
188
  export { stripEngineMetadata } from "./internal/llm.js";
188
189
  export { EVENT_PROMPT_REGISTRY, eventDefaultOn } from "./prompt-assembly/event-registry.js";
189
- export { DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffExpressible, declaredOffSpelling, resolveReasoningProfile, } from "./brain/reasoning.js";
190
+ export { DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, reasoningEffortLevelsOf, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffExpressible, declaredOffSpelling, resolveReasoningProfile, } from "./brain/reasoning.js";
190
191
  export { DESIGN_REVIEW_PROMPTS, CODE_REVIEW_PROMPT, SCENARIO_REGISTRY, runScenario, } from "./scenarios/scenario-registry.js";
191
192
  export { teacherMode, TEACHER_PROFILE, } from "./scenarios/teacher-quickstart.js";
192
193
  export { loadOrchestrationEnv, DEFAULT_REASONING_INTENSITY, } from "./scenarios/env.js";
@@ -277,7 +277,7 @@ export interface RunWorkflowToolDeps {
277
277
  * tighten-only ruling; the workflow lane was the remaining gap — a read-only host's workflow
278
278
  * children spawned with full write hands). Filled ONLY when the clamp is ON (`true`), exactly
279
279
  * like the ctx seat, so an unclamped deployment's mount gains no key. Known at prepare-time
280
- * (frozen TaskSpec snapshot — no lazy getter needed, the `parentCheckpointStoreDisabled` shape). */
280
+ * (frozen TaskSpec snapshot — no lazy getter needed, the `parentCheckpointStore` shape). */
281
281
  parentHandsReadOnly?: true;
282
282
  /** #345 ③ — the HOST task's hard-headless clamp (`interactiveTools: false`), same carriage rules
283
283
  * as `parentHandsReadOnly` above: only the DISABLING value travels, mirroring the subagent lane's
@@ -352,13 +352,13 @@ export interface RunWorkflowToolDeps {
352
352
  autoModeReview?: () => {
353
353
  decider: import("../core/auto-mode.js").AutoModeDecider;
354
354
  } | undefined;
355
- /** 5.30 merge-rescan round 2 — the HOST task's durable OFF SWITCH (`spec.checkpointStore === "disabled"`,
356
- * ruled 2026-08-04 to inherit into every spawned agent, see `buildWorkflowPrimitives`'s call
357
- * above). Known at prepare-time (no lazy getter needed `spec` is available at mount). A dep for
358
- * the same reason as `forwardEvent`/`inheritedGateForChildren`: the auto-mounted tool's execute
359
- * ctx is minimal `{toolCallId, signal}`, so `ctx.checkpointStoreDisabledForChildren` was a DEAD
360
- * read there — the off-switch never actually reached this lane's children. */
361
- parentCheckpointStoreDisabled?: boolean;
355
+ /** The HOST run's RESOLVED checkpoint seat — the store object the host parks in, else the word `"disabled"`
356
+ * (the same two-state value the delegation ctx carries as `ToolExecuteContext.checkpointStoreForChildren`;
357
+ * `ctx` wins on a rich-ctx mount). Every agent this workflow spawns inherits it verbatim unless its own
358
+ * spec (baseline / trusted script) pinned a store — and `"disabled"` lands unconditionally (a removal the
359
+ * baseline must not undo); see `buildWorkflowPrimitives`. A dep for the same reason as `forwardEvent`/
360
+ * `inheritedGateForChildren`: the auto-mounted tool's execute ctx is minimal `{toolCallId, signal}`. */
361
+ parentCheckpointStore?: import("../core/checkpoint-store.js").CheckpointStore | "disabled";
362
362
  /** #235 — the deployment's structured notice sink (`RunnerDeps.onNotice`), forwarded into the governed
363
363
  * build so the fields a script's agent spec wrote that did NOT reach the child are announced instead of
364
364
  * vanishing (`workflow.governance_key_stripped`). #505 ① — the same seat now also carries the OPTIONS
@@ -488,7 +488,7 @@ export async function createRunWorkflowTool(d) {
488
488
  }
489
489
  : governance;
490
490
  const scriptFn = (wfCtx) => {
491
- const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false);
491
+ const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, principal, ctx.checkpointStoreForChildren ?? d.parentCheckpointStore, d.parentReadFace, d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false);
492
492
  return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
493
493
  };
494
494
  if (ctx.signal?.aborted) {
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import type { Model } from "../internal/llm.js";
16
16
  import type { TaskSpec, WorkflowGovernanceBaseline } from "../core/types.js";
17
+ import type { CheckpointStore } from "../core/checkpoint-store.js";
17
18
  import type { WorkflowAgentHandle, WorkflowRunContext } from "./workflow.js";
18
19
  import type { WorkflowPrimitives } from "./workflow-script-runner.js";
19
20
  import { type WorkflowChildCaps } from "./workflow-governance.js";
@@ -36,9 +37,9 @@ export interface WorkflowGovernance {
36
37
  * — filling them here ran ahead of the fold and made the fold's "when unset" guard permanently false.
37
38
  */
38
39
  export declare function buildWorkflowPrimitives(ctx: WorkflowRunContext, governance?: WorkflowGovernance, onAgentSpawn?: (handle: WorkflowAgentHandle) => void, parentPrincipal?: string,
39
- /** ruled 2026-08-04 — the host run set `TaskSpec.checkpointStore: "disabled"` (the per-run durable off
40
- * switch). Every agent this workflow spawns inherits it; see the injection below. */
41
- parentCheckpointStoreDisabled?: boolean,
40
+ /** The host run's RESOLVED checkpoint seat — the store object it parks in, else the word `"disabled"`.
41
+ * Every agent this workflow spawns inherits it; see the injection below. */
42
+ parentCheckpointStore?: CheckpointStore | "disabled",
42
43
  /** 5.30 merge-rescan (design/199 parity gap) — the host run's RESOLVED read-face containment
43
44
  * (post deps/org/mount resolution, not the raw spec value). `TaskSpec.readFace`'s own JSDoc
44
45
  * promises "roots pins this task, and via the tighten-only clamp, its whole delegation subtree" —
@@ -65,7 +65,7 @@ function formatResourceClampNote(notes) {
65
65
  const parts = notes.map((n) => `${n.field}: requested ${n.requested === undefined ? "unset" : n.requested} → applied ${n.applied}`);
66
66
  return `workflow governance tightened this agent's resource limits (${parts.join("; ")})`;
67
67
  }
68
- export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentPrincipal, parentCheckpointStoreDisabled, parentReadFace, parentReadDenyPatterns, parentHandsReadOnly, parentInteractiveToolsOff) {
68
+ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentPrincipal, parentCheckpointStore, parentReadFace, parentReadDenyPatterns, parentHandsReadOnly, parentInteractiveToolsOff) {
69
69
  const agent = (spec, opts) => {
70
70
  if (typeof spec === "string")
71
71
  spec = { objective: spec };
@@ -77,8 +77,8 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentPri
77
77
  if (childSpec.principal === undefined && parentPrincipal !== undefined) {
78
78
  childSpec.principal = parentPrincipal;
79
79
  }
80
- if (parentCheckpointStoreDisabled === true) {
81
- childSpec.checkpointStore = "disabled";
80
+ if (parentCheckpointStore !== undefined && (parentCheckpointStore === "disabled" || childSpec.checkpointStore === undefined)) {
81
+ childSpec.checkpointStore = parentCheckpointStore;
82
82
  }
83
83
  if (parentReadFace) {
84
84
  const pf = parentReadFace();
@@ -328,6 +328,20 @@ export interface WorkflowRun {
328
328
  rev?: number;
329
329
  /** Set when `status === "failed"`: the error the script threw. */
330
330
  error?: string;
331
+ /** #688 C4 (additive) — the MACHINE code of the failure that ended the run, in the same vocabulary as
332
+ * {@link WorkflowAgentRun.errorCode} (`workflow.*` for the orchestration's own refusals, a task's terminal code
333
+ * for a child's failure), so a consumer branches here and never on `error`'s text. Attribution, in order:
334
+ * 1. the thrown value's own string `code` — the run SUSPENDED on a parked agent ⇒ `workflow.agent_parked`
335
+ * (whatever the body threw after the park), a refused spawn ⇒ `workflow.agent_blocked`, the budget /
336
+ * nesting / schema / stall / max-agents refusals ⇒ their `WorkflowBudgetExceededError`… codes, a body that
337
+ * re-threw a typed error ⇒ that error's code;
338
+ * 2. otherwise the FIRST failed agent record carrying a code (earliest `endedAt`, spawn order on a tie) —
339
+ * the body gave up after a child failed and threw in its own words; with several failed children the
340
+ * first to fail is the one taken (a body that threw for an unrelated reason beside a failed child still
341
+ * reads that child's code — `error` keeps the body's own sentence);
342
+ * 3. absent — a body that threw a codeless error with no failed child on the record.
343
+ * Absent on `completed` runs (their per-agent failures ride `agentFailures` + each `WorkflowAgentRun.errorCode`). */
344
+ errorCode?: string;
331
345
  /** The script's RETURN VALUE, bounded + redacted at completion time (same egress
332
346
  * discipline as the notifier `result`), so a terminal `TaskOutput` poll can hand the
333
347
  * model the result instead of sending it in poll circles. Absent on failed runs and runs recorded by
@@ -547,10 +547,47 @@ export interface WorkflowHandle<T> {
547
547
  }
548
548
  /** Cap on items a single `parallel`/`pipeline` call accepts (matches the CC Workflow tool). */
549
549
  export declare const MAX_WORKFLOW_ITEMS = 4096;
550
- /** CC `MTy` (206-pretty.js:17501680; 198 `F0m`): no tool activity for this long marks the attempt STALLED
551
- * (progress-based, unlike the governance perAgentTimeoutSec hard cap, which still bounds each attempt).
552
- * `<= 0` DISABLES the watchdog (CC arms only `if (ae > 0)`, :17477830). */
553
- export declare const WORKFLOW_AGENT_STALL_MS = 180000;
550
+ /**
551
+ * CC `MTy` (206-pretty.js:17501680; 198 `F0m`): no tool activity for this long marks the attempt STALLED
552
+ * (progress-based, unlike the governance perAgentTimeoutSec hard cap, which still bounds each attempt).
553
+ * `<= 0` DISABLES the watchdog (CC arms only `if (ae > 0)`, :17477830).
554
+ *
555
+ * DERIVED, not a number of its own (#682): an OUTER, COARSER clock must never fire before the engine's
556
+ * own. This watchdog does not re-send a model call — it aborts the attempt and restarts the whole agent
557
+ * on a FRESH session, so tool effects from before the stall can be repeated. The engine, one layer down,
558
+ * already answers a silent provider call the cheap way: it re-sends that ONE call. So the floor here is
559
+ * what a single provider call may legitimately occupy while producing NOTHING — the connect wait plus the
560
+ * first-token wait ({@link STALL_CONNECT_MS} + {@link STALL_FIRST_TOKEN_MS}). It was a hand-written
561
+ * 180_000 and CC-same until the first-token bound rose to 600s past it (measured: the same silent call
562
+ * then produced two agent runs on two sessions instead of one run with a re-sent call), which is the
563
+ * systematic inversion a derived value cannot re-open: raise or lower the engine's bound and this follows.
564
+ *
565
+ * WHAT THIS IS NOT (measured, adversarial round — do not read the sum as a proof of ordering). The two
566
+ * clocks do not start together and this one does not reset per inner retry, so it can still fire first:
567
+ * this timer starts before the Runner prepares the attempt (an execution-env factory, a hook, a store
568
+ * read all run inside this window and outside the engine's), and the first-token timer only starts once
569
+ * headers arrive — so the margin by which the inner deadline can land AFTER this one is preparation plus
570
+ * the connect time actually spent, less the {@link STALL_CONNECT_MS} this window already budgets for it
571
+ * (40s of preparation and instant headers puts the first inner deadline 10s past this timer). An engine retry does not re-arm this timer either, so two silent provider attempts
572
+ * exceed it by construction. The derivation removes the case that was guaranteed to invert; it does not
573
+ * make inversion impossible. A deployment that wants the old aggression passes `stallMs` explicitly, and
574
+ * a deployment that wants a real guarantee must set it from its own preparation budget.
575
+ */
576
+ export declare const WORKFLOW_AGENT_STALL_MS: number;
577
+ /**
578
+ * The "took suspiciously long" half of the throttle-shaped-degradation predicate (CC `Nt`,
579
+ * 206-pretty.js:17487400, which spells it `MTy * 0.5` = 90s). It used to be computed from the stall
580
+ * window here too, and that was safe only while the window was CC's own 180_000 — the moment the window
581
+ * became derived from the engine's bounds (#682/#693) the same expression silently moved the throttle
582
+ * threshold 90s → 315s, and a 2-minute empty answer that used to earn a 45s-sleep-and-retry was simply
583
+ * returned. Two unrelated meanings were riding one number. This is the second meaning, at CC's own
584
+ * value, so the progress window can move without touching it.
585
+ *
586
+ * Still a MINIMUM with the window, not a replacement for it: a deployment that shortens `stallMs` below
587
+ * this is asking for a tighter agent, and a throttle threshold above its own stall window could never
588
+ * fire. So the effective threshold is `min(stallMs / 2, this)` — one rule, both directions.
589
+ */
590
+ export declare const WORKFLOW_THROTTLE_SLOW_MS = 90000;
554
591
  /** CC `j_d` (206-pretty.js:17501680; 198 `Mxl`): max stalled-attempt retries per `ctx.agent` call
555
592
  * (initial + 5 = 6 attempts). */
556
593
  export declare const WORKFLOW_AGENT_MAX_RETRIES = 5;
@@ -21,6 +21,7 @@ import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
21
21
  import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
22
22
  import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentParkedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
23
23
  import { WorkflowScriptError } from "./workflow-meta.js";
24
+ import { STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS } from "../brain/timeout.js";
24
25
  export * from "./workflow-types.js";
25
26
  const MAX_TRANSCRIPT_CHARS = 4000;
26
27
  const WORKFLOW_RESULT_MAX = 4000;
@@ -149,6 +150,7 @@ export function workflowAgentCallKey(ordinal, spec, opts) {
149
150
  signal: undefined,
150
151
  taskId: undefined,
151
152
  sessionId: undefined,
153
+ checkpointStore: undefined,
152
154
  tools: spec.tools?.map((t) => t.name).slice().sort(),
153
155
  mcp: spec.mcp?.map((m) => m.name).slice().sort(),
154
156
  outputSchema: opts.schema ?? spec.outputSchema,
@@ -278,7 +280,8 @@ function assertFanOutOptionsShape(v, position) {
278
280
  `Nothing was run.`);
279
281
  }
280
282
  }
281
- export const WORKFLOW_AGENT_STALL_MS = 180_000;
283
+ export const WORKFLOW_AGENT_STALL_MS = STALL_CONNECT_MS + STALL_FIRST_TOKEN_MS;
284
+ export const WORKFLOW_THROTTLE_SLOW_MS = 90_000;
282
285
  export const WORKFLOW_AGENT_MAX_RETRIES = 5;
283
286
  export const WORKFLOW_AGENT_THROTTLE_BACKOFF_MS = 45_000;
284
287
  function readLegPendingApproval(result, announce) {
@@ -1604,7 +1607,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1604
1607
  result.structuredOutput === undefined &&
1605
1608
  (result.stats.outputTokens ?? Number.POSITIVE_INFINITY) < 50 &&
1606
1609
  lastStopReason === undefined &&
1607
- durationMs > stallMs * 0.5;
1610
+ durationMs > Math.min(stallMs * 0.5, WORKFLOW_THROTTLE_SLOW_MS);
1608
1611
  if (degraded && attempts === 1 && !throttleRetried && drive === undefined) {
1609
1612
  throttleRetried = true;
1610
1613
  lastAttemptReason = "throttled";
@@ -1689,7 +1692,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1689
1692
  ...(err instanceof WorkflowAgentBlockedError ? { code: WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } : err instanceof WorkflowBudgetExceededError ? { code: err.code } : {}),
1690
1693
  message: boundedRedactedSummary(err instanceof Error ? err.message : String(err), 500),
1691
1694
  },
1692
- stats: { turns: 0, tokens: 0, costMicroUsd: 0 },
1695
+ stats: { turns: 0, tokens: 0, costMicroUsd: 0, usageMissing: true },
1693
1696
  }, label).catch(() => undefined);
1694
1697
  if (!((err instanceof WorkflowBudgetExceededError || err instanceof WorkflowAgentParkedError) && rec.startedAt === undefined))
1695
1698
  await journaled;
@@ -1801,7 +1804,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1801
1804
  sessionId: "",
1802
1805
  terminal: { kind: "failed", code: refusal.code, message: boundedRedactedSummary(refusal.message, 500) },
1803
1806
  result: boundedRedactedSummary(refusal.message, 500),
1804
- stats: { turns: 0, tokens: 0, costMicroUsd: 0 },
1807
+ stats: { turns: 0, tokens: 0, costMicroUsd: 0, usageMissing: true },
1805
1808
  }).catch(() => undefined);
1806
1809
  throw refusal;
1807
1810
  }
@@ -1875,7 +1878,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1875
1878
  sessionId: "",
1876
1879
  terminal: { kind: "failed", code: WORKFLOW_SPAWN_BLOCKED_ERROR_CODE, message: boundedRedactedSummary(err.message, 500) },
1877
1880
  result: boundedRedactedSummary(err.message, 500),
1878
- stats: { turns: 0, tokens: 0, costMicroUsd: 0 },
1881
+ stats: { turns: 0, tokens: 0, costMicroUsd: 0, usageMissing: true },
1879
1882
  }).catch(() => undefined);
1880
1883
  }
1881
1884
  recordFailed();
@@ -2224,6 +2227,21 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
2224
2227
  err = parkedError();
2225
2228
  run.error = err instanceof Error ? err.message : String(err);
2226
2229
  closeAbandonedAgents(run.endedAt);
2230
+ {
2231
+ let thrownCode;
2232
+ try {
2233
+ thrownCode = err !== null && (typeof err === "object" || typeof err === "function") ? err.code : undefined;
2234
+ }
2235
+ catch {
2236
+ thrownCode = undefined;
2237
+ }
2238
+ const firstFailed = run.agents
2239
+ .filter((a) => a.status === "failed" && typeof a.errorCode === "string" && a.errorCode !== "")
2240
+ .sort((a, b) => (a.endedAt ?? Number.POSITIVE_INFINITY) - (b.endedAt ?? Number.POSITIVE_INFINITY))[0];
2241
+ const code = typeof thrownCode === "string" && thrownCode !== "" ? thrownCode : firstFailed?.errorCode;
2242
+ if (code !== undefined)
2243
+ run.errorCode = code;
2244
+ }
2227
2245
  restampPhaseFailures();
2228
2246
  const failedRunFailures = countAgentFailures();
2229
2247
  if (failedRunFailures > 0)
@@ -56,6 +56,47 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
56
56
  * that adds nothing the reader cannot already see.
57
57
  */
58
58
  export declare function bashReversibilityProbe(allow?: readonly string[], boundary?: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => ReversibilityVerdict;
59
+ /**
60
+ * The READ-BOUNDARY seat of the full shell: a `ToolSpec.reversibilityProbe` that answers exactly ONE
61
+ * question — does the deployment's read boundary object to where this command reads? — and nothing about
62
+ * the command's shape. It is the shell face of the rule every read face obeys: the structured readers
63
+ * (Read/Grep/Glob) refuse a deny-listed or out-of-root target; the shell reader gets a MANDATED ask for the
64
+ * same target. The engine installs it on Bash and Monitor whenever a real writable shell is mounted and the
65
+ * shellGate doctrine adds no residual-risk classifier of its own (`shellGate:"off"`, the default); under
66
+ * `"classify"` the doctrine installs {@link bashReversibilityProbe} instead, which composes THIS judgment
67
+ * with the read-only shape classifier, and under `"always"` the tier itself is the mandate and no probe runs.
68
+ * The boundary is therefore judged under every doctrine; the doctrine decides only what happens to the
69
+ * residual shell risk.
70
+ *
71
+ * The verdict, in the probe vocabulary the gate reads (`reversible` here means "the boundary raised no
72
+ * question — the seat vouches the call may run"):
73
+ * · the boundary DECLARED an objection — a deny-listed operand, a recursive walk under the deny judge, an
74
+ * operand outside the roots — ⇒ `{ reversible: false, mandated: true }` (+ the recursive-reach `cause`);
75
+ * · the boundary could NOT READ where the command reads — an unexpanded glob operand (the compound face
76
+ * reports it as undecided), a `~`-rooted operand with no home declared, a working-directory move the walk
77
+ * cannot model — ⇒ `{ reversible: false }` (fail-closed; a plain ask, which a stored allow rule may retire —
78
+ * the seat did not declare anything, it declined to guess);
79
+ * · otherwise ⇒ `{ reversible: true }`. This includes every command whose SHAPE the read faces refuse whole
80
+ * (an unlisted program, a redirection, a substitution, a segment that is not a reader): those shapes carry
81
+ * no read-face verdict, so this seat has no question to ask — the residual is the doctrine's, and under
82
+ * `"off"` the doctrine asks nothing. `run_in_background` is likewise not this seat's question (it changes
83
+ * WHEN the command runs, never WHERE it reads).
84
+ *
85
+ * Reach = the read faces' reach, and nothing wider: the compound face walks the operands of a command whose
86
+ * every segment is a listed reader, and the bounded poll-loop face walks a loop body of listed readers. A
87
+ * read hidden in a shape the faces refuse whole (`cat x | <unlisted>`, `$(cat x)`, `cat $DIR/x`, a launcher
88
+ * `env cat x`, a path-prefixed `/bin/cat x`) is not seen here — deny-listed or out-of-root alike — exactly
89
+ * the shapes the `"classify"` doctrine exists to ask about. The text-level containment gate the classify
90
+ * seat consults on its demotion arms is deliberately not consulted here ({@link boundaryDeclared}: it may
91
+ * add a mandate to an ask, never originate one). The poll-loop face reports its refusal as one sentence,
92
+ * so the boundary's share of it is read by removing the boundary's judging seats
93
+ * ({@link pollLoopBoundaryVerdict}).
94
+ *
95
+ * The reader set is the engine's classify set ({@link BASH_CLASSIFY_DEFAULT_ALLOW}) — here it is NOT an
96
+ * auto-allow list but the set of programs whose operands the faces read; a wider set means more reads are
97
+ * judged, never that more runs are vouched.
98
+ */
99
+ export declare function bashReadBoundaryProbe(boundary: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => ReversibilityVerdict;
59
100
  /**
60
101
  * design/199 D-6 — the FULL shell's contract id, single-sourced: both shell faces share the wire
61
102
  * name "Bash", so this id is the ONE structural discriminator between the write-capable shell and
@@ -25,40 +25,100 @@ function operandFamily(paths) {
25
25
  export function bashReversibilityProbe(allow, boundary) {
26
26
  const allowSet = new Set(allow ?? BASH_CLASSIFY_DEFAULT_ALLOW);
27
27
  return (args) => {
28
- const a = args;
29
- const command = a?.command;
30
- if (typeof command !== "string")
28
+ const command = shellCommandOf(args);
29
+ if (command === undefined)
31
30
  return { reversible: false };
32
31
  const resolved = typeof boundary === "function" ? boundary() : boundary;
33
- const outOfRootGate = () => resolved !== undefined && classifyOutOfRootReadGate(command, allowSet, resolved).gated ? { mandated: true } : {};
34
- const boundaryGate = (verdict) => verdict.readDenied === true || (verdict.recursiveReadPaths !== undefined && verdict.recursiveReadPaths.length > 0) ? { mandated: true } : outOfRootGate();
35
- const backgrounded = a?.run_in_background === true;
32
+ const boundaryGate = (verdict) => readBoundaryMandate(command, allowSet, resolved, verdict);
33
+ const outOfRootGate = () => readBoundaryMandate(command, allowSet, resolved, {});
34
+ const backgrounded = isBackgroundShellCall(args);
36
35
  const detailed = classifyCompoundReadonlyDetailed(command, allowSet, resolved);
37
36
  if (backgrounded)
38
37
  return { reversible: false, ...boundaryGate(detailed) };
39
38
  if (detailed.reason === undefined) {
40
39
  if (detailed.undecidedPaths !== undefined && detailed.undecidedPaths.length > 0) {
41
- const recursive = detailed.recursiveReadPaths;
42
- if (recursive !== undefined && recursive.length > 0) {
43
- const recursiveSet = new Set(recursive);
44
- const others = detailed.undecidedPaths.filter((p) => !recursiveSet.has(p));
45
- return {
46
- reversible: false,
47
- ...boundaryGate(detailed),
48
- cause: {
49
- code: RECURSIVE_READ_CAUSE_CODE,
50
- roots: operandFamily(recursive),
51
- ...(others.length > 0 ? { further: operandFamily(others) } : {}),
52
- },
53
- };
54
- }
40
+ const cause = recursiveReadCause(detailed);
41
+ if (cause !== undefined)
42
+ return { reversible: false, ...boundaryGate(detailed), cause };
55
43
  return { reversible: false, ...outOfRootGate() };
56
44
  }
57
45
  return { reversible: true };
58
46
  }
59
- return classifyBoundedReadonlyPollLoop(command, allowSet, resolved) === undefined
60
- ? { reversible: true }
61
- : { reversible: false, ...boundaryGate(detailed) };
47
+ const loopRefusal = classifyBoundedReadonlyPollLoop(command, allowSet, resolved);
48
+ if (loopRefusal === undefined)
49
+ return { reversible: true };
50
+ return { reversible: false, ...(pollLoopBoundaryVerdict(command, allowSet, resolved, loopRefusal) === "declared" ? { mandated: true } : boundaryGate(detailed)) };
51
+ };
52
+ }
53
+ function attributeRefusal(face, resolved, fullRefusal) {
54
+ if (resolved === undefined)
55
+ return "shape";
56
+ const { denyMatch: _deny, ...withoutDenyJudge } = resolved;
57
+ const opened = face({ ...withoutDenyJudge, face: "open" });
58
+ if (opened === undefined || opened !== fullRefusal)
59
+ return "declared";
60
+ return face(undefined) === opened ? "shape" : "unread";
61
+ }
62
+ function pollLoopBoundaryVerdict(command, allowSet, resolved, fullRefusal) {
63
+ return attributeRefusal((b) => classifyBoundedReadonlyPollLoop(command, allowSet, b), resolved, fullRefusal);
64
+ }
65
+ function boundaryDeclared(verdict) {
66
+ return denyJudgeSpoke(verdict) || verdict.outOfRootRead === true;
67
+ }
68
+ function denyJudgeSpoke(verdict) {
69
+ return verdict.readDenied === true || (verdict.recursiveReadPaths !== undefined && verdict.recursiveReadPaths.length > 0);
70
+ }
71
+ function shellCommandOf(args) {
72
+ const command = args?.command;
73
+ return typeof command === "string" ? command : undefined;
74
+ }
75
+ function isBackgroundShellCall(args) {
76
+ return args?.run_in_background === true;
77
+ }
78
+ function readBoundaryMandate(command, allowSet, resolved, verdict) {
79
+ if (denyJudgeSpoke(verdict))
80
+ return { mandated: true };
81
+ return resolved !== undefined && classifyOutOfRootReadGate(command, allowSet, resolved).gated ? { mandated: true } : {};
82
+ }
83
+ function recursiveReadCause(detailed) {
84
+ const recursive = detailed.recursiveReadPaths;
85
+ if (recursive === undefined || recursive.length === 0)
86
+ return undefined;
87
+ const recursiveSet = new Set(recursive);
88
+ const others = (detailed.undecidedPaths ?? []).filter((p) => !recursiveSet.has(p));
89
+ return {
90
+ code: RECURSIVE_READ_CAUSE_CODE,
91
+ roots: operandFamily(recursive),
92
+ ...(others.length > 0 ? { further: operandFamily(others) } : {}),
93
+ };
94
+ }
95
+ export function bashReadBoundaryProbe(boundary) {
96
+ const allowSet = new Set(BASH_CLASSIFY_DEFAULT_ALLOW);
97
+ return (args) => {
98
+ const command = shellCommandOf(args);
99
+ if (command === undefined)
100
+ return { reversible: false };
101
+ const resolved = typeof boundary === "function" ? boundary() : boundary;
102
+ if (resolved === undefined)
103
+ return { reversible: true };
104
+ const detailed = classifyCompoundReadonlyDetailed(command, allowSet, resolved);
105
+ if (boundaryDeclared(detailed)) {
106
+ const cause = recursiveReadCause(detailed);
107
+ return { reversible: false, mandated: true, ...(cause !== undefined ? { cause } : {}) };
108
+ }
109
+ if (detailed.reason === undefined) {
110
+ return detailed.undecidedPaths !== undefined && detailed.undecidedPaths.length > 0 ? { reversible: false } : { reversible: true };
111
+ }
112
+ const compound = attributeRefusal((b) => classifyCompoundReadonlyDetailed(command, allowSet, b).reason, resolved, detailed.reason);
113
+ if (compound === "declared")
114
+ return { reversible: false, mandated: true };
115
+ if (compound === "unread")
116
+ return { reversible: false };
117
+ const loopRefusal = classifyBoundedReadonlyPollLoop(command, allowSet, resolved);
118
+ if (loopRefusal === undefined)
119
+ return { reversible: true };
120
+ const loop = pollLoopBoundaryVerdict(command, allowSet, resolved, loopRefusal);
121
+ return loop === "shape" ? { reversible: true } : loop === "declared" ? { reversible: false, mandated: true } : { reversible: false };
62
122
  };
63
123
  }
64
124
  export { FULL_SHELL_CONTRACT_ID } from "../../core/tool-catalog-entries.js";
@@ -563,13 +623,10 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
563
623
  })),
564
624
  }),
565
625
  isConcurrencySafe: (args) => {
566
- const cmd = args?.command;
567
- if (typeof cmd !== "string")
568
- return false;
569
- const bg = args.run_in_background;
570
- if (bg === true)
626
+ const command = shellCommandOf(args);
627
+ if (command === undefined || isBackgroundShellCall(args))
571
628
  return false;
572
- return coarseReadonlyCheck(cmd, new Set(BASH_READONLY_DEFAULT_ALLOW)) === undefined;
629
+ return coarseReadonlyCheck(command, new Set(BASH_READONLY_DEFAULT_ALLOW)) === undefined;
573
630
  },
574
631
  execute: async (args, ctx) => {
575
632
  const { command, timeout, run_in_background, description } = args;
@@ -6,6 +6,7 @@ import { shellQuote } from "./search.js";
6
6
  import { isNotebookPath } from "./notebook.js";
7
7
  import { sharpImageDownsampler } from "../../core/mcp.js";
8
8
  import { deliverEngineNotice } from "../../core/types.js";
9
+ import { announceEnvKnob } from "../../core/env-knob-announce.js";
9
10
  import { openSystemReminder } from "../../core/reminder-mint.js";
10
11
  export const MAX_READ_BYTES = 256 * 1024;
11
12
  export const SLICED_READ_MAX_BYTES = 64 * 1024 * 1024;
@@ -181,10 +182,15 @@ export function bashTimeoutCapsSec(caps) {
181
182
  const BASH_DEFAULT_MAX_OUTPUT_CHARS = 30_000;
182
183
  const BASH_MAX_OUTPUT_CHARS_CEILING = 150_000;
183
184
  export function bashMaxOutputChars() {
184
- const raw = Number(process.env.BASH_MAX_OUTPUT_LENGTH);
185
- if (!Number.isFinite(raw) || raw <= 0)
185
+ const rawEnv = process.env.BASH_MAX_OUTPUT_LENGTH;
186
+ if (rawEnv === undefined || rawEnv.trim() === "")
186
187
  return BASH_DEFAULT_MAX_OUTPUT_CHARS;
187
- return Math.min(Math.floor(raw), BASH_MAX_OUTPUT_CHARS_CEILING);
188
+ const raw = Number(rawEnv);
189
+ if (!Number.isInteger(raw) || raw <= 0) {
190
+ announceEnvKnob(`BASH_MAX_OUTPUT_LENGTH=${rawEnv} was ignored — it is not a positive whole number of characters. Using ${BASH_DEFAULT_MAX_OUTPUT_CHARS} instead.`);
191
+ return BASH_DEFAULT_MAX_OUTPUT_CHARS;
192
+ }
193
+ return Math.min(raw, BASH_MAX_OUTPUT_CHARS_CEILING);
188
194
  }
189
195
  export const FILE_PATH_PARAMS = {
190
196
  file_path: Type.String({ description: "File path (within the configured root)." }),
@@ -65,7 +65,7 @@ export interface ReadDenyMatcher {
65
65
  * exactly one. Unknown tier names refuse loudly (#123) — this array is the validation source and the
66
66
  * admin-face vocabulary, never re-spelled downstream.
67
67
  */
68
- export declare const READ_DENY_BUILTIN_TIERS: readonly ["credentials", "shell-history", "browser", "wallet"];
68
+ export declare const READ_DENY_BUILTIN_TIERS: readonly ["credentials", "shell-history", "browser", "wallet", "agent-config"];
69
69
  /** One member of {@link READ_DENY_BUILTIN_TIERS}. */
70
70
  export type ReadDenyBuiltinTier = (typeof READ_DENY_BUILTIN_TIERS)[number];
71
71
  /**
@@ -90,17 +90,33 @@ export interface ReadDenyBuiltinRow {
90
90
  * and a false refusal on a fixture is the cheap direction (deny errs strict).
91
91
  *
92
92
  * NOT listed, deliberately: `.env`/`.env.*` (ruled out — workspace material; the WRITE guard still
93
- * covers them); `*.pem`/`*.key`/`*.p12`/`*.pfx` (repo certificates/test keys are routinely READ
94
- * refusing them breaks ordinary work, the write guard covers the mutation direction); `.git/hooks`/
95
- * `.git/config` (reads are harmless; writes are the escalation and stay guarded); `.claude*`/
96
- * `.mcp.json` (agent config is routinely read for debugging); the engine's own data root
97
- * (transcript integrity policy owns it with an ask on the write face).
93
+ * covers them; the ENGINE's own `.env` under its data root is a different file and IS listed, see the
94
+ * agent-config tier); `*.pem`/`*.key`/`*.p12`/`*.pfx` (repo certificates/test keys are routinely
95
+ * READ refusing them breaks ordinary work, the write guard covers the mutation direction);
96
+ * `.git/hooks`/`.git/config` (reads are harmless; writes are the escalation and stay guarded); the
97
+ * engine's own data root as a WHOLE (transcript integrity policy owns it with an ask on the write
98
+ * face, and the memory library the model is told to read and edit lives under it).
99
+ *
100
+ * agent-config tier — the agent's OWN configuration files whose `env` / server blocks carry API keys
101
+ * in cleartext (the shell's `settings.json` env block, a project `.mcp.json` server env, the engine
102
+ * host's data-root `.env`). A structured read of one of these used to land the key verbatim in the
103
+ * tool result and therefore in the session record. Rows name FILES, not the config DIRECTORIES:
104
+ * `.sema`/`.claude` also hold agents, skills, hooks and (under the shell's layout,
105
+ * `~/.sema/engine-data`) the engine data root with the memory library — material the model reads
106
+ * and edits as ordinary work, and a directory-wide row would need a data-root passthrough (one more
107
+ * rule) to keep it readable. The write face guards the directories (`sensitive-path-policy.ts`): a
108
+ * write there is self-authorization; a read of a non-credential file there is debugging. Token
109
+ * STORES of the same tooling (`.credentials.json`, `.codex/auth.json`, …) are credentials rows, not
110
+ * agent-config rows: a deployment that switches this tier off to debug its agent configuration must
111
+ * not also expose the OAuth stores beside it.
98
112
  */
99
113
  export declare const READ_FACE_BUILTIN_DENY_TABLE: readonly ReadDenyBuiltinRow[];
100
114
  /**
101
115
  * The DEFAULT tier selection (#245 ruling ②): every tier EXCEPT `shell-history` — history files are
102
- * unrestricted unless a deployment opts the tier in. Derived, not re-listed: a future tier added to
103
- * the closed set is default-ON unless this expression is deliberately revisited.
116
+ * unrestricted unless a deployment opts the tier in. Derived, not re-listed: a tier added to the
117
+ * closed set is default-ON unless this expression is deliberately revisited (`agent-config` joined
118
+ * default-ON this way: an unconfigured deployment refuses the agent's own key-bearing settings files;
119
+ * a deployment that debugs agent configuration selects `tiers` without it).
104
120
  */
105
121
  export declare const READ_DENY_DEFAULT_TIERS: readonly ReadDenyBuiltinTier[];
106
122
  /**