@sema-agent/core 7.17.7 → 7.19.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 +233 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/anthropic.js +6 -3
- package/dist/brain/open-responses.js +10 -4
- package/dist/brain/openai.js +5 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/ask-origin.d.ts +16 -0
- package/dist/core/ask-origin.js +8 -0
- package/dist/core/auto-mode-arming.d.ts +6 -0
- package/dist/core/auto-mode-arming.js +5 -5
- package/dist/core/auto-mode-defaults.d.ts +43 -15
- package/dist/core/auto-mode-defaults.js +4 -1
- package/dist/core/auto-mode.d.ts +184 -59
- package/dist/core/auto-mode.js +188 -13
- package/dist/core/checkpoint-store.d.ts +23 -2
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/engine-notice.d.ts +77 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-fold.js +5 -1
- package/dist/core/gate-lanes.js +9 -8
- package/dist/core/gate-outcome.d.ts +42 -2
- package/dist/core/gate-outcome.js +23 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +227 -0
- package/dist/core/runner/classifier-ladder.js +214 -0
- package/dist/core/runner/contracts.d.ts +71 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/denial-limit-arms.js +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
- package/dist/core/runner/prepare-config-doors.d.ts +7 -1
- package/dist/core/runner/prepare-config-doors.js +13 -13
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +7 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +19 -14
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-apply.js +3 -3
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +45 -45
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +15 -5
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +83 -48
- package/dist/core/runner/synthetic-tools.js +69 -32
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner/turn-attachments.d.ts +43 -12
- package/dist/core/runner/turn-attachments.js +58 -14
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/sensitive-path-policy.d.ts +58 -17
- package/dist/core/sensitive-path-policy.js +33 -5
- package/dist/core/skill-listing-budget.d.ts +112 -0
- package/dist/core/skill-listing-budget.js +58 -0
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +46 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +19 -4
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +45 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/core/write-protect.d.ts +8 -3
- package/dist/core/write-protect.js +8 -5
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +13 -8
- package/dist/index.js +11 -7
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
- package/dist/tools/fs/bash-readonly-classifier.js +180 -558
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +165 -25
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +18 -3
- package/dist/tools/fs/safety.js +11 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +99 -13
- package/wire-changes/core@7.17.7.json +49 -0
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run's TOOL-PROGRESS lane — the single mint of the `tool_progress` frame: its clock, its cadence,
|
|
3
|
+
* and the heartbeat that keeps a silent long-running tool call visible.
|
|
4
|
+
*
|
|
5
|
+
* TWO PRODUCERS, ONE MINT. A tool that observes its own work ticks fast and carries what it produced: the
|
|
6
|
+
* shell's execute leg watches its command's output and calls `emit` about once a second. Every OTHER call is
|
|
7
|
+
* opaque here — the runner knows only that it started and has not ended — so this lane runs a slow beat for
|
|
8
|
+
* any in-flight call silent for {@link TOOL_HEARTBEAT_MS}.
|
|
9
|
+
*
|
|
10
|
+
* THE LANE OWNS TIME, the producer owns FACTS. A producer reports what it has seen (the tail, the totals),
|
|
11
|
+
* never how long it has run: `elapsedTimeSeconds` is computed HERE from one origin per call, because the two
|
|
12
|
+
* producers do not start their clocks together — the station registers a call when it ENTERS EXECUTION, a
|
|
13
|
+
* shell command's own clock could only start at its spawn. With two origins a 30 s beat could be followed
|
|
14
|
+
* moments later by a shell tick reporting 1 s, breaking the monotonicity the frame promises.
|
|
15
|
+
*
|
|
16
|
+
* THE CADENCE HAS ONE ENFORCEMENT POINT, same reason: a producer's timer is a SAMPLING clock, not the
|
|
17
|
+
* contract. `emit` drops a frame landing inside {@link TOOL_PROGRESS_MIN_INTERVAL_MS} of that call's last.
|
|
18
|
+
*
|
|
19
|
+
* ONE RULE FOR THE BEAT: a call is beaten when nothing has been emitted FOR IT for a whole interval,
|
|
20
|
+
* counting its own start as the first such event — covering both "it only just started" and "the tool
|
|
21
|
+
* already ticks faster than this". The interval is the RUN's, so a first beat lands anywhere in
|
|
22
|
+
* [TOOL_HEARTBEAT_MS, 2 × TOOL_HEARTBEAT_MS): the price of one timer per run, stated rather than hidden.
|
|
23
|
+
*
|
|
24
|
+
* THE CLOCK IS MONOTONIC: every predicate here is a DIFFERENCE of two readings (is this call due a beat, is
|
|
25
|
+
* this offer inside the floor, how long has it run), and off the wall clock a backward step — NTP, a
|
|
26
|
+
* container's clock settling after a resume — makes each difference negative and silences BOTH producers
|
|
27
|
+
* until it catches up: the exact failure this lane exists to prevent.
|
|
28
|
+
*
|
|
29
|
+
* LIVENESS ONLY — see the `tool_progress` member's contract in `task-event.ts`. The timer is `unref`'d and
|
|
30
|
+
* every emission is fault-contained. */
|
|
31
|
+
import type { TaskEvent, TaskEventIdentity } from "../types.js";
|
|
32
|
+
import type { ToolProgressLane } from "./contracts.js";
|
|
33
|
+
/** How long a tool call may stay silent before this lane beats for it, in ms. ONE SOURCE, and CC
|
|
34
|
+
* 2.1.259's own per-tool heartbeat interval (`Cct`) — this engine keeps the cadence and folds the beat
|
|
35
|
+
* into the one progress frame instead of minting a second shape (registered divergence). */
|
|
36
|
+
export declare const TOOL_HEARTBEAT_MS = 30000;
|
|
37
|
+
/** The floor between two frames of ONE call — the `tool_progress.cadence` contract, enforced here and
|
|
38
|
+
* nowhere else so it holds across producers rather than inside each one. */
|
|
39
|
+
export declare const TOOL_PROGRESS_MIN_INTERVAL_MS = 1000;
|
|
40
|
+
/** Build the lane over the run's content pusher (`pushContent` — the ONE seat that puts a frame on this
|
|
41
|
+
* run's queue and, for a sub-agent, forwards it to the parent's sink) and its identity mint. */
|
|
42
|
+
export declare function createToolProgressLane(push: (e: TaskEvent) => void, ident: () => TaskEventIdentity): ToolProgressLane;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const TOOL_HEARTBEAT_MS = 30_000;
|
|
2
|
+
export const TOOL_PROGRESS_MIN_INTERVAL_MS = 1_000;
|
|
3
|
+
const monotonicNow = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
4
|
+
export function createToolProgressLane(push, ident) {
|
|
5
|
+
const inFlight = new Map();
|
|
6
|
+
let timer;
|
|
7
|
+
const arm = () => {
|
|
8
|
+
if (timer !== undefined)
|
|
9
|
+
return;
|
|
10
|
+
timer = setInterval(beat, TOOL_HEARTBEAT_MS);
|
|
11
|
+
timer.unref?.();
|
|
12
|
+
};
|
|
13
|
+
const emit = (body) => {
|
|
14
|
+
const now = monotonicNow();
|
|
15
|
+
let live = inFlight.get(body.toolCallId);
|
|
16
|
+
if (live === undefined) {
|
|
17
|
+
live = { toolName: body.toolName, startedAt: now, lastAt: now - TOOL_PROGRESS_MIN_INTERVAL_MS };
|
|
18
|
+
inFlight.set(body.toolCallId, live);
|
|
19
|
+
arm();
|
|
20
|
+
}
|
|
21
|
+
if (now - live.lastAt < TOOL_PROGRESS_MIN_INTERVAL_MS)
|
|
22
|
+
return;
|
|
23
|
+
live.lastAt = now;
|
|
24
|
+
try {
|
|
25
|
+
push({ type: "tool_progress", ...body, elapsedTimeSeconds: Math.floor((now - live.startedAt) / 1000), ...ident() });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function beat() {
|
|
31
|
+
const now = monotonicNow();
|
|
32
|
+
for (const [toolCallId, live] of inFlight) {
|
|
33
|
+
if (now - live.lastAt < TOOL_HEARTBEAT_MS)
|
|
34
|
+
continue;
|
|
35
|
+
emit({ toolCallId, toolName: live.toolName });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const disarm = () => {
|
|
39
|
+
if (timer === undefined)
|
|
40
|
+
return;
|
|
41
|
+
clearInterval(timer);
|
|
42
|
+
timer = undefined;
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
emit,
|
|
46
|
+
noteStart(toolCallId, toolName) {
|
|
47
|
+
const startedAt = monotonicNow();
|
|
48
|
+
inFlight.set(toolCallId, { toolName, startedAt, lastAt: startedAt });
|
|
49
|
+
arm();
|
|
50
|
+
},
|
|
51
|
+
noteEnd(toolCallId) {
|
|
52
|
+
inFlight.delete(toolCallId);
|
|
53
|
+
if (inFlight.size === 0)
|
|
54
|
+
disarm();
|
|
55
|
+
},
|
|
56
|
+
clear() {
|
|
57
|
+
inFlight.clear();
|
|
58
|
+
disarm();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpDroppedTool } from "../mcp.js";
|
|
2
2
|
import type { TaskSpec } from "../types.js";
|
|
3
|
-
import { type SkillListingEntry } from "./synthetic-tools.js";
|
|
3
|
+
import { type SkillListingEntry, type SkillsBlockRender } from "./synthetic-tools.js";
|
|
4
|
+
import { type ResolvedSkillListingBudget } from "../skill-listing-budget.js";
|
|
4
5
|
/**
|
|
5
6
|
* Turn-boundary attachment seam: the PURE producer layer (CC `getContextAttachments`
|
|
6
7
|
* parity). This module is the single home of the CC-parity cadence judgment + reminder copy (提示词主权批:
|
|
@@ -545,6 +546,9 @@ export interface AttachmentInputs {
|
|
|
545
546
|
* the delta path serves dynamic-installation deployments and tests, same posture as
|
|
546
547
|
* `agentListing`). Only passed when `config.skillsListing` is on. */
|
|
547
548
|
skillsListing?: ReadonlyArray<SkillListingEntry>;
|
|
549
|
+
/** The lowered skills-listing budget seats, resolved once at the config door. Absent ⇒ the module
|
|
550
|
+
* defaults, i.e. byte-identical to the pre-seat render. */
|
|
551
|
+
skillsListingBudget?: ResolvedSkillListingBudget;
|
|
548
552
|
/** G1 续批 mcp_instructions: pending deltas snapshotted from `MaterializedMcp.instructionsDelta`.
|
|
549
553
|
* CALLER-owned consumption on intact cap survival (same `newTools` discipline). */
|
|
550
554
|
mcpInstructions?: {
|
|
@@ -849,21 +853,48 @@ export declare function commitAgentListing(state: AttachmentState, entries: Read
|
|
|
849
853
|
export declare const SKILLS_LISTING_DELTA_HEADER = "New skills are now available:";
|
|
850
854
|
/** [c209-C] skills removal-arm header — mirrors the agent-listing removal arm's phrasing. */
|
|
851
855
|
export declare const SKILLS_LISTING_REMOVED_HEADER = "The following skills are no longer available:";
|
|
856
|
+
/** What one {@link renderSkillsListingFrame} call produced: the render's own report, plus the
|
|
857
|
+
* denominator it is against. */
|
|
858
|
+
export interface SkillsListingFrameRender extends SkillsBlockRender {
|
|
859
|
+
/** How many skills this frame set out to list — the whole roster on an initial frame, the ADDED set
|
|
860
|
+
* on a drift frame. The disclosure's denominator, read off the frame that was actually delivered
|
|
861
|
+
* rather than re-derived from the roster. */
|
|
862
|
+
readonly listed: number;
|
|
863
|
+
}
|
|
852
864
|
/**
|
|
853
|
-
* [c209-C] skills_listing producer — the skills counterpart of {@link renderAgentListingDelta}
|
|
854
|
-
* First call (no announced map) = the full
|
|
855
|
-
* the exact bytes that used to sit in the stable system prompt
|
|
856
|
-
* classic ships its skills listing the same way, as a
|
|
857
|
-
* Later calls announce only drift, name-keyed like the
|
|
858
|
-
* {@link SKILLS_LISTING_DELTA_HEADER} with the SAME `- name: description` line
|
|
859
|
-
* removals under {@link SKILLS_LISTING_REMOVED_HEADER} + `- <name>` lines,
|
|
860
|
-
* CC-verbatim {@link AMBIENT_CONTEXT_NOTE} (agent-removal-arm parity). A description
|
|
861
|
-
* existing name is not re-announced (name-keyed, agent-arm parity).
|
|
865
|
+
* [c209-C] skills_listing producer — the skills counterpart of {@link renderAgentListingDelta}, with
|
|
866
|
+
* the degradation report the disclosure lane announces. First call (no announced map) = the full
|
|
867
|
+
* `<skills>` disclosure block VERBATIM (the exact bytes that used to sit in the stable system prompt
|
|
868
|
+
* now ride the reminder lane; CC 209 classic ships its skills listing the same way, as a
|
|
869
|
+
* first-user-turn reminder ~5.9KB, report §5.1). Later calls announce only drift, name-keyed like the
|
|
870
|
+
* agent arm: adds under {@link SKILLS_LISTING_DELTA_HEADER} with the SAME `- name: description` line
|
|
871
|
+
* shape the block uses, removals under {@link SKILLS_LISTING_REMOVED_HEADER} + `- <name>` lines,
|
|
872
|
+
* followed by the CC-verbatim {@link AMBIENT_CONTEXT_NOTE} (agent-removal-arm parity). A description
|
|
873
|
+
* change on an existing name is not re-announced (name-keyed, agent-arm parity).
|
|
874
|
+
*
|
|
875
|
+
* ONE RULER FOR EVERY FRAME: the drift frame is measured against the SAME resolved budget as the
|
|
876
|
+
* initial block, arms and joiners included, and its ADD arm degrades through the same three tiers
|
|
877
|
+
* ({@link renderSkillLinesWithin}). An unmeasured drift frame is the block's own failure mode one lane
|
|
878
|
+
* over: a roster's worth of new skills renders past `ATTACHMENT_BYTE_CAP`, `capAttachments` clips the
|
|
879
|
+
* body, a clipped body never equals the intact-survival re-render, and the frame is re-rendered and
|
|
880
|
+
* re-clipped at every boundary for the life of the task. The split: the ambient tail and the removal
|
|
881
|
+
* arm's one-line floor are reserved first (both constants), the ADD arm degrades into what is left,
|
|
882
|
+
* and the removal arm spends whatever the add arm did not — so the frame's size is the resolved budget
|
|
883
|
+
* or, when even the two floors do not fit it, a small constant, never a function of the roster.
|
|
884
|
+
*
|
|
885
|
+
* The report covers the ADD arm only: `nameOnly` / `dropped` name skills this frame listed without a
|
|
886
|
+
* description or could not list at all. A departed name that did not fit is not a listed skill.
|
|
862
887
|
*
|
|
863
888
|
* MED-3① discipline: PURE — the announced-set advance is CALLER-owned via {@link commitSkillsListing},
|
|
864
|
-
* committed only on first-frame delivery or intact cap survival at a boundary.
|
|
889
|
+
* committed only on first-frame delivery or intact cap survival at a boundary. The commit records
|
|
890
|
+
* EVERY entry, including one this frame dropped: the alternative is a frame re-rendering the same
|
|
891
|
+
* un-listable tail at every boundary, which is the replay this budget exists to end. The `Skill` tool
|
|
892
|
+
* serves a dropped skill, and `skills.listing_truncated` tells the deployment it happened.
|
|
865
893
|
*/
|
|
866
|
-
export declare function
|
|
894
|
+
export declare function renderSkillsListingFrame(state: AttachmentState, entries: ReadonlyArray<SkillListingEntry>, budget?: ResolvedSkillListingBudget): SkillsListingFrameRender | undefined;
|
|
895
|
+
/** The text-only face of {@link renderSkillsListingFrame} — what the delivery lanes render, and what
|
|
896
|
+
* the intact-survival predicate re-renders to compare. */
|
|
897
|
+
export declare function renderSkillsListingDelta(state: AttachmentState, entries: ReadonlyArray<SkillListingEntry>, budget?: ResolvedSkillListingBudget): string | undefined;
|
|
867
898
|
/** [c209-C] commit half of {@link renderSkillsListingDelta} — same MED-3① contract as
|
|
868
899
|
* {@link commitAgentListing}: called by the run loop only on first-frame delivery or when the
|
|
869
900
|
* `skills_listing` block survived the capped bundle INTACT; a clipped/dropped block leaves the
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PRESENT_PLAN_TOOL_NAME } from "../present-plan-tool.js";
|
|
2
2
|
import { delimitUntrustedWithClip, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
3
3
|
import { protocolOf } from "../protocol-table.js";
|
|
4
|
-
import {
|
|
4
|
+
import { renderSkillLinesWithin, renderSkillsBlock } from "./synthetic-tools.js";
|
|
5
|
+
import { SKILL_LISTING_DESC_MAX_CHARS_DEFAULT, SKILLS_BLOCK_MAX_BYTES } from "../skill-listing-budget.js";
|
|
5
6
|
import { TOOL_SEARCH_NAME as TOOL_SEARCH_TOOL_NAME } from "./tool-disclosure.js";
|
|
6
7
|
export const TODO_REMINDER_CONFIG = {
|
|
7
8
|
TURNS_SINCE_WRITE: 10,
|
|
@@ -209,7 +210,9 @@ export function collectDueAttachments(state, inp) {
|
|
|
209
210
|
const agentListingBody = inp.config.agentListing && inp.agentListing !== undefined
|
|
210
211
|
? renderAgentListingDelta(state, inp.agentListing, inp.agentToolName ?? "Agent", inp.agentModels)
|
|
211
212
|
: undefined;
|
|
212
|
-
const skillsListingBody = inp.config.skillsListing && inp.skillsListing !== undefined
|
|
213
|
+
const skillsListingBody = inp.config.skillsListing && inp.skillsListing !== undefined
|
|
214
|
+
? renderSkillsListingDelta(state, inp.skillsListing, inp.skillsListingBudget)
|
|
215
|
+
: undefined;
|
|
213
216
|
const listingPair = [];
|
|
214
217
|
if (agentListingBody !== undefined)
|
|
215
218
|
listingPair.push({ source: "agent_listing", body: agentListingBody });
|
|
@@ -545,10 +548,9 @@ export function renderAgentListingDelta(state, entries, toolName, models) {
|
|
|
545
548
|
return blocks.join("\n\n");
|
|
546
549
|
}
|
|
547
550
|
const announcedIds = new Set([...announced.keys()].map(listingIdentity));
|
|
551
|
+
const entryIds = new Set(entries.map((e) => listingIdentity(e.name)));
|
|
548
552
|
const added = entries.filter((e) => !announcedIds.has(listingIdentity(e.name))).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
549
|
-
const removed = [...announced.keys()]
|
|
550
|
-
.filter((n) => !entries.some((e) => listingIdentity(e.name) === listingIdentity(n)))
|
|
551
|
-
.sort();
|
|
553
|
+
const removed = [...announced.keys()].filter((n) => !entryIds.has(listingIdentity(n))).sort();
|
|
552
554
|
const modelsDrifted = state.announcedModels !== undefined &&
|
|
553
555
|
models !== undefined &&
|
|
554
556
|
(state.announcedModels.length !== models.length ||
|
|
@@ -594,26 +596,68 @@ export function commitAgentListing(state, entries, models) {
|
|
|
594
596
|
}
|
|
595
597
|
export const SKILLS_LISTING_DELTA_HEADER = "New skills are now available:";
|
|
596
598
|
export const SKILLS_LISTING_REMOVED_HEADER = "The following skills are no longer available:";
|
|
597
|
-
|
|
599
|
+
const SKILLS_DELTA_BLOCK_JOINER = "\n\n";
|
|
600
|
+
function skillsRemovalArmFloor(count) {
|
|
601
|
+
return [SKILLS_LISTING_REMOVED_HEADER, `(… +${count} more are no longer available)`].join("\n");
|
|
602
|
+
}
|
|
603
|
+
function renderSkillsRemovalArm(removed, maxBytes) {
|
|
604
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
605
|
+
const lines = removed.map((n) => `- ${sanitizeUntrustedText(n, SHELLED_BODY_ENVELOPE_TAGS)}`);
|
|
606
|
+
const head = bytes(SKILLS_LISTING_REMOVED_HEADER);
|
|
607
|
+
const lineCost = (l) => bytes(l) + 1;
|
|
608
|
+
const prefix = new Array(lines.length + 1);
|
|
609
|
+
prefix[0] = 0;
|
|
610
|
+
for (let i = 0; i < lines.length; i++)
|
|
611
|
+
prefix[i + 1] = prefix[i] + lineCost(lines[i]);
|
|
612
|
+
if (head + prefix[lines.length] <= maxBytes)
|
|
613
|
+
return [SKILLS_LISTING_REMOVED_HEADER, ...lines].join("\n");
|
|
614
|
+
for (let m = lines.length - 1; m >= 0; m--) {
|
|
615
|
+
const tail = `(… +${lines.length - m} more are no longer available)`;
|
|
616
|
+
if (head + prefix[m] + lineCost(tail) <= maxBytes)
|
|
617
|
+
return [SKILLS_LISTING_REMOVED_HEADER, ...lines.slice(0, m), tail].join("\n");
|
|
618
|
+
}
|
|
619
|
+
return skillsRemovalArmFloor(lines.length);
|
|
620
|
+
}
|
|
621
|
+
export function renderSkillsListingFrame(state, entries, budget) {
|
|
598
622
|
const announced = state.announcedSkills;
|
|
599
623
|
if (announced === undefined) {
|
|
600
624
|
if (entries.length === 0)
|
|
601
625
|
return undefined;
|
|
602
|
-
|
|
626
|
+
const block = renderSkillsBlock(entries, budget);
|
|
627
|
+
return block === undefined ? undefined : { ...block, listed: entries.length };
|
|
603
628
|
}
|
|
604
629
|
const announcedIds = new Set([...announced.keys()].map(listingIdentity));
|
|
630
|
+
const entryIds = new Set(entries.map((e) => listingIdentity(e.name)));
|
|
605
631
|
const added = entries.filter((e) => !announcedIds.has(listingIdentity(e.name)));
|
|
606
|
-
const removed = [...announced.keys()].filter((n) => !
|
|
632
|
+
const removed = [...announced.keys()].filter((n) => !entryIds.has(listingIdentity(n)));
|
|
607
633
|
if (added.length === 0 && removed.length === 0)
|
|
608
634
|
return undefined;
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
635
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
636
|
+
const joiner = bytes(SKILLS_DELTA_BLOCK_JOINER);
|
|
637
|
+
const frameMaxBytes = Math.min(budget?.blockMaxBytes ?? SKILLS_BLOCK_MAX_BYTES, SKILLS_BLOCK_MAX_BYTES);
|
|
638
|
+
const ambientCost = removed.length > 0 ? joiner + bytes(AMBIENT_CONTEXT_NOTE) : 0;
|
|
639
|
+
const removalReserve = removed.length > 0 ? joiner + bytes(skillsRemovalArmFloor(removed.length)) : 0;
|
|
640
|
+
const addArm = added.length > 0
|
|
641
|
+
? renderSkillLinesWithin(added, {
|
|
642
|
+
blockMaxBytes: frameMaxBytes - ambientCost - removalReserve,
|
|
643
|
+
descMaxChars: budget?.descMaxChars ?? SKILL_LISTING_DESC_MAX_CHARS_DEFAULT,
|
|
644
|
+
frame: (lines) => [SKILLS_LISTING_DELTA_HEADER, ...lines].join("\n"),
|
|
645
|
+
})
|
|
646
|
+
: undefined;
|
|
647
|
+
const addCost = addArm === undefined ? 0 : bytes(addArm.text) + (removed.length > 0 ? joiner : 0);
|
|
648
|
+
const blocks = addArm === undefined ? [] : [addArm.text];
|
|
612
649
|
if (removed.length > 0) {
|
|
613
|
-
blocks.push(
|
|
614
|
-
blocks.push(AMBIENT_CONTEXT_NOTE);
|
|
650
|
+
blocks.push(renderSkillsRemovalArm(removed, frameMaxBytes - ambientCost - addCost), AMBIENT_CONTEXT_NOTE);
|
|
615
651
|
}
|
|
616
|
-
return
|
|
652
|
+
return {
|
|
653
|
+
text: blocks.join(SKILLS_DELTA_BLOCK_JOINER),
|
|
654
|
+
nameOnly: addArm?.nameOnly ?? [],
|
|
655
|
+
dropped: addArm?.dropped ?? [],
|
|
656
|
+
listed: added.length,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
export function renderSkillsListingDelta(state, entries, budget) {
|
|
660
|
+
return renderSkillsListingFrame(state, entries, budget)?.text;
|
|
617
661
|
}
|
|
618
662
|
export function commitSkillsListing(state, entries) {
|
|
619
663
|
state.announcedSkills = new Map(entries.map((e) => [e.name, e.description]));
|
|
@@ -393,7 +393,7 @@ export interface RunnerDeps {
|
|
|
393
393
|
};
|
|
394
394
|
/**
|
|
395
395
|
* design/199 件A — the DEPLOYMENT's read-face declaration
|
|
396
|
-
* ({@link import("../tools/fs/
|
|
396
|
+
* ({@link import("../tools/fs/containment-face.js").ContainmentFace}; see {@link TaskSpec.readFace} for the
|
|
397
397
|
* task layer and the resolution order). An interactive single-user product declares
|
|
398
398
|
* `readFace: "open"` here in one line; the engine default stays "roots" (D-1b). Under org
|
|
399
399
|
* governance this seat may still open (it is the deployment's own declaration); the task layer
|
|
@@ -406,7 +406,19 @@ export interface RunnerDeps {
|
|
|
406
406
|
* later prepare reads the new declaration; announced as `config.read_face_swapped`). A host whose
|
|
407
407
|
* declaration arrives late (a governance source read after boot) swaps it in rather than restarting.
|
|
408
408
|
*/
|
|
409
|
-
readFace?: import("../tools/fs/
|
|
409
|
+
readFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
410
|
+
/**
|
|
411
|
+
* The DEPLOYMENT's WRITE-face declaration
|
|
412
|
+
* ({@link import("../tools/fs/containment-face.js").ContainmentFace}; see {@link TaskSpec.writeFace}
|
|
413
|
+
* for the task layer and the resolution order — the SAME order the read seat runs, one body). The
|
|
414
|
+
* engine default stays "roots". Under org governance this seat may still open (it is the deployment's
|
|
415
|
+
* own declaration); the task layer may not. Beside a read-only (verifier) mount this seat's "open"
|
|
416
|
+
* silently CLAMPS to roots and is ANNOUNCED (`config.write_face_deployment_clamped`); TaskSpec.writeFace's
|
|
417
|
+
* own "open" there refuses loudly. INDEPENDENT of {@link readFace}: neither seat reads the other. A LIVE
|
|
418
|
+
* seat — `Runner.swapDeps({ writeFace })` replaces it without a restart (announced as
|
|
419
|
+
* `config.write_face_swapped`).
|
|
420
|
+
*/
|
|
421
|
+
writeFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
410
422
|
/**
|
|
411
423
|
* design/96 §20 (P1a) — DEPLOYMENT-DECLARED training knowledge cutoff per model id (e.g.
|
|
412
424
|
* `{ "qwen-3.5-35b": "2025-01", "deepseek-pro": "2024-07" }`). When the resolved model id is present, the
|
|
@@ -663,10 +675,28 @@ export interface RunnerDeps {
|
|
|
663
675
|
sessionContext?: readonly string[];
|
|
664
676
|
/** Transcript-window bounds for the classify prompt (defaults: 40 entries / 2000 chars each). */
|
|
665
677
|
window?: import("./auto-mode-prompt.js").AutoModeWindowOptions;
|
|
666
|
-
/** Classify round-trip cap, ms (default 15_000)
|
|
667
|
-
*
|
|
668
|
-
*
|
|
678
|
+
/** Classify round-trip cap, ms (default 15_000) — ONE ladder rung (see `classifierFallback`), not the
|
|
679
|
+
* whole classification. A rung past the cap did not rule: the walk moves to the next candidate, and with
|
|
680
|
+
* none left the call is DENIED with a sentence that says so. The next call is classified afresh. */
|
|
669
681
|
timeoutMs?: number;
|
|
682
|
+
/**
|
|
683
|
+
* What a classification does when the classifier SEAT does not rule (the leg threw / rejected, the cap
|
|
684
|
+
* elapsed, or the reply carried no verdict):
|
|
685
|
+
* - `"main"` (DEFAULT) — re-ask the SAME prompt on this run's own main model, once, and act on what it
|
|
686
|
+
* says; only if that rung does not rule either does the call reach the fail-closed deny. The record
|
|
687
|
+
* says which model answered (`GateDisposition.classifier`) and the first move per (rung, cause)
|
|
688
|
+
* announces `classifier.fallback` to the operator.
|
|
689
|
+
* - `"none"` — one rung: a classifier that does not rule denies the call exactly as it did before this
|
|
690
|
+
* knob existed, at exactly one model call.
|
|
691
|
+
*
|
|
692
|
+
* COST of the default, stated: while the seat is broken every gated call pays a second round-trip
|
|
693
|
+
* (worst case two `timeoutMs` caps); `"none"` buys that back for the deny.
|
|
694
|
+
*
|
|
695
|
+
* A value outside the closed set ({@link import("./auto-mode.js").AUTO_MODE_CLASSIFIER_FALLBACKS}) is
|
|
696
|
+
* REFUSED loudly at prepare (`config.auto_mode_classifier_fallback_invalid`) — never clamped, never read
|
|
697
|
+
* as the default.
|
|
698
|
+
*/
|
|
699
|
+
classifierFallback?: import("./auto-mode.js").AutoModeClassifierFallback;
|
|
670
700
|
/**
|
|
671
701
|
* The classifier DENIAL LIMIT (CC 2.1.250 `FO`/`AKe`): a run whose classifier keeps blocking falls
|
|
672
702
|
* back to a PERSON instead of being denied without end. Per run: a `block` first increments the
|
|
@@ -1357,8 +1387,9 @@ export interface RunnerDeps {
|
|
|
1357
1387
|
* knob. ② The engine's own candidates — `ToolSpec.defer === true`, every REMOTE-PROTOCOL tool name
|
|
1358
1388
|
* (MCP **and** A2A peer tools), and built-in tools that declare their own `defer` — sit behind ONE
|
|
1359
1389
|
* threshold gate, all or nothing: deferred iff their inlined size (name + description + schema; the
|
|
1360
|
-
* candidate set only — the caller's list, pins and non-candidates are not measured) reaches
|
|
1361
|
-
* model's context window
|
|
1390
|
+
* candidate set only — the caller's list, pins and non-candidates are not measured) reaches the
|
|
1391
|
+
* {@link toolSearch} threshold (10% of the model's context window when that seat is absent); below that
|
|
1392
|
+
* they ship inline in full and no ToolSearch is mounted.
|
|
1362
1393
|
*
|
|
1363
1394
|
* `"auto"` is a CANDIDATE-WIDENING knob, not a second gate: it adds every caller spec — undeclared
|
|
1364
1395
|
* caller tools included — to the candidate set, measured and decided by the same threshold. Engine
|
|
@@ -1369,6 +1400,16 @@ export interface RunnerDeps {
|
|
|
1369
1400
|
* if this text drifts.
|
|
1370
1401
|
*/
|
|
1371
1402
|
deferMode?: "auto";
|
|
1403
|
+
/**
|
|
1404
|
+
* Rule ②'s GATE POLICY — when the engine's candidate set ships as placeholders — in three states:
|
|
1405
|
+
* `true` (always), `false` (never; with no caller list no ToolSearch mounts at all), `{mode:"auto",
|
|
1406
|
+
* thresholdPercent}` (the threshold gate at that percent of the model's context window). Absent ⇒
|
|
1407
|
+
* `{mode:"auto", thresholdPercent:10}`. ORTHOGONAL to {@link deferMode}, which decides WHICH tools are
|
|
1408
|
+
* candidates; neither touches rule ① ({@link TaskSpec.deferTools} still defers,
|
|
1409
|
+
* {@link TaskSpec.alwaysLoadTools} still pins first). A value outside the domain refuses at prepare
|
|
1410
|
+
* (`config.tool_search_invalid`), a `thresholdPercent` outside a finite `[0, 100]` included.
|
|
1411
|
+
*/
|
|
1412
|
+
toolSearch?: import("./tool-search-setting.js").ToolSearchSetting;
|
|
1372
1413
|
/**
|
|
1373
1414
|
* design/84 Seam C — compact-boundary COST optimization (data-gated, default OFF). A FUNCTION seam (a
|
|
1374
1415
|
* trusted control-plane injection), so it lives on `RunnerDeps` — NOT `TaskSpec`: `TaskSpec` is the
|
|
@@ -3,6 +3,22 @@ import type { ToolPolicy } from "./tool-policy.js";
|
|
|
3
3
|
/** fs hand tools that take a `path` and write — the reliably-guardable surface. NotebookEdit joined
|
|
4
4
|
* in [886]①a (mirror of the fs-write-gate's covered set): its target is `notebook_path`, and a
|
|
5
5
|
* guard that ignored notebook writes to the same paths by default was the same hole one tool away. */
|
|
6
|
+
/**
|
|
7
|
+
* The agent-configuration block of {@link RECOMMENDED_SENSITIVE_PATTERNS} MIRRORS the read tier (spelled as
|
|
8
|
+
* literals so the closed set stays statically readable; a set-equality pin keeps the mirror exact): the read tier's rows already encode this severity's predicate ("an agent's own config
|
|
9
|
+
* FILE, whose `env`/`mcpServers` block is a credential or a command line the host launches") and argue
|
|
10
|
+
* each one. Shared is the VOCABULARY, not the judgment.
|
|
11
|
+
*
|
|
12
|
+
* @contract sensitive_paths.agent_config_tier_is_shared — the write deny set's agent-configuration
|
|
13
|
+
* block IS the read face's `agent-config` tier, row for row: one table, two judges, and the two faces
|
|
14
|
+
* cannot drift into disagreeing about which files are an agent's own configuration. The CONFIGURATION
|
|
15
|
+
* DIRECTORIES (`.claude`, `.sema`) are on neither face's deny side — their write guard is ASK
|
|
16
|
+
* severity, where an approver or a persisted allow rule can clear it. Their `hooks/` and `agents/`
|
|
17
|
+
* sub-directories are the two exceptions and they are NOT part of this mirror: a hook script is an executable
|
|
18
|
+
* body and an agent definition re-authorizes the agent on load, both guarded on the write face only, by
|
|
19
|
+
* hand-kept rows of the list below.
|
|
20
|
+
*/
|
|
21
|
+
export declare function agentConfigDenyPatterns(): readonly string[];
|
|
6
22
|
/**
|
|
7
23
|
* RECOMMENDED default pattern set for {@link createSensitivePathPolicy} (additive export).
|
|
8
24
|
* NO upstream verbatim anchor exists (cc-anchors/2.1.209 carries no sensitive/dangerous file-pattern
|
|
@@ -20,13 +36,15 @@ import type { ToolPolicy } from "./tool-policy.js";
|
|
|
20
36
|
*
|
|
21
37
|
* HRD-PRM-10 — sema's own data root (`$AGENT_DATA_DIR ?? ~/.ai-agent`) is excluded BY CONSTRUCTION,
|
|
22
38
|
* not by the absence of an entry. The exclusion used to be "just don't add a pattern that names it",
|
|
23
|
-
* which held only as long as no entry happened to match one of its ANCESTOR segments
|
|
24
|
-
* died the
|
|
39
|
+
* which held only as long as no entry happened to match one of its ANCESTOR segments — and that
|
|
40
|
+
* assumption died the first time a directory-wide config-root entry was on this list: the shell sets
|
|
25
41
|
* `AGENT_DATA_DIR = <configRoot>/engine-data` with a configRoot of `~/.sema`, so every path under the
|
|
26
|
-
* data root carries a `.sema` segment, and
|
|
27
|
-
*
|
|
42
|
+
* data root carries a `.sema` segment, and the entry swallowed the whole data root with it —
|
|
43
|
+
* including the memory library the model is INSTRUCTED to write with the Write tool
|
|
28
44
|
* (`MEMORY_INSTRUCTION_TEMPLATE`), i.e. every memory write refused, on a default server wiring.
|
|
29
|
-
*
|
|
45
|
+
* That entry is gone (see the severity layering below), but the exclusion stays mechanical — a
|
|
46
|
+
* deployment may site its data root beneath ANY guarded directory, and both tiers honour it. A
|
|
47
|
+
* target INSIDE the data root is matched on its path RELATIVE to
|
|
30
48
|
* that root (see {@link createSensitivePathPolicy}'s `dataRoot`), so the root's own ancestry is not the
|
|
31
49
|
* target's business, while a genuinely sensitive path INSIDE it (`<dataRoot>/x/.ssh/id_rsa`) still
|
|
32
50
|
* matches. The `sessions/` subtree keeps its own owner: `createTranscriptIntegrityPolicy` is wired
|
|
@@ -34,8 +52,22 @@ import type { ToolPolicy } from "./tool-policy.js";
|
|
|
34
52
|
* transcripts is routine» and a legitimate operator write must remain clearable by judgment — and a
|
|
35
53
|
* deny here would outrank that ask under the deny > ask > allow fold and silently convert it into a
|
|
36
54
|
* hard refusal for every deployment that wires this list.
|
|
55
|
+
*
|
|
56
|
+
* SEVERITY LAYERING for the agent-configuration class: the law is {@link agentConfigDenyPatterns} —
|
|
57
|
+
* configuration FILES here, the DIRECTORY one step down on {@link RECOMMENDED_SENSITIVE_ASK_PATTERNS}
|
|
58
|
+
* (same judge, so the severity drops and the reach does not); a directory also holds the person's own
|
|
59
|
+
* material, and the directory-wide entry turned an explicit instruction to edit `.claude/projects/…`
|
|
60
|
+
* into a refusal nobody could lift (a deny outranks an ask under the fold).
|
|
37
61
|
*/
|
|
38
62
|
export declare const RECOMMENDED_SENSITIVE_PATTERNS: readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* The ASK-severity half of the same guard: the agent-configuration DIRECTORIES, on the SAME judge
|
|
65
|
+
* ({@link createSensitivePathPolicy}'s `askPatterns` seat, whose ABSENCE reads as this list) — only the
|
|
66
|
+
* directories, since the configuration FILES inside them are deny material above. It lives HERE and not
|
|
67
|
+
* only on `WRITE_PROTECTED_DEFAULT_TABLE`, which names the same segments but judges LEXICALLY: a
|
|
68
|
+
* symlink with an innocent name walks past that one, so demoting to it alone trades severity for REACH.
|
|
69
|
+
*/
|
|
70
|
+
export declare const RECOMMENDED_SENSITIVE_ASK_PATTERNS: readonly string[];
|
|
39
71
|
/**
|
|
40
72
|
* HRD-PRM-10 — the engine data root, read from the same knob its two consumers read
|
|
41
73
|
* (`$AGENT_DATA_DIR ?? ~/.ai-agent`, off the HOST at wiring time, NFC-normalized) and returned in
|
|
@@ -53,27 +85,36 @@ export declare const RECOMMENDED_SENSITIVE_PATTERNS: readonly string[];
|
|
|
53
85
|
*/
|
|
54
86
|
export declare function dataRootSpellings(explicit: string | undefined): string[];
|
|
55
87
|
/**
|
|
56
|
-
* Build a composable policy that
|
|
57
|
-
*
|
|
58
|
-
* set
|
|
88
|
+
* Build a composable policy that judges writes (via the guarded fs tools) by their CANONICAL target,
|
|
89
|
+
* at TWO severities from ONE judge. `patterns` is the DENY set — deployment-supplied, no default list
|
|
90
|
+
* is shipped; `askPatterns` is the ASK set, judged the same way and consulted only where the deny set
|
|
91
|
+
* misses, so a path on both denies. Both empty = a no-op allow.
|
|
92
|
+
*
|
|
93
|
+
* @contract sensitive_paths.two_severities_one_judge — ONE canonicalizing judge answers both
|
|
94
|
+
* severities: a deny is a refusal no approver and no persisted allow rule can lift, an ask is one
|
|
95
|
+
* explicit look that either of them may clear. Lowering a path's severity here never lowers its REACH
|
|
96
|
+
* — both tiers see the real target (symlinks resolved, relative spellings resolved against the call's
|
|
97
|
+
* live cwd), which the synchronous write-protection TABLE deliberately does not.
|
|
59
98
|
*
|
|
60
99
|
* `env` is the same ExecutionEnv the hands run against (inject the one you pass to the Runner), so
|
|
61
100
|
* canonicalization sees the task's real filesystem (a remote/E2B task resolves in its container).
|
|
62
|
-
* `rootPath`
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* the guard judges the file the tool will actually write. `rootPath` still serves every deployment that
|
|
66
|
-
* calls this policy outside a Runner.
|
|
101
|
+
* `rootPath` plays the same fallback-base role it plays on the sibling write gate (see
|
|
102
|
+
* {@link import("./fs-write-gate-policy.js").FsWriteGatePolicyOptions.rootPath}); the call's live
|
|
103
|
+
* `ToolCallRequest.cwd` outranks it for the target.
|
|
67
104
|
*
|
|
68
|
-
* Fail-open by design: a tool with no string `path
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
105
|
+
* Fail-open by design: a tool with no string `path` returns `allow` here — this guard adds friction on
|
|
106
|
+
* top of the other gates, it is not the only line of defense, and it must not turn an unrelated tool
|
|
107
|
+
* call into a spurious verdict. The verdicts it owns are narrow and certain: "the real target IS a
|
|
108
|
+
* guarded path → deny / ask". (A target it could not RESOLVE is the one fail-CLOSED arm, below.)
|
|
72
109
|
*/
|
|
73
110
|
export declare function createSensitivePathPolicy(opts: {
|
|
74
111
|
env: ExecutionEnv;
|
|
75
112
|
/** `readonly` accepted so {@link RECOMMENDED_SENSITIVE_PATTERNS} can be passed as-is (widening, non-breaking). */
|
|
76
113
|
patterns: readonly string[];
|
|
114
|
+
/** The ASK-severity set, same syntax and same judge as `patterns`, consulted only where the deny set
|
|
115
|
+
* misses. ABSENT ⇒ {@link RECOMMENDED_SENSITIVE_ASK_PATTERNS}, so a deployment already wiring this
|
|
116
|
+
* guard keeps its reach over those directories without a code change; `[]` = no ask tier. */
|
|
117
|
+
askPatterns?: readonly string[];
|
|
77
118
|
rootPath?: string;
|
|
78
119
|
/**
|
|
79
120
|
* HRD-PRM-10 — the engine data root this deployment runs on. Default
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { canonicalizeTarget, expandHomeTilde, isAbsoluteForFamily, isWinFormPath, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
|
|
4
|
-
import { compileSegmentPattern, matchSegmentPatterns } from "../tools/fs/read-deny.js";
|
|
4
|
+
import { compileSegmentPattern, matchSegmentPatterns, READ_FACE_BUILTIN_DENY_TABLE } from "../tools/fs/read-deny.js";
|
|
5
5
|
import { isProtectedWrite, protectivePathTargetOf } from "./tool-registry.js";
|
|
6
6
|
import { pathTargetBaseOf } from "./effective-path-target.js";
|
|
7
|
+
export function agentConfigDenyPatterns() {
|
|
8
|
+
return READ_FACE_BUILTIN_DENY_TABLE.filter((row) => row.tier === "agent-config").map((row) => row.pattern);
|
|
9
|
+
}
|
|
7
10
|
export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
8
11
|
".env",
|
|
9
12
|
".env.*",
|
|
@@ -18,10 +21,25 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
18
21
|
".git/hooks",
|
|
19
22
|
".git/config",
|
|
20
23
|
".git/modules",
|
|
21
|
-
".
|
|
22
|
-
".
|
|
23
|
-
".sema",
|
|
24
|
+
".sema/settings.json",
|
|
25
|
+
".sema/settings.local.json",
|
|
24
26
|
".sema.*",
|
|
27
|
+
".claude/settings.json",
|
|
28
|
+
".claude/settings.local.json",
|
|
29
|
+
".claude.*",
|
|
30
|
+
".mcp.json",
|
|
31
|
+
".ai-agent/.env",
|
|
32
|
+
".sema/engine-data/.env",
|
|
33
|
+
".codex/config.toml",
|
|
34
|
+
".cursor/mcp.json",
|
|
35
|
+
".continue/config.json",
|
|
36
|
+
".continue/config.yaml",
|
|
37
|
+
".aider.conf.yml",
|
|
38
|
+
".gemini/settings.json",
|
|
39
|
+
".claude/hooks",
|
|
40
|
+
".sema/hooks",
|
|
41
|
+
".claude/agents",
|
|
42
|
+
".sema/agents",
|
|
25
43
|
".sema-notifier-build",
|
|
26
44
|
".aws",
|
|
27
45
|
".config/gcloud",
|
|
@@ -32,6 +50,7 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
32
50
|
".bash_history",
|
|
33
51
|
".zsh_history",
|
|
34
52
|
];
|
|
53
|
+
export const RECOMMENDED_SENSITIVE_ASK_PATTERNS = [".claude", ".sema"];
|
|
35
54
|
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
36
55
|
export function dataRootSpellings(explicit) {
|
|
37
56
|
const raw = explicit ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
@@ -77,6 +96,7 @@ function matchSensitive(canonicalKey, compiled, aliasResolved) {
|
|
|
77
96
|
}
|
|
78
97
|
export function createSensitivePathPolicy(opts) {
|
|
79
98
|
const compiled = compilePatterns(opts.patterns);
|
|
99
|
+
const compiledAsk = compilePatterns(opts.askPatterns ?? RECOMMENDED_SENSITIVE_ASK_PATTERNS);
|
|
80
100
|
const dataRootPaths = dataRootSpellings(opts.dataRoot);
|
|
81
101
|
const dataRootKeys = async (signal) => {
|
|
82
102
|
const keys = [];
|
|
@@ -94,7 +114,7 @@ export function createSensitivePathPolicy(opts) {
|
|
|
94
114
|
const guardedByName = opts.tools !== undefined ? new Set(opts.tools) : undefined;
|
|
95
115
|
return {
|
|
96
116
|
async check(req, signal) {
|
|
97
|
-
if (compiled.length === 0)
|
|
117
|
+
if (compiled.length === 0 && compiledAsk.length === 0)
|
|
98
118
|
return { action: "allow" };
|
|
99
119
|
if (guardedByName !== undefined ? !guardedByName.has(req.toolName) : !isProtectedWrite(req))
|
|
100
120
|
return { action: "allow" };
|
|
@@ -129,6 +149,14 @@ export function createSensitivePathPolicy(opts) {
|
|
|
129
149
|
decisionReason: "safety",
|
|
130
150
|
};
|
|
131
151
|
}
|
|
152
|
+
const askHit = matchSensitive(judged, compiledAsk, canon.aliasResolved === true);
|
|
153
|
+
if (askHit) {
|
|
154
|
+
return {
|
|
155
|
+
action: "ask",
|
|
156
|
+
message: `this write's real target resolves inside the guarded directory "${askHit}" — explicit approval required`,
|
|
157
|
+
decisionReason: "safety",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
132
160
|
return { action: "allow" };
|
|
133
161
|
},
|
|
134
162
|
};
|