@sema-agent/core 7.18.0 → 7.20.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 +276 -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 +8 -3
- package/dist/brain/open-responses.js +12 -4
- package/dist/brain/openai.js +7 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +6 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- 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 +74 -26
- package/dist/core/auto-mode.js +80 -11
- package/dist/core/checkpoint-store.d.ts +7 -0
- package/dist/core/checkpoint-store.js +2 -2
- 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 +89 -41
- 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 +20 -48
- package/dist/core/gate-lanes.js +17 -9
- package/dist/core/gate-outcome.d.ts +34 -6
- package/dist/core/gate-outcome.js +7 -0
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +18 -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/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- 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 +154 -11
- package/dist/core/runner/classifier-ladder.js +170 -10
- package/dist/core/runner/contracts.d.ts +46 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +7 -6
- 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-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +20 -31
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +8 -10
- 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 +8 -0
- package/dist/core/runner/prepare-gate-stations.js +6 -2
- 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-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +3 -2
- 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/resume-apply.js +3 -3
- 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 +31 -16
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -25
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +41 -29
- package/dist/core/runner/synthetic-tools.js +25 -40
- 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 +160 -53
- package/dist/core/runner/turn-attachments.js +153 -51
- 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 +84 -17
- package/dist/core/sensitive-path-policy.js +37 -19
- package/dist/core/skill-listing-budget.d.ts +106 -0
- package/dist/core/skill-listing-budget.js +57 -0
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +29 -2
- package/dist/core/tool-policy.d.ts +31 -5
- package/dist/core/tool-policy.js +45 -32
- package/dist/core/tool-spec.d.ts +37 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +105 -20
- package/dist/core/write-protect.js +89 -32
- package/dist/index.d.ts +8 -7
- 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 +40 -2
- package/dist/tools/fs/bash-readonly-classifier.js +376 -41
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +19 -28
- 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/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/safety.d.ts +15 -0
- package/dist/tools/fs/safety.js +7 -0
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +31 -5
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/wire-changes/core@7.20.0.json +568 -0
|
@@ -1,7 +1,9 @@
|
|
|
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_DEFAULT_BUDGET } from "../skill-listing-budget.js";
|
|
6
|
+
import { foldListingWithin, foldRemovalArmWithin, removalArmFloor } from "./listing-fold.js";
|
|
5
7
|
import { TOOL_SEARCH_NAME as TOOL_SEARCH_TOOL_NAME } from "./tool-disclosure.js";
|
|
6
8
|
export const TODO_REMINDER_CONFIG = {
|
|
7
9
|
TURNS_SINCE_WRITE: 10,
|
|
@@ -23,6 +25,33 @@ export const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
|
23
25
|
export const ATTACHMENT_BYTE_CAP = 8 * 1024;
|
|
24
26
|
const PROJECTION_ITEMS_MAX = 50;
|
|
25
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
|
+
}
|
|
26
55
|
const ATTACHMENT_TAGS_DEFAULT = [...SHELLED_BODY_ENVELOPE_TAGS];
|
|
27
56
|
const ATTACHMENT_TAGS_OWNED = {
|
|
28
57
|
skills_listing: SHELLED_BODY_ENVELOPE_TAGS.filter((t) => t !== "skills"),
|
|
@@ -207,21 +236,15 @@ export function collectDueAttachments(state, inp) {
|
|
|
207
236
|
(out ??= []).push({ source: "tools_delta", body });
|
|
208
237
|
}
|
|
209
238
|
const agentListingBody = inp.config.agentListing && inp.agentListing !== undefined
|
|
210
|
-
? renderAgentListingDelta(state, inp.agentListing, inp.agentToolName ?? "Agent", inp.agentModels)
|
|
239
|
+
? renderAgentListingDelta(state, inp.agentListing, inp.agentToolName ?? "Agent", inp.agentModels, inp.listingBudget)
|
|
211
240
|
: undefined;
|
|
212
|
-
const skillsListingBody = inp.config.skillsListing && inp.skillsListing !== undefined ? renderSkillsListingDelta(state, inp.skillsListing) : undefined;
|
|
213
|
-
const listingPair = [];
|
|
214
241
|
if (agentListingBody !== undefined)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
listingPair.push(att);
|
|
222
|
-
}
|
|
223
|
-
for (const att of listingPair)
|
|
224
|
-
(out ??= []).push(att);
|
|
242
|
+
(out ??= []).push({ source: "agent_listing", body: agentListingBody });
|
|
243
|
+
const skillsListingBody = inp.config.skillsListing && inp.skillsListing !== undefined
|
|
244
|
+
? renderSkillsListingDelta(state, inp.skillsListing, inp.listingBudget)
|
|
245
|
+
: undefined;
|
|
246
|
+
if (skillsListingBody !== undefined)
|
|
247
|
+
(out ??= []).push({ source: "skills_listing", body: skillsListingBody });
|
|
225
248
|
if (inp.config.mcpInstructions && inp.mcpInstructions !== undefined) {
|
|
226
249
|
const body = renderMcpInstructionsDelta(inp.mcpInstructions.added, inp.mcpInstructions.removed);
|
|
227
250
|
if (body !== undefined)
|
|
@@ -241,9 +264,6 @@ export function collectDueAttachments(state, inp) {
|
|
|
241
264
|
if (out === undefined)
|
|
242
265
|
return EMPTY;
|
|
243
266
|
const capped = capAttachments(out);
|
|
244
|
-
if (skillsListingBody !== undefined) {
|
|
245
|
-
state.skillsListingStarved = !capped.some((a) => a.source === "skills_listing" && a.body === skillsListingBody);
|
|
246
|
-
}
|
|
247
267
|
if (pendingSurfaced !== undefined && capped.some((a) => a.source === "changed_files")) {
|
|
248
268
|
for (const c of pendingSurfaced)
|
|
249
269
|
state.surfacedMtime.set(c.path, c.mtimeMs);
|
|
@@ -431,15 +451,19 @@ export function renderToolsDelta(input) {
|
|
|
431
451
|
const added = input.added ?? [];
|
|
432
452
|
if (added.length > 0) {
|
|
433
453
|
const swapped = input.staticFace === true ? (input.swappedUnderStatic ?? []).filter((n) => added.includes(n)) : [];
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
: "")
|
|
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);
|
|
443
467
|
}
|
|
444
468
|
const readded = input.readded ?? [];
|
|
445
469
|
if (readded.length > 0) {
|
|
@@ -527,45 +551,66 @@ function listingIdentity(name) {
|
|
|
527
551
|
export function agentListingDeltaHeader(toolName) {
|
|
528
552
|
return `New agent types are now available for the ${sanitizeUntrustedText(toolName, SHELLED_BODY_ENVELOPE_TAGS)} tool:`;
|
|
529
553
|
}
|
|
530
|
-
export function
|
|
554
|
+
export function renderAgentListingFrame(state, entries, toolName, models, budget = SKILL_LISTING_DEFAULT_BUDGET) {
|
|
531
555
|
const safe = (t) => sanitizeUntrustedText(t, SHELLED_BODY_ENVELOPE_TAGS);
|
|
532
556
|
const line = (e) => {
|
|
533
557
|
const base = e.description ? `- ${safe(e.name)}: ${safe(e.description)}` : `- ${safe(e.name)}`;
|
|
534
558
|
return `${base} (Tools: ${safe(e.tools ?? AGENT_TOOLS_NOTE_DEFAULT)})`;
|
|
535
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
|
+
});
|
|
536
571
|
const announced = state.announcedAgentTypes;
|
|
537
572
|
if (announced === undefined) {
|
|
538
573
|
if (entries.length === 0)
|
|
539
574
|
return undefined;
|
|
540
|
-
const
|
|
575
|
+
const tail = [];
|
|
541
576
|
if (entries.length > 1)
|
|
542
|
-
|
|
577
|
+
tail.push(AGENT_CONCURRENCY_NOTE);
|
|
543
578
|
if (models !== undefined && models.length > 0)
|
|
544
|
-
|
|
545
|
-
|
|
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 };
|
|
546
583
|
}
|
|
547
584
|
const announcedIds = new Set([...announced.keys()].map(listingIdentity));
|
|
585
|
+
const entryIds = new Set(entries.map((e) => listingIdentity(e.name)));
|
|
548
586
|
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();
|
|
587
|
+
const removed = [...announced.keys()].filter((n) => !entryIds.has(listingIdentity(n))).sort();
|
|
552
588
|
const modelsDrifted = state.announcedModels !== undefined &&
|
|
553
589
|
models !== undefined &&
|
|
554
590
|
(state.announcedModels.length !== models.length ||
|
|
555
591
|
state.announcedModels.some((m, i) => listingIdentity(m) !== listingIdentity(models[i])));
|
|
556
592
|
if (added.length === 0 && removed.length === 0 && !modelsDrifted)
|
|
557
593
|
return undefined;
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
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];
|
|
561
601
|
if (removed.length > 0) {
|
|
562
|
-
blocks.push(
|
|
602
|
+
blocks.push(foldRemovalArmWithin(removed, AGENT_LISTING_REMOVED_HEADER, budget.blockMaxBytes - modelsCost - ambientCost - addCost));
|
|
563
603
|
blocks.push(AMBIENT_CONTEXT_NOTE);
|
|
564
604
|
}
|
|
565
|
-
if (
|
|
566
|
-
blocks.push(
|
|
567
|
-
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;
|
|
568
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";
|
|
569
614
|
export const MODELS_AVAILABLE_PREFIX = "Models available for the 'model' parameter: ";
|
|
570
615
|
function modelsAvailableLine(models) {
|
|
571
616
|
const named = models.map((m) => sanitizeUntrustedText(m, SHELLED_BODY_ENVELOPE_TAGS));
|
|
@@ -594,26 +639,47 @@ export function commitAgentListing(state, entries, models) {
|
|
|
594
639
|
}
|
|
595
640
|
export const SKILLS_LISTING_DELTA_HEADER = "New skills are now available:";
|
|
596
641
|
export const SKILLS_LISTING_REMOVED_HEADER = "The following skills are no longer available:";
|
|
597
|
-
|
|
642
|
+
const SKILLS_DELTA_BLOCK_JOINER = "\n\n";
|
|
643
|
+
export function renderSkillsListingFrame(state, entries, budget = SKILL_LISTING_DEFAULT_BUDGET) {
|
|
598
644
|
const announced = state.announcedSkills;
|
|
599
645
|
if (announced === undefined) {
|
|
600
646
|
if (entries.length === 0)
|
|
601
647
|
return undefined;
|
|
602
|
-
|
|
648
|
+
const block = renderSkillsBlock(entries, budget);
|
|
649
|
+
return block === undefined ? undefined : { ...block, listed: entries.length };
|
|
603
650
|
}
|
|
604
651
|
const announcedIds = new Set([...announced.keys()].map(listingIdentity));
|
|
652
|
+
const entryIds = new Set(entries.map((e) => listingIdentity(e.name)));
|
|
605
653
|
const added = entries.filter((e) => !announcedIds.has(listingIdentity(e.name)));
|
|
606
|
-
const removed = [...announced.keys()].filter((n) => !
|
|
654
|
+
const removed = [...announced.keys()].filter((n) => !entryIds.has(listingIdentity(n)));
|
|
607
655
|
if (added.length === 0 && removed.length === 0)
|
|
608
656
|
return undefined;
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
657
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
658
|
+
const joiner = bytes(SKILLS_DELTA_BLOCK_JOINER);
|
|
659
|
+
const frameMaxBytes = budget.blockMaxBytes;
|
|
660
|
+
const ambientCost = removed.length > 0 ? joiner + bytes(AMBIENT_CONTEXT_NOTE) : 0;
|
|
661
|
+
const removalReserve = removed.length > 0 ? joiner + bytes(removalArmFloor(SKILLS_LISTING_REMOVED_HEADER, removed.length)) : 0;
|
|
662
|
+
const addArm = added.length > 0
|
|
663
|
+
? renderSkillLinesWithin(added, {
|
|
664
|
+
blockMaxBytes: frameMaxBytes - ambientCost - removalReserve,
|
|
665
|
+
descMaxChars: budget.descMaxChars,
|
|
666
|
+
frame: (lines) => [SKILLS_LISTING_DELTA_HEADER, ...lines].join("\n"),
|
|
667
|
+
})
|
|
668
|
+
: undefined;
|
|
669
|
+
const addCost = addArm === undefined ? 0 : bytes(addArm.text) + (removed.length > 0 ? joiner : 0);
|
|
670
|
+
const blocks = addArm === undefined ? [] : [addArm.text];
|
|
612
671
|
if (removed.length > 0) {
|
|
613
|
-
blocks.push(
|
|
614
|
-
blocks.push(AMBIENT_CONTEXT_NOTE);
|
|
672
|
+
blocks.push(foldRemovalArmWithin(removed, SKILLS_LISTING_REMOVED_HEADER, frameMaxBytes - ambientCost - addCost), AMBIENT_CONTEXT_NOTE);
|
|
615
673
|
}
|
|
616
|
-
return
|
|
674
|
+
return {
|
|
675
|
+
text: blocks.join(SKILLS_DELTA_BLOCK_JOINER),
|
|
676
|
+
nameOnly: addArm?.nameOnly ?? [],
|
|
677
|
+
dropped: addArm?.dropped ?? [],
|
|
678
|
+
listed: added.length,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
export function renderSkillsListingDelta(state, entries, budget) {
|
|
682
|
+
return renderSkillsListingFrame(state, entries, budget)?.text;
|
|
617
683
|
}
|
|
618
684
|
export function commitSkillsListing(state, entries) {
|
|
619
685
|
state.announcedSkills = new Map(entries.map((e) => [e.name, e.description]));
|
|
@@ -659,6 +725,37 @@ export function selectMcpDroppedBatch(entries) {
|
|
|
659
725
|
}
|
|
660
726
|
return out;
|
|
661
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
|
+
}
|
|
662
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.`;
|
|
663
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.`;
|
|
664
761
|
const TRUNCATION_MARKER = "\n…[truncated]";
|
|
@@ -666,12 +763,17 @@ const MIN_KEEP_BYTES = 160;
|
|
|
666
763
|
function capAttachments(atts) {
|
|
667
764
|
let total = 0;
|
|
668
765
|
for (const a of atts)
|
|
669
|
-
|
|
766
|
+
if (!isIntactSource(a.source))
|
|
767
|
+
total += Buffer.byteLength(a.body, "utf8");
|
|
670
768
|
if (total <= ATTACHMENT_BYTE_CAP)
|
|
671
769
|
return atts;
|
|
672
770
|
let budget = ATTACHMENT_BYTE_CAP;
|
|
673
771
|
const out = [];
|
|
674
772
|
for (const a of atts) {
|
|
773
|
+
if (isIntactSource(a.source)) {
|
|
774
|
+
out.push(a);
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
675
777
|
const len = Buffer.byteLength(a.body, "utf8");
|
|
676
778
|
if (len <= budget) {
|
|
677
779
|
out.push(a);
|
|
@@ -56,6 +56,11 @@ export interface SafeNotifierOptions {
|
|
|
56
56
|
*/
|
|
57
57
|
readonly onError?: (failure: SafeNotifyFailure) => void;
|
|
58
58
|
}
|
|
59
|
+
/** The sentence of a contained failure, for a consumer that puts it in front of a person. Total by the
|
|
60
|
+
* same rule as {@link containedError}: an error object handed in from elsewhere (a `lastErrorAt`
|
|
61
|
+
* reading, a rejection an adapter wrapped) may still carry a hostile `message`, and the disclosure a
|
|
62
|
+
* site composes from it must not be the thing that faults. */
|
|
63
|
+
export declare function safeErrorMessage(error: unknown): string;
|
|
59
64
|
export interface SafeNotifier {
|
|
60
65
|
/**
|
|
61
66
|
* Run one host-callback invocation, containing any throw. Returns `true` when it completed, `false` when it
|
package/dist/core/safe-notify.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
function containedError(thrown) {
|
|
2
|
+
try {
|
|
3
|
+
if (thrown instanceof Error && typeof thrown.message === "string")
|
|
4
|
+
return thrown;
|
|
5
|
+
return new Error(String(thrown));
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return new Error(`a thrown value of type ${typeof thrown} that cannot be inspected or converted to text`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function safeErrorMessage(error) {
|
|
12
|
+
try {
|
|
13
|
+
const m = error?.message;
|
|
14
|
+
if (typeof m === "string")
|
|
15
|
+
return m;
|
|
16
|
+
return String(error);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return "a failure whose message could not be read";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
1
22
|
export function createSafeNotifier(opts) {
|
|
2
23
|
const onError = opts?.onError;
|
|
3
24
|
let sites;
|
|
4
25
|
let total = 0;
|
|
5
26
|
const record = (site, thrown) => {
|
|
6
|
-
const error =
|
|
27
|
+
const error = containedError(thrown);
|
|
7
28
|
total += 1;
|
|
8
29
|
if (sites === undefined)
|
|
9
30
|
sites = new Map();
|
|
@@ -60,8 +81,9 @@ export function createSafeNotifier(opts) {
|
|
|
60
81
|
export function observeThenableRejection(r, notifier, site) {
|
|
61
82
|
if (typeof r?.then === "function") {
|
|
62
83
|
r.then(undefined, (err) => {
|
|
84
|
+
const error = containedError(err);
|
|
63
85
|
notifier.notify(() => {
|
|
64
|
-
throw
|
|
86
|
+
throw error;
|
|
65
87
|
}, site);
|
|
66
88
|
});
|
|
67
89
|
}
|
|
@@ -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,6 +52,13 @@ 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 the always-on write-protection table
|
|
58
|
+
* (`WRITE_PROTECTED_DEFAULT_TABLE`'s `.claude` / `.sema` rows — the same reach, since that judge sees the
|
|
59
|
+
* real target too, {@link import("./write-protect.js").WriteProtectionMatcher.judge}); a directory also
|
|
60
|
+
* holds the person's own material, and a directory-wide DENY entry turned an explicit instruction to edit
|
|
61
|
+
* `.claude/projects/…` into a refusal nobody could lift (a deny outranks an ask under the fold).
|
|
37
62
|
*/
|
|
38
63
|
export declare const RECOMMENDED_SENSITIVE_PATTERNS: readonly string[];
|
|
39
64
|
/**
|
|
@@ -53,22 +78,64 @@ export declare const RECOMMENDED_SENSITIVE_PATTERNS: readonly string[];
|
|
|
53
78
|
*/
|
|
54
79
|
export declare function dataRootSpellings(explicit: string | undefined): string[];
|
|
55
80
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
81
|
+
* The data root's identity in the SAME namespace a judged key lives in: every declared spelling
|
|
82
|
+
* ({@link dataRootSpellings}) run through the SAME `canonicalizeTarget` that mints the target key. Not a
|
|
83
|
+
* second canonicalization — the same one, on the other operand, which is what lets
|
|
84
|
+
* {@link relativeUnderRoot}'s comparison be exact.
|
|
85
|
+
*
|
|
86
|
+
* Resolved PER CALL, deliberately not cached. A cached canonical root is a snapshot of the symlink
|
|
87
|
+
* topology at first use: with a data root reached through a symlink, retargeting it left the policy
|
|
88
|
+
* still exempting the OLD target for the life of the run. The module header already accepts paying
|
|
89
|
+
* `canonicalizeTarget` twice for a guarded write as the price of a decoupled gate; this is the same
|
|
90
|
+
* bargain on the other operand, and a request-scoped cache is the same note's answer if profiling
|
|
91
|
+
* ever asks for one. A spelling that cannot be resolved contributes NO exemption (the guard's side wins).
|
|
92
|
+
*
|
|
93
|
+
* ONE resolver for both write judges (the deny set here, the always-on table's target view) — an
|
|
94
|
+
* engine-internal seam, like the two neighbours above.
|
|
95
|
+
*/
|
|
96
|
+
export declare function canonicalDataRootKeys(env: ExecutionEnv, spellings: readonly string[], signal?: AbortSignal): Promise<string[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Is `targetKey` inside the data root `rootKey`, and if so what is its path relative to it? Returns
|
|
99
|
+
* the relative sub-path (`""` when the target IS the root), or `null` for everything else.
|
|
100
|
+
*
|
|
101
|
+
* 🔴 EXACT, and deliberately nothing else. Both operands are minted by the SAME canonicalizer, so
|
|
102
|
+
* every transformation this comparison could apply — a case fold, an NFC pass, splitting a foreign
|
|
103
|
+
* separator family, unioning several readings of the root — can only ADD matches. On the DENY side of
|
|
104
|
+
* this file that is the safe direction and several of those transforms are applied on purpose (a
|
|
105
|
+
* spelling filter is not a path filter). Here the direction inverts: this test grants an EXEMPTION, so
|
|
106
|
+
* an added match is not a wider guard, it is a bypass of one — each of case folding, an NFC pass and a
|
|
107
|
+
* foreign-family read was measured leaking an unrelated sibling into the exemption (their pins name the
|
|
108
|
+
* shapes). The rule: compare what the canonicalizer produced, byte for byte, every uncertainty falling
|
|
109
|
+
* to the deny side.
|
|
110
|
+
*
|
|
111
|
+
* The separator family comes from the ROOT key (a backslash is an ordinary filename character on
|
|
112
|
+
* POSIX, so a root `/x/.sema/data` must not swallow the sibling FILE `/x/.sema/data\evil`), and a root
|
|
113
|
+
* that reduces to nothing exempts nothing rather than exempting the filesystem.
|
|
114
|
+
*
|
|
115
|
+
* Exported as an ENGINE-INTERNAL seam (not re-exported from `src/index.ts`), like {@link dataRootSpellings}:
|
|
116
|
+
* the always-on write-protection judge applies the same exemption on its target view, and a second
|
|
117
|
+
* containment test would be a second place for the two judges to disagree about what "inside" means.
|
|
118
|
+
*/
|
|
119
|
+
export declare function relativeUnderRoot(targetKey: string, rootKey: string): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* Build a composable policy that DENIES writes (via the guarded fs tools) whose CANONICAL target lands on
|
|
122
|
+
* a guarded pattern. `patterns` is deployment-supplied — no default list is shipped; empty = a no-op allow.
|
|
123
|
+
* A deny is a refusal no approver and no persisted allow rule can lift. The ASK severity of the same class
|
|
124
|
+
* (the agent-configuration DIRECTORIES) is not a tier here: it is the always-on write-protection table's,
|
|
125
|
+
* which judges the same real target ({@link import("./write-protect.js").WriteProtectionMatcher.judge} —
|
|
126
|
+
* its contract sentence `write_protection.judges_the_real_target` says the two differ in vocabulary and
|
|
127
|
+
* severity, never in reach).
|
|
59
128
|
*
|
|
60
129
|
* `env` is the same ExecutionEnv the hands run against (inject the one you pass to the Runner), so
|
|
61
130
|
* 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.
|
|
131
|
+
* `rootPath` plays the same fallback-base role it plays on the sibling write gate (see
|
|
132
|
+
* {@link import("./fs-write-gate-policy.js").FsWriteGatePolicyOptions.rootPath}); the call's live
|
|
133
|
+
* `ToolCallRequest.cwd` outranks it for the target.
|
|
67
134
|
*
|
|
68
|
-
* Fail-open by design: a tool with no string `path
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
135
|
+
* Fail-open by design: a tool with no string `path` returns `allow` here — this guard adds friction on
|
|
136
|
+
* top of the other gates, it is not the only line of defense, and it must not turn an unrelated tool
|
|
137
|
+
* call into a spurious verdict. The verdict it owns is narrow and certain: "the real target IS a
|
|
138
|
+
* guarded path → deny". (A target it could not RESOLVE is the one fail-CLOSED arm, below.)
|
|
72
139
|
*/
|
|
73
140
|
export declare function createSensitivePathPolicy(opts: {
|
|
74
141
|
env: ExecutionEnv;
|
|
@@ -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",
|
|
@@ -46,7 +64,20 @@ export function dataRootSpellings(explicit) {
|
|
|
46
64
|
}
|
|
47
65
|
return out;
|
|
48
66
|
}
|
|
49
|
-
function
|
|
67
|
+
export async function canonicalDataRootKeys(env, spellings, signal) {
|
|
68
|
+
const keys = [];
|
|
69
|
+
for (const spelling of spellings) {
|
|
70
|
+
try {
|
|
71
|
+
const r = await canonicalizeTarget(env, spelling, signal, pathFamilyOf({ root: spelling }));
|
|
72
|
+
if (r.ok && !keys.includes(r.key))
|
|
73
|
+
keys.push(r.key);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return keys;
|
|
79
|
+
}
|
|
80
|
+
export function relativeUnderRoot(targetKey, rootKey) {
|
|
50
81
|
const seps = isWinFormPath(rootKey) ? ["\\", "/"] : ["/"];
|
|
51
82
|
let root = rootKey;
|
|
52
83
|
while (root.length > 0 && seps.includes(root[root.length - 1]))
|
|
@@ -78,19 +109,6 @@ function matchSensitive(canonicalKey, compiled, aliasResolved) {
|
|
|
78
109
|
export function createSensitivePathPolicy(opts) {
|
|
79
110
|
const compiled = compilePatterns(opts.patterns);
|
|
80
111
|
const dataRootPaths = dataRootSpellings(opts.dataRoot);
|
|
81
|
-
const dataRootKeys = async (signal) => {
|
|
82
|
-
const keys = [];
|
|
83
|
-
for (const spelling of dataRootPaths) {
|
|
84
|
-
try {
|
|
85
|
-
const r = await canonicalizeTarget(opts.env, spelling, signal, pathFamilyOf({ root: spelling }));
|
|
86
|
-
if (r.ok && !keys.includes(r.key))
|
|
87
|
-
keys.push(r.key);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return keys;
|
|
93
|
-
};
|
|
94
112
|
const guardedByName = opts.tools !== undefined ? new Set(opts.tools) : undefined;
|
|
95
113
|
return {
|
|
96
114
|
async check(req, signal) {
|
|
@@ -113,7 +131,7 @@ export function createSensitivePathPolicy(opts) {
|
|
|
113
131
|
};
|
|
114
132
|
}
|
|
115
133
|
let relative = null;
|
|
116
|
-
for (const rootKey of await
|
|
134
|
+
for (const rootKey of await canonicalDataRootKeys(opts.env, dataRootPaths, signal)) {
|
|
117
135
|
relative = relativeUnderRoot(canon.key, rootKey);
|
|
118
136
|
if (relative !== null)
|
|
119
137
|
break;
|