@sema-agent/core 7.10.0 → 7.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/dist/agents/child-model-seat.d.ts +45 -18
- package/dist/agents/child-model-seat.js +12 -8
- package/dist/agents/subagent.js +6 -5
- package/dist/agents/teacher.js +2 -2
- package/dist/core/auto-mode-defaults.d.ts +19 -3
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +24 -20
- package/dist/core/auto-mode.js +12 -12
- package/dist/core/gate-fold.js +1 -0
- package/dist/core/gate-lanes.d.ts +6 -1
- package/dist/core/gate-lanes.js +45 -18
- package/dist/core/governance-codes.d.ts +1 -0
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +13 -0
- package/dist/core/permission-rule-model.d.ts +5 -3
- package/dist/core/permission-rule-model.js +7 -3
- package/dist/core/persisted-rule-arms.js +4 -3
- package/dist/core/read-only-shell-table.d.ts +87 -0
- package/dist/core/read-only-shell-table.js +485 -0
- package/dist/core/read-only-shell.d.ts +42 -0
- package/dist/core/read-only-shell.js +316 -0
- package/dist/core/roles.d.ts +3 -2
- package/dist/core/runner/contracts.d.ts +70 -0
- package/dist/core/runner/gate-exit.d.ts +5 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +38 -12
- package/dist/core/runner/prepare-gate-stations.js +9 -0
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/prepare-turn-wiring.js +1 -1
- package/dist/core/runner/runtask.js +34 -510
- package/dist/core/runner/stream-halt-verbs.d.ts +38 -0
- package/dist/core/runner/stream-halt-verbs.js +82 -0
- package/dist/core/runner/stream-lifecycle-verbs.d.ts +34 -0
- package/dist/core/runner/stream-lifecycle-verbs.js +126 -0
- package/dist/core/runner/stream-reap.d.ts +30 -0
- package/dist/core/runner/stream-reap.js +40 -0
- package/dist/core/runner/stream-settle-backstop.d.ts +38 -0
- package/dist/core/runner/stream-settle-backstop.js +113 -0
- package/dist/core/runner/stream-steer-verb.d.ts +30 -0
- package/dist/core/runner/stream-steer-verb.js +185 -0
- package/dist/core/shell-lexer.d.ts +18 -0
- package/dist/core/shell-lexer.js +17 -10
- package/dist/core/shell-wrapper-table.js +8 -5
- package/dist/core/tool-policy.d.ts +4 -1
- package/dist/core/tool-policy.js +1 -1
- package/dist/core/tools.d.ts +28 -7
- package/dist/core/tools.js +44 -4
- package/dist/core/trace.d.ts +15 -0
- package/dist/engine/harness/agent-harness.d.ts +3 -1
- package/dist/engine/harness/agent-harness.js +1 -1
- package/dist/engine/harness/types.d.ts +4 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/orchestration/run-workflow-tool.d.ts +5 -2
- package/dist/orchestration/run-workflow-tool.js +2 -1
- package/dist/orchestration/workflow-governance.d.ts +3 -2
- package/dist/orchestration/workflow-primitives.d.ts +4 -1
- package/dist/orchestration/workflow-primitives.js +1 -6
- package/dist/orchestration/workflow.d.ts +12 -4
- package/dist/orchestration/workflow.js +24 -7
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +5 -0
- package/dist/tools/fs/bash-readonly-classifier.js +1 -0
- package/dist/tools/fs/fs-bash.js +3 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +35 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { uuidv7 } from "../../internal/harness.js";
|
|
2
|
+
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
3
|
+
import { LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS } from "../checkpoint-store.js";
|
|
4
|
+
import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
|
|
5
|
+
import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
|
|
6
|
+
import { isSystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
7
|
+
import { deliverEngineNotice } from "../types.js";
|
|
8
|
+
import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
9
|
+
import { nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
|
|
10
|
+
export function streamSteerVerb(input) {
|
|
11
|
+
const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input;
|
|
12
|
+
let steerChain = Promise.resolve();
|
|
13
|
+
const steer = async (text, options) => {
|
|
14
|
+
const trusted = options?.trusted ? true : false;
|
|
15
|
+
if (trusted && sanitizeUntrustedText(text) !== text) {
|
|
16
|
+
throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content");
|
|
17
|
+
}
|
|
18
|
+
const inputId = options?.inputId;
|
|
19
|
+
if (inputId !== undefined) {
|
|
20
|
+
if (typeof inputId !== "string") {
|
|
21
|
+
throw steeringError("inputId must be a string when supplied", "steering.invalid_content");
|
|
22
|
+
}
|
|
23
|
+
if (inputId === "" || inputId.length > MAX_STEER_INPUT_ID_CHARS) {
|
|
24
|
+
throw steeringError(`inputId must be a non-empty string of at most ${MAX_STEER_INPUT_ID_CHARS} characters`, "steering.invalid_content");
|
|
25
|
+
}
|
|
26
|
+
if (inputId === LEGACY_PENDING_STEER_INPUT_ID) {
|
|
27
|
+
throw steeringError(`inputId "${LEGACY_PENDING_STEER_INPUT_ID}" is reserved for a pre-queue parked steer and cannot be supplied by a caller`, "steering.invalid_content");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const priorityIn = options?.priority;
|
|
31
|
+
if (priorityIn !== undefined && !isSystemInjectionPriority(priorityIn)) {
|
|
32
|
+
throw steeringError(`priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when supplied`, "steering.invalid_content");
|
|
33
|
+
}
|
|
34
|
+
const priority = priorityIn ?? "next";
|
|
35
|
+
const actorIn = options?.actor;
|
|
36
|
+
const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn);
|
|
37
|
+
const projected = projectHumanInput({ text, actor, source: "steer" });
|
|
38
|
+
const effectiveInputId = typeof inputId === "string" ? inputId : uuidv7();
|
|
39
|
+
const parkRecord = {
|
|
40
|
+
text,
|
|
41
|
+
trusted,
|
|
42
|
+
inputId: effectiveInputId,
|
|
43
|
+
...(priorityIn !== undefined ? { priority } : {}),
|
|
44
|
+
...(actor !== undefined ? { actor } : {}),
|
|
45
|
+
};
|
|
46
|
+
let payload;
|
|
47
|
+
let mintsAFrame;
|
|
48
|
+
let replay;
|
|
49
|
+
const noteAccepted = (h) => {
|
|
50
|
+
if (!mintsAFrame)
|
|
51
|
+
return;
|
|
52
|
+
if (typeof inputId === "string")
|
|
53
|
+
acceptedSteerInputs.set(inputId, replay);
|
|
54
|
+
queue.push({
|
|
55
|
+
...buildHumanInputEvent({
|
|
56
|
+
carrier: "steer",
|
|
57
|
+
source: "steer",
|
|
58
|
+
delivery: "queued",
|
|
59
|
+
sessionSeq: nextHumanInputSeq(h.harness),
|
|
60
|
+
inputId: effectiveInputId,
|
|
61
|
+
...(actor !== undefined ? { actor } : {}),
|
|
62
|
+
...(actor?.issuer !== undefined ? { issuer: actor.issuer } : {}),
|
|
63
|
+
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
64
|
+
}),
|
|
65
|
+
eventId: uuidv7(),
|
|
66
|
+
...(internals?.parentToolCallId !== undefined
|
|
67
|
+
? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) }
|
|
68
|
+
: {}),
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const deliver = async () => {
|
|
72
|
+
if (live.resultValue)
|
|
73
|
+
throw steeringError("the task has already finished");
|
|
74
|
+
const h = live.handle ?? (await orTimeout(ready));
|
|
75
|
+
if (!h)
|
|
76
|
+
throw steeringError("the task is not running");
|
|
77
|
+
payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
|
|
78
|
+
mintsAFrame = payload.trim().length !== 0;
|
|
79
|
+
replay = { payload, trusted, priority, ...(actor !== undefined ? { actor } : {}) };
|
|
80
|
+
if (typeof inputId === "string") {
|
|
81
|
+
const prior = acceptedSteerInputs.get(inputId);
|
|
82
|
+
if (prior !== undefined) {
|
|
83
|
+
if (live.resultValue !== undefined || h.loop.ended)
|
|
84
|
+
throw steeringError("the task is no longer running");
|
|
85
|
+
if (!sameAcceptedSteerInput(prior, replay)) {
|
|
86
|
+
throw steeringError("a different steering instruction was already accepted under this inputId — re-issue this one with a fresh inputId " +
|
|
87
|
+
"(an identical payload would have been an idempotent retry)", "steering.duplicate_input_id");
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (live.resultValue !== undefined || h.loop.ended)
|
|
93
|
+
throw steeringError("the task is no longer running");
|
|
94
|
+
if (mintsAFrame) {
|
|
95
|
+
const screen = (spec.hooks ?? runner.deps.hooks)?.userPromptSubmit;
|
|
96
|
+
if (screen !== undefined) {
|
|
97
|
+
let decision;
|
|
98
|
+
try {
|
|
99
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
|
|
100
|
+
if (seat.expired) {
|
|
101
|
+
if (seat.cause === "timeout") {
|
|
102
|
+
try {
|
|
103
|
+
runner.deps.onError?.(hookSeatExpiredError("userPromptSubmit", h.hookTimeoutMs, seat.cause, "the steering input was NOT accepted (fail-closed) and the caller was refused typed"), { phase: "hook", sessionId: h.sessionId });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw steeringError(seat.cause === "timeout"
|
|
109
|
+
? `the deployment's userPromptSubmit hook did not answer within its ${h.hookTimeoutMs}ms bound while screening this steering input; the input was NOT accepted (fail-closed)`
|
|
110
|
+
: `the task was cancelled while the deployment's userPromptSubmit hook was still screening this steering input; the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
|
|
111
|
+
}
|
|
112
|
+
decision = seat.value;
|
|
113
|
+
}
|
|
114
|
+
catch (hookErr) {
|
|
115
|
+
if (hookErr instanceof Error && hookErr.code === "steering.blocked_by_hook")
|
|
116
|
+
throw hookErr;
|
|
117
|
+
const err = hookErr instanceof Error ? hookErr : new Error(String(hookErr));
|
|
118
|
+
try {
|
|
119
|
+
runner.deps.onError?.(err, { phase: "hook", sessionId: h.sessionId });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
}
|
|
123
|
+
throw steeringError(`the deployment's userPromptSubmit hook crashed while screening this steering input (${inlineUntrusted(err.message)}); the input was NOT accepted (fail-closed)`, "steering.blocked_by_hook");
|
|
124
|
+
}
|
|
125
|
+
if (decision?.block !== undefined && decision.block !== "") {
|
|
126
|
+
throw steeringError(`the deployment's userPromptSubmit hook blocked this steering input: ${inlineUntrusted(decision.block)}`, "steering.blocked_by_hook");
|
|
127
|
+
}
|
|
128
|
+
if (decision?.additionalContext !== undefined && decision.additionalContext !== "") {
|
|
129
|
+
payload = `${formatHookFeedback(decision.additionalContext, h.reminderMark)}\n\n${payload}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const injectFramed = async () => {
|
|
134
|
+
const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
|
|
135
|
+
if (priority === "later") {
|
|
136
|
+
await h.harness.followUp(payload, noteOptions);
|
|
137
|
+
noteAccepted(h);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const frame = await h.harness.steer(payload, { ...noteOptions, ...(priority === "now" ? { immediate: true } : {}) });
|
|
141
|
+
noteAccepted(h);
|
|
142
|
+
if (priority === "now" && frame !== undefined && h.harness.interruptTurn(frame)) {
|
|
143
|
+
deliverEngineNotice(runner.deps.onNotice, {
|
|
144
|
+
code: "task.turn_interrupted",
|
|
145
|
+
message: "a caller-provenance steer with priority \"now\" interrupted the running turn: in-flight work was cut at " +
|
|
146
|
+
"a manufactured boundary (finished tool calls keep their real results; never-started ones settle as " +
|
|
147
|
+
"interrupted) and the run continues with the steer at the queue head.",
|
|
148
|
+
detail: {
|
|
149
|
+
inputId: effectiveInputId,
|
|
150
|
+
sessionId: h.sessionId,
|
|
151
|
+
runId: h.runId,
|
|
152
|
+
...(actor?.id !== undefined ? { actorId: actor.id } : {}),
|
|
153
|
+
...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
await injectFramed();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
if (!(e instanceof Error && e.code === "invalid_state"))
|
|
164
|
+
throw e;
|
|
165
|
+
}
|
|
166
|
+
const birthDeadline = Date.now() + READY_TIMEOUT_MS;
|
|
167
|
+
while (live.resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
|
|
168
|
+
try {
|
|
169
|
+
await injectFramed();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
catch (e2) {
|
|
173
|
+
if (!(e2 instanceof Error && e2.code === "invalid_state"))
|
|
174
|
+
throw e2;
|
|
175
|
+
}
|
|
176
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
177
|
+
}
|
|
178
|
+
throw steeringError("the task is no longer running");
|
|
179
|
+
};
|
|
180
|
+
const p = steerChain.then(deliver);
|
|
181
|
+
steerChain = p.then(() => undefined, () => undefined);
|
|
182
|
+
return p;
|
|
183
|
+
};
|
|
184
|
+
return { steer };
|
|
185
|
+
}
|
|
@@ -9,10 +9,20 @@ export interface ShellWord {
|
|
|
9
9
|
* yield zero, one or several words. */
|
|
10
10
|
readonly expands: false | "one" | "many";
|
|
11
11
|
}
|
|
12
|
+
/** One redirection of a segment, as read: the operator and its operand word (a heredoc's operand is its
|
|
13
|
+
* delimiter; the IO number or `{name}` before the operator is folded into the operator's meaning and not
|
|
14
|
+
* kept). The tightening reader never reads these — a rule speaks about program runs; the read-only
|
|
15
|
+
* reader does (an output redirection is a write). */
|
|
16
|
+
export interface ShellRedirection {
|
|
17
|
+
readonly op: string;
|
|
18
|
+
readonly target: ShellWord;
|
|
19
|
+
}
|
|
12
20
|
/** One program run the lexer found, after keyword stripping. */
|
|
13
21
|
export interface ShellSegment {
|
|
14
22
|
/** The run as SPELLED (a leading keyword removed): the first candidate a rule is compared against. */
|
|
15
23
|
readonly argv: readonly ShellWord[];
|
|
24
|
+
/** The segment's redirections in source order (see {@link ShellRedirection}); empty when it has none. */
|
|
25
|
+
readonly redirections: readonly ShellRedirection[];
|
|
16
26
|
/** The deeper candidates, one per peeled layer (leading assignments, then each wrapper of the table):
|
|
17
27
|
* `sudo -u root rm -r x` ⇒ `[[rm, -r, x]]`. Empty when nothing peeled. */
|
|
18
28
|
readonly peeled: readonly (readonly ShellWord[])[];
|
|
@@ -34,6 +44,14 @@ export interface ShellCommandShape {
|
|
|
34
44
|
/** A subshell, group, control-structure keyword or dropped empty piece bounded the segments: the
|
|
35
45
|
* connector list is not one flat chain a compound rule could spell. */
|
|
36
46
|
readonly grouped: boolean;
|
|
47
|
+
/** A `&` connector anywhere — between two segments or after the last one (`ls &`): some segment runs in the
|
|
48
|
+
* background, past any approval-time reading of the line. The connector list alone cannot say so for a
|
|
49
|
+
* trailing `&` (a connector rides BETWEEN segments). */
|
|
50
|
+
readonly backgrounded: boolean;
|
|
51
|
+
/** A redirection on a piece that runs NO program (`> x` alone, `[ -f a ] > x`, a keyword's own redirection):
|
|
52
|
+
* such a piece is dropped from `segments` (the tightening reader asks about program runs, and it runs
|
|
53
|
+
* none) — but the shell still opens the target, so a reader that asks about EFFECTS must refuse the line. */
|
|
54
|
+
readonly strayRedirection: boolean;
|
|
37
55
|
}
|
|
38
56
|
export { SHELL_WRAPPER_TABLE, type ShellWrapperName } from "./shell-wrapper-table.js";
|
|
39
57
|
/** Longer than this and the command is not read at all (one unreadable segment): every pass is linear,
|
package/dist/core/shell-lexer.js
CHANGED
|
@@ -13,11 +13,13 @@ const ALL_OPS = [...CONNECTOR_OPS, ...REDIRECT_OPS];
|
|
|
13
13
|
export const MAX_SHELL_READ_CHARS = 10_000;
|
|
14
14
|
export function readShellCommand(source) {
|
|
15
15
|
if (source.length > MAX_SHELL_READ_CHARS)
|
|
16
|
-
return { segments: [{ argv: [], peeled: [], unreadable: `the command is longer than ${MAX_SHELL_READ_CHARS} characters — not read` }], connectors: [], grouped: false };
|
|
16
|
+
return { segments: [{ argv: [], redirections: [], peeled: [], unreadable: `the command is longer than ${MAX_SHELL_READ_CHARS} characters — not read` }], connectors: [], grouped: false, backgrounded: false, strayRedirection: false };
|
|
17
17
|
const segments = [];
|
|
18
18
|
const connectors = [];
|
|
19
19
|
let grouped = false;
|
|
20
|
-
let
|
|
20
|
+
let backgrounded = false;
|
|
21
|
+
let strayRedirection = false;
|
|
22
|
+
let seg = { words: [], redirections: [] };
|
|
21
23
|
let word = freshWord();
|
|
22
24
|
let pendingRedirect;
|
|
23
25
|
let heredocs = [];
|
|
@@ -40,6 +42,7 @@ export function readShellCommand(source) {
|
|
|
40
42
|
badDelimiter = true;
|
|
41
43
|
heredocs.push({ delimiter: word.text, quoted: word.raw !== word.text, dash: pendingRedirect === "<<-" });
|
|
42
44
|
}
|
|
45
|
+
seg.redirections.push({ op: pendingRedirect, target: publicWord(word) });
|
|
43
46
|
pendingRedirect = undefined;
|
|
44
47
|
}
|
|
45
48
|
else {
|
|
@@ -56,17 +59,19 @@ export function readShellCommand(source) {
|
|
|
56
59
|
let built = buildSegment(seg);
|
|
57
60
|
if (built === "keyworded")
|
|
58
61
|
grouped = true;
|
|
62
|
+
if ((built === "empty" || built === "keyworded") && seg.redirections.length > 0)
|
|
63
|
+
strayRedirection = true;
|
|
59
64
|
if (built === "empty" || built === "keyworded") {
|
|
60
65
|
const continuation = connector === "\n" && lastConnector !== undefined && lastConnector !== ";" && lastConnector !== "&" && lastConnector !== "\n";
|
|
61
66
|
const legal = built === "keyworded" || afterBoundary || connector === undefined || connector === "\n" || lastConnector === "\n";
|
|
62
67
|
if (!legal)
|
|
63
|
-
built = { argv: [], peeled: [], unreadable: `an empty command before \`${connector}\` — a syntax error` };
|
|
68
|
+
built = { argv: [], redirections: seg.redirections, peeled: [], unreadable: `an empty command before \`${connector}\` — a syntax error` };
|
|
64
69
|
else {
|
|
65
70
|
if (connector !== undefined && connector !== "\n" && segments.length > 0)
|
|
66
71
|
grouped = true;
|
|
67
72
|
if (connector !== undefined && !continuation)
|
|
68
73
|
lastConnector = connector;
|
|
69
|
-
seg = { words: [] };
|
|
74
|
+
seg = { words: [], redirections: [] };
|
|
70
75
|
afterBoundary = false;
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
@@ -76,7 +81,7 @@ export function readShellCommand(source) {
|
|
|
76
81
|
segments.push(built);
|
|
77
82
|
if (connector !== undefined)
|
|
78
83
|
lastConnector = connector;
|
|
79
|
-
seg = { words: [] };
|
|
84
|
+
seg = { words: [], redirections: [] };
|
|
80
85
|
afterBoundary = false;
|
|
81
86
|
};
|
|
82
87
|
const boundary = () => { finishSegment(undefined); grouped = true; afterBoundary = true; lastConnector = undefined; };
|
|
@@ -187,6 +192,8 @@ export function readShellCommand(source) {
|
|
|
187
192
|
i += op.length;
|
|
188
193
|
continue;
|
|
189
194
|
}
|
|
195
|
+
if (op === "&")
|
|
196
|
+
backgrounded = true;
|
|
190
197
|
finishSegment(op);
|
|
191
198
|
i += op.length;
|
|
192
199
|
continue;
|
|
@@ -271,7 +278,7 @@ export function readShellCommand(source) {
|
|
|
271
278
|
seg.unreadable ??= `the connector \`${lastConnector}\` has no command after it — a syntax error`;
|
|
272
279
|
finishSegment(undefined);
|
|
273
280
|
}
|
|
274
|
-
return { segments, connectors, grouped };
|
|
281
|
+
return { segments, connectors, grouped, backgrounded, strayRedirection };
|
|
275
282
|
}
|
|
276
283
|
const publicWord = ({ text, raw, expands }) => ({ text, raw, expands });
|
|
277
284
|
const SUBSTITUTION_UNREAD = "a command substitution runs a program this reader does not read";
|
|
@@ -281,8 +288,8 @@ export function isFullyReadable(shape) {
|
|
|
281
288
|
}
|
|
282
289
|
function buildSegment(seg) {
|
|
283
290
|
if (seg.unreadable !== undefined)
|
|
284
|
-
return { argv: seg.words.map(publicWord), peeled: [], unreadable: seg.unreadable };
|
|
285
|
-
const carried = () => (seg.substitution === true ? { argv: [], peeled: [], peelUnreadable: SUBSTITUTION_UNREAD } : keyworded ? "keyworded" : "empty");
|
|
291
|
+
return { argv: seg.words.map(publicWord), redirections: seg.redirections, peeled: [], unreadable: seg.unreadable };
|
|
292
|
+
const carried = () => (seg.substitution === true ? { argv: [], redirections: seg.redirections, peeled: [], peelUnreadable: SUBSTITUTION_UNREAD } : keyworded ? "keyworded" : "empty");
|
|
286
293
|
let keyworded = false;
|
|
287
294
|
let k0 = 0;
|
|
288
295
|
while (k0 < seg.words.length && seg.words[k0].expands === false && (PREFIX_KEYWORDS.has(seg.words[k0].text) || CLOSING_KEYWORDS.has(seg.words[k0].text))) {
|
|
@@ -298,7 +305,7 @@ function buildSegment(seg) {
|
|
|
298
305
|
if (head.expands === false && (head.text === "[[" || head.text === "["))
|
|
299
306
|
return carried();
|
|
300
307
|
if (head.expands === false && OPAQUE_KEYWORDS.has(head.text))
|
|
301
|
-
return { argv: words.map(publicWord), peeled: [], unreadable: `a \`${head.text}\` control structure — its commands need a syntax tree` };
|
|
308
|
+
return { argv: words.map(publicWord), redirections: seg.redirections, peeled: [], unreadable: `a \`${head.text}\` control structure — its commands need a syntax tree` };
|
|
302
309
|
const argv = words.map(publicWord);
|
|
303
310
|
const peeled = [];
|
|
304
311
|
let peelUnreadable;
|
|
@@ -338,7 +345,7 @@ function buildSegment(seg) {
|
|
|
338
345
|
}
|
|
339
346
|
if (seg.substitution === true || [argv, ...peeled].some((c) => c[0]?.expands === false && evaluatesOperand(c)))
|
|
340
347
|
peelUnreadable ??= SUBSTITUTION_UNREAD;
|
|
341
|
-
return { argv, peeled, ...(peelUnreadable !== undefined ? { peelUnreadable } : {}) };
|
|
348
|
+
return { argv, redirections: seg.redirections, peeled, ...(peelUnreadable !== undefined ? { peelUnreadable } : {}) };
|
|
342
349
|
}
|
|
343
350
|
function unwrap(words) {
|
|
344
351
|
const head = words[0];
|
|
@@ -50,9 +50,12 @@ export function evaluatesOperand(run) {
|
|
|
50
50
|
return run.some((w) => namesArrayElement(w.text) || namesArrayElement(w.raw) || (integer && /^[A-Za-z_][A-Za-z0-9_]*\+?=.*[A-Za-z_$]/.test(w.text)));
|
|
51
51
|
}
|
|
52
52
|
function namesArrayElement(word) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
for (let open = word.indexOf("["); open > 0; open = word.indexOf("[", open + 1)) {
|
|
54
|
+
if (!/[A-Za-z0-9_]/.test(word[open - 1]))
|
|
55
|
+
continue;
|
|
56
|
+
const close = word.indexOf("]", open);
|
|
57
|
+
if (close > 0 && /[A-Za-z_$]/.test(word.slice(open + 1, close)))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
58
61
|
}
|
|
@@ -99,7 +99,10 @@ export interface ToolCallRequest {
|
|
|
99
99
|
* - `"org_unavailable"` — an org-governed deployment could not adjudicate against a snapshot, so the
|
|
100
100
|
* whole decision boundary failed closed (see `ORG_UNAVAILABLE_DECISION_REASON`, the single
|
|
101
101
|
* spelling this word is minted from). */
|
|
102
|
-
|
|
102
|
+
/** `read_only` (#619): the shell tool's command was PROVABLY read-only by the engine's transcribed upstream
|
|
103
|
+
* tables (`readOnlyShellVerdict`) and the allow layer let it run without a question — after every
|
|
104
|
+
* deny/ask lane and the person's own allow rules, never over a mandated ask. */
|
|
105
|
+
declare const DECISION_REASONS: readonly ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only"];
|
|
103
106
|
export type DecisionReason = (typeof DECISION_REASONS)[number];
|
|
104
107
|
/** Read the engine-attested settlement off a funneled decision FOR the named call (the gate's exit is the
|
|
105
108
|
* one consumer): an attestation bound to a different call is a replayed object and answers absence.
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -10,7 +10,7 @@ import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./unt
|
|
|
10
10
|
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
|
|
11
11
|
import { protocolOf } from "./protocol-table.js";
|
|
12
12
|
import { isAbsolutePathForm, isWinFormPath, writeTargetPath } from "../tools/fs/safety.js";
|
|
13
|
-
const DECISION_REASONS = ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable"];
|
|
13
|
+
const DECISION_REASONS = ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only"];
|
|
14
14
|
const DECISION_REASON_SET = new Set(DECISION_REASONS);
|
|
15
15
|
function settledByNobody(kind) {
|
|
16
16
|
return { kind, who: { party: "none" }, when: Date.now() };
|
package/dist/core/tools.d.ts
CHANGED
|
@@ -15,14 +15,34 @@ export declare function errorResult(text: string, details?: unknown): {
|
|
|
15
15
|
isError: true;
|
|
16
16
|
details?: unknown;
|
|
17
17
|
};
|
|
18
|
-
/**
|
|
18
|
+
/** The seat's function: the mount's ctx builder and the very object being mounted (the seal compares it to the stamped one). */
|
|
19
|
+
type DefineToolRebind = (enrich: ToolCtxEnricher, mounted: object) => AgentTool;
|
|
20
|
+
/** True iff `x` is an `AgentTool` this module's `defineTool` itself constructed — see {@link DEFINE_TOOL_BRAND}.
|
|
21
|
+
* An OWN property read: an object that merely INHERITS the brand (`Object.create(product)`, a Proxy over it)
|
|
22
|
+
* is not the product — the rebind seat would rebuild the product and drop whatever the derived object
|
|
23
|
+
* overrode, so such objects are not recognised here and fall to the spec arm's contract (which they do not
|
|
24
|
+
* meet either; see {@link stampDefineToolBrand} for the one supported wrapper form). */
|
|
19
25
|
export declare function isDefineToolProduct(x: unknown): x is AgentTool;
|
|
20
|
-
/** RB-362 类修同源章点 — the ONE place the brand is stamped
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
+
/** RB-362 类修同源章点 — the ONE place the brand is stamped, and with it the rebind seat (a branded object
|
|
27
|
+
* ALWAYS carries one; that invariant is what lets the caller mount rebind every product without a
|
|
28
|
+
* "cannot enrich" arm). `defineTool` uses it on its own product; the only OTHER legitimate caller is a
|
|
29
|
+
* wrapper that (a) starts from a branded product, (b) preserves the AgentTool
|
|
30
|
+
* `execute(toolCallId, rawParams, signal, onUpdate)` contract faithfully and (c) hands over a `rebind`
|
|
31
|
+
* that re-wraps the REBOUND inner product the same way (e.g. teacher.ts's logging wrapper) — so a mount
|
|
32
|
+
* rebinding the wrapper gets a wrapper over an enriched product, not an enriched product without the
|
|
33
|
+
* wrapper. A shallow copy that does NOT re-stamp deliberately loses the brand — that is the brand's
|
|
34
|
+
* documented survival contract, not an accident. Internal only. */
|
|
35
|
+
export declare function stampDefineToolBrand<T extends object>(tool: T, rebind: DefineToolRebind): T;
|
|
36
|
+
/**
|
|
37
|
+
* A branded product REBOUND to a mount's ctx builder — the product's own `execute` rebuilt over its own
|
|
38
|
+
* spec with `enrich` applied per call (see {@link ToolCtxEnricher}; a builder the product was built with
|
|
39
|
+
* runs AFTER the mount's, refining the run's trusted seats, and the per-call identity is re-stamped after
|
|
40
|
+
* both). This is how a `defineTool()` product handed to `TaskSpec.tools` receives exactly the ctx a raw
|
|
41
|
+
* `ToolSpec` on the same mount receives: one mount law, whatever object shape the caller handed in.
|
|
42
|
+
* Throws on an unbranded object — the caller checks {@link isDefineToolProduct} first; a branded object
|
|
43
|
+
* without the seat cannot be constructed (both are written at the one stamp site).
|
|
44
|
+
*/
|
|
45
|
+
export declare function rebindDefineToolCtx(product: AgentTool, enrich: ToolCtxEnricher): AgentTool;
|
|
26
46
|
/**
|
|
27
47
|
* RB-409 — a per-call ctx builder a MOUNT hands to {@link defineTool}.
|
|
28
48
|
*
|
|
@@ -58,3 +78,4 @@ export interface DefineToolOptions {
|
|
|
58
78
|
}
|
|
59
79
|
/** Adapt a friendly ToolSpec into the vendored AgentTool the agent loop expects. */
|
|
60
80
|
export declare function defineTool<TParams extends TSchema = TSchema>(spec: ToolSpec<TParams>, options?: DefineToolOptions): AgentTool<TParams>;
|
|
81
|
+
export {};
|
package/dist/core/tools.js
CHANGED
|
@@ -26,13 +26,46 @@ export function errorResult(text, details) {
|
|
|
26
26
|
return details === undefined ? { content: text, isError: true } : { content: text, isError: true, details };
|
|
27
27
|
}
|
|
28
28
|
const DEFINE_TOOL_BRAND = Symbol("sema.core.defineTool.product");
|
|
29
|
+
const DEFINE_TOOL_REBIND = Symbol("sema.core.defineTool.rebind");
|
|
29
30
|
export function isDefineToolProduct(x) {
|
|
30
|
-
return typeof x === "object" && x !== null && x
|
|
31
|
+
return typeof x === "object" && x !== null && Object.getOwnPropertyDescriptor(x, DEFINE_TOOL_BRAND)?.value === true;
|
|
31
32
|
}
|
|
32
|
-
export function stampDefineToolBrand(tool) {
|
|
33
|
+
export function stampDefineToolBrand(tool, rebind) {
|
|
33
34
|
Object.defineProperty(tool, DEFINE_TOOL_BRAND, { value: true, enumerable: false });
|
|
35
|
+
Object.defineProperty(tool, DEFINE_TOOL_REBIND, { value: sealedRebind(tool, rebind), enumerable: false });
|
|
34
36
|
return tool;
|
|
35
37
|
}
|
|
38
|
+
function sealedRebind(stamped, rebuild) {
|
|
39
|
+
const ownExecute = Object.getOwnPropertyDescriptor(stamped, "execute")?.value;
|
|
40
|
+
const stillSealed = () => {
|
|
41
|
+
const d = Object.getOwnPropertyDescriptor(stamped, "execute");
|
|
42
|
+
return d !== undefined && "value" in d && d.value === ownExecute;
|
|
43
|
+
};
|
|
44
|
+
return (enrich, mounted) => {
|
|
45
|
+
if (mounted !== stamped) {
|
|
46
|
+
throw new Error(`defineTool product ${JSON.stringify(stamped.name)}: the object being mounted is not the one this brand was stamped on (a derived object — prototype, proxy or descriptor copy — carrying a borrowed brand), so it cannot be rebound to the run's ctx without dropping its own overrides — wrap the product as a NEW object (stampDefineToolBrand with a rebind that re-wraps the rebound inner product), or author it as a raw ToolSpec`);
|
|
47
|
+
}
|
|
48
|
+
const refuse = () => {
|
|
49
|
+
throw new Error(`defineTool product ${JSON.stringify(stamped.name)}: its execute is not the own data property the factory wrote (replaced, or turned into an accessor, after the factory built it), so it cannot be rebound to the run's ctx without dropping the replacement — wrap the product as a NEW object (stampDefineToolBrand with a rebind that re-wraps the rebound inner product), or author it as a raw ToolSpec`);
|
|
50
|
+
};
|
|
51
|
+
if (!stillSealed())
|
|
52
|
+
refuse();
|
|
53
|
+
const rebound = rebuild(enrich, mounted);
|
|
54
|
+
if (!stillSealed())
|
|
55
|
+
refuse();
|
|
56
|
+
return rebound;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function rebindDefineToolCtx(product, enrich) {
|
|
60
|
+
const rebind = Object.getOwnPropertyDescriptor(product, DEFINE_TOOL_REBIND)?.value;
|
|
61
|
+
if (typeof rebind !== "function")
|
|
62
|
+
throw new Error(`rebindDefineToolCtx: ${product.name} is not a defineTool product (no rebind seat)`);
|
|
63
|
+
return rebind(enrich, product);
|
|
64
|
+
}
|
|
65
|
+
function stampCallIdentity(enriched, toolCallId, signal) {
|
|
66
|
+
const own = (value) => ({ value, enumerable: true, configurable: true, writable: true });
|
|
67
|
+
return Object.create(Object.getPrototypeOf(enriched), { ...Object.getOwnPropertyDescriptors(enriched), toolCallId: own(toolCallId), signal: own(signal) });
|
|
68
|
+
}
|
|
36
69
|
export function defineTool(spec, options) {
|
|
37
70
|
const executionMode = spec.executionMode ?? (spec.effect === "read" ? "parallel" : "sequential");
|
|
38
71
|
const tool = {
|
|
@@ -77,7 +110,8 @@ export function defineTool(spec, options) {
|
|
|
77
110
|
let ret;
|
|
78
111
|
try {
|
|
79
112
|
const baseCtx = { toolCallId, signal };
|
|
80
|
-
|
|
113
|
+
const ctx = options?.enrichCtx ? stampCallIdentity(options.enrichCtx(baseCtx), toolCallId, signal) : baseCtx;
|
|
114
|
+
ret = await spec.execute(params, ctx);
|
|
81
115
|
}
|
|
82
116
|
catch (err) {
|
|
83
117
|
const wrapped = new Error(formatToolError(err));
|
|
@@ -105,6 +139,12 @@ export function defineTool(spec, options) {
|
|
|
105
139
|
...(spec.aliases && spec.aliases.length > 0 ? { aliases: spec.aliases } : {}),
|
|
106
140
|
});
|
|
107
141
|
}
|
|
108
|
-
stampDefineToolBrand(tool)
|
|
142
|
+
stampDefineToolBrand(tool, (enrich) => {
|
|
143
|
+
const composed = options?.enrichCtx === undefined ? enrich : (base) => options.enrichCtx(enrich(base));
|
|
144
|
+
const rebuilt = defineTool(spec, { ...options, enrichCtx: composed });
|
|
145
|
+
const { [DEFINE_TOOL_BRAND]: _brand, [DEFINE_TOOL_REBIND]: _seat, ...face } = Object.getOwnPropertyDescriptors(tool);
|
|
146
|
+
const copy = Object.create(Object.getPrototypeOf(tool), { ...face, execute: { value: rebuilt.execute, enumerable: true, configurable: true, writable: true } });
|
|
147
|
+
return stampDefineToolBrand(copy, (again) => rebindDefineToolCtx(rebuilt, again));
|
|
148
|
+
});
|
|
109
149
|
return tool;
|
|
110
150
|
}
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -425,6 +425,21 @@ export type TraceEvent = {
|
|
|
425
425
|
toolCallId: string;
|
|
426
426
|
rules: readonly string[];
|
|
427
427
|
ts: number;
|
|
428
|
+
} | {
|
|
429
|
+
/**
|
|
430
|
+
* #619 — the READ-ONLY reader cleared a shell call, so no person and no classifier was asked: the
|
|
431
|
+
* allow layer's second attribution channel beside `permission.persisted_rule_allowed`. `command` is
|
|
432
|
+
* the FINAL command the gate judged (a policy rewrite included) — model-authored text, carried
|
|
433
|
+
* verbatim as the audit fact this frame exists for (a consumer rendering it applies its own
|
|
434
|
+
* display sanitizer, as it does for `tool_start` args).
|
|
435
|
+
*/
|
|
436
|
+
kind: "permission.read_only_allowed";
|
|
437
|
+
version: 1;
|
|
438
|
+
taskId: string;
|
|
439
|
+
toolName: string;
|
|
440
|
+
toolCallId: string;
|
|
441
|
+
command: string;
|
|
442
|
+
ts: number;
|
|
428
443
|
} | {
|
|
429
444
|
/**
|
|
430
445
|
* design/179 — the persisted allow-rule store could not be read, so this call was adjudicated with
|
|
@@ -131,6 +131,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
|
|
|
131
131
|
private runPromise?;
|
|
132
132
|
private pendingSessionWrites;
|
|
133
133
|
private model;
|
|
134
|
+
/** The run's thinking level; `undefined` = undeclared (provider default, no wire key) — distinct from "off". */
|
|
134
135
|
private thinkingLevel;
|
|
135
136
|
/** RB-30 terminal fix — runner-set sink for engine-note payloads left undrained at agent_end. */
|
|
136
137
|
onUndrainedEngineNotes?: (payloads: unknown[]) => void;
|
|
@@ -410,7 +411,8 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
|
|
|
410
411
|
} & UserMessageProvenance): Promise<void>;
|
|
411
412
|
appendMessage(message: AgentMessage): Promise<void>;
|
|
412
413
|
getModel(): Model;
|
|
413
|
-
|
|
414
|
+
/** The current level, or `undefined` when none was declared (provider default — no wire key). */
|
|
415
|
+
getThinkingLevel(): ThinkingLevel | undefined;
|
|
414
416
|
setModel(model: Model): Promise<void>;
|
|
415
417
|
setThinkingLevel(level: ThinkingLevel): Promise<void>;
|
|
416
418
|
setActiveTools(toolNames: string[]): Promise<void>;
|
|
@@ -277,7 +277,7 @@ export class AgentHarness {
|
|
|
277
277
|
this.tools.set(tool.name, tool);
|
|
278
278
|
}
|
|
279
279
|
this.model = options.model;
|
|
280
|
-
this.thinkingLevel = options.thinkingLevel
|
|
280
|
+
this.thinkingLevel = options.thinkingLevel;
|
|
281
281
|
this.activeToolNames =
|
|
282
282
|
options.activeToolNames ?? (options.tools ?? []).map((tool) => tool.name);
|
|
283
283
|
this.steeringQueueMode = options.steeringMode ?? "one-at-a-time";
|
|
@@ -1178,7 +1178,8 @@ export interface ModelSelectEvent {
|
|
|
1178
1178
|
export interface ThinkingLevelSelectEvent {
|
|
1179
1179
|
type: "thinking_level_select";
|
|
1180
1180
|
level: ThinkingLevel;
|
|
1181
|
-
|
|
1181
|
+
/** `undefined` when the run had no declared level before this selection (provider default). */
|
|
1182
|
+
previousLevel: ThinkingLevel | undefined;
|
|
1182
1183
|
}
|
|
1183
1184
|
export interface ResourcesUpdateEvent<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate> {
|
|
1184
1185
|
type: "resources_update";
|
|
@@ -1346,7 +1347,8 @@ export interface AgentHarnessOptions<TSkill extends Skill = Skill, TPromptTempla
|
|
|
1346
1347
|
env: ExecutionEnv;
|
|
1347
1348
|
session: Session;
|
|
1348
1349
|
model: Model;
|
|
1349
|
-
|
|
1350
|
+
/** `undefined` = no level declared (provider default). */
|
|
1351
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
1350
1352
|
activeTools: TTool[];
|
|
1351
1353
|
resources: AgentHarnessResources<TSkill, TPromptTemplate>;
|
|
1352
1354
|
}) => string | Promise<string>);
|
package/dist/index.d.ts
CHANGED
|
@@ -195,7 +195,9 @@ export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isVa
|
|
|
195
195
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
|
|
196
196
|
export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, type PermissionRuleStore, type PermissionRuleStoreProvider, type PermissionRuleStoreConfig, type EffectivePermissionRules, type EffectivePermissionRule, type RemovedPermissionRule, type RuleSource, } from "./core/permission-rule-provider.js";
|
|
197
197
|
export { InMemorySessionRulePartition, type SessionRulePartition, type SessionRuleAdd, type SessionRuleApplyResult, } from "./core/permission-rule-session.js";
|
|
198
|
-
export { readShellCommand, isFullyReadable, MAX_SHELL_READ_CHARS, SHELL_WRAPPER_TABLE, type ShellCommandShape, type ShellSegment, type ShellWord, type ShellWrapperName, } from "./core/shell-lexer.js";
|
|
198
|
+
export { readShellCommand, isFullyReadable, MAX_SHELL_READ_CHARS, SHELL_WRAPPER_TABLE, type ShellCommandShape, type ShellSegment, type ShellWord, type ShellRedirection, type ShellWrapperName, } from "./core/shell-lexer.js";
|
|
199
|
+
export { readOnlyShellVerdict, type ReadOnlyShellVerdict } from "./core/read-only-shell.js";
|
|
200
|
+
export { READ_ONLY_FLAG_ARITIES, FLAG_VALUE_ACCEPTS, READ_ONLY_COMMAND_TABLE, READ_ONLY_BARE_PROGRAMS, READ_ONLY_GLOB_PROGRAMS, READ_ONLY_EXACT_FORMS, READ_ONLY_BARE_ONLY, FIND_ACTION_PRIMARIES, FIND_VALUE_PRIMARIES, FIND_NEWER_PRIMARY, READ_ONLY_ENV_NAMES, XARGS_READ_ONLY_TARGETS, type ReadOnlyFlagArity, type ReadOnlyCommandRow, } from "./core/read-only-shell-table.js";
|
|
199
201
|
export { orgRuleVerdictFor, type OrgRuleVerdict, orgRuleShadows, unenforceableOrgRules, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, type OrgPermissionRule, type OrgRuleSnapshot, type OrgRuleSnapshotProvider, type OrgRuleStatePersistence, type PersistedOrgRuleState, type OrgRulePartitionConfig, type OrgRuleResolution, type OrgRuleStatus, } from "./core/permission-rule-org.js";
|
|
200
202
|
export { RULE_SYNC_DROP_CODES, type RuleSyncDropReason, type RuleQuarantineReason } from "./core/governance-codes.js";
|
|
201
203
|
export { prepareCardApproval, confirmRuleApproval, type ConfirmResult, type ConfirmRefusalReason, precheckEditedRuleText, type EditedRuleTextPrecheck, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, ruleOffersOfRecord, type RuleTicket, type RuleCandidate, type RuleApprovalKind, type RuleApprovalRecord, type RuleApprovalRecordStore, type RuleOffer2, type StaleRuleApprovalRecord, type RuleConsentDeps, type RedeemResult, type RedeemedBatchMember, type CcImportLayer, type ImportedSettingsLayer, type ImportPreview, } from "./core/permission-rule-consent.js";
|
package/dist/index.js
CHANGED
|
@@ -155,6 +155,8 @@ export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH,
|
|
|
155
155
|
export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, } from "./core/permission-rule-provider.js";
|
|
156
156
|
export { InMemorySessionRulePartition, } from "./core/permission-rule-session.js";
|
|
157
157
|
export { readShellCommand, isFullyReadable, MAX_SHELL_READ_CHARS, SHELL_WRAPPER_TABLE, } from "./core/shell-lexer.js";
|
|
158
|
+
export { readOnlyShellVerdict } from "./core/read-only-shell.js";
|
|
159
|
+
export { READ_ONLY_FLAG_ARITIES, FLAG_VALUE_ACCEPTS, READ_ONLY_COMMAND_TABLE, READ_ONLY_BARE_PROGRAMS, READ_ONLY_GLOB_PROGRAMS, READ_ONLY_EXACT_FORMS, READ_ONLY_BARE_ONLY, FIND_ACTION_PRIMARIES, FIND_VALUE_PRIMARIES, FIND_NEWER_PRIMARY, READ_ONLY_ENV_NAMES, XARGS_READ_ONLY_TARGETS, } from "./core/read-only-shell-table.js";
|
|
158
160
|
export { orgRuleVerdictFor, orgRuleShadows, unenforceableOrgRules, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, } from "./core/permission-rule-org.js";
|
|
159
161
|
export { RULE_SYNC_DROP_CODES } from "./core/governance-codes.js";
|
|
160
162
|
export { prepareCardApproval, confirmRuleApproval, precheckEditedRuleText, redeemRuleTicket, redeemRuleBatch, prepareCcImport, prepareStarterBatch, mintRuleTicket, STARTER_RULES, InMemoryRuleApprovalRecordStore, ruleOffersOfRecord, } from "./core/permission-rule-consent.js";
|
|
@@ -260,8 +260,11 @@ export interface RunWorkflowToolDeps {
|
|
|
260
260
|
/** The HOST task's effective working root — threaded into every spawned agent's trusted internals so a
|
|
261
261
|
* TOC env factory can root the child at the parent's cwd (CC parity, 2026-07-03). */
|
|
262
262
|
parentCwd?: string;
|
|
263
|
-
/** Call-time getter for the
|
|
264
|
-
*
|
|
263
|
+
/** Call-time getter for the level a spawned agent runs at when neither the script, the agentType
|
|
264
|
+
* definition nor the governance baseline set `thinking` (the mount folds the deployment's STATED
|
|
265
|
+
* `roles.subagent.thinking` over the HOST task's current level — the child thinking seat's lower rungs).
|
|
266
|
+
* Threaded to the workflow as `defaultThinking` and applied at the launch site AFTER the agentType fold,
|
|
267
|
+
* the `parentModel` → `defaultModel` companion. */
|
|
265
268
|
parentThinking?: () => import("../core/types.js").TaskSpec["thinking"];
|
|
266
269
|
/** 5.30 merge-rescan (design/199 parity gap) — call-time getter for the HOST task's RESOLVED
|
|
267
270
|
* read-face containment; folds stricter-wins into every spawned workflow child (see
|
|
@@ -488,7 +488,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
488
488
|
}
|
|
489
489
|
: governance;
|
|
490
490
|
const scriptFn = (wfCtx) => {
|
|
491
|
-
const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn,
|
|
491
|
+
const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false);
|
|
492
492
|
return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
|
|
493
493
|
};
|
|
494
494
|
if (ctx.signal?.aborted) {
|
|
@@ -546,6 +546,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
546
546
|
return d.parentMemoryCaptureState !== undefined ? { parentMemoryCaptureState: d.parentMemoryCaptureState } : {};
|
|
547
547
|
})(),
|
|
548
548
|
...(d.parentModel !== undefined ? { defaultModel: d.parentModel } : {}),
|
|
549
|
+
...(d.parentThinking !== undefined ? { defaultThinking: d.parentThinking } : {}),
|
|
549
550
|
...(d.parentGetApiKeyAndHeaders !== undefined ? { defaultGetApiKeyAndHeaders: d.parentGetApiKeyAndHeaders } : {}),
|
|
550
551
|
...(hostDurableApproval !== undefined && d.store !== undefined ? { defaultDurableApproval: { ...hostDurableApproval } } : {}),
|
|
551
552
|
...(resumeFromRunId !== undefined && d.parkedResume !== undefined && d.parkedResume(resumeFromRunId) !== undefined ? { parkedResume: d.parkedResume(resumeFromRunId) } : {}),
|