@sema-agent/core 7.50.1 → 7.51.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 +275 -0
- package/dist/agents/child-failure.d.ts +5 -16
- package/dist/agents/child-failure.js +4 -24
- package/dist/agents/subagent-steps.js +2 -2
- package/dist/agents/subagent.d.ts +4 -13
- package/dist/agents/subagent.js +94 -204
- package/dist/agents/tool-filter.d.ts +2 -2
- package/dist/brain/anthropic.d.ts +1 -1
- package/dist/brain/anthropic.js +7 -5
- package/dist/brain/open-responses.d.ts +1 -1
- package/dist/brain/open-responses.js +3 -2
- package/dist/brain/openai.d.ts +1 -1
- package/dist/brain/openai.js +3 -2
- package/dist/brain/request-route.d.ts +2 -2
- package/dist/brain/route-adjudicator.d.ts +16 -7
- package/dist/brain/route-adjudicator.js +23 -7
- package/dist/brain/route-conformance.js +23 -5
- package/dist/brain/stream-engine.js +9 -3
- package/dist/core/a2a.js +6 -8
- package/dist/core/allow-entry.d.ts +45 -26
- package/dist/core/allow-entry.js +48 -24
- package/dist/core/arg-summary.d.ts +8 -85
- package/dist/core/arg-summary.js +4 -386
- package/dist/core/ask-origin.d.ts +35 -33
- package/dist/core/ask-origin.js +9 -11
- package/dist/core/checkpoint-requirements.d.ts +6 -0
- package/dist/core/checkpoint-requirements.js +1 -0
- package/dist/core/checkpoint-store.d.ts +15 -34
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/classifier-unavailable-arm.d.ts +12 -8
- package/dist/core/classifier-unavailable-arm.js +7 -2
- package/dist/core/credential-vocabulary.d.ts +3 -2
- package/dist/core/credential-vocabulary.js +27 -18
- package/dist/core/delegation-frames.d.ts +40 -15
- package/dist/core/delegation-frames.js +34 -0
- package/dist/core/engine-notice.d.ts +35 -11
- package/dist/core/gate-fold.js +9 -7
- package/dist/core/gate-lanes.js +6 -4
- package/dist/core/gate-outcome.d.ts +87 -11
- package/dist/core/gate-outcome.js +171 -61
- package/dist/core/governance-codes.d.ts +7 -1
- package/dist/core/governance-codes.js +9 -0
- package/dist/core/hooks.d.ts +4 -4
- package/dist/core/hooks.js +4 -3
- package/dist/core/mcp-connection-pool.d.ts +2 -0
- package/dist/core/mcp-connection-pool.js +2 -1
- package/dist/core/mcp-failure.js +2 -1
- package/dist/core/mcp-server-spec.d.ts +27 -5
- package/dist/core/mcp.d.ts +13 -38
- package/dist/core/mcp.js +28 -23
- package/dist/core/media-byte-cap.d.ts +25 -0
- package/dist/core/media-byte-cap.js +18 -2
- package/dist/core/memory-engine/consolidation-driver.js +1 -1
- package/dist/core/mode-policy.d.ts +25 -1
- package/dist/core/mode-policy.js +28 -13
- package/dist/core/own-property-receipt.d.ts +18 -0
- package/dist/core/own-property-receipt.js +39 -0
- package/dist/core/permission-rule-model.d.ts +32 -11
- package/dist/core/permission-rule-model.js +12 -7
- package/dist/core/persisted-rule-arms.d.ts +3 -3
- package/dist/core/prompt-section-seat.d.ts +54 -0
- package/dist/core/prompt-section-seat.js +49 -0
- package/dist/core/protocol-http-auth.d.ts +44 -0
- package/dist/core/protocol-http-auth.js +45 -0
- package/dist/core/read-face-seats.d.ts +30 -0
- package/dist/core/read-face-seats.js +38 -0
- package/dist/core/redaction-passes.d.ts +82 -0
- package/dist/core/redaction-passes.js +378 -0
- package/dist/core/remote-env.d.ts +4 -3
- package/dist/core/resume-startup-cause.d.ts +56 -0
- package/dist/core/resume-startup-cause.js +43 -0
- package/dist/core/rule-denial-sentence.d.ts +2 -2
- package/dist/core/runner/announce-once-ledger.js +2 -2
- package/dist/core/runner/classifier-ladder.d.ts +17 -13
- package/dist/core/runner/classifier-ladder.js +12 -10
- package/dist/core/runner/construction-doors.js +9 -20
- package/dist/core/runner/contracts.d.ts +4 -4
- package/dist/core/runner/gate-exit.js +1 -1
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
- package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
- package/dist/core/runner/person-path-rows.d.ts +4 -3
- package/dist/core/runner/policy-throw.js +4 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
- package/dist/core/runner/prepare-gate-stations.js +3 -3
- package/dist/core/runner/prepare-hands-readface.js +1 -19
- package/dist/core/runner/prepare-inherited-gate.js +10 -9
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
- package/dist/core/runner/prepare-policy-chain.js +10 -13
- package/dist/core/runner/prepare-project-context.d.ts +4 -4
- package/dist/core/runner/prepare-project-context.js +2 -1
- package/dist/core/runner/prepare-prompt-assembly.js +1 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-inputs.js +4 -4
- package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-task.js +6 -4
- package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
- package/dist/core/runner/prepare-turn-wiring.js +8 -7
- package/dist/core/runner/run-harness-handlers.js +2 -2
- package/dist/core/runner/run-leg.js +10 -9
- package/dist/core/runner/run-settle-and-teardown.js +5 -5
- package/dist/core/runner/run-turn-boundary.js +3 -2
- package/dist/core/runner/runtask.d.ts +1 -2
- package/dist/core/runner/runtask.js +8 -12
- package/dist/core/runner/session-rule-policy.d.ts +15 -11
- package/dist/core/runner/session-rule-policy.js +43 -33
- package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
- package/dist/core/runner/stream-settle-backstop.js +2 -1
- package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
- package/dist/core/runner/sub-agent-deny-lists.js +23 -12
- package/dist/core/runner/terminal-account.js +3 -3
- package/dist/core/runner/tool-output-projection.js +3 -8
- package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
- package/dist/core/runner/tool-result-media-seat.js +13 -0
- package/dist/core/runner/turn-attachments.d.ts +13 -12
- package/dist/core/runner/turn-attachments.js +11 -7
- package/dist/core/runner-deps.d.ts +4 -3
- package/dist/core/safety-merge-corpus.js +21 -21
- package/dist/core/secret-env.js +1 -1
- package/dist/core/session-policy-store.d.ts +89 -72
- package/dist/core/session-policy-store.js +100 -93
- package/dist/core/shell-read-silence.d.ts +38 -4
- package/dist/core/shell-read-silence.js +102 -0
- package/dist/core/side-query.d.ts +7 -8
- package/dist/core/skill-grant-binding.d.ts +32 -11
- package/dist/core/skill-grant-binding.js +11 -3
- package/dist/core/skill-grant.d.ts +4 -3
- package/dist/core/skill-grant.js +5 -4
- package/dist/core/skills-directory.d.ts +12 -1
- package/dist/core/skills-directory.js +17 -8
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
- package/dist/core/swappable-deps.d.ts +15 -1
- package/dist/core/swappable-deps.js +25 -1
- package/dist/core/synthetic-read-verdict.d.ts +16 -14
- package/dist/core/synthetic-read-verdict.js +28 -8
- package/dist/core/task-notification.d.ts +13 -3
- package/dist/core/task-notification.js +12 -1
- package/dist/core/task-spec.d.ts +14 -15
- package/dist/core/tool-name-readings.d.ts +0 -4
- package/dist/core/tool-name-readings.js +0 -1
- package/dist/core/tool-policy.d.ts +40 -9
- package/dist/core/tool-policy.js +25 -7
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-egress.d.ts +60 -11
- package/dist/core/untrusted-egress.js +120 -9
- package/dist/core/untrusted-text.js +16 -0
- package/dist/core/wiring-manifest.d.ts +22 -8
- package/dist/core/wiring-manifest.js +12 -4
- package/dist/engine/harness/agent-harness.d.ts +2 -1
- package/dist/engine/harness/agent-harness.js +4 -1
- package/dist/engine/harness/types.d.ts +3 -0
- package/dist/engine/llm/types.d.ts +7 -9
- package/dist/engine/loop/agent-loop.d.ts +8 -3
- package/dist/engine/loop/agent-loop.js +26 -9
- package/dist/engine/loop/types.d.ts +10 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -5
- package/dist/orchestration/run-workflow-tool.js +8 -8
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/assemble.d.ts +3 -1
- package/dist/prompt-assembly/assemble.js +6 -0
- package/dist/prompts/cowork.d.ts +42 -0
- package/dist/prompts/cowork.js +529 -0
- package/dist/prompts/default.d.ts +14 -1
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/session-policy-store.d.ts +6 -3
- package/dist/stores/file/session-policy-store.js +5 -16
- package/dist/tools/artifact/artifact-tool.js +2 -0
- package/dist/tools/fs/image-admission.d.ts +57 -4
- package/dist/tools/fs/image-admission.js +85 -0
- package/dist/tools/fs/path-containment.d.ts +2 -0
- package/dist/tools/fs/path-containment.js +5 -0
- package/dist/tools/fs/shell-read-check.d.ts +31 -6
- package/dist/tools/fs/shell-read-check.js +2 -4
- package/dist/tools/fs/shell-read-environment.d.ts +10 -25
- package/dist/tools/fs/shell-read-environment.js +6 -18
- package/dist/tools/fs/shell-read-establish.d.ts +3 -2
- package/dist/tools/fs/shell-read-establish.js +5 -5
- package/dist/tools/fs/shell-read-judge.d.ts +38 -8
- package/dist/tools/fs/shell-read-judge.js +12 -10
- package/dist/tools/web.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +94 -10
- package/wire-changes/core@7.50.2.json +42 -0
- package/wire-changes/core@7.51.0.json +2421 -0
|
@@ -933,17 +933,21 @@ function firstEligibleAdmittingRule(rules, commandText, call) {
|
|
|
933
933
|
return undefined;
|
|
934
934
|
}
|
|
935
935
|
export function findAdmittingRule(rules, call) {
|
|
936
|
+
const coverage = commandCoverageOf(rules, call);
|
|
937
|
+
return "covering" in coverage ? coverage.covering : undefined;
|
|
938
|
+
}
|
|
939
|
+
export function commandCoverageOf(rules, call) {
|
|
936
940
|
const shape = ruleLaneShapeOf(call.command, MATCH_READING);
|
|
937
941
|
if ("reject" in shape)
|
|
938
|
-
return undefined;
|
|
942
|
+
return { uncovered: undefined };
|
|
939
943
|
const whole = firstEligibleAdmittingRule(rules, call.command, call);
|
|
940
944
|
if (whole !== undefined)
|
|
941
|
-
return [whole];
|
|
945
|
+
return { covering: [whole] };
|
|
942
946
|
const cdDirs = resolveCdSegmentDirectories(shape.segments, call.execCwd ?? call.cwd);
|
|
943
947
|
if (shape.segments.length < 2) {
|
|
944
948
|
const dir = cdDirs[0];
|
|
945
949
|
const lone = dir !== undefined ? firstEligibleDirectoryRule(rules, dir, call) : undefined;
|
|
946
|
-
return lone !== undefined ? [lone] :
|
|
950
|
+
return lone !== undefined ? { covering: [lone] } : { uncovered: shape.segments[0] ?? call.command };
|
|
947
951
|
}
|
|
948
952
|
const covering = [];
|
|
949
953
|
const lineObjection = readOnlyLineObjection(call.command, call.execCwd ?? call.cwd);
|
|
@@ -951,14 +955,14 @@ export function findAdmittingRule(rules, call) {
|
|
|
951
955
|
const dir = cdDirs[i];
|
|
952
956
|
const first = firstEligibleAdmittingRule(rules, segment, call) ?? (dir !== undefined ? firstEligibleDirectoryRule(rules, dir, call) : undefined);
|
|
953
957
|
if (first === undefined) {
|
|
954
|
-
if (readsOnlyAsSegment(segment, lineObjection, call.command))
|
|
958
|
+
if (call.posture !== "narrowing" && readsOnlyAsSegment(segment, lineObjection, call.command))
|
|
955
959
|
continue;
|
|
956
|
-
return
|
|
960
|
+
return { uncovered: segment };
|
|
957
961
|
}
|
|
958
962
|
if (!covering.includes(first))
|
|
959
963
|
covering.push(first);
|
|
960
964
|
}
|
|
961
|
-
return covering.length > 0 ? covering : undefined;
|
|
965
|
+
return covering.length > 0 ? { covering } : { uncovered: undefined };
|
|
962
966
|
}
|
|
963
967
|
function readsOnlyAsSegment(segment, lineObjection, whole) {
|
|
964
968
|
return lineObjection === undefined && !movesShellCursor(segment) && readOnlyShellVerdict(segment, undefined, undefined, () => lineDescriptorsOfText(whole)).readOnly;
|
|
@@ -1088,8 +1092,9 @@ export const UNCOVERED_SEGMENT_REASON_BASELINE = {
|
|
|
1088
1092
|
};
|
|
1089
1093
|
export const RULE_OFFERS_ABSENCE_BASELINE = {
|
|
1090
1094
|
mandated: "This approval is required by the deployment's policy each time; a rule cannot waive it.",
|
|
1095
|
+
allow_outranked: "An allow rule already matches this call, and this question outranks it — another allow rule would be outranked the same way; it is approved per use.",
|
|
1096
|
+
ask_rule_matched: "An ask-each-time rule matches this call and keeps asking — a new allow rule changes nothing while it stands; to stop being asked, review the existing ask rules.",
|
|
1091
1097
|
lane_cannot_speak: "This card has no rule to offer for this exact command; it can be approved per use. A coverable form of what you trust can still get a rule — through a card drawn for that form, or settings.",
|
|
1092
|
-
shadowed: "A rule you already have matches this call and still asks — that standing rule keeps deciding, so a new rule minted here would change nothing; review your existing rules instead.",
|
|
1093
1098
|
};
|
|
1094
1099
|
export function suggestRulesForCommand(command, ctx) {
|
|
1095
1100
|
const shape = ruleLaneShapeOf(command, OFFER_READING);
|
|
@@ -48,7 +48,7 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
|
|
|
48
48
|
* reads the deny (the gate's exit, the approved-edit re-check, a resumed leg's belt) mints the same card from the same fact.
|
|
49
49
|
* · ASK — on an allow, a fresh ask is minted carrying `matchedAskRule` (the marker an explicit `ask`
|
|
50
50
|
* permission rule stamps, so the ask's origin derives as `ask_rule`: the classifier never answers
|
|
51
|
-
* it, the persisted ALLOW arm never clears it, and the rule-offer face reports `
|
|
51
|
+
* it, the persisted ALLOW arm never clears it, and the rule-offer face reports `ask_rule_matched`); on an
|
|
52
52
|
* ask, the marker is stamped if absent and the message names the rule — the standing question
|
|
53
53
|
* outranks a remembered yes across lanes, exactly as the DSL's own order says within one; a deny
|
|
54
54
|
* stays a deny.
|
|
@@ -62,8 +62,8 @@ export declare function disclosedRuleSet(rules: readonly PersistedRuleHitRule[])
|
|
|
62
62
|
* or ask row inside it may forbid this call and nobody can tell, so it fails CLOSED the same way, to the question
|
|
63
63
|
* only a person answers — an allow becomes an ask carrying `requiresRealApproval`, an ask acquires the bit, a deny stays
|
|
64
64
|
* a deny. Every call the lane judges is in this state until a read succeeds again (the lane announces the skipped store,
|
|
65
|
-
* once per run, and reads again on the next call). The two forms differ in WHICH fact the gate records (
|
|
66
|
-
*
|
|
65
|
+
* once per run, and reads again on the next call). The two forms differ in WHICH fact the gate records (one class for
|
|
66
|
+
* both, `rule_unreadable`, bypass-immune) and in the
|
|
67
67
|
* resumed-call belt, which vetoes only the call form: a person already answered that call's question.
|
|
68
68
|
* An allow hit (or no hit) returns the decision untouched: the allow arm is the station's.
|
|
69
69
|
*/
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The named sections whose entries the host may dispose of — a closed set. */
|
|
2
|
+
export declare const DISPOSABLE_PROMPT_SECTIONS: readonly ["core/discovery.mcp-instructions"];
|
|
3
|
+
export type DisposablePromptSection = (typeof DISPOSABLE_PROMPT_SECTIONS)[number];
|
|
4
|
+
/** One entry of a named section, as the engine computed it, before it reaches the model. A frozen copy:
|
|
5
|
+
* writing to it changes nothing. */
|
|
6
|
+
export interface PromptSectionEntry {
|
|
7
|
+
readonly section: DisposablePromptSection;
|
|
8
|
+
/** The entry's key within the section: the MCP server's declared name. */
|
|
9
|
+
readonly server: string;
|
|
10
|
+
/** The text the engine would place: the server's `instructions`, already neutralized. */
|
|
11
|
+
readonly text: string;
|
|
12
|
+
/** The tools this leg mounted from that server, by their namespaced names. */
|
|
13
|
+
readonly mountedTools: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
/** The host's answer. `undefined` = include the entry as computed. */
|
|
16
|
+
export type PromptSectionDisposition = {
|
|
17
|
+
readonly op: "drop";
|
|
18
|
+
} | {
|
|
19
|
+
readonly op: "replace";
|
|
20
|
+
readonly text: string;
|
|
21
|
+
};
|
|
22
|
+
/** Who wrote the text an entry carries into the model's view. */
|
|
23
|
+
export type PromptSectionAuthor = "server" | "host";
|
|
24
|
+
export interface PromptSectionSeat {
|
|
25
|
+
/**
|
|
26
|
+
* Answer the disposition of one entry of a named section ({@link DISPOSABLE_PROMPT_SECTIONS}): return
|
|
27
|
+
* nothing to include it, `{ op: "drop" }` to leave it out of the model's view on every lane, or
|
|
28
|
+
* `{ op: "replace", text }` to place the host's own non-blank text instead — host text, rendered
|
|
29
|
+
* without the untrusted-data fence the server's text rides in, under the same per-entry bound and the same
|
|
30
|
+
* disclosed cut. Called once each time the entry's text
|
|
31
|
+
* enters the run (a re-dial asks again). Synchronous. A throw, a Promise, or any other value includes
|
|
32
|
+
* the entry and raises a coded operator warning (`prompt.section_answer_invalid`).
|
|
33
|
+
*/
|
|
34
|
+
section?(entry: PromptSectionEntry): PromptSectionDisposition | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** One server's entry of the MCP instructions section as the run carries it, after the host's disposition. */
|
|
37
|
+
export interface McpInstructionsEntry {
|
|
38
|
+
readonly server: string;
|
|
39
|
+
readonly text: string;
|
|
40
|
+
readonly author: PromptSectionAuthor;
|
|
41
|
+
}
|
|
42
|
+
/** What one fold leaves in the run: the entry's text and author, absent when dropped, plus the defect
|
|
43
|
+
* sentence when the answer could not be read. */
|
|
44
|
+
export interface DisposedSectionEntry {
|
|
45
|
+
readonly kept?: {
|
|
46
|
+
readonly text: string;
|
|
47
|
+
readonly author: PromptSectionAuthor;
|
|
48
|
+
};
|
|
49
|
+
readonly defect?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Read the seat's answer for one entry. Each member of the answer is read once. */
|
|
52
|
+
export declare function disposeSectionEntry(seat: PromptSectionSeat | undefined, entry: PromptSectionEntry): DisposedSectionEntry;
|
|
53
|
+
/** The operator warning for an answer the fold could not read. */
|
|
54
|
+
export declare function sectionAnswerWarning(defect: string): Error;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { thrownText } from "./thrown-value.js";
|
|
2
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
|
+
import { egressText } from "./untrusted-egress.js";
|
|
4
|
+
export const DISPOSABLE_PROMPT_SECTIONS = Object.freeze(["core/discovery.mcp-instructions"]);
|
|
5
|
+
const ANSWER_KEYS = { drop: ["op"], replace: ["op", "text"] };
|
|
6
|
+
const FOREIGN_VALUE_MAX = 200;
|
|
7
|
+
const STRAY_KEYS_NAMED = 5;
|
|
8
|
+
const DEFECT_MAX = 600;
|
|
9
|
+
const foreign = (s, max) => inlineUntrusted(egressText("same-principal", s), max);
|
|
10
|
+
export function disposeSectionEntry(seat, entry) {
|
|
11
|
+
const computed = { text: entry.text, author: "server" };
|
|
12
|
+
if (seat?.section === undefined)
|
|
13
|
+
return { kept: computed };
|
|
14
|
+
const offered = Object.freeze({ ...entry, mountedTools: Object.freeze([...entry.mountedTools]) });
|
|
15
|
+
const where = `prompt section "${entry.section}" entry "${inlineUntrusted(entry.server, 160)}"`;
|
|
16
|
+
const unread = (why) => ({ kept: computed, defect: inlineUntrusted(`${where}: ${why} — the entry is included as the engine computed it`, DEFECT_MAX) });
|
|
17
|
+
try {
|
|
18
|
+
const answer = seat.section(offered);
|
|
19
|
+
if (answer === undefined)
|
|
20
|
+
return { kept: computed };
|
|
21
|
+
if (answer === null || typeof answer !== "object")
|
|
22
|
+
return unread(`the answer is ${answer === null ? "null" : typeof answer}, not undefined, { op: "drop" } or { op: "replace", text }`);
|
|
23
|
+
const record = answer;
|
|
24
|
+
if (typeof record["then"] === "function") {
|
|
25
|
+
void Promise.resolve(answer).catch(() => undefined);
|
|
26
|
+
return unread("the section seat returned a Promise; it is synchronous");
|
|
27
|
+
}
|
|
28
|
+
const op = record["op"];
|
|
29
|
+
const text = record["text"];
|
|
30
|
+
if (op !== "drop" && op !== "replace")
|
|
31
|
+
return unread(`the answer's op is ${typeof op === "string" ? `"${foreign(op, 40)}"` : typeof op}, not "drop" or "replace"`);
|
|
32
|
+
const stray = Object.keys(record).filter((k) => !ANSWER_KEYS[op].includes(k));
|
|
33
|
+
if (stray.length > 0) {
|
|
34
|
+
const named = stray.slice(0, STRAY_KEYS_NAMED).map((k) => foreign(k, 40)).join(", ");
|
|
35
|
+
return unread(`a "${op}" answer carries ${named}${stray.length > STRAY_KEYS_NAMED ? ` and ${stray.length - STRAY_KEYS_NAMED} more` : ""}`);
|
|
36
|
+
}
|
|
37
|
+
if (op === "drop")
|
|
38
|
+
return {};
|
|
39
|
+
if (typeof text !== "string" || text.trim() === "")
|
|
40
|
+
return unread(`a "replace" answer needs non-blank text`);
|
|
41
|
+
return { kept: { text, author: "host" } };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return unread(`the section seat (or reading its answer) threw (${foreign(thrownText(err), FOREIGN_VALUE_MAX)})`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function sectionAnswerWarning(defect) {
|
|
48
|
+
return Object.assign(new Error(defect), { code: "prompt.section_answer_invalid" });
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP credentials a protocol declaration carries — its static `headers` and the per-task principal
|
|
3
|
+
* header (`principalHeader`) — and the one rule they obey, for MCP and A2A alike: they travel only to the
|
|
4
|
+
* endpoints the declaration names, and every header name has exactly one owner, compared the way HTTP
|
|
5
|
+
* compares names (case-insensitively). The principal header's owner is the engine: it sends the task's
|
|
6
|
+
* principal under that name, or nothing under it when the task has none.
|
|
7
|
+
*
|
|
8
|
+
* A declaration that breaks the rule is that peer's defect: the resolver throws inside the peer's own dial,
|
|
9
|
+
* so the peer is skipped with a coded warning and its siblings still mount. The MCP transport binds its
|
|
10
|
+
* endpoint by dialing only the declared URL with redirects not followed (`declaredEndpointFetch`); the A2A
|
|
11
|
+
* peer, whose card may name another endpoint, is bound by {@link assertEndpointBound} and by
|
|
12
|
+
* {@link redirectPolicy}.
|
|
13
|
+
*/
|
|
14
|
+
import type { ProtocolId } from "./protocol-table.js";
|
|
15
|
+
/** The per-peer HTTP auth declaration every protocol spec carries in the same two fields. */
|
|
16
|
+
export interface ProtocolHttpAuth {
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
principalHeader?: string;
|
|
19
|
+
}
|
|
20
|
+
/** The first header name (any case) the declaration gives two owners, with both spellings. */
|
|
21
|
+
export declare function headerOwnershipCollision(t: ProtocolHttpAuth): {
|
|
22
|
+
readonly first: string;
|
|
23
|
+
readonly second: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the HTTP request headers for one remote peer of `protocol`: the static headers, plus the
|
|
27
|
+
* principal under `principalHeader` when both are present. Throws `config.protocol_header_collision` when
|
|
28
|
+
* one header name has two owners (two static spellings, or a static entry under the principal header's
|
|
29
|
+
* name — which would otherwise be sent merged beside the principal, or alone when the task has none), and
|
|
30
|
+
* on a principal carrying CR/LF. Callers invoke it INSIDE their per-peer boundary.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveProtocolHttpHeaders(protocol: ProtocolId, t: ProtocolHttpAuth, principal?: string): Readonly<Record<string, string>> | undefined;
|
|
33
|
+
/** The engine's default request headers under the declared ones: a declared name, in any case, owns that header
|
|
34
|
+
* (the default of the same name is not sent beside it). */
|
|
35
|
+
export declare function underDeclaredHeaders(defaults: Readonly<Record<string, string>>, declared: Record<string, string> | undefined): Record<string, string>;
|
|
36
|
+
/** Whether a resolved header set carries anything the declaration bound to its endpoints. */
|
|
37
|
+
export declare function carriesDeclaredHeaders(headers: Record<string, string> | undefined): boolean;
|
|
38
|
+
/** A request carrying declared headers does not follow a redirect (the next hop is not a declared endpoint). */
|
|
39
|
+
export declare function redirectPolicy(headers: Record<string, string> | undefined): "manual" | "follow";
|
|
40
|
+
/**
|
|
41
|
+
* Refuse an endpoint a peer advertised (an A2A card's interface URL) when it lies outside every origin the
|
|
42
|
+
* declaration names and the peer's requests carry declared headers: `config.protocol_endpoint_unbound`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function assertEndpointBound(protocol: ProtocolId, peer: string, endpoint: string, declared: readonly string[], headers: Record<string, string> | undefined): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
2
|
+
export function headerOwnershipCollision(t) {
|
|
3
|
+
const owners = new Map();
|
|
4
|
+
for (const name of [...Object.keys(t.headers ?? {}), ...(t.principalHeader ? [t.principalHeader] : [])]) {
|
|
5
|
+
const prior = owners.get(name.toLowerCase());
|
|
6
|
+
if (prior !== undefined)
|
|
7
|
+
return { first: prior, second: name };
|
|
8
|
+
owners.set(name.toLowerCase(), name);
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
export function resolveProtocolHttpHeaders(protocol, t, principal) {
|
|
13
|
+
const principalHeader = t.principalHeader;
|
|
14
|
+
const declared = t.headers;
|
|
15
|
+
const own = { ...(declared !== undefined ? { headers: { ...declared } } : {}), ...(principalHeader !== undefined ? { principalHeader } : {}) };
|
|
16
|
+
const collision = headerOwnershipCollision(own);
|
|
17
|
+
if (collision !== undefined) {
|
|
18
|
+
throw Object.assign(new Error(`${protocol}: the declaration names the header "${inlineUntrusted(collision.first, 80)}" twice ("${inlineUntrusted(collision.second, 80)}" — header names are case-insensitive); ${principalHeader !== undefined && collision.second === principalHeader ? "the principal header is the engine's to send, so remove the static entry" : "keep one spelling"} — this peer is not dialed`), { code: "config.protocol_header_collision" });
|
|
19
|
+
}
|
|
20
|
+
if (principalHeader && principal !== undefined) {
|
|
21
|
+
if (/[\r\n]/.test(principal))
|
|
22
|
+
throw new Error(`${protocol}: principal must not contain CR/LF (header injection guard)`);
|
|
23
|
+
return Object.freeze({ ...own.headers, [principalHeader]: principal });
|
|
24
|
+
}
|
|
25
|
+
return own.headers === undefined ? undefined : Object.freeze(own.headers);
|
|
26
|
+
}
|
|
27
|
+
export function underDeclaredHeaders(defaults, declared) {
|
|
28
|
+
const owned = new Set(Object.keys(declared ?? {}).map((k) => k.toLowerCase()));
|
|
29
|
+
return { ...Object.fromEntries(Object.entries(defaults).filter(([k]) => !owned.has(k.toLowerCase()))), ...declared };
|
|
30
|
+
}
|
|
31
|
+
export function carriesDeclaredHeaders(headers) {
|
|
32
|
+
return headers !== undefined && Object.keys(headers).length > 0;
|
|
33
|
+
}
|
|
34
|
+
export function redirectPolicy(headers) {
|
|
35
|
+
return carriesDeclaredHeaders(headers) ? "manual" : "follow";
|
|
36
|
+
}
|
|
37
|
+
export function assertEndpointBound(protocol, peer, endpoint, declared, headers) {
|
|
38
|
+
if (!carriesDeclaredHeaders(headers))
|
|
39
|
+
return;
|
|
40
|
+
const origins = declared.map((u) => new URL(u).origin);
|
|
41
|
+
const at = new URL(endpoint).origin;
|
|
42
|
+
if (origins.includes(at))
|
|
43
|
+
return;
|
|
44
|
+
throw Object.assign(new Error(`${protocol}: peer "${inlineUntrusted(peer, 80)}" advertises the endpoint origin ${inlineUntrusted(at, 120)}, outside the origins its declaration names (${origins.map((o) => inlineUntrusted(o, 120)).join(", ")}); the declared headers travel only to those — declare that origin as the peer's url (and the card's location as cardUrl) or drop the headers`), { code: "config.protocol_endpoint_unbound" });
|
|
45
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value gates of the read-face list seats, shared by the two places that admit a value into them: the
|
|
3
|
+
* prepare door (every leg reads the seats it was handed) and the hot-swap door (`Runner.swapDeps`). One gate,
|
|
4
|
+
* so a value the swap admits is a value every later prepare admits.
|
|
5
|
+
*/
|
|
6
|
+
import { type ReadDenyEntry } from "../tools/fs/index.js";
|
|
7
|
+
/** Array-shape door for the read-face list seats (the tool-face door's twin).
|
|
8
|
+
* These seats are typed `readonly string[]` but arrive from wire JSON / plain-JS deployments with no
|
|
9
|
+
* runtime narrowing, and every consumer ITERATES them — `for (const dir of dirs)` and spread.
|
|
10
|
+
* A bare string satisfies both iteration protocols CHARACTER BY CHARACTER, so `"/tmp/x"` admits the
|
|
11
|
+
* single-character root `"/"` into a write-capable allowlist and a string deny "pattern" dissolves
|
|
12
|
+
* into meaningless one-character entries with zero refusal. Garbage must refuse loudly at the door,
|
|
13
|
+
* never be consumed letter by letter. */
|
|
14
|
+
export declare function assertStringArraySeat(value: unknown, seat: string, entryLaw: "strings" | "compiler-owned"): void;
|
|
15
|
+
/** The three DEPLOYMENT read-deny seats. */
|
|
16
|
+
export declare const READ_DENY_DEPLOYMENT_SEATS: readonly ["readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"];
|
|
17
|
+
export type ReadDenyDeploymentSeat = (typeof READ_DENY_DEPLOYMENT_SEATS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* The copy a read-deny seat keeps of its declared value — the one copy law of both doors that admit one (`new Runner`
|
|
20
|
+
* and the hot swap): a list becomes a frozen list of frozen entry copies (each entry's members read once), so a later
|
|
21
|
+
* write to the caller's list or entry objects cannot move the policy in force; anything else is kept as given, for the
|
|
22
|
+
* seat's value gate to refuse.
|
|
23
|
+
*/
|
|
24
|
+
export declare function copyReadDenySeat(value: unknown): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Screen one deployment read-deny seat's value with the laws a prepare applies to it (the array door and the
|
|
27
|
+
* one compiler's entry law for the additions; the built-in table's tier / row-name law for the other two) and
|
|
28
|
+
* return a copy; `undefined` stays `undefined` (no declaration).
|
|
29
|
+
*/
|
|
30
|
+
export declare function screenReadDenySeat(seat: ReadDenyDeploymentSeat, value: unknown): readonly ReadDenyEntry[] | readonly string[] | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { compileReadDeny, readDenyBuiltinConfigOf } from "../tools/fs/index.js";
|
|
2
|
+
export function assertStringArraySeat(value, seat, entryLaw) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return;
|
|
5
|
+
if (!Array.isArray(value)) {
|
|
6
|
+
const e = new Error(`${seat} must be an array or absent (got ${value === null ? "null" : typeof value}) — a non-array here would be iterated character by character, so it refuses loudly instead.`);
|
|
7
|
+
e.code = "config.read_face_seat";
|
|
8
|
+
throw e;
|
|
9
|
+
}
|
|
10
|
+
if (entryLaw !== "strings")
|
|
11
|
+
return;
|
|
12
|
+
const entries = value;
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
if (typeof entry !== "string") {
|
|
15
|
+
const e = new Error(`${seat} entries must be strings (got ${entry === null ? "null" : typeof entry}).`);
|
|
16
|
+
e.code = "config.read_face_seat";
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const READ_DENY_DEPLOYMENT_SEATS = Object.freeze(["readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"]);
|
|
22
|
+
export function copyReadDenySeat(value) {
|
|
23
|
+
if (!Array.isArray(value))
|
|
24
|
+
return value;
|
|
25
|
+
return Object.freeze(value.map((e) => (e !== null && typeof e === "object" ? Object.freeze({ ...e }) : e)));
|
|
26
|
+
}
|
|
27
|
+
export function screenReadDenySeat(seat, value) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (seat === "readDenyPatterns") {
|
|
31
|
+
assertStringArraySeat(value, `RunnerDeps.${seat}`, "compiler-owned");
|
|
32
|
+
const entries = copyReadDenySeat(value);
|
|
33
|
+
compileReadDeny(entries, seat, { tiers: [] });
|
|
34
|
+
return entries;
|
|
35
|
+
}
|
|
36
|
+
const config = readDenyBuiltinConfigOf(seat === "readDenyBuiltinTiers" ? { tiers: value } : { exclude: value });
|
|
37
|
+
return Object.freeze([...((seat === "readDenyBuiltinTiers" ? config.tiers : config.exclude) ?? [])]);
|
|
38
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The redaction pass engine every egress tier runs on, and the core bracket RENDERER of the vocabulary's answer.
|
|
3
|
+
* Which text is a credential is the vocabulary's answer (credential-vocabulary.ts); which passes a text meets is
|
|
4
|
+
* its tier's (untrusted-egress.ts).
|
|
5
|
+
*/
|
|
6
|
+
import type { RedactionConfidence, RedactionSource, ValueFamilyRow } from "./credential-vocabulary.js";
|
|
7
|
+
export type { RedactionConfidence, RedactionSource } from "./credential-vocabulary.js";
|
|
8
|
+
/** One redaction hit: what was detected, where (ORIGINAL-input coordinates), how confidently, the marker
|
|
9
|
+
* literal written in its place, which rule table minted it, and — present when a vocabulary row minted it — the
|
|
10
|
+
* value family (the row's stable id; a host-leak hit such as a URL or a path names none). */
|
|
11
|
+
export interface RedactionFinding {
|
|
12
|
+
kind: string;
|
|
13
|
+
span: readonly [number, number];
|
|
14
|
+
confidence: RedactionConfidence;
|
|
15
|
+
marker: string;
|
|
16
|
+
source: RedactionSource;
|
|
17
|
+
family?: string;
|
|
18
|
+
}
|
|
19
|
+
/** The minimum a finding must declare to be summarizable. Both text findings ({@link RedactionFinding})
|
|
20
|
+
* and the env arm's entry findings satisfy it, so {@link summarizeRedactions} is ONE helper rather than
|
|
21
|
+
* one per material kind. */
|
|
22
|
+
export interface SummarizableFinding {
|
|
23
|
+
kind: string;
|
|
24
|
+
confidence: RedactionConfidence;
|
|
25
|
+
}
|
|
26
|
+
/** ANG-3 (envelope declaration) — render findings as ONE human-readable line, e.g.
|
|
27
|
+
* `"3 redactions: prefixed-token (high) x1, url (medium) x1, fs-path (low) x1"`, so an upper layer can
|
|
28
|
+
* declare to its consumer how much was removed and how confidently.
|
|
29
|
+
*
|
|
30
|
+
* PURE, and deliberately only that: it reads the array, returns a string, and injects nothing anywhere.
|
|
31
|
+
* Attaching this line to an output is the caller's decision — a redaction pipeline that silently
|
|
32
|
+
* appended prose to the very text it was asked to bound would break both the size contract
|
|
33
|
+
* (`boundedString`) and the byte-invariance the whole channel is built on.
|
|
34
|
+
*
|
|
35
|
+
* Grouping is by kind AND confidence together: collapsing on kind alone would report whichever
|
|
36
|
+
* confidence happened to arrive first for the rest of that group, which is exactly the silent
|
|
37
|
+
* mis-statement ANG-4 exists to prevent. */
|
|
38
|
+
export declare function summarizeRedactions(findings: readonly SummarizableFinding[]): string;
|
|
39
|
+
/** The optional collector every redaction entry point accepts. `preexistingMarkers` (ANG-11) counts
|
|
40
|
+
* marker-shaped literals ALREADY present in the input before any pass ran — a nonzero value means the
|
|
41
|
+
* text arrived pre-redacted or carries caller-declared markers, either way "marker present" is not proof this
|
|
42
|
+
* pipeline ran. Caller-owned: pass `{ findings: [] }` and read it back after the call. */
|
|
43
|
+
export interface RedactionReport {
|
|
44
|
+
findings: RedactionFinding[];
|
|
45
|
+
preexistingMarkers?: number;
|
|
46
|
+
}
|
|
47
|
+
/** A declared redaction pass — the unit {@link runRedactionPasses} executes and reports on. */
|
|
48
|
+
export interface RedactionPass {
|
|
49
|
+
kind: string;
|
|
50
|
+
confidence: RedactionConfidence;
|
|
51
|
+
marker: string;
|
|
52
|
+
/** The rule table that owns this pass's rule — copied verbatim onto every finding it mints (ANG-11), whoever
|
|
53
|
+
* runs the pass. */
|
|
54
|
+
source: RedactionSource;
|
|
55
|
+
re: RegExp;
|
|
56
|
+
/** A literal replacement (only `$1` substitution is used by these passes) or a replacer fn. */
|
|
57
|
+
replace: string | ((match: string, ...groups: string[]) => string);
|
|
58
|
+
/** The value family a hit belongs to: one id, or — for a pass joining several rows — the id of the row that
|
|
59
|
+
* matches at the hit's offset in the pass input. Absent = the pass is no vocabulary row. */
|
|
60
|
+
family?: string | ((input: string, at: number) => string);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Run the declared passes in order, collecting findings (original-input spans) when a report is given.
|
|
64
|
+
*
|
|
65
|
+
* FORMAT CHARACTERS (#532): the passes scan a VIEW of the input with every `\p{Cf}` removed, so a
|
|
66
|
+
* credential split by a zero-width character is one token to the scanner, not two fragments of which
|
|
67
|
+
* only the first (or neither) matches. The OUTPUT keeps every format character that sits OUTSIDE a
|
|
68
|
+
* replaced span — a joiner in an emoji sequence, a non-joiner in Persian text, a soft hyphen in a word
|
|
69
|
+
* — and drops only those INSIDE the span the marker replaces (they were part of the secret). A
|
|
70
|
+
* format-free input takes the exact historical path (byte-identical output). The view's replacements and
|
|
71
|
+
* every boundary scan's are taken as disjoint spans of their input (a later pass that matched across an
|
|
72
|
+
* earlier pass's inserted text is rendered from the removals instead), so the spans always map back
|
|
73
|
+
* exactly and no scan is skipped.
|
|
74
|
+
*/
|
|
75
|
+
export declare function runRedactionPasses(input: string, passes: readonly RedactionPass[], report?: RedactionReport): string;
|
|
76
|
+
/**
|
|
77
|
+
* The egress view rendered as passes, in the vocabulary's order: one pass per group (a group of several rows is ONE
|
|
78
|
+
* leftmost alternation, its hits attributed to the row that matches at the hit's offset). A row whose wide form
|
|
79
|
+
* captures a label keeps it ahead of the marker; `replacers` renders a kind whose marker placement is a policy (the
|
|
80
|
+
* URL userinfo).
|
|
81
|
+
*/
|
|
82
|
+
export declare function egressPasses(groups: ReadonlyArray<readonly ValueFamilyRow[]>, replacers?: Readonly<Record<string, (match: string, ...groups: string[]) => string>>): RedactionPass[];
|