@sema-agent/core 7.34.0 → 7.35.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 +123 -3
- package/dist/brain/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/ask-origin.d.ts +34 -6
- package/dist/core/ask-origin.js +24 -23
- package/dist/core/ask-question.js +2 -1
- package/dist/core/checkpoint-store.d.ts +4 -0
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +5 -4
- package/dist/core/governance-codes.d.ts +17 -1
- package/dist/core/governance-codes.js +11 -1
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +3 -0
- package/dist/core/mode-policy.js +5 -0
- package/dist/core/permission-rule-model.d.ts +4 -0
- package/dist/core/permission-rule-model.js +4 -0
- package/dist/core/permission-rules.js +21 -1
- package/dist/core/persisted-rule-arms.d.ts +4 -1
- package/dist/core/persisted-rule-arms.js +4 -0
- package/dist/core/retired-keys.js +1 -1
- package/dist/core/runner/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- package/dist/core/runner/contracts.d.ts +25 -3
- package/dist/core/runner/denial-limit-arms.d.ts +6 -1
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +14 -11
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -3
- package/dist/core/runner/prepare-gate-stations.js +15 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +10 -4
- package/dist/core/runner/prepare-park-ask.d.ts +4 -2
- package/dist/core/runner/prepare-park-ask.js +5 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -2
- package/dist/core/runner/prepare-policy-chain.js +171 -175
- package/dist/core/runner/prepare-task.js +7 -4
- package/dist/core/runner/run-brain-sinks.js +3 -1
- package/dist/core/runner/run-leg.js +5 -3
- package/dist/core/runner/run-notification-lane.d.ts +3 -0
- package/dist/core/runner/run-notification-lane.js +2 -1
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +5 -11
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- package/dist/core/secret-env.d.ts +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.js +2 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/tool-policy.d.ts +31 -6
- package/dist/core/tool-policy.js +35 -16
- package/dist/core/tool-registry.d.ts +36 -0
- package/dist/core/tool-registry.js +22 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/trace.d.ts +11 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -1
- package/dist/engine/execution-env/node-execution-env.js +5 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/stores/file/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- package/dist/tools/fs/file-read-check.d.ts +2 -1
- package/dist/tools/fs/file-read-check.js +15 -7
- package/dist/tools/fs/shell-read-check.d.ts +10 -2
- package/dist/tools/fs/shell-read-check.js +12 -8
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +15 -1
- package/wire-changes/core@7.35.0.json +584 -0
|
@@ -513,6 +513,7 @@ export async function runLeg(input) {
|
|
|
513
513
|
}
|
|
514
514
|
prepared.abortController.abort();
|
|
515
515
|
prepared.releaseSignal();
|
|
516
|
+
prepared.endModeCeiling();
|
|
516
517
|
strandedHumanAnswers = prepared.settleContentAskBindings();
|
|
517
518
|
try {
|
|
518
519
|
prepared.harness.recoverUndrainedEngineNotes();
|
|
@@ -633,7 +634,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
633
634
|
}
|
|
634
635
|
if (prepared.permissionRuleOrg !== undefined) {
|
|
635
636
|
const orgVerdict = prepared.permissionRuleOrg
|
|
636
|
-
.adjudicate({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId })
|
|
637
|
+
.adjudicate({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)) })
|
|
637
638
|
.catch(() => ({ status: "unavailable", disclosures: ["the org adjudication face threw on resume"] }));
|
|
638
639
|
let orgWaitCancelled = false;
|
|
639
640
|
const org = await settleOrgVerdictWithin(orgVerdict, { status: "unavailable", disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`] }, {
|
|
@@ -716,7 +717,8 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
716
717
|
}
|
|
717
718
|
if (prepared.shellReadCheck !== undefined) {
|
|
718
719
|
const edited = outcome.updatedInput !== undefined && !deepJsonEqual(outcome.updatedInput, pendingAction.args);
|
|
719
|
-
const
|
|
720
|
+
const readCall = { toolName: pendingAction.toolName, ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)) };
|
|
721
|
+
const judged = prepared.shellReadCheck.judge(readCall, resolvedArgs, prepared.cwdRef?.current, prepared.abortController.signal, {
|
|
720
722
|
toolCallId: pendingAction.toolCallId,
|
|
721
723
|
...(edited ? { seat: "rejudge" } : {}),
|
|
722
724
|
});
|
|
@@ -728,7 +730,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
728
730
|
}
|
|
729
731
|
catch {
|
|
730
732
|
answered = false;
|
|
731
|
-
verdict = prepared.shellReadCheck.unanswered(
|
|
733
|
+
verdict = prepared.shellReadCheck.unanswered(readCall, "the read station could not answer on the resumed leg");
|
|
732
734
|
}
|
|
733
735
|
if (!answered || verdict.disposition === "refuse" || (edited && verdict.disposition !== "vouch")) {
|
|
734
736
|
const readDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${verdict.sentence ?? "the read boundary does not vouch for what it reads on this leg"} This approval is spent — the call has to be re-issued and approved again.`, prepared.reminderMark);
|
|
@@ -4,6 +4,7 @@ import type { SessionStore } from "../session.js";
|
|
|
4
4
|
import { type NotificationInjectionOutcome, type PendingSessionNotifications, type SystemInjectionPriority, type TaskNotificationPayload } from "../task-notification.js";
|
|
5
5
|
import type { RunnerDeps, TaskEvent, TaskSpec } from "../types.js";
|
|
6
6
|
import type { SessionReadFileStates } from "./session-read-file-states.js";
|
|
7
|
+
import type { SessionModeSeats } from "./session-mode-seats.js";
|
|
7
8
|
import type { NotificationLaneBindings, Prepared, PrepareTaskFn, ResumeRun, RunInternals, RunnerDepsSeat, RunnerSelfSeat, TaskIdRef } from "./contracts.js";
|
|
8
9
|
export interface RunNotificationLaneInput<R> {
|
|
9
10
|
/** borrowed-readonly — the task spec: prepare's input, and the principal the peer scope is minted from. */
|
|
@@ -26,6 +27,8 @@ export interface RunNotificationLaneInput<R> {
|
|
|
26
27
|
sessions: SessionStore;
|
|
27
28
|
/** borrowed-mutable — the Runner's own per-session read-state seat, ALWAYS this Runner's (a caller-supplied one is overridden). */
|
|
28
29
|
sessionReadStates: SessionReadFileStates;
|
|
30
|
+
/** borrowed-mutable — the Runner's per-session permission-mode seats, ALWAYS this Runner's (a caller-supplied one is overridden). */
|
|
31
|
+
sessionModeSeats: SessionModeSeats;
|
|
29
32
|
/** borrowed-mutable — the Runner's per-session parked-notification store: the torn-down lane and the escalation pend here. */
|
|
30
33
|
pendingSessionNotifications: PendingSessionNotifications;
|
|
31
34
|
/** borrowed-readonly — the orchestrator's entry (a lane may not name the orchestrator; the driver hands it in), as the ONE
|
|
@@ -8,7 +8,7 @@ export function runNotificationLane(input) {
|
|
|
8
8
|
return obligations.run(() => runNotificationLaneAs(input, obligations));
|
|
9
9
|
}
|
|
10
10
|
async function runNotificationLaneAs(input, obligations) {
|
|
11
|
-
const { spec, queue, resume, internals, taskIdRef, runner, self, sessions, sessionReadStates, pendingSessionNotifications, prepareTask, next } = input;
|
|
11
|
+
const { spec, queue, resume, internals, taskIdRef, runner, self, sessions, sessionReadStates, sessionModeSeats, pendingSessionNotifications, prepareTask, next } = input;
|
|
12
12
|
const prepareResume = resume
|
|
13
13
|
? {
|
|
14
14
|
leafId: resume.cp.leafId,
|
|
@@ -118,6 +118,7 @@ async function runNotificationLaneAs(input, obligations) {
|
|
|
118
118
|
peerInboundChainRef,
|
|
119
119
|
detachHub,
|
|
120
120
|
sessionReadStates,
|
|
121
|
+
sessionModeSeats,
|
|
121
122
|
onTaskNotification: (notification, opts) => {
|
|
122
123
|
try {
|
|
123
124
|
upstreamTaskNotification?.(notification, opts);
|
|
@@ -22,6 +22,7 @@ export declare class Runner implements RunnerSelfSeat {
|
|
|
22
22
|
* authority: a durable resume's checkpoint seed wins on its leg, and a miss (cross-process,
|
|
23
23
|
* evicted) falls back to the transcript-replay rung. See {@link SessionReadFileStates}. */
|
|
24
24
|
private readonly sessionReadStates;
|
|
25
|
+
private readonly sessionModeSeats;
|
|
25
26
|
/** task notifications born BETWEEN turns (run torn down / harness already idle), parked
|
|
26
27
|
* per session and drained into the session's next run at its first boundary. Runner-lived (outlives any
|
|
27
28
|
* single run, like the registry handles that produce into it); bounded + drop-disclosing, see
|
|
@@ -16,9 +16,10 @@ import { TtlSessionStore } from "../session-store.js";
|
|
|
16
16
|
import { errorCodeOf } from "./assemble-result.js";
|
|
17
17
|
import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
|
|
18
18
|
import { SessionReadFileStates } from "./session-read-file-states.js";
|
|
19
|
+
import { SessionModeSeats } from "./session-mode-seats.js";
|
|
19
20
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
20
21
|
import { PAUSE_REGISTRY } from "../pause-registry.js";
|
|
21
|
-
import {
|
|
22
|
+
import { wrapToolPolicy } from "../tool-policy.js";
|
|
22
23
|
import { PendingSessionNotifications } from "../task-notification.js";
|
|
23
24
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
24
25
|
import { assertContextSettingsValue, assertContextSettingsWords, contextOverlayFor } from "../context-settings.js";
|
|
@@ -65,6 +66,7 @@ export class Runner {
|
|
|
65
66
|
sessions;
|
|
66
67
|
sessionLocks = new Map();
|
|
67
68
|
sessionReadStates = new SessionReadFileStates();
|
|
69
|
+
sessionModeSeats = new SessionModeSeats();
|
|
68
70
|
pendingSessionNotifications = new PendingSessionNotifications();
|
|
69
71
|
parentConstraintRegistry = new Map();
|
|
70
72
|
suspendedEnvReaps = new Map();
|
|
@@ -174,15 +176,7 @@ export class Runner {
|
|
|
174
176
|
return {
|
|
175
177
|
...(deps.toolPolicy !== undefined
|
|
176
178
|
? {
|
|
177
|
-
toolPolicy: (() =>
|
|
178
|
-
const sets = toolPolicyNameSets(deps.toolPolicy);
|
|
179
|
-
const wrapped = {
|
|
180
|
-
check: (req, signal) => deps.toolPolicy.check(req, signal),
|
|
181
|
-
};
|
|
182
|
-
if (sets.length > 0)
|
|
183
|
-
wrapped.nameSets = sets;
|
|
184
|
-
return wrapped;
|
|
185
|
-
})(),
|
|
179
|
+
toolPolicy: wrapToolPolicy(deps.toolPolicy, (req, signal) => deps.toolPolicy.check(req, signal)),
|
|
186
180
|
}
|
|
187
181
|
: {}),
|
|
188
182
|
...(hooks !== undefined ? { hooks } : {}),
|
|
@@ -445,7 +439,7 @@ export class Runner {
|
|
|
445
439
|
const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
|
|
446
440
|
return runNotificationLane({
|
|
447
441
|
spec, queue, resume, internals, taskIdRef,
|
|
448
|
-
runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, pendingSessionNotifications: this.pendingSessionNotifications,
|
|
442
|
+
runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, sessionModeSeats: this.sessionModeSeats, pendingSessionNotifications: this.pendingSessionNotifications,
|
|
449
443
|
prepareTask,
|
|
450
444
|
next: ({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }) => {
|
|
451
445
|
const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, internalsNotifier, undrainedUserAtEnd, userInputLostAtEnd } = runIdentityWiring({
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Runner's per-session PERMISSION-MODE seats: the mode a session's CURRENT leg runs under, as the object a
|
|
3
|
+
* child's ceiling falls back to once the leg that spawned it has ended. A child's ceiling used to be the controller
|
|
4
|
+
* of the leg that spawned it for the child's whole life, so a session-scoped background child kept that leg's mode
|
|
5
|
+
* after the leg ended — the next leg of the same session, started in `plan`, did not reach it. The chain now carries
|
|
6
|
+
* a LEG ceiling: the spawning leg's live mode while that leg runs, and the session's seat after it. Each leg binds
|
|
7
|
+
* itself to its session's seat when its prepare commits, so the seat answers the most recently committed leg; a leg
|
|
8
|
+
* that is still running keeps answering its own children, whatever another leg of the session commits meanwhile.
|
|
9
|
+
*/
|
|
10
|
+
import type { PermissionMode } from "../ask-origin.js";
|
|
11
|
+
import type { PermissionModeReceipt } from "../mode-policy.js";
|
|
12
|
+
/** A child leg whose ceiling is a leg ceiling: its receipt is read before a ceiling move and re-judged after it. */
|
|
13
|
+
export interface ModeCeilingHolder {
|
|
14
|
+
receipt(): PermissionModeReceipt;
|
|
15
|
+
/** The ceiling's source moved; `before` is this holder's receipt under the previous source. */
|
|
16
|
+
ceilingRebound(before: PermissionModeReceipt): void;
|
|
17
|
+
}
|
|
18
|
+
/** One leg's ceiling, as the chain carries it (`InheritedGate.permissionModeCeiling`). */
|
|
19
|
+
export interface LegModeCeiling {
|
|
20
|
+
/** The spawning leg's live mode while it runs; the session's current leg's after it (`default` before any leg of
|
|
21
|
+
* the session committed — no child can read it then). */
|
|
22
|
+
readonly current: PermissionMode;
|
|
23
|
+
readonly sessionId: string;
|
|
24
|
+
/** A child leg holds the ceiling until `signal` aborts (its own leg's end): each move of the ceiling re-judges it. */
|
|
25
|
+
hold(holder: ModeCeilingHolder, signal: AbortSignal): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The seats of one Runner, keyed by session id and held WEAKLY: a seat lives while a leg ceiling holds it, and the
|
|
29
|
+
* next leg of the session finds the same object as long as any child still reads it. In-process only — a session
|
|
30
|
+
* continued on another Runner instance or in another process binds that Runner's seat.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SessionModeSeats {
|
|
33
|
+
private readonly seats;
|
|
34
|
+
private readonly reaper;
|
|
35
|
+
private seatOf;
|
|
36
|
+
/**
|
|
37
|
+
* A leg's ceiling over its session's seat, the leg's commit (once its prepare succeeded: the leg becomes the
|
|
38
|
+
* session's current leg) and its end (once the leg has STOPPED — its loop settled, not merely cancelled: a cancelled
|
|
39
|
+
* leg whose tool is still pending keeps answering its own children).
|
|
40
|
+
*/
|
|
41
|
+
legCeiling(sessionId: string, leg: {
|
|
42
|
+
readonly current: PermissionMode;
|
|
43
|
+
}): {
|
|
44
|
+
ceiling: LegModeCeiling;
|
|
45
|
+
commit: () => void;
|
|
46
|
+
end: () => void;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
class Seat {
|
|
2
|
+
sessionId;
|
|
3
|
+
leg;
|
|
4
|
+
holders = new Set();
|
|
5
|
+
constructor(sessionId) {
|
|
6
|
+
this.sessionId = sessionId;
|
|
7
|
+
}
|
|
8
|
+
get current() {
|
|
9
|
+
return this.leg?.current ?? "default";
|
|
10
|
+
}
|
|
11
|
+
addHolder(holder, via, signal) {
|
|
12
|
+
if (signal.aborted)
|
|
13
|
+
return;
|
|
14
|
+
const entry = { holder, via };
|
|
15
|
+
this.holders.add(entry);
|
|
16
|
+
signal.addEventListener("abort", () => this.holders.delete(entry), { once: true });
|
|
17
|
+
}
|
|
18
|
+
moveCeiling(affected, move) {
|
|
19
|
+
const before = [...this.holders].filter((e) => affected(e.via)).map((e) => [e.holder, e.holder.receipt()]);
|
|
20
|
+
move();
|
|
21
|
+
for (const [holder, receipt] of before)
|
|
22
|
+
holder.ceilingRebound(receipt);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
class LegCeiling {
|
|
26
|
+
leg;
|
|
27
|
+
seat;
|
|
28
|
+
live = true;
|
|
29
|
+
constructor(leg, seat) {
|
|
30
|
+
this.leg = leg;
|
|
31
|
+
this.seat = seat;
|
|
32
|
+
}
|
|
33
|
+
get sessionId() {
|
|
34
|
+
return this.seat.sessionId;
|
|
35
|
+
}
|
|
36
|
+
get current() {
|
|
37
|
+
return this.live ? this.leg.current : this.seat.current;
|
|
38
|
+
}
|
|
39
|
+
hold(holder, signal) {
|
|
40
|
+
this.seat.addHolder(holder, this, signal);
|
|
41
|
+
}
|
|
42
|
+
commit() {
|
|
43
|
+
if (this.seat.leg === this.leg)
|
|
44
|
+
return;
|
|
45
|
+
this.seat.moveCeiling((via) => !via.live, () => (this.seat.leg = this.leg));
|
|
46
|
+
}
|
|
47
|
+
end() {
|
|
48
|
+
if (!this.live)
|
|
49
|
+
return;
|
|
50
|
+
this.seat.moveCeiling((via) => via === this, () => (this.live = false));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class SessionModeSeats {
|
|
54
|
+
seats = new Map();
|
|
55
|
+
reaper = new FinalizationRegistry(({ sessionId, ref }) => {
|
|
56
|
+
if (this.seats.get(sessionId) === ref)
|
|
57
|
+
this.seats.delete(sessionId);
|
|
58
|
+
});
|
|
59
|
+
seatOf(sessionId) {
|
|
60
|
+
const live = this.seats.get(sessionId)?.deref();
|
|
61
|
+
if (live !== undefined)
|
|
62
|
+
return live;
|
|
63
|
+
const seat = new Seat(sessionId);
|
|
64
|
+
const ref = new WeakRef(seat);
|
|
65
|
+
this.seats.set(sessionId, ref);
|
|
66
|
+
this.reaper.register(seat, { sessionId, ref });
|
|
67
|
+
return seat;
|
|
68
|
+
}
|
|
69
|
+
legCeiling(sessionId, leg) {
|
|
70
|
+
const ceiling = new LegCeiling(leg, this.seatOf(sessionId));
|
|
71
|
+
return { ceiling, commit: () => ceiling.commit(), end: () => ceiling.end() };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -2,6 +2,7 @@ import { canonicalizeTarget, isWinFormPath, pathFamilyOf, writeTargetPath } from
|
|
|
2
2
|
import { createCoarseCommandNamePolicy, namespacedCoveringEntries, namespacedCoveringHit } from "../tool-policy.js";
|
|
3
3
|
import { continuesLineage } from "../session-policy-store.js";
|
|
4
4
|
import { thrownText } from "../thrown-value.js";
|
|
5
|
+
import { toolNameSetHas } from "../tool-roster.js";
|
|
5
6
|
import { ruleDenialSentence } from "../rule-denial-sentence.js";
|
|
6
7
|
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
7
8
|
import { declaredPathTargetOf, isProtectedWrite, pathConfinableWriteToolNames, skillScopeWriteToolNames } from "../tool-registry.js";
|
|
@@ -136,9 +137,9 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
136
137
|
async check(req, signal) {
|
|
137
138
|
const { toolDeny, toolAllow, toolDenyCovering, toolAllowCovering, cmdPolicy, allowDirs } = compile(await readRules(req.toolCallId, signal));
|
|
138
139
|
const toolName = req.toolName;
|
|
139
|
-
if (toolDeny
|
|
140
|
+
if (toolNameSetHas(toolDeny, req) || namespacedCoveringHit(toolDenyCovering, toolName))
|
|
140
141
|
return deny(req.toolName);
|
|
141
|
-
if (toolAllow && !toolAllow
|
|
142
|
+
if (toolAllow && !toolNameSetHas(toolAllow, req) && !namespacedCoveringHit(toolAllowCovering, toolName)) {
|
|
142
143
|
return deny(req.toolName, "it is not in the session-rule allowlist");
|
|
143
144
|
}
|
|
144
145
|
if (cmdPolicy) {
|
|
@@ -1,77 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The environment a spawned child inherits: which of this process's variables it must not see.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* downstream adapter composes its own child env; only the secret PATTERN is shared with it.
|
|
4
|
+
* A child the engine spawns may run what the MODEL chose (a tool shell, a gate over model-written code, a language
|
|
5
|
+
* server). Under the `"scrub"` policy (the default) it inherits every variable but those the judgment below names —
|
|
6
|
+
* deleted whole, never rewritten; `"all"` and a `string[]` allow list are the caller's explicit alternatives
|
|
7
|
+
* ({@link inheritedHostEnv}). One judgment per variable ({@link classifySecretEnvEntry}), the first rule that answers:
|
|
8
|
+
* 1. exempt — git's environment config and the proxy variables, handed on as written ({@link isExemptEnvName});
|
|
9
|
+
* 2. `exact-name` — a known credential name ({@link CREDENTIAL_ENV_NAMES});
|
|
10
|
+
* 3. `prefix-rule` — the OpenTelemetry settings ({@link OTEL_ENV_RE});
|
|
11
|
+
* 4. `namespace-rule` — a credential word as any segment of a name in the engine's own `SEMA_` namespace;
|
|
12
|
+
* 5. `name-rule` — a credential-shaped name ({@link nameIsCredentialShaped}) off {@link NON_SECRET_ENV_EXACT_NAMES};
|
|
13
|
+
* 6. `value-rule` — a value that carries a credential ({@link valueCarriesCredential}).
|
|
14
|
+
* {@link isSecretEnvKey} and {@link scrubSecretEnv} both answer with this one judgment. The exempt families, the
|
|
15
|
+
* credential names, the name shape and the value predicate follow the reference implementation's subprocess-env scrub
|
|
16
|
+
* as written, three ways stricter: plural `TOKENS` is a credential word unless a count word touches it, the GitHub
|
|
17
|
+
* token names are not held back from the name shape, and the name shape always runs.
|
|
19
18
|
*/
|
|
20
19
|
import type { RedactionConfidence, RedactionSource } from "./arg-summary.js";
|
|
21
20
|
import { type EngineNotice } from "./engine-notice.js";
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly value?: string;
|
|
27
|
-
}
|
|
28
|
-
export declare function readGitConfigParameters(text: string): GitConfigEntry[] | undefined;
|
|
29
|
-
/** Which rule classified a key as secret-shaped — the env arm's `kind` vocabulary. */
|
|
30
|
-
export type SecretEnvFindingKind = "suffix-rule" | "namespace-rule" | "exact-name" | "family-rule";
|
|
31
|
-
/** One env entry the `scrub` policy withheld — dropped, or, for a member of git's config record, handed on holding a
|
|
32
|
-
* placeholder half: the key (the VALUE is never carried — this record is meant to be loggable), the rule that
|
|
33
|
-
* classified it, how strong that claim is, and the minting module. */
|
|
21
|
+
/** Which rule withheld an entry. */
|
|
22
|
+
export type SecretEnvFindingKind = "exact-name" | "name-rule" | "namespace-rule" | "prefix-rule" | "value-rule";
|
|
23
|
+
/** One env entry the `"scrub"` policy withheld (deleted): its name — never its value, so the record is loggable —, the
|
|
24
|
+
* rule that withheld it, how strong that claim is, and the minting module. */
|
|
34
25
|
export interface SecretEnvFinding {
|
|
35
26
|
key: string;
|
|
36
27
|
kind: SecretEnvFindingKind;
|
|
37
28
|
confidence: RedactionConfidence;
|
|
38
29
|
source: RedactionSource;
|
|
39
30
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
export declare function classifyExactEnvName(key: string, credentialNames: ReadonlySet<string>, exemptNames: ReadonlySet<string>): ExactEnvNameVerdict;
|
|
48
|
-
/** Is `key` a secret-pattern env var (one the fail-closed `scrub` policy drops before spawning a shell)? Pass the
|
|
49
|
-
* entry's `value` — and the environment it sits in as `env`, and the child's `platform` — to get exactly the scrub's
|
|
50
|
-
* answer for that entry: a git config member is judged with its index group, which only the environment holds; without
|
|
51
|
-
* a value the name alone decides (a `GIT_CONFIG_KEY_<i>` then reads as secret — the name cannot clear itself).
|
|
52
|
-
* `platform` is as for {@link scrubSecretEnv}. */
|
|
53
|
-
export declare function isSecretEnvKey(key: string, value?: string, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
54
|
-
/** Drop every secret-pattern key (see {@link isSecretEnvKey}) from an env object — the fail-closed `scrub`
|
|
55
|
-
* policy. git's indexed record is withheld entry by entry and never renumbered ({@link readGitEnvConfigRecord}): a
|
|
56
|
-
* withheld entry git reads keeps its names, each holding its half of the placeholder entry `sema.withheld` (key) / an
|
|
57
|
-
* empty string (value), so `GIT_CONFIG_COUNT` and every other index reach the child as the host wrote them. `findings`
|
|
58
|
-
* is an OPTIONAL caller-owned collector: pass an array to learn which entries were withheld (by name) and why, read it
|
|
59
|
-
* back after the call. A finding's name is absent from the result unless it is such a record member, which is present
|
|
60
|
-
* holding the placeholder half. `platform` is the platform the child runs on — where git reads this environment:
|
|
61
|
-
* Windows reads env names case-insensitively, so there any spelling of a record member is that member; elsewhere git
|
|
62
|
-
* looks up the capitalised names only and every other spelling is judged on its own, apart from the record. It defaults
|
|
63
|
-
* to this process's own platform, the answer for a child spawned on this host; a host whose child runs elsewhere (a
|
|
64
|
-
* remote or sandboxed execution environment) passes that environment's platform. */
|
|
65
|
-
export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[], platform?: NodeJS.Platform): NodeJS.ProcessEnv;
|
|
31
|
+
/** Does the `"scrub"` policy withhold `key`? With `value`, exactly the answer {@link scrubSecretEnv} gives for that
|
|
32
|
+
* entry; without it, the answer for its name alone (the value rule then has nothing to read). */
|
|
33
|
+
export declare function isSecretEnvKey(key: string, value?: string): boolean;
|
|
34
|
+
/** The `"scrub"` policy: `env` without every entry {@link isSecretEnvKey} withholds, the rest as written. `findings` is
|
|
35
|
+
* an optional caller-owned collector: each withheld entry is pushed onto it (name, rule, confidence), none of them
|
|
36
|
+
* present in the result. `env` itself is not modified. */
|
|
37
|
+
export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
|
|
66
38
|
/** How much of this process's environment a child spawned on this host inherits — the `inheritEnv` value. */
|
|
67
39
|
export type InheritEnvPolicy = "all" | "scrub" | string[];
|
|
68
40
|
/**
|
|
69
41
|
* The environment a child spawned ON THIS HOST inherits, by an {@link InheritEnvPolicy}: `"scrub"` (and absent) is
|
|
70
|
-
* this process's environment through {@link scrubSecretEnv}
|
|
42
|
+
* this process's environment through {@link scrubSecretEnv}; `"all"` is this process's environment
|
|
71
43
|
* verbatim (the explicit opt-out); a `string[]` is exactly the named variables that are set, verbatim — a strict allow
|
|
72
44
|
* list, so a name it leaves out (`PATH` included) is not inherited. `findings` collects what the scrub withheld, and
|
|
73
|
-
* only the `"scrub"` arm adds to it. Any other value reads as `"scrub"
|
|
74
|
-
*
|
|
45
|
+
* only the `"scrub"` arm adds to it. Any other value reads as `"scrub"`; both spawn sites that take a policy — the
|
|
46
|
+
* tool shell's `NodeExecutionEnv` (at construction) and the oracle gate (per gate) — refuse a malformed one through
|
|
47
|
+
* {@link inheritEnvPolicyProblem} before it gets here.
|
|
75
48
|
*/
|
|
76
49
|
export declare function inheritedHostEnv(policy: InheritEnvPolicy | undefined, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
|
|
77
50
|
/** Why `value` is not an {@link InheritEnvPolicy} (absent counts as one — the scrub), or `undefined` when it is. */
|
|
@@ -86,4 +59,3 @@ export declare function __resetSecretEnvScrubNoticeAnnouncement(): void;
|
|
|
86
59
|
* findings array is a no-op.
|
|
87
60
|
*/
|
|
88
61
|
export declare function announceSecretEnvScrub(onNotice: ((notice: EngineNotice) => void) | undefined, findings: readonly SecretEnvFinding[]): void;
|
|
89
|
-
export {};
|