@sema-agent/core 7.33.7 → 7.34.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 +357 -0
- package/dist/agents/cross-session-judge.d.ts +16 -7
- package/dist/agents/cross-session-judge.js +18 -15
- package/dist/agents/peer-session-drain.d.ts +4 -0
- package/dist/agents/peer-session-drain.js +5 -2
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +12 -3
- package/dist/brain/anthropic.js +1 -0
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +351 -154
- package/dist/core/ask-origin.js +223 -45
- package/dist/core/ask-unresolvable-notice.d.ts +5 -2
- package/dist/core/ask-unresolvable-notice.js +4 -3
- package/dist/core/auto-mode-arming.d.ts +17 -4
- package/dist/core/auto-mode-arming.js +31 -6
- package/dist/core/auto-mode-defaults.d.ts +16 -7
- package/dist/core/auto-mode-defaults.js +2 -1
- package/dist/core/auto-mode-directive.d.ts +18 -30
- package/dist/core/auto-mode-directive.js +9 -18
- package/dist/core/auto-mode-prompt.d.ts +62 -1
- package/dist/core/auto-mode-prompt.js +42 -4
- package/dist/core/auto-mode-rebuild.d.ts +2 -1
- package/dist/core/auto-mode-rebuild.js +1 -0
- package/dist/core/auto-mode.d.ts +32 -3
- package/dist/core/auto-mode.js +80 -41
- package/dist/core/checkpoint-store.d.ts +50 -35
- package/dist/core/checkpoint-store.js +58 -3
- package/dist/core/child-clamp-seats.js +1 -0
- package/dist/core/classifier-unavailable-arm.d.ts +57 -0
- package/dist/core/classifier-unavailable-arm.js +17 -0
- package/dist/core/effective-path-target.d.ts +12 -0
- package/dist/core/effective-path-target.js +18 -7
- package/dist/core/engine-notice.d.ts +53 -2
- package/dist/core/fs-write-gate-policy.d.ts +5 -1
- package/dist/core/fs-write-gate-policy.js +4 -1
- package/dist/core/gate-fold.js +17 -1
- package/dist/core/gate-lanes.js +111 -46
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +12 -0
- package/dist/core/hooks.d.ts +50 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/mode-policy.d.ts +182 -0
- package/dist/core/mode-policy.js +179 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +16 -3
- package/dist/core/permission-rule-model.js +36 -3
- package/dist/core/permission-rule-provider.d.ts +22 -11
- package/dist/core/permission-rule-provider.js +17 -7
- package/dist/core/permission-rules.d.ts +23 -1
- package/dist/core/permission-rules.js +35 -8
- package/dist/core/persisted-rule-arms.d.ts +10 -9
- package/dist/core/persisted-rule-arms.js +9 -6
- package/dist/core/present-plan-tool.d.ts +1 -1
- package/dist/core/present-plan-tool.js +3 -3
- package/dist/core/read-only-shell.d.ts +19 -19
- package/dist/core/read-only-shell.js +38 -12
- package/dist/core/retired-keys.d.ts +9 -3
- package/dist/core/retired-keys.js +5 -1
- package/dist/core/runner/classifier-ladder.d.ts +43 -1
- package/dist/core/runner/classifier-ladder.js +29 -0
- package/dist/core/runner/contracts.d.ts +37 -35
- package/dist/core/runner/denial-limit-arms.d.ts +30 -7
- package/dist/core/runner/denial-limit-arms.js +15 -9
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.d.ts +30 -4
- package/dist/core/runner/gate-exit.js +28 -7
- package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
- package/dist/core/runner/permission-rule-lanes.js +44 -40
- package/dist/core/runner/persisted-path-rows.d.ts +64 -0
- package/dist/core/runner/persisted-path-rows.js +89 -0
- package/dist/core/runner/prepare-artifact.d.ts +5 -1
- package/dist/core/runner/prepare-artifact.js +9 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
- package/dist/core/runner/prepare-ask-lane.js +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
- package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
- package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
- package/dist/core/runner/prepare-gate-stations.js +32 -19
- package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
- package/dist/core/runner/prepare-hands-readface.js +64 -53
- package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
- package/dist/core/runner/prepare-inherited-gate.js +37 -23
- package/dist/core/runner/prepare-park-ask.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.js +2 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
- package/dist/core/runner/prepare-policy-chain.js +39 -12
- package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
- package/dist/core/runner/prepare-protocol-tools.js +13 -3
- package/dist/core/runner/prepare-run-refs.d.ts +6 -6
- package/dist/core/runner/prepare-run-refs.js +9 -4
- package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
- package/dist/core/runner/prepare-safety-scan.js +19 -3
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
- package/dist/core/runner/prepare-suspend-saga.js +6 -6
- package/dist/core/runner/prepare-task.js +51 -22
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
- package/dist/core/runner/prepare-wiring-manifest.js +5 -3
- package/dist/core/runner/read-root-grant.d.ts +9 -0
- package/dist/core/runner/read-root-grant.js +12 -0
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +24 -15
- package/dist/core/runner/run-notification-lane.js +1 -0
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/runtask.js +3 -0
- package/dist/core/runner/stream-mode-verb.d.ts +27 -0
- package/dist/core/runner/stream-mode-verb.js +19 -0
- package/dist/core/runner/turn-attachments.d.ts +3 -3
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -1
- package/dist/core/runner/write-protection-station.js +6 -3
- package/dist/core/runner-deps.d.ts +31 -34
- package/dist/core/runtime-caps.d.ts +12 -3
- package/dist/core/safety-axis-vocab.d.ts +2 -2
- package/dist/core/safety-axis-vocab.js +1 -1
- package/dist/core/secret-env.d.ts +16 -2
- package/dist/core/secret-env.js +25 -1
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +39 -30
- package/dist/core/spec-contract.d.ts +15 -0
- package/dist/core/spec-contract.js +19 -10
- package/dist/core/task-event.d.ts +1 -1
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +41 -89
- package/dist/core/task-stream.d.ts +16 -0
- package/dist/core/terminal-cause.js +1 -0
- package/dist/core/tighten-task-spec.d.ts +2 -2
- package/dist/core/tighten-task-spec.js +12 -14
- package/dist/core/tool-catalog-entries.js +4 -3
- package/dist/core/tool-policy.d.ts +54 -25
- package/dist/core/tool-policy.js +43 -2
- package/dist/core/tool-registry.d.ts +2 -0
- package/dist/core/tool-registry.js +3 -0
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +7 -6
- package/dist/core/types.d.ts +2 -1
- package/dist/core/wiring-manifest.d.ts +31 -5
- package/dist/core/wiring-manifest.js +28 -1
- package/dist/core/write-protect.d.ts +11 -1
- package/dist/core/write-protect.js +26 -15
- package/dist/engine/execution-env/kill-tree.js +4 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
- package/dist/engine/execution-env/node-execution-env.js +34 -35
- package/dist/engine/harness/types.d.ts +6 -7
- package/dist/engine/llm/types.d.ts +2 -4
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.js +12 -3
- package/dist/orchestration/run-spec.d.ts +4 -0
- package/dist/orchestration/run-spec.js +4 -3
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-size-guideline.js +2 -2
- package/dist/stores/file/fs-atomic.js +2 -1
- package/dist/tools/artifact/artifact-tool.d.ts +17 -0
- package/dist/tools/artifact/artifact-tool.js +16 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
- package/dist/tools/fs/bash-readonly-classifier.js +88 -0
- package/dist/tools/fs/containment-face.d.ts +9 -3
- package/dist/tools/fs/containment-face.js +2 -0
- package/dist/tools/fs/file-read-check.d.ts +49 -0
- package/dist/tools/fs/file-read-check.js +71 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-read.d.ts +1 -0
- package/dist/tools/fs/fs-search-tools.d.ts +2 -2
- package/dist/tools/fs/fs-search-tools.js +4 -4
- package/dist/tools/fs/fs-shared.js +20 -6
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +39 -3
- package/dist/tools/fs/index.js +38 -15
- package/dist/tools/fs/read-deny-home.d.ts +93 -0
- package/dist/tools/fs/read-deny-home.js +225 -0
- package/dist/tools/fs/read-deny.d.ts +176 -34
- package/dist/tools/fs/read-deny.js +268 -61
- package/dist/tools/fs/safety.d.ts +6 -1
- package/dist/tools/fs/safety.js +18 -5
- package/dist/tools/fs/search.d.ts +3 -0
- package/dist/tools/fs/search.js +47 -9
- package/dist/tools/fs/shell-read-check.d.ts +42 -11
- package/dist/tools/fs/shell-read-check.js +188 -13
- package/dist/tools/fs/shell-read-establish.d.ts +7 -3
- package/dist/tools/fs/shell-read-establish.js +134 -21
- package/dist/tools/fs/shell-read-identity.d.ts +7 -0
- package/dist/tools/fs/shell-read-identity.js +21 -0
- package/dist/tools/fs/shell-read-judge.d.ts +92 -32
- package/dist/tools/fs/shell-read-judge.js +43 -18
- package/dist/tools/fs/shell-read-plan.d.ts +4 -0
- package/dist/tools/fs/shell-read-plan.js +26 -4
- package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
- package/dist/tools/fs/shell-read-write-targets.js +173 -0
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +103 -11
- package/wire-changes/core@7.33.8.json +165 -0
- package/wire-changes/core@7.34.0.json +2621 -0
|
@@ -5,7 +5,10 @@ import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
|
5
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
6
6
|
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
7
7
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
8
|
+
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
8
9
|
import { thrownText } from "../thrown-value.js";
|
|
10
|
+
import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
11
|
+
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
|
|
9
12
|
export const PERSISTED_RULE_TOOL = "Bash";
|
|
10
13
|
const HANDOFF_BOUND = 256;
|
|
11
14
|
export function inheritedAskRuleEvidence(provider) {
|
|
@@ -59,10 +62,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
59
62
|
if (provider === undefined)
|
|
60
63
|
return { personal: undefined, org: undefined };
|
|
61
64
|
const anonymous = cfg.principal === undefined || cfg.principal === "";
|
|
62
|
-
const store =
|
|
63
|
-
?
|
|
64
|
-
provider.forLocalOwner()
|
|
65
|
-
: provider.forPrincipal(anonymous ? undefined : cfg.principal);
|
|
65
|
+
const store = ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared);
|
|
66
66
|
const handed = new Map();
|
|
67
67
|
const read = () => store.effective({ sessionId: cfg.sessionId }).then((view) => {
|
|
68
68
|
for (const line of view.disclosures)
|
|
@@ -112,43 +112,47 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
112
112
|
},
|
|
113
113
|
}
|
|
114
114
|
: undefined;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
view = await claim(req.toolCallId);
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
cfg.onDisclosure(thrownText(err));
|
|
129
|
-
return { unreadable: true };
|
|
130
|
-
}
|
|
131
|
-
if (view.unreadable !== undefined) {
|
|
132
|
-
cfg.onDisclosure(view.unreadable);
|
|
133
|
-
return { unreadable: true };
|
|
134
|
-
}
|
|
135
|
-
const table = view.rules;
|
|
136
|
-
const liveCwd = cfg.liveCwd();
|
|
137
|
-
if (grammar === "path") {
|
|
138
|
-
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
|
|
139
|
-
}
|
|
140
|
-
const command = req.args?.command;
|
|
141
|
-
if (typeof command !== "string")
|
|
142
|
-
return undefined;
|
|
143
|
-
const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
|
|
144
|
-
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
145
|
-
if (verdict !== undefined)
|
|
146
|
-
return persistedRuleAnswerOf(verdict);
|
|
147
|
-
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
148
|
-
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
149
|
-
},
|
|
115
|
+
const personalAdjudicate = async (req) => {
|
|
116
|
+
if (anonymous && !cfg.localOwnerDeclared)
|
|
117
|
+
return undefined;
|
|
118
|
+
const grammar = ruleToolGrammarOf(req.toolName);
|
|
119
|
+
if (grammar === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
let view;
|
|
122
|
+
try {
|
|
123
|
+
view = await withGateTimeout(claim(req.toolCallId), ORG_ADJUDICATION_TIMEOUT_MS, cfg.signal, "persistedRules");
|
|
150
124
|
}
|
|
151
|
-
|
|
125
|
+
catch (err) {
|
|
126
|
+
return gateWaitFailureOf(err) === "aborted" ? { unreadable: true } : skipped(thrownText(err));
|
|
127
|
+
}
|
|
128
|
+
if (view.unreadable !== undefined)
|
|
129
|
+
skipped(view.unreadable, view.unreadableSources);
|
|
130
|
+
try {
|
|
131
|
+
const answer = judgeAgainst(view.rules, req, grammar);
|
|
132
|
+
return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
const why = `judging this call against the rows threw: ${thrownText(err)}`;
|
|
136
|
+
cfg.onDisclosure(`the persisted rule lane could not judge a ${req.toolName} call against the rows it read — ${why}`);
|
|
137
|
+
return { unreadable: true, reason: why };
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const judgeAgainst = (table, req, grammar) => {
|
|
141
|
+
const liveCwd = cfg.liveCwd();
|
|
142
|
+
if (grammar === "path")
|
|
143
|
+
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
|
|
144
|
+
const command = req.args?.command;
|
|
145
|
+
if (typeof command !== "string")
|
|
146
|
+
return undefined;
|
|
147
|
+
const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
|
|
148
|
+
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
149
|
+
if (verdict !== undefined)
|
|
150
|
+
return persistedRuleAnswerOf(verdict);
|
|
151
|
+
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
152
|
+
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
153
|
+
};
|
|
154
|
+
const skipped = (why, sources) => (cfg.onDisclosure(why), cfg.onStoreUnreadable("call", sources), { unreadable: true });
|
|
155
|
+
const personal = provider.partitions.durable ? { adjudicate: personalAdjudicate } : undefined;
|
|
152
156
|
return { personal, org };
|
|
153
157
|
}
|
|
154
158
|
export function createRuleOffersOf(cfg) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The person's persisted PATH rows as a leg's read face takes them at prepare (the gate's rule lanes read the same store
|
|
3
|
+
* per call).
|
|
4
|
+
*/
|
|
5
|
+
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
6
|
+
import type { PathRuleDenyRow } from "../../tools/fs/read-deny.js";
|
|
7
|
+
import type { RunnerDeps } from "../runner-deps.js";
|
|
8
|
+
/** The person's persisted PATH rows a leg's read face takes at prepare ({@link persistedPathRowsOf}). */
|
|
9
|
+
export interface PersistedPathRows {
|
|
10
|
+
/** Every live `Read(//dir/**)` allow row's directory (lexical, absolute): a standing yes to reads under it. */
|
|
11
|
+
readonly readDirectories: readonly string[];
|
|
12
|
+
/** Every live face-keyed path DENY row (`Read(...)`, `Edit(...)`), as a deny-table row resolved at the task's bases. */
|
|
13
|
+
readonly denyRows: readonly PathRuleDenyRow[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The person's persisted PATH rows, read ONCE at prepare from the same store query the gate's lanes read per call: a
|
|
17
|
+
* `Read(//dir/**)` allow row is a READ DIRECTORY of the run (the Bash read station, Read / Grep / Glob — one set), and a
|
|
18
|
+
* `Read(...)` / `Edit(...)` deny row joins the run's read / write deny tables (every read face, the shell's file commands
|
|
19
|
+
* and traversals). One read, two directions, so a person's allow row never reaches further than their deny rows: a
|
|
20
|
+
* directory it admits still refuses the paths a deny row names. A row the store gains mid-run applies from the next
|
|
21
|
+
* prepare; the in-flight grant (`readRootGrant`) is the mid-run door. The store not armed, or the anonymous floor ⇒ no
|
|
22
|
+
* rows. A read that failed (or did not answer in time) ⇒ no rows, and a partition the read could not read ⇒ none of ITS
|
|
23
|
+
* rows (the partition that was read contributes its own): what could not be read is skipped for this run's tables — its
|
|
24
|
+
* directories widen nothing and its deny rows judge nothing here — disclosed on the trace and announced to the person
|
|
25
|
+
* (`onUnread`); the next prepare reads again, as it would for a row the store gained mid-run. A deny row whose
|
|
26
|
+
* pattern needs a base the task cannot supply (`~/` with no declared home) is left to the per-call lane and disclosed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function persistedPathRowsOf(cfg: {
|
|
29
|
+
provider: PermissionRuleStoreProvider | undefined;
|
|
30
|
+
localOwnerDeclared: boolean;
|
|
31
|
+
principal: string | undefined;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
root: string;
|
|
34
|
+
home: string | undefined;
|
|
35
|
+
onDisclosure: (message: string) => void;
|
|
36
|
+
/** The person is told the store (or the named sources of it) was skipped ({@link ruleStoreUnreadableNotice}). */
|
|
37
|
+
onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
38
|
+
/** The run's cut: a read that has not answered by then (or by the gate's await bound) reads as unreadable. */
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}): Promise<PersistedPathRows>;
|
|
41
|
+
/**
|
|
42
|
+
* The run's ONE `permission.rule_store_unavailable` notice (audience user): the person's rules — the whole store, or the
|
|
43
|
+
* sources of it whose partition could not be read — were skipped; minted by whichever read found it first (the prepare
|
|
44
|
+
* read of the path rows, or a call's own read on the rule lane), once per run however many reads fail after it (the
|
|
45
|
+
* prepare sentence says a later call may be judged without them too). The trace keeps every read's line.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice"], ids: {
|
|
48
|
+
readonly sessionId: string;
|
|
49
|
+
readonly runId: string;
|
|
50
|
+
}): (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
51
|
+
/** {@link persistedPathRowsOf} for one task's prepare: only where a read face mounts (hands), disclosed on the run's trace,
|
|
52
|
+
* cut by the run's own signals. `undefined` = nothing to read, returned synchronously: a run with no person's store (or
|
|
53
|
+
* no hands) gains no suspension point on its prepare. */
|
|
54
|
+
export declare function persistedPathRowsOfTask(t: {
|
|
55
|
+
deps: Pick<RunnerDeps, "permissionRuleStore" | "localOwnerRules" | "tracer">;
|
|
56
|
+
principal: string | undefined;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
root: string;
|
|
59
|
+
home: string | undefined;
|
|
60
|
+
taskId: string;
|
|
61
|
+
handsEnabled: boolean;
|
|
62
|
+
signals: readonly AbortSignal[];
|
|
63
|
+
onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
64
|
+
}): Promise<PersistedPathRows> | undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
2
|
+
import { READ_RULE_TOOL, eligiblePersisted, isUsablePathBase, ruleBasesNeeded } from "../permission-rule-model.js";
|
|
3
|
+
import { isAbsolutePathForm, pathFamilyOf } from "../../tools/fs/safety.js";
|
|
4
|
+
import { PATH_DENY_RULE_FACES, pathRuleDenyRowOf } from "../permission-rules.js";
|
|
5
|
+
import { inlineUntrusted } from "../untrusted-text.js";
|
|
6
|
+
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
7
|
+
import { thrownText } from "../thrown-value.js";
|
|
8
|
+
import { emitTrace } from "../trace.js";
|
|
9
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
10
|
+
export async function persistedPathRowsOf(cfg) {
|
|
11
|
+
const none = { readDirectories: [], denyRows: [] };
|
|
12
|
+
const provider = cfg.provider;
|
|
13
|
+
if (provider === undefined || !readsPersistedPathRows(provider, cfg.principal, cfg.localOwnerDeclared))
|
|
14
|
+
return none;
|
|
15
|
+
let view;
|
|
16
|
+
try {
|
|
17
|
+
view = await withGateTimeout(ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared).effective({ sessionId: cfg.sessionId }), DEFAULT_GATE_AWAIT_TIMEOUT_MS, cfg.signal, "persistedPathRows");
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
return gateWaitFailureOf(err) === "aborted" ? none : skipped(cfg, thrownText(err));
|
|
21
|
+
}
|
|
22
|
+
if (view.unreadable !== undefined)
|
|
23
|
+
skipped(cfg, view.unreadable, view.unreadableSources);
|
|
24
|
+
const call = { tool: READ_RULE_TOOL, cwd: cfg.root, sessionId: cfg.sessionId };
|
|
25
|
+
const bases = { root: cfg.root, ...(cfg.home !== undefined && isAbsolutePathForm(cfg.home) ? { home: cfg.home } : {}) };
|
|
26
|
+
const family = pathFamilyOf(bases);
|
|
27
|
+
const readDirectories = [];
|
|
28
|
+
const denyRows = [];
|
|
29
|
+
for (const rule of view.rules) {
|
|
30
|
+
if (rule.status !== "live")
|
|
31
|
+
continue;
|
|
32
|
+
if (rule.behavior === "allow" && rule.tool === READ_RULE_TOOL && rule.match === "subpath" && eligiblePersisted(rule, call, "allow")) {
|
|
33
|
+
if (!readDirectories.includes(rule.command))
|
|
34
|
+
readDirectories.push(rule.command);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const faces = PATH_DENY_RULE_FACES[rule.tool];
|
|
38
|
+
if (rule.behavior !== "deny" || faces === undefined || (rule.match !== "path" && rule.match !== "subpath") || !eligiblePersisted(rule, { ...call, tool: rule.tool }, "deny"))
|
|
39
|
+
continue;
|
|
40
|
+
const missing = ruleBasesNeeded(rule).filter((b) => !isUsablePathBase(b === "cwd" ? bases.root : bases[b], family));
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
cfg.onDisclosure(`the persisted deny rule ${inlineUntrusted(rule.rule, 200)} needs a ${missing.join(" / ")} base this task does not declare — the shell's file commands and the search tools do not judge it on this leg`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
denyRows.push(pathRuleDenyRowOf({ parsed: rule, ruleText: rule.rule, faces }, bases));
|
|
46
|
+
}
|
|
47
|
+
return { readDirectories, denyRows };
|
|
48
|
+
}
|
|
49
|
+
function skipped(cfg, why, sources) {
|
|
50
|
+
cfg.onDisclosure(`the persisted path rules could not be read at prepare, so this run's read directories and read / write deny tables take none of ${sources !== undefined ? `the ${sourcesWord(sources)} rows` : "their rows"} (the next prepare reads again): ${why}`);
|
|
51
|
+
cfg.onUnread("prepare", sources);
|
|
52
|
+
return { readDirectories: [], denyRows: [] };
|
|
53
|
+
}
|
|
54
|
+
function sourcesWord(sources) {
|
|
55
|
+
return sources.length <= 1 ? sources.join("") : `${sources.slice(0, -1).join(", ")} and ${sources[sources.length - 1]}`;
|
|
56
|
+
}
|
|
57
|
+
const SKIPPED_STORE_SENTENCE = {
|
|
58
|
+
prepare: (which) => `${which} could not be read when this run started, so this run's file and shell checks skip their path rows (Read / Edit deny rows and Read directory rows) — a deny row among them protects nothing on this run. Each call's own rule check reads the rules again (a call whose own read fails too is judged without them), and the next run reads the path rows again.`,
|
|
59
|
+
call: (which) => `${which} could not be read for a call on this run, so that call's own rule check skipped them: none of their allow, ask or deny rows took part in it, and a deny row among them protected nothing there (path rows the run took when it started still apply to its file and shell checks). Every later call reads them again, and they apply again as soon as a read succeeds.`,
|
|
60
|
+
};
|
|
61
|
+
export function ruleStoreUnreadableNotice(onNotice, ids) {
|
|
62
|
+
let sent = false;
|
|
63
|
+
return (at, sources) => {
|
|
64
|
+
if (sent)
|
|
65
|
+
return;
|
|
66
|
+
sent = true;
|
|
67
|
+
const which = sources !== undefined && sources.length > 0 ? `your persisted permission rules from the ${sourcesWord(sources)} sources` : "your persisted permission rules";
|
|
68
|
+
deliverEngineNotice(onNotice, { code: "permission.rule_store_unavailable", message: SKIPPED_STORE_SENTENCE[at](which), detail: { at, sessionId: ids.sessionId, runId: ids.runId } });
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function readsPersistedPathRows(provider, principal, localOwnerDeclared) {
|
|
72
|
+
return provider.partitions.durable && ((principal !== undefined && principal !== "") || localOwnerDeclared);
|
|
73
|
+
}
|
|
74
|
+
export function persistedPathRowsOfTask(t) {
|
|
75
|
+
const provider = t.deps.permissionRuleStore;
|
|
76
|
+
if (!t.handsEnabled || provider === undefined || !readsPersistedPathRows(provider, t.principal, t.deps.localOwnerRules === true))
|
|
77
|
+
return undefined;
|
|
78
|
+
return persistedPathRowsOf({
|
|
79
|
+
provider,
|
|
80
|
+
localOwnerDeclared: t.deps.localOwnerRules === true,
|
|
81
|
+
principal: t.principal,
|
|
82
|
+
sessionId: t.sessionId,
|
|
83
|
+
root: t.root,
|
|
84
|
+
home: t.home,
|
|
85
|
+
onDisclosure: (message) => emitTrace(t.deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: t.taskId, message, ts: Date.now() })),
|
|
86
|
+
onUnread: t.onUnread,
|
|
87
|
+
signal: AbortSignal.any([...t.signals]),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* publishes the declared store into the run's {@link ArtifactStoreRef} for that second point.
|
|
13
13
|
* Synchronous: the stretch has no await (the file read is a closure the tool calls later).
|
|
14
14
|
*/
|
|
15
|
+
import type { PermissionModeController } from "../mode-policy.js";
|
|
15
16
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
16
17
|
import type { OnAsk } from "../tool-policy.js";
|
|
17
18
|
import { type ArtifactPlane } from "../artifact-host.js";
|
|
@@ -22,7 +23,7 @@ import { type ArtifactStoreRef } from "./read-root-grant.js";
|
|
|
22
23
|
import type { Prepared } from "./contracts.js";
|
|
23
24
|
export interface PrepareArtifactInput {
|
|
24
25
|
/** borrowed-readonly — the deployment's hosting seat and the notice sink the refusal arm announces on. */
|
|
25
|
-
deps: Pick<RunnerDeps, "artifactHost" | "onNotice">;
|
|
26
|
+
deps: Pick<RunnerDeps, "artifactHost" | "onNotice" | "tracer">;
|
|
26
27
|
/** borrowed-readonly — the engine-minted run id: the host's `onMount` key and every call context's `runId`. */
|
|
27
28
|
runId: string;
|
|
28
29
|
/** borrowed-readonly — the host task id, the call context's audit attribution. */
|
|
@@ -34,6 +35,9 @@ export interface PrepareArtifactInput {
|
|
|
34
35
|
/** borrowed-readonly — the run's effective approver (`spec.onAsk ?? deps.onAsk`, the seat the tool gate resolves at):
|
|
35
36
|
* the tool's shared-read consent gate rides it as a mount dep (a core mount's execute ctx is minimal). */
|
|
36
37
|
onAsk: OnAsk | undefined;
|
|
38
|
+
/** borrowed-readonly — the leg's live permission-mode seat; the tool's shared-read consent gate arbitrates at it before
|
|
39
|
+
* the approver is consulted (a released read is delivered, undisclosed to nobody: the gate records it). */
|
|
40
|
+
permissionMode: PermissionModeController;
|
|
37
41
|
/** borrowed-readonly — the run's resolved env: the byte source of a published local file. */
|
|
38
42
|
executionEnv: ExecutionEnv;
|
|
39
43
|
/** borrowed-readonly — the canonical task root the fs fence contains reads to; undefined ⇒ no local-file publish. */
|
|
@@ -6,11 +6,12 @@ import { defineTool } from "../tools.js";
|
|
|
6
6
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
7
|
import { physicalPathOfExisting } from "../physical-path.js";
|
|
8
8
|
import { resolveKey, violationDetails, violationText } from "../../tools/fs/safety.js";
|
|
9
|
-
import { rootAdmittingArtifactStore } from "./read-root-grant.js";
|
|
9
|
+
import { rootAdmittingArtifactStore, withdrawStandingGrantsAdmitting } from "./read-root-grant.js";
|
|
10
|
+
import { emitTrace } from "../trace.js";
|
|
10
11
|
import { createArtifactTool } from "../../tools/artifact/artifact-tool.js";
|
|
11
12
|
import { thrownText } from "../thrown-value.js";
|
|
12
13
|
export function prepareArtifact(input) {
|
|
13
|
-
const { deps, runId, taskId, sessionId, agentName, onAsk, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, artifactStoreRef } = input;
|
|
14
|
+
const { deps, runId, taskId, sessionId, agentName, onAsk, permissionMode, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, artifactStoreRef } = input;
|
|
14
15
|
const host = deps.artifactHost;
|
|
15
16
|
if (host === undefined)
|
|
16
17
|
return { artifactPlane: undefined };
|
|
@@ -29,6 +30,11 @@ export function prepareArtifact(input) {
|
|
|
29
30
|
const storePhysical = storageDir !== undefined && rootCanonical !== undefined ? physicalPathOfExisting(storageDir) : undefined;
|
|
30
31
|
if (artifactStoreRef !== undefined)
|
|
31
32
|
artifactStoreRef.current = storePhysical;
|
|
33
|
+
if (artifactStoreRef !== undefined && storePhysical !== undefined) {
|
|
34
|
+
for (const entry of withdrawStandingGrantsAdmitting(storePhysical, artifactStoreRef)) {
|
|
35
|
+
emitTrace(deps.tracer, () => ({ kind: "config.additional_directory_skipped", version: 1, taskId, entry, field: "persistedReadRule", reason: `admits_artifact_store: the local artifact store ${storageDir} lies inside it or it inside the store — the Artifact tool's ledger and stored files stay outside every root the file tools reach`, ts: Date.now() }));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
32
38
|
if (storePhysical !== undefined && rootCanonical !== undefined) {
|
|
33
39
|
const overlapping = rootAdmittingArtifactStore(storePhysical, [rootCanonical, ...additionalRootsCanonical()]);
|
|
34
40
|
if (overlapping !== undefined)
|
|
@@ -60,7 +66,7 @@ export function prepareArtifact(input) {
|
|
|
60
66
|
return { ok: false, reason: "too_large", size: r.value.byteLength, limit: maxBytes, judgedOn: "bytes" };
|
|
61
67
|
return { ok: true, key: rk.key, bytes: r.value };
|
|
62
68
|
};
|
|
63
|
-
const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile, ...(onAsk !== undefined ? { onAsk } : {}) });
|
|
69
|
+
const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile, ...(onAsk !== undefined ? { onAsk } : {}), permissionMode: () => permissionMode.current, ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}) });
|
|
64
70
|
roster.mount(firstPartyOffload(defineTool(artifactSpec)), { source: "builtin", mountedBy: "optional" });
|
|
65
71
|
envHandToolNames.add(ARTIFACT_TOOL_NAME);
|
|
66
72
|
return { artifactPlane: verdict.capabilities.plane };
|
|
@@ -83,9 +83,11 @@ export interface PrepareAskLaneInput {
|
|
|
83
83
|
egress?: boolean;
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
|
-
/** borrowed-mutable — the auto-mode denial tracker
|
|
87
|
-
* window on it iff the seat about to be called is live (the tracker's one writer). */
|
|
88
|
-
autoModeDenialTracking:
|
|
86
|
+
/** borrowed-mutable — the auto-mode denial tracker's live seat (undefined until an arming). Writer here: the carry mint
|
|
87
|
+
* arms the auto-deny window on it iff the seat about to be called is live (the tracker's one writer). */
|
|
88
|
+
autoModeDenialTracking: {
|
|
89
|
+
readonly current: AutoModeDenialTracker | undefined;
|
|
90
|
+
};
|
|
89
91
|
/** borrowed-readonly — the REBOUND spec; only `taskId` is read (the late-settlement observer's tag). */
|
|
90
92
|
spec: Pick<TaskSpec, "taskId">;
|
|
91
93
|
/** borrowed-readonly — the deployment seats this lane reports on, as a Pick over the SAME `deps` object (receiver
|
|
@@ -103,7 +103,7 @@ export function prepareAskLane(input) {
|
|
|
103
103
|
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
104
104
|
...(decision.action === "ask" ? askMandateMember(decision) : {}),
|
|
105
105
|
...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
|
|
106
|
-
...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking) : {}),
|
|
106
|
+
...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking.current) : {}),
|
|
107
107
|
...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
|
|
108
108
|
...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
|
|
109
109
|
...(decision.action === "ask" && decision.probeReadRootCandidate !== undefined ? { readRootCandidate: decision.probeReadRootCandidate } : {}),
|
|
@@ -24,6 +24,7 @@ import { forkGovernanceDenial } from "../../agents/subagent.js";
|
|
|
24
24
|
import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
|
|
25
25
|
import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
26
26
|
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
27
|
+
import type { PermissionModeController } from "../mode-policy.js";
|
|
27
28
|
import { type AutoModeDecider, type AutoModeDenialTracker } from "../auto-mode.js";
|
|
28
29
|
import { type CheckpointSeat } from "../checkpoint-seat.js";
|
|
29
30
|
import { type ComplianceCapability } from "../compliance.js";
|
|
@@ -109,10 +110,23 @@ export interface PrepareCapsAndWorkflowInput {
|
|
|
109
110
|
requestReview: (opts?: {
|
|
110
111
|
reason?: string;
|
|
111
112
|
}) => void;
|
|
112
|
-
/** borrowed-readonly — the plan-mode primitive the EnterPlanMode tool calls
|
|
113
|
-
enterPlanMode: () => void
|
|
114
|
-
/** borrowed-readonly — the
|
|
115
|
-
|
|
113
|
+
/** borrowed-readonly — the plan-mode primitive the EnterPlanMode tool calls: a steer of the leg's mode seat into `plan`. */
|
|
114
|
+
enterPlanMode: () => Promise<void>;
|
|
115
|
+
/** borrowed-readonly — the leg's live permission-mode seat: the arming's first arm is `declared === "auto"`, and the
|
|
116
|
+
* arming facts (the deny bits, the face, the re-entrant arm) are bound onto it here. */
|
|
117
|
+
permissionMode: PermissionModeController;
|
|
118
|
+
/** borrowed-readonly — LATE-BOUND: the effective read-deny table the hands phase compiles (the rows the classifier's deny
|
|
119
|
+
* paragraph renders); read at ARMING, which the driver runs after that phase — never at this phase's own call. */
|
|
120
|
+
readDenyEntries: () => {
|
|
121
|
+
readonly entries: ReadonlyArray<{
|
|
122
|
+
readonly pattern: string;
|
|
123
|
+
readonly caseSensitive?: boolean;
|
|
124
|
+
}>;
|
|
125
|
+
readonly ruleRows: ReadonlyArray<{
|
|
126
|
+
readonly tool: string;
|
|
127
|
+
readonly pattern: string;
|
|
128
|
+
}>;
|
|
129
|
+
};
|
|
116
130
|
/** borrowed-readonly — the peer lane's mount verdict (the classifier's cross-session rule fills only while mounted). */
|
|
117
131
|
peerLaneActive: boolean;
|
|
118
132
|
/** borrowed-readonly — whether the BUILT-IN SendMessage is on this face (the lane rule's second term). */
|
|
@@ -153,15 +167,26 @@ export interface PrepareCapsAndWorkflowResult {
|
|
|
153
167
|
agentForkDenial: ReturnType<typeof forkGovernanceDenial>;
|
|
154
168
|
/** owned — the observers grant (`allowObservers === true`, explicit opt-in). */
|
|
155
169
|
observersActive: boolean;
|
|
156
|
-
/** owned — the arming outcome as a closed reason (the manifest's auto-mode face)
|
|
157
|
-
|
|
158
|
-
autoModeArmReason: AutoModeArmFact;
|
|
159
|
-
/** owned — the armed classifier
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
/** owned — the arming outcome as a closed reason (the manifest's auto-mode face), read LIVE: a leg steered into `auto`
|
|
171
|
+
* after prepare arms then, and the fact follows. */
|
|
172
|
+
autoModeArmReason: () => AutoModeArmFact;
|
|
173
|
+
/** owned — the armed classifier's live seat (`current` undefined until an arming succeeds — at prepare for an `auto`
|
|
174
|
+
* leg, or at a steer into `auto`); every consumer reads `current` at its own call. */
|
|
175
|
+
autoModeDeciderRef: {
|
|
176
|
+
readonly current: AutoModeDecider | undefined;
|
|
177
|
+
};
|
|
178
|
+
/** owned — the denial tracker built beside the decider (same face, same arming), same live seat. */
|
|
179
|
+
autoModeDenialTrackingRef: {
|
|
180
|
+
readonly current: AutoModeDenialTracker | undefined;
|
|
181
|
+
};
|
|
182
|
+
/** owned — the serializable arming recipe (persistArming opt-in only), same live seat. */
|
|
183
|
+
autoModeArmingRef: {
|
|
184
|
+
readonly current: AutoModeArmingRecipe | undefined;
|
|
185
|
+
};
|
|
186
|
+
/** owned — arm the classifier for THIS leg (idempotent; a no-op unless the effective mode is `auto` and the face and the
|
|
187
|
+
* deny bit admit it). The driver calls it once after the hands phase (the deny table must exist first); the mode seat
|
|
188
|
+
* calls it again on a steer into `auto`. */
|
|
189
|
+
armAutoMode: () => Promise<void>;
|
|
165
190
|
/** owned — self-orchestration is ACTIVE for this leg (deployment ready ∧ not entitlement-denied); drives the prompt injection. */
|
|
166
191
|
selfOrchestrationActive: boolean;
|
|
167
192
|
/** owned — the Workflow tool is on the roster (the delegation surface's second door). */
|
|
@@ -192,6 +217,7 @@ declare const RUNTIME_CAP_KEYS: {
|
|
|
192
217
|
readonly forceDurableGate: true;
|
|
193
218
|
readonly autoMode: true;
|
|
194
219
|
readonly autoModeDenySource: true;
|
|
220
|
+
readonly bypassPermissions: true;
|
|
195
221
|
readonly allowMemoryOptOut: true;
|
|
196
222
|
};
|
|
197
223
|
export type RuntimeCapKeysCoverEveryCap = AssertAllKeysHandled<Exclude<keyof RuntimeCaps, keyof typeof RUNTIME_CAP_KEYS>>;
|
|
@@ -7,8 +7,9 @@ import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js"
|
|
|
7
7
|
import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-runner.js";
|
|
8
8
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
9
9
|
import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { AUTO_MODE_CLASSIFIER_MAX_RETRIES } from "../auto-mode-defaults.js";
|
|
11
|
+
import { buildAutoModePrompt, readDenyClassifierRules, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
|
|
12
|
+
import { autoModeClassifierFallbackOf, createAutoModeDecider, createAutoModeDenialTracker, screenAutoModeKnobs } from "../auto-mode.js";
|
|
12
13
|
import { checkpointStoreOfSeat } from "../checkpoint-seat.js";
|
|
13
14
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, resolveComplianceDenies } from "../compliance.js";
|
|
14
15
|
import { createPresentPlanTool, createEnterPlanModeTool } from "../present-plan-tool.js";
|
|
@@ -16,7 +17,7 @@ import { brainToRuntime } from "../runtime.js";
|
|
|
16
17
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
17
18
|
import { defineTool, isDefineToolProduct, rebindDefineToolCtx } from "../tools.js";
|
|
18
19
|
const MOUNT_REMEDY = "author it as a plain-object ToolSpec (own `name` and `execute`), hand the defineTool product itself, or wrap a product through the supported wrapper form (a fresh object re-stamped with `stampDefineToolBrand` whose rebind re-wraps the rebound product)";
|
|
19
|
-
import {
|
|
20
|
+
import { isAutoModeDenySource } from "../runtime-caps.js";
|
|
20
21
|
import { derivedRouteFallsBack } from "./derived-route-fallback.js";
|
|
21
22
|
import { CLASSIFIER_ATTEMPT_SUFFIX, CLASSIFIER_THINK_SEAT, announceClassifierRoleDefaulted, classifierLadderRungs, classifierReplyAsksAgain, classifierReplyFailure, classifierReplyVerdictText, classifierRoundObserver, classifierRungSeat, classifierSeatModel } from "./classifier-ladder.js";
|
|
22
23
|
import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
|
|
@@ -38,6 +39,7 @@ const RUNTIME_CAP_KEYS = {
|
|
|
38
39
|
forceDurableGate: true,
|
|
39
40
|
autoMode: true,
|
|
40
41
|
autoModeDenySource: true,
|
|
42
|
+
bypassPermissions: true,
|
|
41
43
|
allowMemoryOptOut: true,
|
|
42
44
|
};
|
|
43
45
|
function snapshotRuntimeCaps(record) {
|
|
@@ -54,7 +56,7 @@ function screenAutoModeCap(caps, onError, sessionId) {
|
|
|
54
56
|
return { caps, faulted: false };
|
|
55
57
|
if (typeof caps !== "object" || caps === null || Array.isArray(caps)) {
|
|
56
58
|
onError?.(new Error(`runtimeCapsResolver returned a non-record value (${JSON.stringify(caps)}) — read as a resolver fault: every per-principal capability is DENIED for this run (auto mode is not armed; asks flow the original chain). Return a RuntimeCaps object or undefined.`), { phase: "config", sessionId });
|
|
57
|
-
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, faulted: true };
|
|
59
|
+
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false, bypassPermissions: false }, faulted: true };
|
|
58
60
|
}
|
|
59
61
|
let record;
|
|
60
62
|
try {
|
|
@@ -62,22 +64,34 @@ function screenAutoModeCap(caps, onError, sessionId) {
|
|
|
62
64
|
}
|
|
63
65
|
catch (err) {
|
|
64
66
|
onError?.(new Error(`runtimeCapsResolver's record threw while being read (${thrownText(err)}) — read as a resolver fault: every per-principal capability is DENIED for this run (auto mode is not armed; asks flow the original chain).`), { phase: "config", sessionId });
|
|
65
|
-
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, faulted: true };
|
|
67
|
+
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false, bypassPermissions: false }, faulted: true };
|
|
66
68
|
}
|
|
69
|
+
let screened = record;
|
|
70
|
+
let faulted = false;
|
|
71
|
+
const fault = (message) => {
|
|
72
|
+
onError?.(new Error(`runtimeCapsResolver returned ${message}`), { phase: "config", sessionId });
|
|
73
|
+
faulted = true;
|
|
74
|
+
};
|
|
67
75
|
const { autoMode, autoModeDenySource } = record;
|
|
68
76
|
if (autoModeDenySource !== undefined && (!isAutoModeDenySource(autoModeDenySource) || autoMode !== false)) {
|
|
69
|
-
|
|
70
|
-
const { autoModeDenySource: _refused, ...rest } =
|
|
71
|
-
|
|
77
|
+
fault(`autoModeDenySource=${JSON.stringify(autoModeDenySource)} beside autoMode=${JSON.stringify(autoMode)} — read as a resolver fault: auto mode is DENIED for this run (the classifier is not armed; asks flow to the approver / park)`);
|
|
78
|
+
const { autoModeDenySource: _refused, ...rest } = screened;
|
|
79
|
+
screened = { ...rest, autoMode: false };
|
|
72
80
|
}
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
else if (autoMode !== undefined && typeof autoMode !== "boolean") {
|
|
82
|
+
fault(`a non-boolean autoMode (${JSON.stringify(autoMode)}) — read as a resolver fault: auto mode is DENIED for this run (the classifier is not armed; asks flow the same way they do with no auto-mode face)`);
|
|
83
|
+
screened = { ...screened, autoMode: false };
|
|
84
|
+
}
|
|
85
|
+
if (record.bypassPermissions !== undefined && typeof record.bypassPermissions !== "boolean") {
|
|
86
|
+
fault(`a non-boolean bypassPermissions (${JSON.stringify(record.bypassPermissions)}) — read as a resolver fault: bypassPermissions is DENIED for this run (a requested bypass folds back to default)`);
|
|
87
|
+
screened = { ...screened, bypassPermissions: false };
|
|
88
|
+
}
|
|
89
|
+
return { caps: screened, faulted };
|
|
77
90
|
}
|
|
78
|
-
function mintPersistedArming(am, laneRule, classifierSystemPrompt, onError, sessionId) {
|
|
91
|
+
function mintPersistedArming(am, laneRule, classifierSystemPrompt, derivedDenyRules, onError, sessionId) {
|
|
79
92
|
const arming = autoModeArmingRecipeOf(laneRule ? { ...am, crossSessionMessagesRule: true } : am, {
|
|
80
93
|
promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
|
|
94
|
+
derivedDenyRules,
|
|
81
95
|
});
|
|
82
96
|
if (arming === undefined) {
|
|
83
97
|
onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
|
|
@@ -100,7 +114,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
|
|
|
100
114
|
return { armed: true, reason: "armed" };
|
|
101
115
|
}
|
|
102
116
|
export async function prepareCapsAndWorkflow(input) {
|
|
103
|
-
const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode,
|
|
117
|
+
const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
|
|
104
118
|
const roster = new RosterBuilder(toolEffects);
|
|
105
119
|
for (const t of spec.tools ?? []) {
|
|
106
120
|
const mounted = mountCallerTool(t);
|
|
@@ -151,7 +165,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
151
165
|
}
|
|
152
166
|
catch (err) {
|
|
153
167
|
deps.onError?.(asThrownError(err), { phase: "config", sessionId });
|
|
154
|
-
runtimeCaps = { allowWorkflows: false, allowFork: false, autoMode: false };
|
|
168
|
+
runtimeCaps = { allowWorkflows: false, allowFork: false, autoMode: false, bypassPermissions: false };
|
|
155
169
|
runtimeCapsFaulted = true;
|
|
156
170
|
}
|
|
157
171
|
const screened = screenAutoModeCap(runtimeCaps, deps.onError, sessionId);
|
|
@@ -193,11 +207,17 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
193
207
|
}
|
|
194
208
|
const agentForkDenial = forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork);
|
|
195
209
|
const observersActive = runtimeCaps?.allowObservers === true;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const autoModeArmReason = autoModeArmReasonOf(
|
|
200
|
-
if (
|
|
210
|
+
const autoModeDeciderRef = { current: undefined };
|
|
211
|
+
const autoModeDenialTrackingRef = { current: undefined };
|
|
212
|
+
const autoModeArmingRef = { current: undefined };
|
|
213
|
+
const autoModeArmReason = () => autoModeArmReasonOf(permissionMode.declared === "auto", deps.autoMode !== undefined, runtimeCaps, runtimeCapsFaulted);
|
|
214
|
+
if (deps.autoMode !== undefined)
|
|
215
|
+
screenAutoModeKnobs(deps.autoMode);
|
|
216
|
+
const armAutoMode = async () => {
|
|
217
|
+
if (autoModeDeciderRef.current !== undefined)
|
|
218
|
+
return;
|
|
219
|
+
if (permissionMode.declared !== "auto" || deps.autoMode === undefined || runtimeCaps?.autoMode === false)
|
|
220
|
+
return;
|
|
201
221
|
const am = deps.autoMode;
|
|
202
222
|
const classifierSeat = classifierSeatModel({ deps, main: model });
|
|
203
223
|
let classifierModel = classifierSeat.model;
|
|
@@ -206,11 +226,14 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
206
226
|
}
|
|
207
227
|
announceClassifierRoleDefaulted({ onNotice: deps.onNotice, seat: { ...classifierSeat, model: classifierModel }, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) });
|
|
208
228
|
const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
|
|
209
|
-
const
|
|
229
|
+
const table = readDenyEntries();
|
|
230
|
+
const derivedDenyRules = readDenyClassifierRules(table.entries, table.ruleRows);
|
|
231
|
+
let classifierSystemPromptMemo;
|
|
232
|
+
const classifierSystemPrompt = () => (classifierSystemPromptMemo ??= buildAutoModePrompt({ ...am, derivedDenyRules, ...(classifierLaneRule ? { crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : {}) }));
|
|
210
233
|
const classifierRuntime = brainToRuntime(deps.brain);
|
|
211
234
|
const classifierRungs = classifierLadderRungs(am.classifierFallback, classifierModel, model);
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
const denialTracking = createAutoModeDenialTracker(am.denialLimit);
|
|
236
|
+
const decider = createAutoModeDecider({
|
|
214
237
|
...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
|
|
215
238
|
candidates: classifierRungs.map((m) => m.id),
|
|
216
239
|
onClassified: classifierRoundObserver({ tracer: deps.tracer, onNotice: deps.onNotice, taskId: hostTaskId, seatModelId: classifierModel.id, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) }),
|
|
@@ -229,8 +252,9 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
229
252
|
}
|
|
230
253
|
const classifierAuth = await spec.getApiKeyAndHeaders?.(rungModel);
|
|
231
254
|
const request = async (seat, suffix) => {
|
|
232
|
-
const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
|
|
255
|
+
const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt(), messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
|
|
233
256
|
signal,
|
|
257
|
+
maxRetries: AUTO_MODE_CLASSIFIER_MAX_RETRIES,
|
|
234
258
|
...seat,
|
|
235
259
|
...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
|
|
236
260
|
...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
|
|
@@ -246,9 +270,14 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
246
270
|
return classifierReplyVerdictText(response);
|
|
247
271
|
},
|
|
248
272
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
273
|
+
const arming = am.persistArming === true ? mintPersistedArming(am, classifierLaneRule, classifierSystemPrompt(), derivedDenyRules, deps.onError, sessionId) : undefined;
|
|
274
|
+
if (autoModeDeciderRef.current !== undefined)
|
|
275
|
+
return;
|
|
276
|
+
autoModeDeciderRef.current = decider;
|
|
277
|
+
autoModeDenialTrackingRef.current = denialTracking;
|
|
278
|
+
autoModeArmingRef.current = arming;
|
|
279
|
+
};
|
|
280
|
+
permissionMode.bindArming({ caps: runtimeCaps, autoFacePresent: deps.autoMode !== undefined, armAuto: armAutoMode, onArmFault: (e) => deps.onError?.(asThrownError(e), { phase: "config", sessionId }) });
|
|
252
281
|
const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
|
|
253
282
|
const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
|
|
254
283
|
if (deploymentWorkflowReady && runtimeCaps?.allowWorkflows === false) {
|
|
@@ -313,7 +342,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
313
342
|
const co = memoryEngineSession?.captureOptOut;
|
|
314
343
|
return assembleParentCaptureState(co?.optedOut() ?? false, co?.indeterminate() ?? false, ctl, [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }]);
|
|
315
344
|
},
|
|
316
|
-
autoModeReview: () => (
|
|
345
|
+
autoModeReview: () => (autoModeDeciderRef.current !== undefined ? { decider: autoModeDeciderRef.current } : undefined),
|
|
317
346
|
workflowDepth: internals?.workflowDepth,
|
|
318
347
|
parentCwd: taskRootFinal,
|
|
319
348
|
parentThinking: () => childThinkingSeat(undefined, deploymentSubagentThinking(runnerSelf.agentCatalog?.roles), harnessRef.current?.getThinkingLevel() ?? thinking).thinking,
|
|
@@ -332,5 +361,5 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
332
361
|
inheritedGateForChildren,
|
|
333
362
|
}), { source: "builtin", mountedBy: "workflow" });
|
|
334
363
|
}
|
|
335
|
-
return { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason,
|
|
364
|
+
return { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDeciderRef, autoModeDenialTrackingRef, autoModeArmingRef, armAutoMode, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline };
|
|
336
365
|
}
|