@sema-agent/core 7.19.0 → 7.20.1
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 +199 -2
- package/dist/agents/subagent.js +12 -20
- package/dist/agents/teacher.js +2 -5
- package/dist/agents/verify.js +21 -10
- package/dist/brain/anthropic.js +3 -1
- package/dist/brain/open-responses.js +4 -2
- package/dist/brain/openai.js +3 -1
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +4 -0
- package/dist/core/auto-mode.d.ts +37 -18
- package/dist/core/auto-mode.js +1 -1
- package/dist/core/child-clamp-seats.d.ts +79 -0
- package/dist/core/child-clamp-seats.js +29 -0
- package/dist/core/engine-notice.d.ts +76 -49
- package/dist/core/fs-write-gate-policy.d.ts +17 -9
- package/dist/core/fs-write-gate-policy.js +51 -4
- package/dist/core/gate-bounded-await.d.ts +38 -0
- package/dist/core/gate-bounded-await.js +53 -0
- package/dist/core/gate-fold.js +16 -48
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +9 -6
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +8 -8
- package/dist/core/memory-engine/engine.d.ts +14 -9
- package/dist/core/memory-engine/engine.js +21 -8
- package/dist/core/memory-engine/layout.d.ts +31 -0
- package/dist/core/memory-engine/layout.js +68 -3
- package/dist/core/memory-engine/tools.js +6 -0
- package/dist/core/memory-engine/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
- package/dist/core/runner/advertised-writable-dirs.js +51 -3
- package/dist/core/runner/announce-once-ledger.d.ts +15 -0
- package/dist/core/runner/announce-once-ledger.js +8 -0
- package/dist/core/runner/classifier-ladder.d.ts +5 -0
- package/dist/core/runner/classifier-ladder.js +13 -2
- package/dist/core/runner/contracts.d.ts +44 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +5 -4
- package/dist/core/runner/env-capability-census.d.ts +30 -0
- package/dist/core/runner/env-capability-census.js +38 -0
- package/dist/core/runner/forward-sink.d.ts +32 -0
- package/dist/core/runner/forward-sink.js +41 -0
- package/dist/core/runner/gate-exit.d.ts +4 -0
- package/dist/core/runner/gate-exit.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +0 -9
- package/dist/core/runner/listing-fold.d.ts +57 -0
- package/dist/core/runner/listing-fold.js +75 -0
- package/dist/core/runner/prepare-execution-env.d.ts +1 -1
- package/dist/core/runner/prepare-execution-env.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +3 -5
- package/dist/core/runner/prepare-listings.d.ts +3 -0
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory.js +7 -1
- package/dist/core/runner/prepare-policy-chain.js +5 -5
- package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
- package/dist/core/runner/prepare-run-refs.d.ts +4 -2
- package/dist/core/runner/prepare-run-refs.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -1
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/run-clock-and-content.js +3 -7
- package/dist/core/runner/run-harness-handlers.js +10 -10
- package/dist/core/runner/run-leg.js +28 -19
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -33
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +13 -39
- package/dist/core/runner/synthetic-tools.js +15 -59
- package/dist/core/runner/task-root-canonical.d.ts +45 -0
- package/dist/core/runner/task-root-canonical.js +18 -0
- package/dist/core/runner/turn-attachments.d.ts +125 -49
- package/dist/core/runner/turn-attachments.js +124 -66
- package/dist/core/runner/write-protection-station.d.ts +59 -0
- package/dist/core/runner/write-protection-station.js +32 -0
- package/dist/core/safe-notify.d.ts +5 -0
- package/dist/core/safe-notify.js +24 -2
- package/dist/core/sensitive-path-policy.d.ts +53 -27
- package/dist/core/sensitive-path-policy.js +16 -26
- package/dist/core/skill-listing-budget.d.ts +34 -40
- package/dist/core/skill-listing-budget.js +25 -26
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +8 -7
- package/dist/core/tool-policy.d.ts +12 -1
- package/dist/core/tool-policy.js +39 -25
- package/dist/core/wiring-manifest.d.ts +31 -0
- package/dist/core/wiring-manifest.js +1 -0
- package/dist/core/write-protect.d.ts +124 -18
- package/dist/core/write-protect.js +91 -34
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/orchestration/workflow-primitives.js +10 -20
- package/dist/stores/file/memory-store.d.ts +5 -2
- package/dist/stores/file/memory-store.js +8 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
- package/dist/tools/fs/bash-readonly-classifier.js +318 -36
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +12 -25
- package/dist/tools/fs/fs-shared.d.ts +22 -0
- package/dist/tools/fs/fs-shared.js +30 -0
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/index.js +1 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +25 -3
- package/wire-changes/core@7.20.0.json +568 -0
- package/wire-changes/core@7.20.1.json +183 -0
|
@@ -77,9 +77,90 @@ export declare const CHANGED_FILES_MAX = 20;
|
|
|
77
77
|
* formatter storms remain a documented false-positive source (hash-confirm 抗假阳 = v2).
|
|
78
78
|
*/
|
|
79
79
|
export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
80
|
-
/** Per-boundary
|
|
80
|
+
/** Per-boundary byte cap over the bundle's CLIPPABLE frames (weak-model context economics). Intact-class
|
|
81
|
+
* frames ride outside it — see {@link ATTACHMENT_DELIVERY_CLASS}. */
|
|
81
82
|
export declare const ATTACHMENT_BYTE_CAP: number;
|
|
82
83
|
export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "total_tokens_reminder" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
84
|
+
/** How the bundle cap treats a source — see {@link ATTACHMENT_DELIVERY_CLASS}. */
|
|
85
|
+
export type AttachmentDeliveryClass = {
|
|
86
|
+
readonly class: "clippable";
|
|
87
|
+
} | {
|
|
88
|
+
readonly class: "intact";
|
|
89
|
+
/** The producer's own byte bound for the frame it renders from these inputs — the promise the lane
|
|
90
|
+
* relies on instead of clipping. A closed form of bounded quantities (a resolved budget, a list cap,
|
|
91
|
+
* a per-item cap, a constant), never of unbounded remote text. */
|
|
92
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* @contract attachments.delivery_class — every attachment source is one of two classes, declared HERE
|
|
96
|
+
* and nowhere else. An INTACT source's consumption is "recorded when delivered" (a listing's announced
|
|
97
|
+
* set, a drained pending list, an append-⇔-commit fixed frame), so a clipped frame could never be
|
|
98
|
+
* recorded and would re-render at every boundary; the lane therefore never clips or drops it, and the
|
|
99
|
+
* producer bounds it (`bound`). A CLIPPABLE source is a window-based reminder that re-fires at the next
|
|
100
|
+
* quiet boundary, so the bundle cap ({@link ATTACHMENT_BYTE_CAP}) may clip or drop it. `satisfies
|
|
101
|
+
* Record<AttachmentSource, …>` is the totality guard: a new source that is not classed here — or an
|
|
102
|
+
* intact one without its bound — does not compile.
|
|
103
|
+
*/
|
|
104
|
+
export declare const ATTACHMENT_DELIVERY_CLASS: {
|
|
105
|
+
readonly todo_reminder: {
|
|
106
|
+
readonly class: "clippable";
|
|
107
|
+
};
|
|
108
|
+
readonly task_reminder: {
|
|
109
|
+
readonly class: "clippable";
|
|
110
|
+
};
|
|
111
|
+
readonly tool_search_usage_reminder: {
|
|
112
|
+
readonly class: "clippable";
|
|
113
|
+
};
|
|
114
|
+
readonly changed_files: {
|
|
115
|
+
readonly class: "clippable";
|
|
116
|
+
};
|
|
117
|
+
readonly plan_mode: {
|
|
118
|
+
readonly class: "clippable";
|
|
119
|
+
};
|
|
120
|
+
readonly budget_usd: {
|
|
121
|
+
readonly class: "clippable";
|
|
122
|
+
};
|
|
123
|
+
readonly total_tokens_reminder: {
|
|
124
|
+
readonly class: "clippable";
|
|
125
|
+
};
|
|
126
|
+
readonly background_tasks: {
|
|
127
|
+
readonly class: "clippable";
|
|
128
|
+
};
|
|
129
|
+
readonly tools_delta: {
|
|
130
|
+
readonly class: "intact";
|
|
131
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
132
|
+
};
|
|
133
|
+
readonly agent_listing: {
|
|
134
|
+
readonly class: "intact";
|
|
135
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
136
|
+
};
|
|
137
|
+
readonly skills_listing: {
|
|
138
|
+
readonly class: "intact";
|
|
139
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
140
|
+
};
|
|
141
|
+
readonly mcp_instructions: {
|
|
142
|
+
readonly class: "intact";
|
|
143
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
144
|
+
};
|
|
145
|
+
readonly mcp_dropped_tools: {
|
|
146
|
+
readonly class: "intact";
|
|
147
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
148
|
+
};
|
|
149
|
+
readonly date_change: {
|
|
150
|
+
readonly class: "intact";
|
|
151
|
+
readonly bound: () => number;
|
|
152
|
+
};
|
|
153
|
+
readonly instructions_change: {
|
|
154
|
+
readonly class: "intact";
|
|
155
|
+
readonly bound: () => number;
|
|
156
|
+
};
|
|
157
|
+
readonly workflow_size_guideline_change: {
|
|
158
|
+
readonly class: "intact";
|
|
159
|
+
readonly bound: () => number;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
/** Whether the bundle cap must leave this source whole ({@link ATTACHMENT_DELIVERY_CLASS}). */
|
|
163
|
+
export declare function isIntactSource(source: AttachmentSource): boolean;
|
|
83
164
|
/** G1 续批 — one agent type on the delegation tool's roster (ToolSpec.agentListing, filled by
|
|
84
165
|
* createSubagentTool: defined agents + the built-in fork when offered). */
|
|
85
166
|
export interface AgentListingEntry {
|
|
@@ -196,14 +277,6 @@ export interface AttachmentState {
|
|
|
196
277
|
* `announcedListings.models` so a catalog change across a suspend IS re-announced (the drift
|
|
197
278
|
* line rides the first boundary's agent_listing frame). */
|
|
198
279
|
announcedModels?: readonly string[];
|
|
199
|
-
/** [c209-C] R2 C4② — same-boundary anti-starvation latch: set by {@link collectDueAttachments}
|
|
200
|
-
* when a rendered skills_listing block did NOT survive its own bundle intact (clipped/starved —
|
|
201
|
-
* typically because the higher-priority agent_listing spent the byte budget first), cleared when
|
|
202
|
-
* one survives. While set, the NEXT boundary orders skills_listing BEFORE agent_listing, so two
|
|
203
|
-
* listing frames that cannot share one 8KB bundle alternate to completion instead of the
|
|
204
|
-
* agent frame permanently starving the skills frame (each frame individually fits the cap by
|
|
205
|
-
* construction — agent roster ≤ cap by deployment size, skills block ≤ SKILLS_BLOCK_MAX_BYTES). */
|
|
206
|
-
skillsListingStarved?: boolean;
|
|
207
280
|
/**
|
|
208
281
|
* The `padded-countdown` arm's SMOOTHING FLOOR: the highest usage figure this run has
|
|
209
282
|
* already published against. CC's `cumulativeUsed` (bundle: `Math.max(storedFloor, rolledOver +
|
|
@@ -546,9 +619,10 @@ export interface AttachmentInputs {
|
|
|
546
619
|
* the delta path serves dynamic-installation deployments and tests, same posture as
|
|
547
620
|
* `agentListing`). Only passed when `config.skillsListing` is on. */
|
|
548
621
|
skillsListing?: ReadonlyArray<SkillListingEntry>;
|
|
549
|
-
/** The
|
|
550
|
-
*
|
|
551
|
-
|
|
622
|
+
/** The listing family's budget, resolved once at the config door: `agent_listing` and `skills_listing`
|
|
623
|
+
* are each folded into it (each frame measured against the whole number, not a share). Absent ⇒ the
|
|
624
|
+
* seat reader's no-knob answer. */
|
|
625
|
+
listingBudget?: ResolvedSkillListingBudget;
|
|
552
626
|
/** G1 续批 mcp_instructions: pending deltas snapshotted from `MaterializedMcp.instructionsDelta`.
|
|
553
627
|
* CALLER-owned consumption on intact cap survival (same `newTools` discipline). */
|
|
554
628
|
mcpInstructions?: {
|
|
@@ -823,11 +897,24 @@ export declare function agentListingDeltaHeader(toolName: string): string;
|
|
|
823
897
|
* no re-render byte-identity exposure of its own.
|
|
824
898
|
*
|
|
825
899
|
* PURE — never mutates `state`. The announced-set advance is CALLER-owned via
|
|
826
|
-
* {@link commitAgentListing}, committed
|
|
827
|
-
*
|
|
828
|
-
*
|
|
900
|
+
* {@link commitAgentListing}, committed on the carrying message's receipt (`FrameReceipt`).
|
|
901
|
+
*
|
|
902
|
+
* The roster is folded into the listing budget by the one folding engine ({@link foldListingWithin}),
|
|
903
|
+
* the skills block's sibling: every type stays listed, the last-declared ones lose their detail first,
|
|
904
|
+
* and the frame's size is this producer's own promise ({@link ATTACHMENT_DELIVERY_CLASS}). The report
|
|
905
|
+
* (`nameOnly` / `dropped`) covers the roster on an initial frame and the added set on a drift frame.
|
|
829
906
|
*/
|
|
830
|
-
export declare function
|
|
907
|
+
export declare function renderAgentListingFrame(state: AttachmentState, entries: ReadonlyArray<AgentListingEntry>, toolName: string, models?: readonly string[], budget?: ResolvedSkillListingBudget): AgentListingFrameRender | undefined;
|
|
908
|
+
/** The text-only face of {@link renderAgentListingFrame} — what the delivery lanes render. */
|
|
909
|
+
export declare function renderAgentListingDelta(state: AttachmentState, entries: ReadonlyArray<AgentListingEntry>, toolName: string, models?: readonly string[], budget?: ResolvedSkillListingBudget): string | undefined;
|
|
910
|
+
/** What one {@link renderAgentListingFrame} call produced: the frame, the report, and how many types it set out to list. */
|
|
911
|
+
export interface AgentListingFrameRender {
|
|
912
|
+
text: string;
|
|
913
|
+
nameOnly: readonly string[];
|
|
914
|
+
dropped: readonly string[];
|
|
915
|
+
/** The denominator for the disclosure: the whole roster on an initial frame, the added set on a drift frame. */
|
|
916
|
+
listed: number;
|
|
917
|
+
}
|
|
831
918
|
/** [c209-C] R2 C7 — the Q4 catalog line, single-sourced for the initial frame's tail AND the
|
|
832
919
|
* drift re-announce arm so the two can never diverge byte-wise. [c209-C] 收口 C7 补口: an
|
|
833
920
|
* EMPTY catalog renders an honest "(none)" — reachable only from the DRIFT arm (a known announced
|
|
@@ -841,9 +928,8 @@ export declare const MODELS_AVAILABLE_PREFIX = "Models available for the 'model'
|
|
|
841
928
|
export declare function replayAnnouncedModels(texts: readonly string[]): readonly string[] | undefined;
|
|
842
929
|
/**
|
|
843
930
|
* MED-3① commit half of {@link renderAgentListingDelta}: mark the CURRENT roster as announced. The run
|
|
844
|
-
* loop calls this
|
|
845
|
-
*
|
|
846
|
-
* announced map untouched so the next quiet boundary re-announces. Snapshotting `entries` wholesale is
|
|
931
|
+
* loop calls this on the receipt of the message that carried the frame; an undelivered frame leaves the
|
|
932
|
+
* announced map untouched so the next boundary re-announces. Snapshotting `entries` wholesale is
|
|
847
933
|
* equivalent to add/remove bookkeeping (the diff is name-keyed; descriptions are never re-diffed).
|
|
848
934
|
*/
|
|
849
935
|
export declare function commitAgentListing(state: AttachmentState, entries: ReadonlyArray<AgentListingEntry>, models?: readonly string[]): void;
|
|
@@ -874,10 +960,8 @@ export interface SkillsListingFrameRender extends SkillsBlockRender {
|
|
|
874
960
|
*
|
|
875
961
|
* ONE RULER FOR EVERY FRAME: the drift frame is measured against the SAME resolved budget as the
|
|
876
962
|
* initial block, arms and joiners included, and its ADD arm degrades through the same three tiers
|
|
877
|
-
* ({@link renderSkillLinesWithin}).
|
|
878
|
-
*
|
|
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
|
|
963
|
+
* ({@link renderSkillLinesWithin}). The frame is an intact-class attachment — the lane never clips
|
|
964
|
+
* it — so this measurement is its whole bound. The split: the ambient tail and the removal
|
|
881
965
|
* arm's one-line floor are reserved first (both constants), the ADD arm degrades into what is left,
|
|
882
966
|
* and the removal arm spends whatever the add arm did not — so the frame's size is the resolved budget
|
|
883
967
|
* or, when even the two floors do not fit it, a small constant, never a function of the roster.
|
|
@@ -886,7 +970,7 @@ export interface SkillsListingFrameRender extends SkillsBlockRender {
|
|
|
886
970
|
* description or could not list at all. A departed name that did not fit is not a listed skill.
|
|
887
971
|
*
|
|
888
972
|
* MED-3① discipline: PURE — the announced-set advance is CALLER-owned via {@link commitSkillsListing},
|
|
889
|
-
* committed
|
|
973
|
+
* committed on the carrying message's receipt. The commit records
|
|
890
974
|
* EVERY entry, including one this frame dropped: the alternative is a frame re-rendering the same
|
|
891
975
|
* un-listable tail at every boundary, which is the replay this budget exists to end. The `Skill` tool
|
|
892
976
|
* serves a dropped skill, and `skills.listing_truncated` tells the deployment it happened.
|
|
@@ -896,19 +980,16 @@ export declare function renderSkillsListingFrame(state: AttachmentState, entries
|
|
|
896
980
|
* the intact-survival predicate re-renders to compare. */
|
|
897
981
|
export declare function renderSkillsListingDelta(state: AttachmentState, entries: ReadonlyArray<SkillListingEntry>, budget?: ResolvedSkillListingBudget): string | undefined;
|
|
898
982
|
/** [c209-C] commit half of {@link renderSkillsListingDelta} — same MED-3① contract as
|
|
899
|
-
* {@link commitAgentListing}: called by the run loop
|
|
900
|
-
*
|
|
901
|
-
* announced map untouched so the next quiet boundary re-announces. */
|
|
983
|
+
* {@link commitAgentListing}: called by the run loop on the receipt of the message that carried the
|
|
984
|
+
* frame; an undelivered frame leaves the announced map untouched so the next boundary re-announces. */
|
|
902
985
|
export declare function commitSkillsListing(state: AttachmentState, entries: ReadonlyArray<SkillListingEntry>): void;
|
|
903
986
|
/**
|
|
904
987
|
* Per-server bound (in CODE POINTS, matching every other clipper in `untrusted-text.ts`) on
|
|
905
|
-
* ONE MCP server's `instructions` text. The text is SERVER-controlled and UNBOUNDED on the wire
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* Sized at the delta lane's own order of magnitude (the 8KB frame budget), leaving fence + header
|
|
911
|
-
* room under the bundle cap once the "…" truncation marker lands.
|
|
988
|
+
* ONE MCP server's `instructions` text. The text is SERVER-controlled and UNBOUNDED on the wire, and
|
|
989
|
+
* neither lane it rides has a cap above this one: the stable system-prompt lane has none, and the
|
|
990
|
+
* `mcp_instructions` attachment is intact-class (the bundle cap never clips it), so this bound IS the
|
|
991
|
+
* producer's promise — without it a single server could pin an arbitrarily large blob into the cached
|
|
992
|
+
* prompt prefix or into one boundary frame.
|
|
912
993
|
*/
|
|
913
994
|
export declare const MCP_INSTRUCTIONS_MAX_CHARS: number;
|
|
914
995
|
/**
|
|
@@ -942,8 +1023,8 @@ export declare function fenceMcpServerInstructions(server: string, text: string)
|
|
|
942
1023
|
* the text is additionally already `sanitizeUntrustedText`-neutralized at the mcp.ts producer).
|
|
943
1024
|
* The fence is now applied through {@link fenceMcpServerInstructions}, shared with the
|
|
944
1025
|
* STABLE system-prompt lane, so the two lanes can never again carry the same text with different
|
|
945
|
-
* trust postures — and it carries a per-server {@link MCP_INSTRUCTIONS_MAX_CHARS} bound (
|
|
946
|
-
*
|
|
1026
|
+
* trust postures — and it carries a per-server {@link MCP_INSTRUCTIONS_MAX_CHARS} bound (the
|
|
1027
|
+
* frame is intact-class: its size is this producer's promise, servers × that cap).
|
|
947
1028
|
* The disconnect list is followed by the CC-verbatim {@link AMBIENT_CONTEXT_NOTE} (CC `Emr`,
|
|
948
1029
|
* pretty.js:599240/599992 — resolved by the 2026-07-10 parity spot audit).
|
|
949
1030
|
* Removal names are `\n`-joined WITHOUT a `- ` prefix (CC-exact; the agent-listing removal list DOES
|
|
@@ -974,21 +1055,16 @@ export declare function renderMcpInstructionsDelta(added: ReadonlyArray<{
|
|
|
974
1055
|
* loop's intact-survival consumption check (tools_delta discipline).
|
|
975
1056
|
*/
|
|
976
1057
|
export declare function renderMcpDroppedTools(entries: ReadonlyArray<McpDroppedTool>): string;
|
|
977
|
-
/**
|
|
978
|
-
*
|
|
1058
|
+
/** Per-boundary byte bound for one mcp_dropped_tools frame — the producer's own promise for an
|
|
1059
|
+
* intact-class frame ({@link ATTACHMENT_DELIVERY_CLASS}), so one batch reads as one bounded frame. */
|
|
979
1060
|
export declare const MCP_DROPPED_BATCH_MAX_BYTES = 4096;
|
|
980
1061
|
/**
|
|
981
|
-
*
|
|
982
|
-
* render
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
* tail rides later boundaries. Pure/deterministic so the drain predicate can re-derive the exact
|
|
988
|
-
* snapshot. the ≥1 floor is SAFE only because entries are bounded at the mcp.ts
|
|
989
|
-
* producer (tool ≤160 cps, reason ≤240 cps, both single-lined) — one rendered entry is always a
|
|
990
|
-
* few hundred bytes, far under both this bound and the 8KB bundle cap, so the single-entry frame
|
|
991
|
-
* can never be cap-clipped into a permanent re-render wedge.
|
|
1062
|
+
* Bounded-batch selection for the dropped-tools announce: the longest prefix of COMPLETE entries whose
|
|
1063
|
+
* render fits {@link MCP_DROPPED_BATCH_MAX_BYTES} (always ≥1), so an oversized list drains
|
|
1064
|
+
* monotonically across boundaries — the run loop splices exactly the delivered batch, the tail rides
|
|
1065
|
+
* later boundaries. Pure/deterministic so the drain predicate can re-derive the exact snapshot. The ≥1
|
|
1066
|
+
* floor is SAFE because entries are bounded at the mcp.ts producer (tool ≤160 cps, reason ≤240 cps,
|
|
1067
|
+
* both single-lined) — one rendered entry is always a few hundred bytes.
|
|
992
1068
|
*/
|
|
993
1069
|
export declare function selectMcpDroppedBatch<T extends McpDroppedTool>(entries: ReadonlyArray<T>): T[];
|
|
994
1070
|
/**
|
|
@@ -2,7 +2,8 @@ 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
4
|
import { renderSkillLinesWithin, renderSkillsBlock } from "./synthetic-tools.js";
|
|
5
|
-
import {
|
|
5
|
+
import { SKILL_LISTING_DEFAULT_BUDGET } from "../skill-listing-budget.js";
|
|
6
|
+
import { foldListingWithin, foldRemovalArmWithin, removalArmFloor } from "./listing-fold.js";
|
|
6
7
|
import { TOOL_SEARCH_NAME as TOOL_SEARCH_TOOL_NAME } from "./tool-disclosure.js";
|
|
7
8
|
export const TODO_REMINDER_CONFIG = {
|
|
8
9
|
TURNS_SINCE_WRITE: 10,
|
|
@@ -24,6 +25,33 @@ export const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
|
24
25
|
export const ATTACHMENT_BYTE_CAP = 8 * 1024;
|
|
25
26
|
const PROJECTION_ITEMS_MAX = 50;
|
|
26
27
|
const PROJECTION_CONTENT_MAX = 80;
|
|
28
|
+
const LISTING_FRAME_FLOOR_BYTES = 768;
|
|
29
|
+
const DATE_CHANGE_MAX_BYTES = 160;
|
|
30
|
+
const WORKFLOW_SIZE_GUIDELINE_CHANGE_MAX_BYTES = 256;
|
|
31
|
+
export const ATTACHMENT_DELIVERY_CLASS = {
|
|
32
|
+
todo_reminder: { class: "clippable" },
|
|
33
|
+
task_reminder: { class: "clippable" },
|
|
34
|
+
tool_search_usage_reminder: { class: "clippable" },
|
|
35
|
+
changed_files: { class: "clippable" },
|
|
36
|
+
plan_mode: { class: "clippable" },
|
|
37
|
+
budget_usd: { class: "clippable" },
|
|
38
|
+
total_tokens_reminder: { class: "clippable" },
|
|
39
|
+
background_tasks: { class: "clippable" },
|
|
40
|
+
tools_delta: { class: "intact", bound: (inp) => toolsDeltaBound(inp) },
|
|
41
|
+
agent_listing: { class: "intact", bound: (inp) => listingFrameBound(inp.listingBudget) + (inp.agentModels !== undefined ? Buffer.byteLength(modelsAvailableLine(inp.agentModels), "utf8") : 0) },
|
|
42
|
+
skills_listing: { class: "intact", bound: (inp) => listingFrameBound(inp.listingBudget) },
|
|
43
|
+
mcp_instructions: { class: "intact", bound: (inp) => mcpInstructionsBound(inp) },
|
|
44
|
+
mcp_dropped_tools: { class: "intact", bound: (inp) => mcpDroppedToolsBound(inp) },
|
|
45
|
+
date_change: { class: "intact", bound: () => DATE_CHANGE_MAX_BYTES },
|
|
46
|
+
instructions_change: { class: "intact", bound: () => INSTRUCTIONS_CHANGE_BYTE_CAP },
|
|
47
|
+
workflow_size_guideline_change: { class: "intact", bound: () => WORKFLOW_SIZE_GUIDELINE_CHANGE_MAX_BYTES },
|
|
48
|
+
};
|
|
49
|
+
export function isIntactSource(source) {
|
|
50
|
+
return ATTACHMENT_DELIVERY_CLASS[source].class === "intact";
|
|
51
|
+
}
|
|
52
|
+
function listingFrameBound(budget) {
|
|
53
|
+
return Math.max((budget ?? SKILL_LISTING_DEFAULT_BUDGET).blockMaxBytes, LISTING_FRAME_FLOOR_BYTES);
|
|
54
|
+
}
|
|
27
55
|
const ATTACHMENT_TAGS_DEFAULT = [...SHELLED_BODY_ENVELOPE_TAGS];
|
|
28
56
|
const ATTACHMENT_TAGS_OWNED = {
|
|
29
57
|
skills_listing: SHELLED_BODY_ENVELOPE_TAGS.filter((t) => t !== "skills"),
|
|
@@ -208,23 +236,15 @@ export function collectDueAttachments(state, inp) {
|
|
|
208
236
|
(out ??= []).push({ source: "tools_delta", body });
|
|
209
237
|
}
|
|
210
238
|
const agentListingBody = inp.config.agentListing && inp.agentListing !== undefined
|
|
211
|
-
? renderAgentListingDelta(state, inp.agentListing, inp.agentToolName ?? "Agent", inp.agentModels)
|
|
239
|
+
? renderAgentListingDelta(state, inp.agentListing, inp.agentToolName ?? "Agent", inp.agentModels, inp.listingBudget)
|
|
212
240
|
: undefined;
|
|
241
|
+
if (agentListingBody !== undefined)
|
|
242
|
+
(out ??= []).push({ source: "agent_listing", body: agentListingBody });
|
|
213
243
|
const skillsListingBody = inp.config.skillsListing && inp.skillsListing !== undefined
|
|
214
|
-
? renderSkillsListingDelta(state, inp.skillsListing, inp.
|
|
244
|
+
? renderSkillsListingDelta(state, inp.skillsListing, inp.listingBudget)
|
|
215
245
|
: undefined;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
listingPair.push({ source: "agent_listing", body: agentListingBody });
|
|
219
|
-
if (skillsListingBody !== undefined) {
|
|
220
|
-
const att = { source: "skills_listing", body: skillsListingBody };
|
|
221
|
-
if (state.skillsListingStarved === true)
|
|
222
|
-
listingPair.unshift(att);
|
|
223
|
-
else
|
|
224
|
-
listingPair.push(att);
|
|
225
|
-
}
|
|
226
|
-
for (const att of listingPair)
|
|
227
|
-
(out ??= []).push(att);
|
|
246
|
+
if (skillsListingBody !== undefined)
|
|
247
|
+
(out ??= []).push({ source: "skills_listing", body: skillsListingBody });
|
|
228
248
|
if (inp.config.mcpInstructions && inp.mcpInstructions !== undefined) {
|
|
229
249
|
const body = renderMcpInstructionsDelta(inp.mcpInstructions.added, inp.mcpInstructions.removed);
|
|
230
250
|
if (body !== undefined)
|
|
@@ -244,9 +264,6 @@ export function collectDueAttachments(state, inp) {
|
|
|
244
264
|
if (out === undefined)
|
|
245
265
|
return EMPTY;
|
|
246
266
|
const capped = capAttachments(out);
|
|
247
|
-
if (skillsListingBody !== undefined) {
|
|
248
|
-
state.skillsListingStarved = !capped.some((a) => a.source === "skills_listing" && a.body === skillsListingBody);
|
|
249
|
-
}
|
|
250
267
|
if (pendingSurfaced !== undefined && capped.some((a) => a.source === "changed_files")) {
|
|
251
268
|
for (const c of pendingSurfaced)
|
|
252
269
|
state.surfacedMtime.set(c.path, c.mtimeMs);
|
|
@@ -434,15 +451,19 @@ export function renderToolsDelta(input) {
|
|
|
434
451
|
const added = input.added ?? [];
|
|
435
452
|
if (added.length > 0) {
|
|
436
453
|
const swapped = input.staticFace === true ? (input.swappedUnderStatic ?? []).filter((n) => added.includes(n)) : [];
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
: "")
|
|
454
|
+
const grouped = added.length > TOOLS_DELTA_LIST_MAX;
|
|
455
|
+
const exception = swapped.length > 0
|
|
456
|
+
? grouped
|
|
457
|
+
? `\nException: ${swapped.length} of them were too large to inline, so their full declarations are in the tools list instead.`
|
|
458
|
+
: `\nException: ${swapped.join(", ")} — too large to inline, so the full declaration is in the tools list instead.`
|
|
459
|
+
: "";
|
|
460
|
+
blocks.push((grouped ? `${added.length} deferred tools are now ${input.staticFace === true ? "active" : "available"}: ${groupByMcpServer(added)}. ` : "") +
|
|
461
|
+
(input.staticFace === true
|
|
462
|
+
? (grouped ? "Call them directly. " : "The following deferred tools are now active — call them directly. ") +
|
|
463
|
+
`The tools list keeps compact placeholder entries, so re-run ToolSearch ("select:<name>") if you need a parameter schema${grouped ? "." : ":\n"}`
|
|
464
|
+
: (grouped ? "" : "The following deferred tools are now available. ") + `Their full schemas are loaded — call them directly like any other tool${grouped ? "." : ":\n"}`) +
|
|
465
|
+
(grouped ? "" : added.map((n) => `- ${n}`).join("\n")) +
|
|
466
|
+
exception);
|
|
446
467
|
}
|
|
447
468
|
const readded = input.readded ?? [];
|
|
448
469
|
if (readded.length > 0) {
|
|
@@ -530,22 +551,35 @@ function listingIdentity(name) {
|
|
|
530
551
|
export function agentListingDeltaHeader(toolName) {
|
|
531
552
|
return `New agent types are now available for the ${sanitizeUntrustedText(toolName, SHELLED_BODY_ENVELOPE_TAGS)} tool:`;
|
|
532
553
|
}
|
|
533
|
-
export function
|
|
554
|
+
export function renderAgentListingFrame(state, entries, toolName, models, budget = SKILL_LISTING_DEFAULT_BUDGET) {
|
|
534
555
|
const safe = (t) => sanitizeUntrustedText(t, SHELLED_BODY_ENVELOPE_TAGS);
|
|
535
556
|
const line = (e) => {
|
|
536
557
|
const base = e.description ? `- ${safe(e.name)}: ${safe(e.description)}` : `- ${safe(e.name)}`;
|
|
537
558
|
return `${base} (Tools: ${safe(e.tools ?? AGENT_TOOLS_NOTE_DEFAULT)})`;
|
|
538
559
|
};
|
|
560
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
561
|
+
const joiner = bytes(AGENT_DELTA_BLOCK_JOINER);
|
|
562
|
+
const fold = (items, header, maxBytes) => foldListingWithin(items, {
|
|
563
|
+
blockMaxBytes: maxBytes,
|
|
564
|
+
full: line,
|
|
565
|
+
nameOnly: (e) => `- ${safe(e.name)}`,
|
|
566
|
+
nameOf: (e) => e.name,
|
|
567
|
+
capMarker: AGENT_LISTING_CAP_MARKER,
|
|
568
|
+
overflowLine: (n) => `(… +${n} more agent types not listed)`,
|
|
569
|
+
frame: (lines) => [header, ...lines].join("\n"),
|
|
570
|
+
});
|
|
539
571
|
const announced = state.announcedAgentTypes;
|
|
540
572
|
if (announced === undefined) {
|
|
541
573
|
if (entries.length === 0)
|
|
542
574
|
return undefined;
|
|
543
|
-
const
|
|
575
|
+
const tail = [];
|
|
544
576
|
if (entries.length > 1)
|
|
545
|
-
|
|
577
|
+
tail.push(AGENT_CONCURRENCY_NOTE);
|
|
546
578
|
if (models !== undefined && models.length > 0)
|
|
547
|
-
|
|
548
|
-
|
|
579
|
+
tail.push(modelsAvailableLine(models));
|
|
580
|
+
const tailCost = tail.reduce((acc, t) => acc + joiner + bytes(t), 0);
|
|
581
|
+
const roster = fold(entries, agentListingInitialHeader(toolName), budget.blockMaxBytes - tailCost);
|
|
582
|
+
return { text: [roster.text, ...tail].join(AGENT_DELTA_BLOCK_JOINER), nameOnly: roster.nameOnly, dropped: roster.dropped, listed: entries.length };
|
|
549
583
|
}
|
|
550
584
|
const announcedIds = new Set([...announced.keys()].map(listingIdentity));
|
|
551
585
|
const entryIds = new Set(entries.map((e) => listingIdentity(e.name)));
|
|
@@ -557,17 +591,26 @@ export function renderAgentListingDelta(state, entries, toolName, models) {
|
|
|
557
591
|
state.announcedModels.some((m, i) => listingIdentity(m) !== listingIdentity(models[i])));
|
|
558
592
|
if (added.length === 0 && removed.length === 0 && !modelsDrifted)
|
|
559
593
|
return undefined;
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
594
|
+
const modelsLine = modelsDrifted ? modelsAvailableLine(models) : undefined;
|
|
595
|
+
const modelsCost = modelsLine !== undefined ? joiner + bytes(modelsLine) : 0;
|
|
596
|
+
const ambientCost = removed.length > 0 ? joiner + bytes(AMBIENT_CONTEXT_NOTE) : 0;
|
|
597
|
+
const removalReserve = removed.length > 0 ? joiner + bytes(removalArmFloor(AGENT_LISTING_REMOVED_HEADER, removed.length)) : 0;
|
|
598
|
+
const addArm = added.length > 0 ? fold(added, agentListingDeltaHeader(toolName), budget.blockMaxBytes - modelsCost - ambientCost - removalReserve) : undefined;
|
|
599
|
+
const addCost = addArm === undefined ? 0 : bytes(addArm.text) + joiner;
|
|
600
|
+
const blocks = addArm === undefined ? [] : [addArm.text];
|
|
563
601
|
if (removed.length > 0) {
|
|
564
|
-
blocks.push(
|
|
602
|
+
blocks.push(foldRemovalArmWithin(removed, AGENT_LISTING_REMOVED_HEADER, budget.blockMaxBytes - modelsCost - ambientCost - addCost));
|
|
565
603
|
blocks.push(AMBIENT_CONTEXT_NOTE);
|
|
566
604
|
}
|
|
567
|
-
if (
|
|
568
|
-
blocks.push(
|
|
569
|
-
return blocks.join(
|
|
605
|
+
if (modelsLine !== undefined)
|
|
606
|
+
blocks.push(modelsLine);
|
|
607
|
+
return { text: blocks.join(AGENT_DELTA_BLOCK_JOINER), nameOnly: addArm?.nameOnly ?? [], dropped: addArm?.dropped ?? [], listed: added.length };
|
|
608
|
+
}
|
|
609
|
+
export function renderAgentListingDelta(state, entries, toolName, models, budget) {
|
|
610
|
+
return renderAgentListingFrame(state, entries, toolName, models, budget)?.text;
|
|
570
611
|
}
|
|
612
|
+
const AGENT_LISTING_CAP_MARKER = "(listing budget reached — lower-priority agent types are listed name-only)";
|
|
613
|
+
const AGENT_DELTA_BLOCK_JOINER = "\n\n";
|
|
571
614
|
export const MODELS_AVAILABLE_PREFIX = "Models available for the 'model' parameter: ";
|
|
572
615
|
function modelsAvailableLine(models) {
|
|
573
616
|
const named = models.map((m) => sanitizeUntrustedText(m, SHELLED_BODY_ENVELOPE_TAGS));
|
|
@@ -597,28 +640,7 @@ export function commitAgentListing(state, entries, models) {
|
|
|
597
640
|
export const SKILLS_LISTING_DELTA_HEADER = "New skills are now available:";
|
|
598
641
|
export const SKILLS_LISTING_REMOVED_HEADER = "The following skills are no longer available:";
|
|
599
642
|
const SKILLS_DELTA_BLOCK_JOINER = "\n\n";
|
|
600
|
-
function
|
|
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) {
|
|
643
|
+
export function renderSkillsListingFrame(state, entries, budget = SKILL_LISTING_DEFAULT_BUDGET) {
|
|
622
644
|
const announced = state.announcedSkills;
|
|
623
645
|
if (announced === undefined) {
|
|
624
646
|
if (entries.length === 0)
|
|
@@ -634,20 +656,20 @@ export function renderSkillsListingFrame(state, entries, budget) {
|
|
|
634
656
|
return undefined;
|
|
635
657
|
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
636
658
|
const joiner = bytes(SKILLS_DELTA_BLOCK_JOINER);
|
|
637
|
-
const frameMaxBytes =
|
|
659
|
+
const frameMaxBytes = budget.blockMaxBytes;
|
|
638
660
|
const ambientCost = removed.length > 0 ? joiner + bytes(AMBIENT_CONTEXT_NOTE) : 0;
|
|
639
|
-
const removalReserve = removed.length > 0 ? joiner + bytes(
|
|
661
|
+
const removalReserve = removed.length > 0 ? joiner + bytes(removalArmFloor(SKILLS_LISTING_REMOVED_HEADER, removed.length)) : 0;
|
|
640
662
|
const addArm = added.length > 0
|
|
641
663
|
? renderSkillLinesWithin(added, {
|
|
642
664
|
blockMaxBytes: frameMaxBytes - ambientCost - removalReserve,
|
|
643
|
-
descMaxChars: budget
|
|
665
|
+
descMaxChars: budget.descMaxChars,
|
|
644
666
|
frame: (lines) => [SKILLS_LISTING_DELTA_HEADER, ...lines].join("\n"),
|
|
645
667
|
})
|
|
646
668
|
: undefined;
|
|
647
669
|
const addCost = addArm === undefined ? 0 : bytes(addArm.text) + (removed.length > 0 ? joiner : 0);
|
|
648
670
|
const blocks = addArm === undefined ? [] : [addArm.text];
|
|
649
671
|
if (removed.length > 0) {
|
|
650
|
-
blocks.push(
|
|
672
|
+
blocks.push(foldRemovalArmWithin(removed, SKILLS_LISTING_REMOVED_HEADER, frameMaxBytes - ambientCost - addCost), AMBIENT_CONTEXT_NOTE);
|
|
651
673
|
}
|
|
652
674
|
return {
|
|
653
675
|
text: blocks.join(SKILLS_DELTA_BLOCK_JOINER),
|
|
@@ -703,6 +725,37 @@ export function selectMcpDroppedBatch(entries) {
|
|
|
703
725
|
}
|
|
704
726
|
return out;
|
|
705
727
|
}
|
|
728
|
+
const TOOLS_DELTA_FIXED_MAX_BYTES = 2048;
|
|
729
|
+
function toolsDeltaBound(inp) {
|
|
730
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
731
|
+
const list = (names) => (names.length > TOOLS_DELTA_LIST_MAX ? bytes(groupByMcpServer(names)) : names.reduce((acc, n) => acc + bytes(n) + 3, 0));
|
|
732
|
+
const swapped = (names) => names.reduce((acc, n) => acc + bytes(n) + 2, 0);
|
|
733
|
+
const mcp = inp.mcpToolsDelta;
|
|
734
|
+
return (TOOLS_DELTA_FIXED_MAX_BYTES +
|
|
735
|
+
list(inp.newTools ?? []) +
|
|
736
|
+
swapped(inp.newToolsSwappedUnderStatic ?? []) +
|
|
737
|
+
bytes(groupByMcpServer(mcp?.readded ?? [])) +
|
|
738
|
+
swapped(inp.readdedToolsSwappedUnderStatic ?? []) +
|
|
739
|
+
list(mcp?.removed ?? []) +
|
|
740
|
+
(mcp?.failedServers ?? []).slice(0, TOOLS_DELTA_LIST_MAX).reduce((acc, f) => acc + bytes(f.name) + bytes(f.error ?? "") + 8, 0));
|
|
741
|
+
}
|
|
742
|
+
const MCP_INSTRUCTIONS_FENCE_MAX_BYTES = 512;
|
|
743
|
+
function mcpInstructionsBound(inp) {
|
|
744
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
745
|
+
const m = inp.mcpInstructions;
|
|
746
|
+
if (m === undefined)
|
|
747
|
+
return 0;
|
|
748
|
+
return (512 +
|
|
749
|
+
m.added.reduce((acc, a) => acc + MCP_INSTRUCTIONS_MAX_CHARS * 4 + bytes(a.server) + MCP_INSTRUCTIONS_FENCE_MAX_BYTES, 0) +
|
|
750
|
+
m.removed.reduce((acc, n) => acc + bytes(n) + 1, 0));
|
|
751
|
+
}
|
|
752
|
+
const MCP_DROPPED_FIXED_MAX_BYTES = 1024;
|
|
753
|
+
function mcpDroppedToolsBound(inp) {
|
|
754
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
755
|
+
const first = inp.mcpDroppedTools?.[0];
|
|
756
|
+
const single = first === undefined ? 0 : MCP_DROPPED_FIXED_MAX_BYTES + bytes(first.tool) + bytes(first.server) + bytes(first.reason) + 32;
|
|
757
|
+
return Math.max(MCP_DROPPED_BATCH_MAX_BYTES, single);
|
|
758
|
+
}
|
|
706
759
|
const PLAN_MODE_FULL_BODY = `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and when you have formed a concrete plan, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. That tool inherently requests plan approval — never ask for approval via plain text.`;
|
|
707
760
|
const PLAN_MODE_SPARSE_BODY = `Plan mode still active (see full instructions earlier in conversation). Read-only: no edits, commits, or config changes. When your plan is ready, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. Never ask about plan approval via plain text.`;
|
|
708
761
|
const TRUNCATION_MARKER = "\n…[truncated]";
|
|
@@ -710,12 +763,17 @@ const MIN_KEEP_BYTES = 160;
|
|
|
710
763
|
function capAttachments(atts) {
|
|
711
764
|
let total = 0;
|
|
712
765
|
for (const a of atts)
|
|
713
|
-
|
|
766
|
+
if (!isIntactSource(a.source))
|
|
767
|
+
total += Buffer.byteLength(a.body, "utf8");
|
|
714
768
|
if (total <= ATTACHMENT_BYTE_CAP)
|
|
715
769
|
return atts;
|
|
716
770
|
let budget = ATTACHMENT_BYTE_CAP;
|
|
717
771
|
const out = [];
|
|
718
772
|
for (const a of atts) {
|
|
773
|
+
if (isIntactSource(a.source)) {
|
|
774
|
+
out.push(a);
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
719
777
|
const len = Buffer.byteLength(a.body, "utf8");
|
|
720
778
|
if (len <= budget) {
|
|
721
779
|
out.push(a);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The leg's write-protection MOUNT — one compile, and the loud outlet for the reading it could not get.
|
|
3
|
+
*
|
|
4
|
+
* The always-on write-protection judge re-judges a table-missing spelling on its RESOLVED real target,
|
|
5
|
+
* which needs a filesystem to resolve in. A deployment that wires no execution environment (a tool-only
|
|
6
|
+
* or MCP-only host) hands it none, so the spelling becomes the whole judgment and a covered write that
|
|
7
|
+
* reaches a protected row only through an alias is cleared. That verdict is the judge's documented
|
|
8
|
+
* absence law and is deliberately unchanged here: the announcement dialect applies (announce, do not
|
|
9
|
+
* refuse) — refusing every covered write instead would break exactly the deployments this shape exists
|
|
10
|
+
* for, and the reference implementation falls back to the same spelling-only set when resolution is
|
|
11
|
+
* unavailable. What this seat removes is the SILENCE.
|
|
12
|
+
*
|
|
13
|
+
* Two readers, one compile: the gate station mounts `check`, and the leg's wiring manifest reports
|
|
14
|
+
* `targetView`. Compiling twice would be two judges free to disagree about the seats they were handed.
|
|
15
|
+
*/
|
|
16
|
+
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
17
|
+
import type { RunnerDeps } from "../runner-deps.js";
|
|
18
|
+
import type { ToolRoster } from "../tool-roster.js";
|
|
19
|
+
import { type WriteProtectionMount } from "../write-protect.js";
|
|
20
|
+
/**
|
|
21
|
+
* Compile the leg's write-protection judge and announce a missing target view.
|
|
22
|
+
*
|
|
23
|
+
* Returns the mount, or `undefined` when the deployment emptied the table (`[]`): no judge, no tighten
|
|
24
|
+
* mounted at all, and nothing announced — an explicit opt-out is not a degrade. A garbage table refuses
|
|
25
|
+
* prepare loudly from here, as it did from the gate station before, so an unusable table cannot lie
|
|
26
|
+
* dormant on a write-tool-less task.
|
|
27
|
+
*
|
|
28
|
+
* The notice fires exactly once per prepared leg and only when a judge with NO target view sits beside at
|
|
29
|
+
* least one tool whose writes it covers. Coverage runs the judge's OWN per-call test ({@link
|
|
30
|
+
* protectivePathTargetOf}) rather than a copy of it, over every name a row DISPATCHES under — its own and
|
|
31
|
+
* its aliases, because the gate hands the judge the name the call was made with. A row that declares a
|
|
32
|
+
* path target is covered by that declaration under every name; a row that declares none is covered under
|
|
33
|
+
* whichever of its names the catalog gives a non-read slot to. Both halves are load-bearing: a tool named
|
|
34
|
+
* `Write` that declares nothing, and a tool aliased to `Write` that declares nothing, are each judged by
|
|
35
|
+
* spelling alone on a hands-less leg, and each would otherwise go unnamed by the very line that exists to
|
|
36
|
+
* name them. Reported by the ROW's own name — what the deployment mounted.
|
|
37
|
+
*
|
|
38
|
+
* SNAPSHOT BOUNDARY, deliberate and disclosed: the roster read here is the LEG-START one, the same one
|
|
39
|
+
* the wiring manifest reports and for the same reason — this is one announcement per prepared leg, not a
|
|
40
|
+
* subscription. A run that gains its FIRST covered write tool after prepare (a mid-run roster delta) is
|
|
41
|
+
* judged by the same spelling-only judge and is not announced; the leg's `writeProtection.targetView`
|
|
42
|
+
* still reports the judge's reading correctly, because the judge never gains an environment mid-run —
|
|
43
|
+
* only the covered SET can widen.
|
|
44
|
+
*/
|
|
45
|
+
export declare function mountWriteProtection(input: {
|
|
46
|
+
/** borrowed-readonly — the deployment seats: the table (`writeProtectedPaths`; absent ⇒ the default
|
|
47
|
+
* table, `[]` ⇒ opted out), the data root the memory library is materialized under
|
|
48
|
+
* (`memoryEngineDir`; absent ⇒ the resolver's env/default chain) and the operator sink. */
|
|
49
|
+
deps: Pick<RunnerDeps, "writeProtectedPaths" | "memoryEngineDir" | "onNotice">;
|
|
50
|
+
/** The HANDS' environment, or `undefined` with no hands — the driver's own predicate, not a second
|
|
51
|
+
* reading of the env seats, so the judge sees exactly the environment the write tools do. */
|
|
52
|
+
env: ExecutionEnv | undefined;
|
|
53
|
+
/** The base a `base: "root"` write tool's relative spelling resolves against. */
|
|
54
|
+
rootPath: string;
|
|
55
|
+
/** The leg's minted roster — what actually MOUNTED. */
|
|
56
|
+
roster: ToolRoster;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
runId: string;
|
|
59
|
+
}): WriteProtectionMount | undefined;
|