@sema-agent/core 5.29.0 → 5.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/dist/agents/send-message-tool.js +2 -0
- package/dist/agents/subagent.d.ts +2 -0
- package/dist/agents/subagent.js +6 -0
- package/dist/agents/teacher.js +2 -0
- package/dist/agents/verify.js +2 -0
- package/dist/core/auto-compaction.d.ts +5 -1
- package/dist/core/auto-compaction.js +10 -1
- package/dist/core/checkpoint-store.d.ts +51 -5
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/hooks.d.ts +12 -1
- package/dist/core/hooks.js +8 -2
- package/dist/core/permission-rules.js +2 -2
- package/dist/core/runner/prepare-task.d.ts +21 -5
- package/dist/core/runner/prepare-task.js +105 -19
- package/dist/core/runner/runtask.js +29 -3
- package/dist/core/runner/session-rule-policy.d.ts +3 -2
- package/dist/core/runner/tool-output-projection.js +1 -1
- package/dist/core/sensitive-path-policy.js +5 -16
- package/dist/core/store-contracts/tool-result-store-contract.js +23 -0
- package/dist/core/tighten-task-spec.js +18 -0
- package/dist/core/tool-policy.d.ts +20 -1
- package/dist/core/tool-policy.js +31 -4
- package/dist/core/tool-result-store.js +3 -1
- package/dist/core/types.d.ts +55 -0
- package/dist/engine/harness/types.d.ts +10 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/orchestration/run-workflow-tool.d.ts +21 -0
- package/dist/orchestration/run-workflow-tool.js +6 -3
- package/dist/orchestration/workflow-primitives.d.ts +10 -1
- package/dist/orchestration/workflow-primitives.js +12 -1
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/types.d.ts +4 -0
- package/dist/prompts/default.d.ts +14 -9
- package/dist/prompts/default.js +13 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +21 -0
- package/dist/tools/fs/bash-readonly-classifier.js +11 -0
- package/dist/tools/fs/fs-bash.d.ts +7 -0
- package/dist/tools/fs/fs-bash.js +8 -3
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-pdf.js +2 -2
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +11 -7
- package/dist/tools/fs/fs-search-tools.d.ts +4 -2
- package/dist/tools/fs/fs-search-tools.js +15 -8
- package/dist/tools/fs/fs-shared.d.ts +5 -1
- package/dist/tools/fs/fs-shared.js +8 -3
- package/dist/tools/fs/index.d.ts +18 -0
- package/dist/tools/fs/index.js +13 -2
- package/dist/tools/fs/read-deny.d.ts +105 -0
- package/dist/tools/fs/read-deny.js +151 -0
- package/dist/tools/fs/read-face.d.ts +43 -0
- package/dist/tools/fs/read-face.js +38 -0
- package/dist/tools/fs/repo-map.d.ts +3 -1
- package/dist/tools/fs/repo-map.js +11 -5
- package/dist/tools/fs/safety.d.ts +33 -11
- package/dist/tools/fs/safety.js +88 -7
- package/dist/tools/fs/search.d.ts +54 -5
- package/dist/tools/fs/search.js +103 -21
- package/package.json +1 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -650,6 +650,21 @@ export interface ToolExecuteContext {
|
|
|
650
650
|
* read-only boundary ended at one level. Strictly a capability REMOVAL — it can never widen a child.
|
|
651
651
|
*/
|
|
652
652
|
handsReadOnly?: true;
|
|
653
|
+
/**
|
|
654
|
+
* design/199 — the READ-face clamp carrier ({@link TaskSpec.readFace}): present (as "roots") ONLY
|
|
655
|
+
* when this run RESOLVED to the roots face, so a delegation tool pins every child spec to roots —
|
|
656
|
+
* without it a roots-narrowed parent's children would resolve the shared deployment seat (possibly
|
|
657
|
+
* "open") and read wider than the parent could. Strictly a tightening carrier: an open parent adds
|
|
658
|
+
* no key, and nothing in the delegation lane can spell "open". F10 ruling (v1): this seat exists
|
|
659
|
+
* ONLY as the delegation carrier — no tool adapts its model-facing behavior on it.
|
|
660
|
+
*/
|
|
661
|
+
readFace?: "roots";
|
|
662
|
+
/**
|
|
663
|
+
* design/199 件B — the parent task's OWN deny-set additions ({@link TaskSpec.readDenyPatterns}),
|
|
664
|
+
* traveling the delegation tree add-only (a child judges at least every entry its parent judged).
|
|
665
|
+
* Runner-filled trusted seat, never a model argument.
|
|
666
|
+
*/
|
|
667
|
+
readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
|
|
653
668
|
/**
|
|
654
669
|
* The HARD-HEADLESS clamp ({@link TaskSpec.interactiveTools} set to `false` — "never mount a
|
|
655
670
|
* human-facing tool on this run, whatever faces exist"), Runner-filled read-only on the same trusted
|
|
@@ -2109,6 +2124,27 @@ export interface TaskSpec {
|
|
|
2109
2124
|
* config read.
|
|
2110
2125
|
*/
|
|
2111
2126
|
additionalReadDirectories?: string[];
|
|
2127
|
+
/**
|
|
2128
|
+
* design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
|
|
2129
|
+
* ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
|
|
2130
|
+
* structured read faces (Read/Grep/Glob/RepoMap, their traversals, the classify shell gate's
|
|
2131
|
+
* auto-allow probe and the compaction attachment reader) in BOTH containment modes. Add-only
|
|
2132
|
+
* everywhere (D-4 zero-shrink ruling): entries here UNION with the built-ins and the deployment's
|
|
2133
|
+
* {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); nothing can remove a
|
|
2134
|
+
* built-in. Bad entry shapes refuse loudly at prepare (#123). The write faces are untouched.
|
|
2135
|
+
*/
|
|
2136
|
+
readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
|
|
2137
|
+
/**
|
|
2138
|
+
* design/199 件A — the TASK layer's read-face containment declaration
|
|
2139
|
+
* ({@link import("../tools/fs/read-face.js").ReadFace}). "roots" pins this task (and, via the
|
|
2140
|
+
* tighten-only clamp, its whole delegation subtree) to the historical containment; "open" lifts
|
|
2141
|
+
* the roots judgment for the structured read faces — refused loudly under org governance (the
|
|
2142
|
+
* task layer only tightens there; the deployment opens via {@link RunnerDeps.readFace}) and
|
|
2143
|
+
* beside `handsReadOnly: true` (the verifier containment is load-bearing). Absent ⇒ the
|
|
2144
|
+
* deployment seat, then the engine default ("roots" — D-1b: an upgrade never opens implicitly).
|
|
2145
|
+
* Bad values refuse loudly (#123). Never affects the write faces.
|
|
2146
|
+
*/
|
|
2147
|
+
readFace?: import("../tools/fs/read-face.js").ReadFace;
|
|
2112
2148
|
/**
|
|
2113
2149
|
* design/80 D-B — opt in to PLAN MODE: mount the first-party `present_plan` tool (CC `ExitPlanMode` parity).
|
|
2114
2150
|
* The model calls it to present a plan; the engine then pauses with a durable `plan_review` checkpoint
|
|
@@ -4265,6 +4301,25 @@ export interface RunnerDeps {
|
|
|
4265
4301
|
* Runner 私有编排字段(taskRegistry/detachHub/execClamp/cwdRef/…)不在此面,永不开放。
|
|
4266
4302
|
*/
|
|
4267
4303
|
hands?: HandsBandOptions;
|
|
4304
|
+
/**
|
|
4305
|
+
* design/199 件B — DEPLOYMENT-layer ADDITIONS to the built-in sensitive-path READ deny set
|
|
4306
|
+
* ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}); see
|
|
4307
|
+
* {@link TaskSpec.readDenyPatterns} for the judged surfaces. Add-only (D-4 zero-shrink ruling):
|
|
4308
|
+
* unions with the built-ins and any task-layer additions; there is no whole-table replacement
|
|
4309
|
+
* escape hatch, and `[]` ≡ absent. Bad entry shapes refuse loudly at prepare (#123).
|
|
4310
|
+
*/
|
|
4311
|
+
readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
|
|
4312
|
+
/**
|
|
4313
|
+
* design/199 件A — the DEPLOYMENT's read-face declaration
|
|
4314
|
+
* ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
|
|
4315
|
+
* task layer and the resolution order). An interactive single-user product declares
|
|
4316
|
+
* `readFace: "open"` here in one line; the engine default stays "roots" (D-1b). Under org
|
|
4317
|
+
* governance this seat may still open (it is the deployment's own declaration); the task layer
|
|
4318
|
+
* may not. Beside a read-only (verifier) mount this seat's "open" silently CLAMPS to roots (a
|
|
4319
|
+
* deployment default cannot override a load-bearing containment wall) — TaskSpec.readFace's own
|
|
4320
|
+
* "open" there is the genuine per-task contradiction, and that one still refuses loudly (#123).
|
|
4321
|
+
*/
|
|
4322
|
+
readFace?: import("../tools/fs/read-face.js").ReadFace;
|
|
4268
4323
|
/**
|
|
4269
4324
|
* design/96 §20 (P1a) — DEPLOYMENT-DECLARED training knowledge cutoff per model id (e.g.
|
|
4270
4325
|
* `{ "qwen-3.5-35b": "2025-01", "deepseek-pro": "2024-07" }`). When the resolved model id is present, the
|
|
@@ -349,6 +349,16 @@ export interface ExecutionEnv extends FileSystem, Shell {
|
|
|
349
349
|
* mechanism exists to prevent.
|
|
350
350
|
*/
|
|
351
351
|
readonly lifetimeStartedAt?: number;
|
|
352
|
+
/**
|
|
353
|
+
* #211 (design/199 seam) — does this environment's path NAMESPACE live on the CONTROL-PLANE HOST's
|
|
354
|
+
* filesystem? Declared by the ADAPTER: `false` for a virtual/sandboxed env whose paths never map
|
|
355
|
+
* onto the host (an in-memory env, a container the host cannot mount) — consumers that record
|
|
356
|
+
* host-path receipts (the workflow workspace observer's `worktreeDir`) then refuse to present this
|
|
357
|
+
* env's cwd as a host path. Omitted ⇒ the historical structural inference stays in force
|
|
358
|
+
* (isRemoteExecutionEnv shape detection; a plain NodeExecutionEnv is host-local). `true` is a
|
|
359
|
+
* harmless explicit spelling of the local default.
|
|
360
|
+
*/
|
|
361
|
+
readonly hostLocalPaths?: boolean;
|
|
352
362
|
}
|
|
353
363
|
/** Base fields shared by append-only session tree entries. */
|
|
354
364
|
export interface SessionTreeEntryBase {
|
package/dist/index.d.ts
CHANGED
|
@@ -87,6 +87,8 @@ export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, Ch
|
|
|
87
87
|
export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
|
|
88
88
|
export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
|
|
89
89
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
90
|
+
export { READ_FACE_DEFAULT_DENY_ENTRIES, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, } from "./tools/fs/index.js";
|
|
91
|
+
export { resolveReadFace, type ReadFace, type ReadFaceInputs } from "./tools/fs/index.js";
|
|
90
92
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
|
|
91
93
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
|
|
92
94
|
export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
|
|
@@ -168,7 +170,7 @@ export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelec
|
|
|
168
170
|
export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, DEFAULT_CONSOLIDATION_TIMEOUT_SEC, normalizeForExactMatch, type ConsolidationParams, type ConsolidationStats, type ConsolidationNote, type ConsolidationLLM, } from "./core/runner/memory-consolidation.js";
|
|
169
171
|
export { consolidateScope, advanceCursorAfterInline, type ConsolidateScopeDeps, type ConsolidateScopeOptions, } from "./core/consolidate-scope.js";
|
|
170
172
|
export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
|
|
171
|
-
export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, type EnvironmentFacts, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, type PromptBlock, analyzePromptCacheFriendliness, assertPromptCacheFriendly, type PromptProvider, type StablePromptContext, type PromptCacheReport, type PromptTextDeclaration, } from "./prompts/default.js";
|
|
173
|
+
export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, type EnvironmentFacts, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, type PromptBlock, analyzePromptCacheFriendliness, assertPromptCacheFriendly, type PromptProvider, type StablePromptContext, type PromptCacheReport, type PromptTextDeclaration, } from "./prompts/default.js";
|
|
172
174
|
export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
|
|
173
175
|
export { compose, validatePack } from "./prompt-assembly/composer.js";
|
|
174
176
|
export { SEMA_DEFAULT_PACK } from "./prompt-assembly/packs/sema-default.js";
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,8 @@ export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
|
|
|
68
68
|
export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
|
|
69
69
|
export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
|
|
70
70
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
71
|
+
export { READ_FACE_DEFAULT_DENY_ENTRIES, compileReadDeny, } from "./tools/fs/index.js";
|
|
72
|
+
export { resolveReadFace } from "./tools/fs/index.js";
|
|
71
73
|
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";
|
|
72
74
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
|
|
73
75
|
export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, } from "./core/usage-window-store.js";
|
|
@@ -131,7 +133,7 @@ export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelec
|
|
|
131
133
|
export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, DEFAULT_CONSOLIDATION_TIMEOUT_SEC, normalizeForExactMatch, } from "./core/runner/memory-consolidation.js";
|
|
132
134
|
export { consolidateScope, advanceCursorAfterInline, } from "./core/consolidate-scope.js";
|
|
133
135
|
export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
|
|
134
|
-
export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, analyzePromptCacheFriendliness, assertPromptCacheFriendly, } from "./prompts/default.js";
|
|
136
|
+
export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, analyzePromptCacheFriendliness, assertPromptCacheFriendly, } from "./prompts/default.js";
|
|
135
137
|
export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
|
|
136
138
|
export { compose, validatePack } from "./prompt-assembly/composer.js";
|
|
137
139
|
export { SEMA_DEFAULT_PACK } from "./prompt-assembly/packs/sema-default.js";
|
|
@@ -219,6 +219,12 @@ export interface RunWorkflowToolDeps {
|
|
|
219
219
|
/** Call-time getter for the HOST task's current thinking level — a spawned agent with no explicit
|
|
220
220
|
* script/baseline `thinking` inherits it (the parentCwd/model-snapshot companion). */
|
|
221
221
|
parentThinking?: () => import("../core/types.js").TaskSpec["thinking"];
|
|
222
|
+
/** 5.30 merge-rescan (design/199 parity gap) — call-time getter for the HOST task's RESOLVED
|
|
223
|
+
* read-face containment; folds stricter-wins into every spawned workflow child (see
|
|
224
|
+
* `buildWorkflowPrimitives`'s injection block for the full rationale). */
|
|
225
|
+
parentReadFace?: () => import("../core/types.js").TaskSpec["readFace"];
|
|
226
|
+
/** Twin of the above for the deny-set additions (union, not replace). */
|
|
227
|
+
parentReadDenyPatterns?: () => import("../core/types.js").TaskSpec["readDenyPatterns"];
|
|
222
228
|
/** [1238](A) — call-time getter for the HOST task's RESOLVED Model object: a spawned agent whose
|
|
223
229
|
* fold chain produced no model inherits the parent's full object (baseUrl/key routing included),
|
|
224
230
|
* mirroring the subagent lane's ctx.model semantics. */
|
|
@@ -254,6 +260,21 @@ export interface RunWorkflowToolDeps {
|
|
|
254
260
|
autoModeReview?: () => {
|
|
255
261
|
decider: import("../core/auto-mode.js").AutoModeDecider;
|
|
256
262
|
} | undefined;
|
|
263
|
+
/** 5.30 merge-rescan round 2 — the HOST task's durable OFF SWITCH (`spec.checkpointStore === null`,
|
|
264
|
+
* ruled 2026-08-04 to inherit into every spawned agent, see `buildWorkflowPrimitives`'s call
|
|
265
|
+
* above). Known at prepare-time (no lazy getter needed — `spec` is available at mount). A dep for
|
|
266
|
+
* the same reason as `forwardEvent`/`inheritedGateForChildren`: the auto-mounted tool's execute
|
|
267
|
+
* ctx is minimal `{toolCallId, signal}`, so `ctx.checkpointStoreDisabledForChildren` was a DEAD
|
|
268
|
+
* read there — the off-switch never actually reached this lane's children. */
|
|
269
|
+
parentCheckpointStoreDisabled?: boolean;
|
|
270
|
+
/** Twin of the above for design/148 S1's center-artifact inheritance (see the `ctx.centerArtifactDigest`/
|
|
271
|
+
* `ctx.centerSourceRevision` reads at the `startWorkflow` options site) — `centerAdoption` resolves
|
|
272
|
+
* LATE in prepare-task.ts (well after this tool's mount point), so this is a call-time getter, not
|
|
273
|
+
* a value, mirroring `parentReadFace`. `ctx.centerArtifactDigest` still wins when a wrapping path
|
|
274
|
+
* provides it. */
|
|
275
|
+
parentCenterArtifactDigest?: () => string | undefined;
|
|
276
|
+
/** Publish-provenance companion of the above. */
|
|
277
|
+
parentCenterSourceRevision?: () => string | undefined;
|
|
257
278
|
}
|
|
258
279
|
/**
|
|
259
280
|
* Build the `run_workflow` tool. ASSERTS the runner is a hard sandbox (defense in depth — prepare-task only
|
|
@@ -380,7 +380,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
380
380
|
return structuredError(`workflow script failed to compile: ${err instanceof Error ? err.message : String(err)}`);
|
|
381
381
|
}
|
|
382
382
|
const scriptFn = (wfCtx) => {
|
|
383
|
-
const primitives = buildWorkflowPrimitives(wfCtx, governance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true);
|
|
383
|
+
const primitives = buildWorkflowPrimitives(wfCtx, governance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns);
|
|
384
384
|
return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
|
|
385
385
|
};
|
|
386
386
|
if (ctx.signal?.aborted) {
|
|
@@ -389,6 +389,9 @@ export async function createRunWorkflowTool(d) {
|
|
|
389
389
|
let handle;
|
|
390
390
|
const workflowTaskId = d.taskRegistry?.mintTaskId("workflow");
|
|
391
391
|
const autoModeReview = ctx.autoModeReview ?? d.autoModeReview?.();
|
|
392
|
+
const centerFromCtx = ctx.centerArtifactDigest !== undefined;
|
|
393
|
+
const parentCenterArtifactDigest = centerFromCtx ? ctx.centerArtifactDigest : d.parentCenterArtifactDigest?.();
|
|
394
|
+
const parentCenterSourceRevision = centerFromCtx ? ctx.centerSourceRevision : d.parentCenterSourceRevision?.();
|
|
392
395
|
try {
|
|
393
396
|
handle = startWorkflow(d.runner, scriptFn, {
|
|
394
397
|
store: d.store,
|
|
@@ -417,8 +420,8 @@ export async function createRunWorkflowTool(d) {
|
|
|
417
420
|
...((ctx.interactionPosture ?? d.parentInteractionPosture) !== undefined ? { interactionPosture: (ctx.interactionPosture ?? d.parentInteractionPosture) } : {}),
|
|
418
421
|
...(d.parentModel !== undefined ? { defaultModel: d.parentModel } : {}),
|
|
419
422
|
...(d.parentGetApiKeyAndHeaders !== undefined ? { defaultGetApiKeyAndHeaders: d.parentGetApiKeyAndHeaders } : {}),
|
|
420
|
-
...(
|
|
421
|
-
...(
|
|
423
|
+
...(parentCenterArtifactDigest !== undefined ? { parentCenterArtifactDigest } : {}),
|
|
424
|
+
...(parentCenterSourceRevision !== undefined ? { parentCenterSourceRevision } : {}),
|
|
422
425
|
...((ctx.forwardEvent ?? d.forwardEvent) !== undefined ? { onForwardEvent: (ctx.forwardEvent ?? d.forwardEvent) } : {}),
|
|
423
426
|
...((ctx.inheritedGateForChildren ?? d.inheritedGateForChildren) !== undefined
|
|
424
427
|
? { inheritedGate: (ctx.inheritedGateForChildren ?? d.inheritedGateForChildren)() }
|
|
@@ -30,4 +30,13 @@ export interface WorkflowGovernance {
|
|
|
30
30
|
export declare function buildWorkflowPrimitives(ctx: WorkflowRunContext, governance?: WorkflowGovernance, onAgentSpawn?: (handle: WorkflowAgentHandle) => void, parentThinking?: () => TaskSpec["thinking"], parentPrincipal?: string,
|
|
31
31
|
/** ruled 2026-08-04 — the host run set `TaskSpec.checkpointStore: null` (the per-run durable off
|
|
32
32
|
* switch). Every agent this workflow spawns inherits it; see the injection below. */
|
|
33
|
-
parentCheckpointStoreDisabled?: boolean
|
|
33
|
+
parentCheckpointStoreDisabled?: boolean,
|
|
34
|
+
/** 5.30 merge-rescan (design/199 parity gap) — the host run's RESOLVED read-face containment
|
|
35
|
+
* (post deps/org/mount resolution, not the raw spec value). `TaskSpec.readFace`'s own JSDoc
|
|
36
|
+
* promises "roots pins this task, and via the tighten-only clamp, its whole delegation subtree" —
|
|
37
|
+
* a workflow child is that subtree; the Task/SendMessage delegation lane already carries this,
|
|
38
|
+
* the workflow lane was the one gap. Lazy (read at spawn time, after prepare has resolved it). */
|
|
39
|
+
parentReadFace?: () => TaskSpec["readFace"],
|
|
40
|
+
/** Twin of the above for the deny-set additions (built-ins always apply; these are the extra
|
|
41
|
+
* entries the host's own deployment/task layers stacked on). */
|
|
42
|
+
parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"]): WorkflowPrimitives;
|
|
@@ -22,7 +22,7 @@ function formatResourceClampNote(notes) {
|
|
|
22
22
|
const parts = notes.map((n) => `${n.field}: requested ${n.requested === undefined ? "unset" : n.requested} → applied ${n.applied}`);
|
|
23
23
|
return `workflow governance tightened this agent's resource limits (${parts.join("; ")})`;
|
|
24
24
|
}
|
|
25
|
-
export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThinking, parentPrincipal, parentCheckpointStoreDisabled) {
|
|
25
|
+
export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThinking, parentPrincipal, parentCheckpointStoreDisabled, parentReadFace, parentReadDenyPatterns) {
|
|
26
26
|
const agent = (spec, opts) => {
|
|
27
27
|
if (typeof spec === "string")
|
|
28
28
|
spec = { objective: spec };
|
|
@@ -42,6 +42,17 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThi
|
|
|
42
42
|
if (parentCheckpointStoreDisabled === true) {
|
|
43
43
|
childSpec.checkpointStore = null;
|
|
44
44
|
}
|
|
45
|
+
if (parentReadFace) {
|
|
46
|
+
const pf = parentReadFace();
|
|
47
|
+
if (pf === "roots" && childSpec.readFace !== "roots")
|
|
48
|
+
childSpec.readFace = "roots";
|
|
49
|
+
}
|
|
50
|
+
if (parentReadDenyPatterns) {
|
|
51
|
+
const pd = parentReadDenyPatterns();
|
|
52
|
+
if (pd !== undefined && pd.length > 0) {
|
|
53
|
+
childSpec.readDenyPatterns = childSpec.readDenyPatterns !== undefined ? [...childSpec.readDenyPatterns, ...pd] : [...pd];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
45
56
|
if (onAgentSpawn) {
|
|
46
57
|
return ctx.agentStream(childSpec, agentOpts).then((handle) => {
|
|
47
58
|
onAgentSpawn(handle);
|
|
@@ -22,6 +22,7 @@ const PROBE_FACTS_OFF = {
|
|
|
22
22
|
isSubagent: false,
|
|
23
23
|
promptProfile: "simple",
|
|
24
24
|
fableMitigations: false,
|
|
25
|
+
readFaceOpen: false,
|
|
25
26
|
};
|
|
26
27
|
const PROBE_FACTS_ON = {
|
|
27
28
|
policyEnabled: true,
|
|
@@ -38,6 +39,7 @@ const PROBE_FACTS_ON = {
|
|
|
38
39
|
isSubagent: true,
|
|
39
40
|
promptProfile: "classic",
|
|
40
41
|
fableMitigations: true,
|
|
42
|
+
readFaceOpen: true,
|
|
41
43
|
};
|
|
42
44
|
const PROBE_VECTORS = [
|
|
43
45
|
PROBE_FACTS_OFF,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CYBER_RISK, EXECUTION_ENVIRONMENT, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, WORKTREE_NOTICE, SUBAGENT_CONSENT_NOTICE, SUBAGENT_DELIVERY_NOTES, harnessHeadLines, } from "../../prompts/default.js";
|
|
1
|
+
import { CYBER_RISK, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, WORKTREE_NOTICE, SUBAGENT_CONSENT_NOTICE, SUBAGENT_DELIVERY_NOTES, harnessHeadLines, } from "../../prompts/default.js";
|
|
2
2
|
import { GOAL_COMPLETION_GUIDANCE, ORCHESTRATION_AWARENESS, ORCHESTRATION_GUIDANCE, ORCHESTRATION_GUIDANCE_DEFERRED, SUPERVISOR_PROMPT, } from "../../prompts/supervisor.js";
|
|
3
3
|
import { TEAMMATE_COMMUNICATION_ADDENDUM } from "../../prompts/coordinator.js";
|
|
4
4
|
import { SIMPLE_ACTION_CAUTION, SIMPLE_ACT_DONT_REDERIVE, SIMPLE_AUTONOMY_FABLE, SIMPLE_COMMUNICATING_FABLE, SIMPLE_CORRECTIONS_FABLE, SIMPLE_DELIVERING_WORK_FABLE, SIMPLE_COMMUNICATING_LEAN, SIMPLE_CONTEXT_MANAGEMENT, SIMPLE_PRONOUNS, SIMPLE_TOOL_PARAM_JSON, SEMA_VERIFY_FRESH, SEMA_EVIDENCE_AUDIT, } from "../../prompts/simple-sections.js";
|
|
@@ -41,7 +41,7 @@ export const SEMA_DEFAULT_PACK = {
|
|
|
41
41
|
rank: 240,
|
|
42
42
|
...CORE,
|
|
43
43
|
admit: (i) => i.facts.policyEnabled || i.facts.isolationEnabled,
|
|
44
|
-
content: () => EXECUTION_ENVIRONMENT,
|
|
44
|
+
content: (i) => (i.facts.readFaceOpen === true ? EXECUTION_ENVIRONMENT_OPEN_READS : EXECUTION_ENVIRONMENT),
|
|
45
45
|
legacyBlockId: "harness.context",
|
|
46
46
|
},
|
|
47
47
|
{ id: "core/simple.communicating", slot: "harness", rank: 250, ...CORE, admit: (i) => i.facts.promptProfile !== "classic", content: (i) => (i.facts.fableMitigations === true ? SIMPLE_COMMUNICATING_FABLE : SIMPLE_COMMUNICATING_LEAN), legacyBlockId: "harness.context" },
|
|
@@ -41,6 +41,10 @@ export interface PromptRuntimeFacts {
|
|
|
41
41
|
policyEnabled: boolean;
|
|
42
42
|
hooksEnabled: boolean;
|
|
43
43
|
isolationEnabled: boolean;
|
|
44
|
+
/** design/199 F8① — the resolved read face is OPEN (renders the execution-environment block's
|
|
45
|
+
* open-reads first bullet). OPTIONAL: absence reads as false (roots wording, byte-identical),
|
|
46
|
+
* so existing fact constructors keep compiling. */
|
|
47
|
+
readFaceOpen?: boolean;
|
|
44
48
|
withinTaskCompactionEnabled: boolean;
|
|
45
49
|
supervisorEnabled: boolean;
|
|
46
50
|
orchestrationEnabled: boolean;
|
|
@@ -161,15 +161,9 @@ export declare const URL_SAFETY = "IMPORTANT: You must NEVER generate or guess U
|
|
|
161
161
|
* runs before EVERY request, which is a stronger reason to warn than CC's.
|
|
162
162
|
*/
|
|
163
163
|
export declare const SUMMARIZE_TOOL_RESULTS = "When working with tool results, write down any important information you might need later in your own response, as the original tool result may be cleared or summarized from the context later.";
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
* we don't run it; this gives the model-visible contract for OUR exec-env (managed container / remote host)
|
|
168
|
-
* + the design/37 policy gate — how to read a restriction (permission/network/policy-deny) vs an ordinary
|
|
169
|
-
* failure, and to not circumvent a genuine restriction. Injected only when isolation OR a policy gate is
|
|
170
|
-
* active (§16.2) — never claim an isolated environment that isn't there.
|
|
171
|
-
*/
|
|
172
|
-
export declare const EXECUTION_ENVIRONMENT = "# Execution environment\nCommands run inside an isolated execution environment (a managed container or remote host), not on the operator's machine. Within it:\n- You can read and write within the project working directory. Writes outside it, or to system paths, may be denied by the environment or the permission policy.\n- Network access may be restricted to an allowlist. A blocked request fails at the network layer \u2014 it does not silently succeed.\n- A permission policy may intercept individual tool calls and deny them. A denied call did not run; do not re-issue the identical call. Follow the denial message's own guidance: a policy denial is something to reason about and adjust to, while a rejection by the user means stop and follow their direction rather than working around it. If you cannot tell why a call was denied, ask the user (via the AskUserQuestion tool, if available) rather than guessing.\n\nWhen a command fails, identify the cause before retrying:\n- Evidence of an environment/permission restriction: \"Operation not permitted\", \"Permission denied\" on an unexpected path, a network timeout/refusal to a host, or an explicit policy-deny message.\n- Ordinary failures (missing file, wrong argument, a non-zero exit from the program itself) are unrelated to isolation \u2014 fix the command rather than treating it as a restriction.\n\nIf a restriction genuinely blocks a necessary action, do NOT attempt to circumvent it (no privilege escalation, no disabling of guards, no destructive workarounds). Adjust your approach, or surface the limitation to the user with the specific evidence you saw.";
|
|
164
|
+
export declare const EXECUTION_ENVIRONMENT: string;
|
|
165
|
+
/** design/199 F8① — the open-read-face variant (first bullet tells the truth about reads). */
|
|
166
|
+
export declare const EXECUTION_ENVIRONMENT_OPEN_READS: string;
|
|
173
167
|
/**
|
|
174
168
|
* design/97 CORE-6 (P1b) — worktree-isolation NOTICE. Composed (via {@link StablePromptContext.worktreeIsolated})
|
|
175
169
|
* only when the task runs in an isolated git worktree, so the model treats inherited paths correctly. Generic
|
|
@@ -291,6 +285,10 @@ export interface EnvironmentFacts {
|
|
|
291
285
|
/** Ruled 2026-08-05 (read-boundary whitelist): extra directories the READ faces may access
|
|
292
286
|
* (canonical) — reads auto-classify inside them, writes are refused exactly as before. */
|
|
293
287
|
additionalReadDirectories?: readonly string[];
|
|
288
|
+
/** design/199 件A — the RESOLVED read-face containment state ("open" | "roots"; additive
|
|
289
|
+
* structured key). Rendered only when "open" (the roots posture is the historical default and
|
|
290
|
+
* renders nothing new — byte-compat). */
|
|
291
|
+
readFace?: "open" | "roots";
|
|
294
292
|
/** OS name (uname -s) — remote = the container's, not the host's. */
|
|
295
293
|
platform?: string;
|
|
296
294
|
/** OS version (uname -r). */
|
|
@@ -444,6 +442,13 @@ export interface StablePromptContext {
|
|
|
444
442
|
* line in {@link harnessContext}. Don't claim a deny mechanism the task doesn't have (§6.3).
|
|
445
443
|
*/
|
|
446
444
|
policyEnabled?: boolean;
|
|
445
|
+
/**
|
|
446
|
+
* design/199 F8① — the resolved read face is OPEN: the `# Execution environment` block's first
|
|
447
|
+
* bullet renders its open-reads variant ({@link EXECUTION_ENVIRONMENT_OPEN_READS}) so the prompt
|
|
448
|
+
* never claims a read fence that is not there. Absent/false ⇒ the historical (roots) wording,
|
|
449
|
+
* byte-identical.
|
|
450
|
+
*/
|
|
451
|
+
readFaceOpen?: boolean;
|
|
447
452
|
/**
|
|
448
453
|
* Whether hooks are wired (design/37) — drives the "hook output is user feedback" line in
|
|
449
454
|
* {@link harnessContext}. Omitted/false → that line is left out (§6.3).
|
package/dist/prompts/default.js
CHANGED
|
@@ -108,9 +108,13 @@ You have no persistent memory store: what is said in this conversation is not au
|
|
|
108
108
|
export const CYBER_RISK = `IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.`;
|
|
109
109
|
export const URL_SAFETY = `IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.`;
|
|
110
110
|
export const SUMMARIZE_TOOL_RESULTS = `When working with tool results, write down any important information you might need later in your own response, as the original tool result may be cleared or summarized from the context later.`;
|
|
111
|
-
|
|
111
|
+
function executionEnvironmentBlock(openReads) {
|
|
112
|
+
const readWriteLine = openReads
|
|
113
|
+
? "- You can read files anywhere this environment exposes (a small sensitive-path deny list applies), and write within the project working directory. Writes outside it, or to system paths, may be denied by the environment or the permission policy."
|
|
114
|
+
: "- You can read and write within the project working directory. Writes outside it, or to system paths, may be denied by the environment or the permission policy.";
|
|
115
|
+
return `# Execution environment
|
|
112
116
|
Commands run inside an isolated execution environment (a managed container or remote host), not on the operator's machine. Within it:
|
|
113
|
-
|
|
117
|
+
${readWriteLine}
|
|
114
118
|
- Network access may be restricted to an allowlist. A blocked request fails at the network layer — it does not silently succeed.
|
|
115
119
|
- A permission policy may intercept individual tool calls and deny them. A denied call did not run; do not re-issue the identical call. Follow the denial message's own guidance: a policy denial is something to reason about and adjust to, while a rejection by the user means stop and follow their direction rather than working around it. If you cannot tell why a call was denied, ask the user (via the AskUserQuestion tool, if available) rather than guessing.
|
|
116
120
|
|
|
@@ -119,6 +123,9 @@ When a command fails, identify the cause before retrying:
|
|
|
119
123
|
- Ordinary failures (missing file, wrong argument, a non-zero exit from the program itself) are unrelated to isolation — fix the command rather than treating it as a restriction.
|
|
120
124
|
|
|
121
125
|
If a restriction genuinely blocks a necessary action, do NOT attempt to circumvent it (no privilege escalation, no disabling of guards, no destructive workarounds). Adjust your approach, or surface the limitation to the user with the specific evidence you saw.`;
|
|
126
|
+
}
|
|
127
|
+
export const EXECUTION_ENVIRONMENT = executionEnvironmentBlock(false);
|
|
128
|
+
export const EXECUTION_ENVIRONMENT_OPEN_READS = executionEnvironmentBlock(true);
|
|
122
129
|
export const WORKTREE_NOTICE = `# Isolated worktree
|
|
123
130
|
This task runs in its own isolated git worktree — a separate working copy whose root is the working directory shown in # Environment, NOT the repository's main checkout. Any absolute path you were given that points at the main checkout (or another worktree) refers to a DIFFERENT copy; translate it to the same relative path under this worktree's root before reading or writing, and operate only within this worktree. A file's content here may differ from the main checkout, so re-read a file in this worktree before editing it rather than assuming an earlier or external view is current.`;
|
|
124
131
|
export const WORKTREE_STASH_WARNING = "The git stash stack is shared with the main checkout and all other worktrees, and other agent sessions may push or pop it concurrently. " +
|
|
@@ -151,7 +158,7 @@ export function harnessHeadLines(ctx) {
|
|
|
151
158
|
export function harnessContext(ctx) {
|
|
152
159
|
const blocks = [harnessHeadLines(ctx), CYBER_RISK, URL_SAFETY, SUMMARIZE_TOOL_RESULTS];
|
|
153
160
|
if (ctx.policyEnabled || ctx.isolationEnabled)
|
|
154
|
-
blocks.push(EXECUTION_ENVIRONMENT);
|
|
161
|
+
blocks.push(ctx.readFaceOpen === true ? EXECUTION_ENVIRONMENT_OPEN_READS : EXECUTION_ENVIRONMENT);
|
|
155
162
|
if (ctx.promptProfile !== "classic") {
|
|
156
163
|
blocks.push(ctx.fableMitigations === true ? SIMPLE_COMMUNICATING_FABLE : SIMPLE_COMMUNICATING_LEAN);
|
|
157
164
|
blocks.push(SIMPLE_PRONOUNS);
|
|
@@ -243,6 +250,9 @@ export function buildEnvironmentContext(facts) {
|
|
|
243
250
|
if (facts.additionalReadDirectories && facts.additionalReadDirectories.length > 0) {
|
|
244
251
|
lines.push(`Additional read-only directories: ${facts.additionalReadDirectories.map((d) => inlineUntrusted(d)).join(", ")}`);
|
|
245
252
|
}
|
|
253
|
+
if (facts.readFace === "open") {
|
|
254
|
+
lines.push("File reads are not confined to the workspace root. A small sensitive-path deny list applies (see refusals for the exact pattern).");
|
|
255
|
+
}
|
|
246
256
|
if (facts.isGitRepo !== undefined)
|
|
247
257
|
lines.push(`Is a git repository: ${facts.isGitRepo ? "yes" : "no"}`);
|
|
248
258
|
if (facts.gitBranch)
|
|
@@ -91,6 +91,27 @@ export interface BashReadonlyRootBoundary {
|
|
|
91
91
|
* out-of-root signal — the classifier does not know where it points, which is a different verdict
|
|
92
92
|
* from knowing it points outside). */
|
|
93
93
|
homeDir?: string;
|
|
94
|
+
/** design/199 件B — the sensitive-path read deny judge over LEXICALLY RESOLVED operands: a hit
|
|
95
|
+
* demotes the command (ask, never auto-allow), independently of the roots — in-root operands are
|
|
96
|
+
* judged too. Returns the matched pattern, or null. TWO named residuals, both inherited from this
|
|
97
|
+
* classifier's declared purity (synchronous, zero I/O — RB-448/RB-451 state the same scope for the
|
|
98
|
+
* containment half): ① operand TARGET matching only — no ancestor intersection, so `grep -r x ~/`
|
|
99
|
+
* whose operand is `~` itself does not demote here (§3.4; the recursive reach residual belongs to
|
|
100
|
+
* the full-bash lane's honest scope note); ② LEXICAL only — an in-root symlink whose target is a
|
|
101
|
+
* guarded path reads as its innocent spelling here, exactly as it does for the containment half
|
|
102
|
+
* (the enforcing/canonicalizing recheck is the bash_readonly leg's job via checkedPaths; the
|
|
103
|
+
* classify auto-allow lane has no I/O seat by contract). The structured read faces judge BOTH
|
|
104
|
+
* views (canonical + lexical) — this seat is the shell lane's honest-friction floor, not its
|
|
105
|
+
* security boundary (§3.0). The `bash_readonly` face deliberately does NOT wire this seat (its
|
|
106
|
+
* roots containment + command allowlist double gate is the deployment's own read-safety
|
|
107
|
+
* declaration — v1 ruling). */
|
|
108
|
+
denyMatch?: (resolvedPath: string) => string | null;
|
|
109
|
+
/** design/199 件A — the resolved read-face containment state. Under "open" the CONTAINMENT half of
|
|
110
|
+
* this boundary is structurally satisfied (an out-of-roots operand is not a demotion; it reports
|
|
111
|
+
* as a checked candidate instead), while the deny half above keeps judging in both faces (§2.0).
|
|
112
|
+
* Absent ⇒ "roots" (byte-compat). The `bash_readonly` face never passes this seat — its
|
|
113
|
+
* containment is load-bearing and never opens. */
|
|
114
|
+
face?: "open" | "roots";
|
|
94
115
|
}
|
|
95
116
|
/**
|
|
96
117
|
* RB-412 — the structured verdict of {@link classifyCompoundReadonlyDetailed}. `reason === undefined`
|
|
@@ -588,6 +588,12 @@ function evaluateReadBoundary(foldedSegments, boundary) {
|
|
|
588
588
|
for (const finding of collectSegmentBoundaryFindings(toks, boundary)) {
|
|
589
589
|
if (finding.kind === "unresolvable")
|
|
590
590
|
return { reason: finding.reason };
|
|
591
|
+
const denied = boundary.denyMatch?.(finding.path);
|
|
592
|
+
if (denied != null) {
|
|
593
|
+
return {
|
|
594
|
+
reason: `a command operand resolves to "${finding.path}", which matches the sensitive-path read deny list (pattern "${denied}") ${NOT_AUTO_ALLOWED}`,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
591
597
|
if (finding.kind === "inside") {
|
|
592
598
|
if (!inside.includes(finding.path))
|
|
593
599
|
inside.push(finding.path);
|
|
@@ -598,6 +604,11 @@ function evaluateReadBoundary(foldedSegments, boundary) {
|
|
|
598
604
|
undecided.push(finding.path);
|
|
599
605
|
continue;
|
|
600
606
|
}
|
|
607
|
+
if (boundary.face === "open") {
|
|
608
|
+
if (!inside.includes(finding.path))
|
|
609
|
+
inside.push(finding.path);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
601
612
|
if (!outside.some((o) => o.path === finding.path))
|
|
602
613
|
outside.push(finding);
|
|
603
614
|
}
|
|
@@ -40,6 +40,13 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
|
|
|
40
40
|
export declare function bashReversibilityProbe(allow?: readonly string[], boundary?: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => {
|
|
41
41
|
reversible: boolean;
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* design/199 D-6 — the FULL shell's contract id, single-sourced: both shell faces share the wire
|
|
45
|
+
* name "Bash", so this id is the ONE structural discriminator between the write-capable shell and
|
|
46
|
+
* the read-only allowlist face (`core.bash_readonly@1`). Consumed by prepare-task's
|
|
47
|
+
* fullShellReachable roster assertion (its first load-bearing consumer).
|
|
48
|
+
*/
|
|
49
|
+
export declare const FULL_SHELL_CONTRACT_ID = "core.bash@1";
|
|
43
50
|
/** The CC-verbatim exit-1 interpretation for `command`, or undefined when exit 1 means a real error.
|
|
44
51
|
* Conservative parse: last `;`/`&&`/`||`/newline statement → last `|` pipeline segment → leading
|
|
45
52
|
* command name (env-assignments skipped, path prefix stripped); `git grep`/`git diff` special-cased
|
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -10,7 +10,7 @@ import { imageMagicMatches, withinAnyRoot } from "./safety.js";
|
|
|
10
10
|
import { isRemoteExecutionEnv, hasDestroy, isIsolated } from "../../core/remote-env.js";
|
|
11
11
|
import { ghRateLimitHint } from "./gh-rate-limit.js";
|
|
12
12
|
import { resolveBashTimeoutCaps, bashTimeoutCapsSec, bashTimeoutArgRefusal, bashTimeoutParamDescription, envErrorDetail, bashMaxOutputChars, clipShellOutput, writeShellOverflowFile, createShellOverflowSpoolFence, shellRecoveryHint, CWD_SENTINEL, BASH_READONLY_CONFINEMENT_NOTE, } from "./fs-shared.js";
|
|
13
|
-
import { BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyBoundedReadonlyPollLoop,
|
|
13
|
+
import { BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyBoundedReadonlyPollLoop, classifyCompoundReadonlyDetailed, classifySimpleCommandReadBoundary, NOT_AUTO_ALLOWED, } from "./bash-readonly-classifier.js";
|
|
14
14
|
export function bashReversibilityProbe(allow, boundary) {
|
|
15
15
|
const allowSet = new Set(allow ?? BASH_READONLY_DEFAULT_ALLOW);
|
|
16
16
|
return (args) => {
|
|
@@ -21,11 +21,16 @@ export function bashReversibilityProbe(allow, boundary) {
|
|
|
21
21
|
if (a?.run_in_background === true)
|
|
22
22
|
return { reversible: false };
|
|
23
23
|
const resolved = typeof boundary === "function" ? boundary() : boundary;
|
|
24
|
-
|
|
24
|
+
const detailed = classifyCompoundReadonlyDetailed(command, allowSet, resolved);
|
|
25
|
+
if (detailed.reason === undefined) {
|
|
26
|
+
if (detailed.undecidedPaths !== undefined && detailed.undecidedPaths.length > 0)
|
|
27
|
+
return { reversible: false };
|
|
25
28
|
return { reversible: true };
|
|
29
|
+
}
|
|
26
30
|
return { reversible: classifyBoundedReadonlyPollLoop(command, allowSet, resolved) === undefined };
|
|
27
31
|
};
|
|
28
32
|
}
|
|
33
|
+
export const FULL_SHELL_CONTRACT_ID = "core.bash@1";
|
|
29
34
|
const EXIT1_INTERPRETATION = {
|
|
30
35
|
grep: "No matches found",
|
|
31
36
|
rg: "No matches found",
|
|
@@ -497,7 +502,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
497
502
|
const bgEnvIsolatedOwned = hasDestroy(env) && isIsolated(env);
|
|
498
503
|
return defineTool({
|
|
499
504
|
name: "Bash",
|
|
500
|
-
contract: { contractId:
|
|
505
|
+
contract: { contractId: FULL_SHELL_CONTRACT_ID, implementationRevision: "1" },
|
|
501
506
|
description: bashDescription(coAuthor, timeoutCaps, bgNotifies, bgRetained, bgSessionScoped, bgEnvIsolatedOwned),
|
|
502
507
|
parameters: Type.Object({
|
|
503
508
|
command: Type.String({ description: "The command to execute" }),
|
|
@@ -33,7 +33,7 @@ type ReadPdfReturn = string | {
|
|
|
33
33
|
* Every degraded return carries `details.fallback = { level, reason }` (telemetry on the structured frame).
|
|
34
34
|
* `caps` absent ⇒ fully capable (byte-compat: native document block; the brain placeholder still guards).
|
|
35
35
|
*/
|
|
36
|
-
export declare function readPdfFile(env: ExecutionEnv, path: string, key: string, pages: string | undefined, signal: AbortSignal | undefined, downsamplerOpt: ReadImageDownsamplerOption, cwd: string, preRead?: Uint8Array, caps?: PdfModelCapabilities): Promise<ReadPdfReturn>;
|
|
36
|
+
export declare function readPdfFile(env: ExecutionEnv, path: string, key: string, pages: string | undefined, signal: AbortSignal | undefined, downsamplerOpt: ReadImageDownsamplerOption, cwd: string, preRead?: Uint8Array, caps?: PdfModelCapabilities, readDeny?: import("./read-deny.js").ReadDenyMatcher): Promise<ReadPdfReturn>;
|
|
37
37
|
/** E1: readPdfFile's own return type stays `ReadPdfReturn` (its INTERNAL string-means-error dispatch
|
|
38
38
|
* contract, shared with pdfPagesToImageBlocks) — the isError flag is applied once, here, at the tool's
|
|
39
39
|
* actual execute() boundary, not inside the helper. */
|
package/dist/tools/fs/fs-pdf.js
CHANGED
|
@@ -30,13 +30,13 @@ function boundPdfExtractedText(text) {
|
|
|
30
30
|
return { body: text, truncated: false };
|
|
31
31
|
return { body: text.slice(0, MAX_READ_BYTES), truncated: true };
|
|
32
32
|
}
|
|
33
|
-
export async function readPdfFile(env, path, key, pages, signal, downsamplerOpt, cwd, preRead, caps) {
|
|
33
|
+
export async function readPdfFile(env, path, key, pages, signal, downsamplerOpt, cwd, preRead, caps, readDeny) {
|
|
34
34
|
const cap = caps ?? { document: true, vision: true };
|
|
35
35
|
const meta = await env.fileInfo(key, signal);
|
|
36
36
|
if (!meta.ok) {
|
|
37
37
|
const ex = await env.exists(key, signal);
|
|
38
38
|
if (ex.ok && !ex.value)
|
|
39
|
-
return `Error (Read): ${await enoentMessage(env, key, cwd, signal)}`;
|
|
39
|
+
return `Error (Read): ${await enoentMessage(env, key, cwd, signal, readDeny)}`;
|
|
40
40
|
return `Error (Read): cannot stat PDF "${path}" to verify its size before reading: ${meta.error.message}`;
|
|
41
41
|
}
|
|
42
42
|
if (meta.value.kind === "directory") {
|
|
@@ -5,4 +5,4 @@ import { type ReadImageDownsamplerOption, type CwdRef } from "./fs-shared.js";
|
|
|
5
5
|
export declare function createReadFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], imageDownsampler?: ReadImageDownsamplerOption, pdfCapabilities?: PdfModelCapabilities, bgOutputReadExemption?: (canonicalKey: string, ctx: {
|
|
6
6
|
taskId?: string;
|
|
7
7
|
principal?: string;
|
|
8
|
-
}) => boolean, readCyberReminder?: boolean): AgentTool;
|
|
8
|
+
}) => boolean, readCyberReminder?: boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./read-face.js").ReadFace): AgentTool;
|
package/dist/tools/fs/fs-read.js
CHANGED
|
@@ -7,14 +7,17 @@ import { MCP_IMAGE_MAX_BASE64, IMAGE_TARGET_RAW_SIZE } from "../../core/mcp.js";
|
|
|
7
7
|
import { PDF_MAX_PAGES_PER_READ, pdfMagicMatches } from "./pdf.js";
|
|
8
8
|
import { MAX_READ_BYTES, SLICED_READ_MAX_BYTES, MAX_IMAGE_READ_BYTES, MAX_IMAGE_DOWNSAMPLE_INPUT_BYTES, NO_DOWNSAMPLER_IMAGE_CAP_HINT, resolveAutoDownsampler, MAX_READ_OUTPUT_CHARS, READ_CYBER_REMINDER, FILE_PATH_PARAMS, countLines, seededFileUnchangedReminder, enoentMessage, } from "./fs-shared.js";
|
|
9
9
|
import { readPdfFile, pdfResultToToolReturn } from "./fs-pdf.js";
|
|
10
|
-
export function createReadFileTool(env, state, rootCanonical, cwdRef, additionalRoots, imageDownsampler, pdfCapabilities, bgOutputReadExemption, readCyberReminder) {
|
|
10
|
+
export function createReadFileTool(env, state, rootCanonical, cwdRef, additionalRoots, imageDownsampler, pdfCapabilities, bgOutputReadExemption, readCyberReminder, readDeny, readFace) {
|
|
11
11
|
const cyberReminder = readCyberReminder === false ? "" : READ_CYBER_REMINDER;
|
|
12
|
+
const pathBoundLine = readFace === "open"
|
|
13
|
+
? "- `file_path` may be relative (resolved against the tracked working directory) or absolute. Reads are not confined to the workspace roots; a small sensitive-path deny list applies.\n"
|
|
14
|
+
: "- `file_path` may be relative (resolved against the tracked working directory) or absolute (within the configured roots).\n";
|
|
12
15
|
return defineTool({
|
|
13
16
|
name: "Read",
|
|
14
17
|
contract: { contractId: "core.read@1", implementationRevision: "1" },
|
|
15
18
|
description: "Reads a file from the local filesystem.\n" +
|
|
16
19
|
"\n" +
|
|
17
|
-
|
|
20
|
+
pathBoundLine +
|
|
18
21
|
"- By default, it reads the whole file (from `offset`, 1-based); a very large file is served as a partial view with an explicit marker and the next-page call.\n" +
|
|
19
22
|
"- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters\n" +
|
|
20
23
|
"- Results are returned using cat -n format, with line numbers starting at 1\n" +
|
|
@@ -53,7 +56,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
53
56
|
content: text,
|
|
54
57
|
details: { type: "file_unchanged", reason, file: { filePath: path, startLine, endLine, totalLines } },
|
|
55
58
|
});
|
|
56
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, bgOutputReadExemption === undefined ? undefined : (key) => bgOutputReadExemption(key, ctx));
|
|
59
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, bgOutputReadExemption === undefined ? undefined : (key) => bgOutputReadExemption(key, ctx), readDeny, readFace);
|
|
57
60
|
if (!r.ok)
|
|
58
61
|
return errorResult(violationText("Read", r.violation), violationDetails(r.violation));
|
|
59
62
|
const isNb = isNotebookPath(r.key);
|
|
@@ -63,7 +66,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
63
66
|
if (!meta.ok) {
|
|
64
67
|
const ex = await env.exists(r.key, ctx.signal);
|
|
65
68
|
if (ex.ok && !ex.value)
|
|
66
|
-
return errorResult(`Error (Read): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal)}`);
|
|
69
|
+
return errorResult(`Error (Read): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal, readDeny)}`);
|
|
67
70
|
return errorResult(`Error (Read): cannot stat image "${path}" to verify its size before reading: ${meta.error.message}`);
|
|
68
71
|
}
|
|
69
72
|
if (meta.value.kind === "directory") {
|
|
@@ -121,7 +124,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
121
124
|
};
|
|
122
125
|
}
|
|
123
126
|
if (r.key.toLowerCase().endsWith(".pdf")) {
|
|
124
|
-
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, undefined, pdfCapabilities));
|
|
127
|
+
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, undefined, pdfCapabilities, readDeny));
|
|
125
128
|
}
|
|
126
129
|
const binaryExt = hasBinaryExtension(r.key);
|
|
127
130
|
const binaryExtRefusal = () => errorResult(`Error (Read): "${path}" appears to be a binary file (by extension); this tool reads UTF-8 text only.`);
|
|
@@ -146,7 +149,8 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
146
149
|
else {
|
|
147
150
|
const ex = await env.exists(r.key, ctx.signal);
|
|
148
151
|
if (ex.ok && !ex.value)
|
|
149
|
-
return errorResult(`Error (Read): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal)}`);
|
|
152
|
+
return errorResult(`Error (Read): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal, readDeny)}`);
|
|
153
|
+
return errorResult(`Error (Read): cannot determine the type of "${path.slice(0, 300)}" (stat failed: ${info.error.message.slice(0, 200)}); refusing to open it — if it is a FIFO/socket/device, reading it would hang. Retry if this was transient, or inspect it with Bash (e.g. \`file\`, \`ls -l\`).`);
|
|
150
154
|
}
|
|
151
155
|
const readBin = await env.readBinaryFile(r.key, ctx.signal);
|
|
152
156
|
if (!readBin.ok)
|
|
@@ -165,7 +169,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
165
169
|
return errorResult(`Error (Read): "${path}" is too large to read in full (${readSize} bytes > ${MAX_READ_BYTES}-byte cap); pass an explicit offset/limit to read a slice, or use grep to search it instead.`);
|
|
166
170
|
}
|
|
167
171
|
if (pdfMagicMatches(readBin.value)) {
|
|
168
|
-
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, readBin.value, pdfCapabilities));
|
|
172
|
+
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, readBin.value, pdfCapabilities, readDeny));
|
|
169
173
|
}
|
|
170
174
|
const magicFormat = binaryMagicFormat(readBin.value);
|
|
171
175
|
if (magicFormat !== undefined) {
|