@sema-agent/core 5.55.0 → 5.57.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 +140 -0
- package/dist/agents/send-message-tool.d.ts +11 -0
- package/dist/agents/send-message-tool.js +81 -13
- package/dist/agents/subagent.js +250 -89
- package/dist/agents/team.d.ts +10 -1
- package/dist/agents/team.js +1 -0
- package/dist/brain/anthropic.js +15 -5
- package/dist/brain/circuit-breaker.js +2 -1
- package/dist/brain/degrading.js +4 -1
- package/dist/brain/failover.js +16 -1
- package/dist/brain/open-responses.js +15 -5
- package/dist/brain/openai.js +16 -5
- package/dist/brain/request-params.d.ts +30 -27
- package/dist/brain/request-params.js +1 -7
- package/dist/brain/route-adjudicator.d.ts +190 -0
- package/dist/brain/route-adjudicator.js +189 -0
- package/dist/brain/route-conformance.d.ts +55 -0
- package/dist/brain/route-conformance.js +136 -0
- package/dist/brain/routing.js +8 -3
- package/dist/core/auto-compaction.d.ts +17 -4
- package/dist/core/auto-compaction.js +3 -0
- package/dist/core/context-edit.d.ts +55 -6
- package/dist/core/context-edit.js +12 -1
- package/dist/core/hooks.d.ts +293 -11
- package/dist/core/hooks.js +158 -11
- package/dist/core/human-input-projection.d.ts +20 -2
- package/dist/core/human-input-projection.js +9 -0
- package/dist/core/mcp.js +4 -4
- package/dist/core/memory-engine/engine.d.ts +15 -5
- package/dist/core/memory-engine/engine.js +3 -1
- package/dist/core/permission-rule-consent.d.ts +45 -0
- package/dist/core/permission-rule-consent.js +40 -11
- package/dist/core/permission-rule-model.d.ts +110 -75
- package/dist/core/permission-rule-model.js +61 -28
- package/dist/core/permission-rules.d.ts +23 -15
- package/dist/core/permission-rules.js +40 -31
- package/dist/core/runner/prepare-task.d.ts +8 -0
- package/dist/core/runner/prepare-task.js +66 -26
- package/dist/core/runner/runtask.d.ts +4 -1
- package/dist/core/runner/runtask.js +206 -21
- package/dist/core/runner/session-rule-policy.js +5 -5
- package/dist/core/scheduler.d.ts +5 -0
- package/dist/core/session-reconcile.d.ts +32 -0
- package/dist/core/session-reconcile.js +15 -0
- package/dist/core/side-query.d.ts +12 -5
- package/dist/core/task-notification.d.ts +34 -7
- package/dist/core/task-notification.js +11 -1
- package/dist/core/task-registry-agent.d.ts +20 -3
- package/dist/core/task-registry-agent.js +31 -2
- package/dist/core/tool-policy.d.ts +14 -9
- package/dist/core/tool-policy.js +27 -22
- package/dist/core/types.d.ts +69 -11
- package/dist/core/untrusted-text.js +8 -0
- package/dist/engine/compaction/compaction.d.ts +77 -7
- package/dist/engine/compaction/compaction.js +98 -9
- package/dist/engine/compaction/utils.d.ts +4 -0
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/harness/agent-harness.d.ts +84 -0
- package/dist/engine/harness/agent-harness.js +114 -13
- package/dist/engine/harness/messages.d.ts +4 -2
- package/dist/engine/harness/messages.js +7 -2
- package/dist/engine/harness/types.d.ts +16 -6
- package/dist/engine/llm/types.d.ts +65 -0
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/engine/session/import-validate.js +10 -0
- package/dist/engine/session/session.js +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/run-spec.js +8 -1
- package/dist/prompts/default.d.ts +12 -6
- package/dist/prompts/default.js +2 -0
- package/dist/scenarios/scenario-registry.d.ts +5 -1
- package/dist/scenarios/scenario-registry.js +4 -2
- package/dist/tools/fs/index.js +8 -1
- package/dist/tools/scheduler-tools.js +28 -6
- package/dist/tools/web.d.ts +15 -0
- package/dist/tools/web.js +8 -2
- package/dist/tools/worktree.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +19 -1
|
@@ -20,26 +20,12 @@ export const SHELL_RESERVED_WORDS = new Set([
|
|
|
20
20
|
"function", "if", "in", "select", "then", "time", "until", "while",
|
|
21
21
|
]);
|
|
22
22
|
export const SUGGESTION_LEXICON = [
|
|
23
|
-
"git status", "git log", "git diff", "git show", "git branch", "git checkout", "git switch",
|
|
24
|
-
"git add", "git commit", "git push", "git pull", "git fetch", "git merge", "git rebase",
|
|
25
|
-
"git tag", "git blame", "git describe", "git cherry-pick", "git restore", "git reset",
|
|
26
|
-
"git rev-parse", "git ls-files", "git grep",
|
|
27
23
|
"git stash list", "git stash show", "git stash push", "git stash pop", "git stash drop",
|
|
28
24
|
"git remote show", "git worktree list", "git submodule update", "git submodule status",
|
|
29
|
-
"
|
|
30
|
-
"npm pack", "npm publish", "npm version", "npm why",
|
|
31
|
-
"pnpm run", "pnpm test", "pnpm ls", "pnpm outdated", "pnpm audit", "pnpm why",
|
|
32
|
-
"yarn run", "yarn test", "yarn workspaces list",
|
|
33
|
-
"cargo build", "cargo test", "cargo run", "cargo check", "cargo clippy", "cargo fmt",
|
|
34
|
-
"cargo doc", "cargo tree", "cargo bench", "cargo update", "cargo metadata",
|
|
35
|
-
"go build", "go test", "go vet", "go fmt", "go doc",
|
|
25
|
+
"yarn workspaces list",
|
|
36
26
|
"go mod tidy", "go mod download", "go mod verify", "go mod graph",
|
|
37
|
-
"docker ps", "docker images", "docker logs", "docker inspect", "docker build", "docker pull",
|
|
38
|
-
"docker push", "docker stop", "docker start", "docker restart", "docker rm", "docker rmi",
|
|
39
27
|
"docker compose up", "docker compose down", "docker compose ps", "docker compose logs",
|
|
40
28
|
"docker compose build", "docker compose pull",
|
|
41
|
-
"kubectl get", "kubectl describe", "kubectl logs", "kubectl apply", "kubectl delete",
|
|
42
|
-
"kubectl diff", "kubectl explain", "kubectl top",
|
|
43
29
|
"kubectl rollout status", "kubectl rollout restart", "kubectl rollout history",
|
|
44
30
|
"gh pr view", "gh pr list", "gh pr diff", "gh pr checks", "gh pr status", "gh pr create",
|
|
45
31
|
"gh pr merge", "gh issue view", "gh issue list", "gh issue create", "gh repo view",
|
|
@@ -48,6 +34,31 @@ export const SUGGESTION_LEXICON = [
|
|
|
48
34
|
"gh search repos", "gh search issues", "gh search prs", "gh search code",
|
|
49
35
|
];
|
|
50
36
|
const LEXICON_BODIES = SUGGESTION_LEXICON.map((b) => b.split(" "));
|
|
37
|
+
const GENERIC_SUBCOMMAND_WORD = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
38
|
+
const SCREENED_HEAD_NAMES = new Set([...BARE_INTERPRETER_NAMES].map((n) => n.toLowerCase()));
|
|
39
|
+
const TRAILING_DOTS = /\.+$/;
|
|
40
|
+
const EXECUTABLE_SUFFIX = /\.(?:exe|com|bat|cmd)$/;
|
|
41
|
+
const PROPOSABLE_HEAD_NAME = /^[A-Za-z0-9._+-]+$/;
|
|
42
|
+
function screenedHeadName(head) {
|
|
43
|
+
let name = commandBasename(head).toLowerCase();
|
|
44
|
+
for (;;) {
|
|
45
|
+
const peeled = name.replace(TRAILING_DOTS, "").replace(EXECUTABLE_SUFFIX, "");
|
|
46
|
+
if (peeled === name || peeled === "")
|
|
47
|
+
return name;
|
|
48
|
+
name = peeled;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function genericPrefixBody(tokens) {
|
|
52
|
+
const head = tokens[0];
|
|
53
|
+
const second = tokens[1];
|
|
54
|
+
if (head === undefined || second === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (!PROPOSABLE_HEAD_NAME.test(commandBasename(head)))
|
|
57
|
+
return undefined;
|
|
58
|
+
if (SCREENED_HEAD_NAMES.has(screenedHeadName(head)))
|
|
59
|
+
return undefined;
|
|
60
|
+
return GENERIC_SUBCOMMAND_WORD.test(second) ? `${head} ${second}` : undefined;
|
|
61
|
+
}
|
|
51
62
|
function longestReviewedBody(tokens) {
|
|
52
63
|
let best;
|
|
53
64
|
for (const words of LEXICON_BODIES) {
|
|
@@ -151,6 +162,9 @@ export function escapeForDisclosure(value) {
|
|
|
151
162
|
});
|
|
152
163
|
return escaped.length <= DISCLOSED_RULE_TEXT_MAX_CHARS ? escaped : `${escaped.slice(0, DISCLOSED_RULE_TEXT_MAX_CHARS)}…`;
|
|
153
164
|
}
|
|
165
|
+
export function hasUnrenderableCharacters(text) {
|
|
166
|
+
return CONTROL_CHARS_RE.test(text);
|
|
167
|
+
}
|
|
154
168
|
export function parseAllowRuleText(text, opts) {
|
|
155
169
|
if (text.length > MAX_RULE_TEXT_CHARS) {
|
|
156
170
|
return reject("invalid.too_long", `rule text exceeds ${MAX_RULE_TEXT_CHARS} characters`);
|
|
@@ -178,12 +192,11 @@ export function parseAllowRuleText(text, opts) {
|
|
|
178
192
|
? reject("invalid.empty_command", `rule "${text}" names no command`)
|
|
179
193
|
: reject("invalid.not_simple_command", `rule "${text}" is not a command this lane can name (${shape.reject})`);
|
|
180
194
|
}
|
|
181
|
-
|
|
182
|
-
return reject("invalid.not_simple_command", `prefix rule "${text}" names more than one command — a prefix admits anything appended to its body, so only the exact form may name a connector chain`);
|
|
183
|
-
}
|
|
184
|
-
const head = commandBasename(shape.names[0] ?? "");
|
|
195
|
+
const head = commandBasename(shape.names[shape.names.length - 1] ?? "");
|
|
185
196
|
if (match === "prefix" && BARE_INTERPRETER_NAMES.has(head) && opts?.direction !== "tighten") {
|
|
186
|
-
return reject("invalid.bare_interpreter_prefix",
|
|
197
|
+
return reject("invalid.bare_interpreter_prefix", shape.segments.length > 1
|
|
198
|
+
? `prefix rule "${text}" leaves its final segment open-ended under the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`
|
|
199
|
+
: `prefix rule "${text}" is headed by the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`);
|
|
187
200
|
}
|
|
188
201
|
const command = foldSpacing(body);
|
|
189
202
|
if (command === undefined) {
|
|
@@ -209,7 +222,10 @@ function admitsUnder(rule, command, reading) {
|
|
|
209
222
|
return false;
|
|
210
223
|
if (rule.match === "exact")
|
|
211
224
|
return folded === rule.command;
|
|
212
|
-
|
|
225
|
+
const bodyShape = ruleLaneShapeOf(rule.command, reading);
|
|
226
|
+
if ("reject" in bodyShape)
|
|
227
|
+
return false;
|
|
228
|
+
if (shape.segments.length !== bodyShape.segments.length)
|
|
213
229
|
return false;
|
|
214
230
|
return folded === rule.command || folded.startsWith(rule.command + " ");
|
|
215
231
|
}
|
|
@@ -259,13 +275,30 @@ export function suggestRulesForCommand(command) {
|
|
|
259
275
|
if ("rule" in exact && exact.rule.match === "exact" && ruleAdmitsCommand(exact.rule, command)) {
|
|
260
276
|
out.push({ rule: exact.rule.rule, match: "exact", command: exact.rule.command });
|
|
261
277
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
278
|
+
const offerPrefix = (body) => {
|
|
279
|
+
if (body === undefined)
|
|
280
|
+
return;
|
|
281
|
+
const text = formatAllowRuleText(body, "prefix");
|
|
282
|
+
const parsed = parseAllowRuleText(text);
|
|
283
|
+
if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, command)) {
|
|
284
|
+
out.push({ rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command });
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const tokensOf = (s) => s.split(/\s+/).filter((t) => t !== "");
|
|
288
|
+
const bodyOf = (tokens) => longestReviewedBody(tokens) ?? genericPrefixBody(tokens);
|
|
289
|
+
if (out.length === 1) {
|
|
290
|
+
if (shape.segments.length === 1) {
|
|
291
|
+
offerPrefix(bodyOf(tokensOf(folded)));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const foldedShape = ruleLaneShapeOf(folded, MATCH_READING);
|
|
295
|
+
if (!("reject" in foldedShape) && foldedShape.segments.length === shape.segments.length) {
|
|
296
|
+
const rawLast = foldedShape.segments[foldedShape.segments.length - 1] ?? "";
|
|
297
|
+
const lastBody = bodyOf(tokensOf(rawLast));
|
|
298
|
+
if (lastBody !== undefined) {
|
|
299
|
+
const cut = folded.length - rawLast.length + (rawLast.length - rawLast.trimStart().length);
|
|
300
|
+
offerPrefix(folded.slice(0, cut) + lastBody);
|
|
301
|
+
}
|
|
269
302
|
}
|
|
270
303
|
}
|
|
271
304
|
}
|
|
@@ -46,6 +46,13 @@ export interface PermissionRuleIssue {
|
|
|
46
46
|
/** design/179 D7: a Bash prefix rule under `bashPrefixLane:"rules-store"` — reported so a migration
|
|
47
47
|
* report can tell "route this through the rule store" apart from "this is a mistake". */
|
|
48
48
|
| "unsupported.bash_prefix_rules_store" | "unsupported.file_glob" | "unsupported.mcp_paren"
|
|
49
|
+
/** A parenthesised rule whose NAME is a covering spelling in a non-MCP protocol namespace
|
|
50
|
+
* (`a2a__<peer>__*(x:y)`, `a2a__<peer>(x:y)`). The param DSL is keyed by EXACT tool name, and a
|
|
51
|
+
* covering spelling is not one — no mounted tool can ever bear it, so the rule compiled clean and
|
|
52
|
+
* matched nothing. Additive member; the MCP spelling keeps its own {@link
|
|
53
|
+
* PermissionRuleIssue.code} `unsupported.mcp_paren` (that refusal is wider — it covers exact MCP
|
|
54
|
+
* names too, for CC parity — and fires first, so no MCP rule's reported code changes). */
|
|
55
|
+
| "unsupported.covering_paren"
|
|
49
56
|
/** HRD-PRM-7: a param-level rule naming a parameter the target tool does not carry. Additive member —
|
|
50
57
|
* the lane that used to compile silently and never match (see {@link FILE_TOOL_PARAMS}). */
|
|
51
58
|
| "unsupported.unknown_param";
|
|
@@ -105,27 +112,28 @@ export declare function parsePermissionRule(rule: string): ParsedPermissionRule;
|
|
|
105
112
|
*/
|
|
106
113
|
export declare function wildcardMatch(pattern: string, value: string): boolean;
|
|
107
114
|
/**
|
|
108
|
-
* Does
|
|
115
|
+
* Does a namespaced RULE name cover `toolName`? — the tail branch of CC's rule matcher `VRp`
|
|
109
116
|
* (:595780), which is UNCONDITIONAL there (its `globMatching` option gates only the whole-name glob
|
|
110
117
|
* branch, so CC's allow lane — `mVo` :595803, which passes no options — matches MCP segments too).
|
|
111
|
-
* Three covering forms, matching CC's three disjuncts:
|
|
112
|
-
* ·
|
|
113
|
-
* ·
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* ·
|
|
118
|
+
* Three covering forms, matching CC's three disjuncts (`<ns>` = any protocol-table prefix):
|
|
119
|
+
* · `<ns>__<peer>` — every tool of that peer (rule tool segment absent);
|
|
120
|
+
* · `<ns>__<peer>__*` — the same set, spelled with the star CC's own settings validator
|
|
121
|
+
* recommends (`"MCP rules do not support patterns in parentheses …
|
|
122
|
+
* use mcp__srv__*"`);
|
|
123
|
+
* · `<ns>__<peer>__get_*` — a glob over the tool segment.
|
|
117
124
|
* A LITERAL tool segment is deliberately NOT covered here: it is an exact tool name, and the exact
|
|
118
125
|
* table already answers it — the same split CC makes with its leading `ruleName === toolName`.
|
|
119
126
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
127
|
+
* Rule and target must be in the SAME namespace, and peer segments compare EXACTLY (CC
|
|
128
|
+
* `s.serverName === a.serverName`). A rule must therefore spell the peer the way the mint does; that
|
|
129
|
+
* is the same string an operator reads off any mounted tool name, and inventing a fuzzier comparison
|
|
130
|
+
* here would let one rule reach a peer — or a protocol — it does not name.
|
|
123
131
|
*/
|
|
124
|
-
export declare function
|
|
125
|
-
/** True ⇔ this rule name is
|
|
126
|
-
* i.e. the shapes {@link
|
|
127
|
-
* keys rules by exact tool name needs this to know which
|
|
128
|
-
export declare function
|
|
132
|
+
export declare function namespacedRuleNameCovers(ruleName: string, toolName: string): boolean;
|
|
133
|
+
/** True ⇔ this rule name is a namespaced name whose reach is a SET of tools rather than one exact tool
|
|
134
|
+
* — i.e. the shapes {@link namespacedRuleNameCovers} answers and an exact name table cannot. Every
|
|
135
|
+
* lane that keys rules by exact tool name needs this to know which entries it must NOT key that way. */
|
|
136
|
+
export declare function isNamespacedCoveringRuleName(name: string): boolean;
|
|
129
137
|
/** Exported for the lockstep guard only (see {@link FILE_TOOL_PARAMS}) — not part of the rule DSL. */
|
|
130
138
|
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
131
139
|
/** Dry-run 校验/迁移报告(codex 127 审 B5):不 throw,返回全部 issues(含 `unsupported.*` 分类,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
1
|
+
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
2
2
|
export const BASH_GENERIC_PARAMS = new Set(["command", "timeout", "description", "run_in_background"]);
|
|
3
3
|
const DEFAULT_CAPS = {
|
|
4
4
|
maxRules: 256,
|
|
@@ -77,31 +77,34 @@ export function wildcardMatch(pattern, value) {
|
|
|
77
77
|
p++;
|
|
78
78
|
return p === pattern.length;
|
|
79
79
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
80
|
+
function parseNamespacedRuleName(name) {
|
|
81
|
+
const ns = protocolOf(name);
|
|
82
|
+
if (ns === undefined)
|
|
82
83
|
return undefined;
|
|
83
|
-
const segments = name.slice(
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
84
|
+
const segments = name.slice(ns.prefix.length).split("__");
|
|
85
|
+
const peer = segments[0];
|
|
86
|
+
if (peer === undefined || peer === "")
|
|
86
87
|
return undefined;
|
|
87
88
|
const rest = segments.slice(1);
|
|
88
|
-
return rest.length > 0 ? {
|
|
89
|
+
return rest.length > 0 ? { protocol: ns.id, peer, tool: rest.join("__") } : { protocol: ns.id, peer };
|
|
89
90
|
}
|
|
90
|
-
export function
|
|
91
|
-
const rule =
|
|
91
|
+
export function namespacedRuleNameCovers(ruleName, toolName) {
|
|
92
|
+
const rule = parseNamespacedRuleName(ruleName);
|
|
92
93
|
if (rule === undefined)
|
|
93
94
|
return false;
|
|
94
|
-
const target =
|
|
95
|
+
const target = parseNamespacedRuleName(toolName);
|
|
95
96
|
if (target === undefined)
|
|
96
97
|
return false;
|
|
97
|
-
if (rule.
|
|
98
|
+
if (rule.protocol !== target.protocol)
|
|
99
|
+
return false;
|
|
100
|
+
if (rule.peer !== target.peer)
|
|
98
101
|
return false;
|
|
99
102
|
if (rule.tool === undefined || rule.tool === "*")
|
|
100
103
|
return true;
|
|
101
104
|
return target.tool !== undefined && rule.tool.includes("*") && wildcardMatch(rule.tool, target.tool);
|
|
102
105
|
}
|
|
103
|
-
export function
|
|
104
|
-
const parsed =
|
|
106
|
+
export function isNamespacedCoveringRuleName(name) {
|
|
107
|
+
const parsed = parseNamespacedRuleName(name);
|
|
105
108
|
return parsed !== undefined && (parsed.tool === undefined || parsed.tool.includes("*"));
|
|
106
109
|
}
|
|
107
110
|
const PRIMARY_FIELDS = {
|
|
@@ -138,13 +141,13 @@ function countStars(s) {
|
|
|
138
141
|
}
|
|
139
142
|
function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
140
143
|
const byTool = new Map();
|
|
141
|
-
const
|
|
144
|
+
const namespacedCovering = { deny: [], ask: [], allow: [] };
|
|
142
145
|
const issues = [];
|
|
143
146
|
const bad = (rule, code, message) => {
|
|
144
147
|
issues.push({ rule, code, message });
|
|
145
148
|
};
|
|
146
149
|
if (rules.length > caps.maxRules) {
|
|
147
|
-
return { byTool,
|
|
150
|
+
return { byTool, namespacedCovering, issues: [{ rule: "", code: "invalid.cap_exceeded", message: `${rules.length} rules > maxRules ${caps.maxRules}` }] };
|
|
148
151
|
}
|
|
149
152
|
for (const r of rules) {
|
|
150
153
|
const text = r.rule;
|
|
@@ -171,14 +174,20 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
const toolName = parsed.toolName;
|
|
174
|
-
|
|
177
|
+
const parenthesised = parsed.ruleContent !== undefined || indexOfUnescaped(text, "(") !== -1;
|
|
178
|
+
if (toolName.startsWith(MCP_NAMESPACE.prefix) && parenthesised) {
|
|
175
179
|
bad(text, "unsupported.mcp_paren", "MCP rules do not support patterns in parentheses (CC parity); use the toolAxes system");
|
|
176
180
|
continue;
|
|
177
181
|
}
|
|
182
|
+
if (parenthesised && isNamespacedCoveringRuleName(toolName)) {
|
|
183
|
+
bad(text, "unsupported.covering_paren", `"${toolName}" reaches a SET of tools, and the generic param DSL is keyed by exact tool name — a parenthesised rule on it can never match. ` +
|
|
184
|
+
`Write the parenthesised rule against one exact tool name, or drop the parentheses to keep the covering rule.`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
178
187
|
const entry = byTool.get(toolName) ?? { bare: {}, param: { deny: [], ask: [] } };
|
|
179
188
|
if (parsed.ruleContent === undefined) {
|
|
180
|
-
if (
|
|
181
|
-
|
|
189
|
+
if (isNamespacedCoveringRuleName(toolName)) {
|
|
190
|
+
namespacedCovering[r.behavior].push({ ruleName: toolName, ruleText: text, ...(r.source !== undefined ? { source: r.source } : {}) });
|
|
182
191
|
continue;
|
|
183
192
|
}
|
|
184
193
|
entry.bare[r.behavior] = { ruleText: text, source: r.source };
|
|
@@ -238,7 +247,7 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
|
238
247
|
entry.param[r.behavior].push({ param, pattern, behavior: r.behavior, ruleText: text, source: r.source });
|
|
239
248
|
byTool.set(toolName, entry);
|
|
240
249
|
}
|
|
241
|
-
return { byTool,
|
|
250
|
+
return { byTool, namespacedCovering, issues };
|
|
242
251
|
}
|
|
243
252
|
export function validatePermissionRules(rules, opts) {
|
|
244
253
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
@@ -272,7 +281,7 @@ function ruleMessage(kind, ruleText, source) {
|
|
|
272
281
|
}
|
|
273
282
|
export function createPermissionRulePolicy(rules, opts) {
|
|
274
283
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
275
|
-
const { byTool,
|
|
284
|
+
const { byTool, namespacedCovering, issues } = compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.bashPrefixLane ?? "reject");
|
|
276
285
|
const setLevelCap = issues.some((i) => i.code === "invalid.cap_exceeded" && i.rule === "");
|
|
277
286
|
if (issues.length > 0 && ((opts?.onInvalidRule ?? "throw") === "throw" || setLevelCap)) {
|
|
278
287
|
const e = new Error(`createPermissionRulePolicy: ${issues.length} invalid/unsupported rule(s):\n` +
|
|
@@ -307,23 +316,23 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
307
316
|
nameSets.allow.push(toolName);
|
|
308
317
|
}
|
|
309
318
|
for (const behavior of ["deny", "ask", "allow"]) {
|
|
310
|
-
for (const r of
|
|
319
|
+
for (const r of namespacedCovering[behavior])
|
|
311
320
|
nameSets[behavior].push(r.ruleName);
|
|
312
321
|
}
|
|
313
|
-
const coveringHit = (lane, toolName) => lane.length === 0 ? undefined : lane.find((r) =>
|
|
314
|
-
const hasCovering =
|
|
322
|
+
const coveringHit = (lane, toolName) => lane.length === 0 ? undefined : lane.find((r) => namespacedRuleNameCovers(r.ruleName, toolName));
|
|
323
|
+
const hasCovering = namespacedCovering.deny.length > 0 || namespacedCovering.ask.length > 0 || namespacedCovering.allow.length > 0;
|
|
315
324
|
return {
|
|
316
325
|
nameSets: [nameSets],
|
|
317
326
|
check(req) {
|
|
318
327
|
const entry = byTool.get(req.toolName);
|
|
319
|
-
const covering = hasCovering && req.toolName
|
|
328
|
+
const covering = hasCovering && protocolOf(req.toolName) !== undefined;
|
|
320
329
|
if (entry?.bare.deny) {
|
|
321
330
|
return { action: "deny", message: ruleMessage("denied", entry.bare.deny.ruleText, entry.bare.deny.source) };
|
|
322
331
|
}
|
|
323
332
|
if (covering) {
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
326
|
-
return { action: "deny", message: ruleMessage("denied",
|
|
333
|
+
const coveringDeny = coveringHit(namespacedCovering.deny, req.toolName);
|
|
334
|
+
if (coveringDeny)
|
|
335
|
+
return { action: "deny", message: ruleMessage("denied", coveringDeny.ruleText, coveringDeny.source) };
|
|
327
336
|
}
|
|
328
337
|
if (entry) {
|
|
329
338
|
const paramDeny = matchParamRules(entry.param.deny, req.args, caps.maxScalarValueChars);
|
|
@@ -335,9 +344,9 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
335
344
|
}
|
|
336
345
|
}
|
|
337
346
|
if (covering) {
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
340
|
-
return { action: "ask", message: ruleMessage("flagged",
|
|
347
|
+
const coveringAsk = coveringHit(namespacedCovering.ask, req.toolName);
|
|
348
|
+
if (coveringAsk)
|
|
349
|
+
return { action: "ask", message: ruleMessage("flagged", coveringAsk.ruleText, coveringAsk.source), matchedAskRule: coveringAsk.ruleText };
|
|
341
350
|
}
|
|
342
351
|
if (entry) {
|
|
343
352
|
const paramAsk = matchParamRules(entry.param.ask, req.args, caps.maxScalarValueChars);
|
|
@@ -348,7 +357,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
348
357
|
return { action: "allow" };
|
|
349
358
|
}
|
|
350
359
|
}
|
|
351
|
-
if (covering && coveringHit(
|
|
360
|
+
if (covering && coveringHit(namespacedCovering.allow, req.toolName)) {
|
|
352
361
|
return { action: "allow" };
|
|
353
362
|
}
|
|
354
363
|
if (defaultAction === "allow")
|
|
@@ -314,6 +314,14 @@ export interface Prepared {
|
|
|
314
314
|
* leg always knows its identity.
|
|
315
315
|
*/
|
|
316
316
|
hookIdentity: HookInvocationIdentity;
|
|
317
|
+
/**
|
|
318
|
+
* The per-invocation TIME BOUND every hook seat of this leg runs under (`Hooks.timeoutMs`, already
|
|
319
|
+
* validated — a garbage value was refused to the default and disclosed ONCE, here, rather than on
|
|
320
|
+
* every tool call). Published for the same reason `hookIdentity` is: the seats live in two files, and
|
|
321
|
+
* a number each station re-derived would be two answers to one wiring question — including two
|
|
322
|
+
* chances to re-refuse the same bad value.
|
|
323
|
+
*/
|
|
324
|
+
hookTimeoutMs: number;
|
|
317
325
|
promptManifest: {
|
|
318
326
|
constitution: "core" | "replaced" | "provider-assembled";
|
|
319
327
|
blocks: Array<{
|
|
@@ -30,16 +30,17 @@ import { isValidReminderMark, mintReminderMark, reminderMarkDeclaration } from "
|
|
|
30
30
|
import { policyAskClassOf } from "../ask-class.js";
|
|
31
31
|
import { emitTrace } from "../trace.js";
|
|
32
32
|
import { createSessionRulePolicy, PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
|
|
33
|
-
import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, mintHookInvocationIdentity, persistedRuleMandateOf, runToolGate } from "../hooks.js";
|
|
33
|
+
import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, mintHookInvocationIdentity, persistedRuleMandateOf, resolveHookTimeoutMs, runHookSeat, hookSeatExpiredError, runToolGate } from "../hooks.js";
|
|
34
34
|
import { createWriteProtectionCheck } from "../write-protect.js";
|
|
35
35
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
36
36
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
37
37
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
38
|
+
import { adjudicateDerivedRoute, fallbackToPrimaryNotice, sameRouteIdentity } from "../../brain/route-adjudicator.js";
|
|
38
39
|
import { STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
|
|
39
40
|
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
40
41
|
import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
41
42
|
import { protocolOf } from "../protocol-table.js";
|
|
42
|
-
import {
|
|
43
|
+
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
|
|
43
44
|
import { pathToUri } from "../lsp-protocol.js";
|
|
44
45
|
import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
|
|
45
46
|
import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
|
|
@@ -302,6 +303,20 @@ function raceAbort(p, signal, onAbort) {
|
|
|
302
303
|
p.then(finish, () => finish(onAbort()));
|
|
303
304
|
});
|
|
304
305
|
}
|
|
306
|
+
function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
|
|
307
|
+
if (hooks?.permissionDenied === undefined)
|
|
308
|
+
return undefined;
|
|
309
|
+
return async (payload) => {
|
|
310
|
+
try {
|
|
311
|
+
const seat = await runHookSeat("permissionDenied", { timeoutMs: hookTimeoutMs, signal }, (sig) => hooks.permissionDenied({ ...payload, signal: sig }));
|
|
312
|
+
if (seat.expired)
|
|
313
|
+
report(hookSeatExpiredError("permissionDenied", hookTimeoutMs, seat.cause, "the deny observation was abandoned; the deny itself is unchanged"));
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
report(err);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
305
320
|
function mcpRevocationWiring(deps) {
|
|
306
321
|
if (deps.mcpRevocations === undefined)
|
|
307
322
|
return undefined;
|
|
@@ -458,6 +473,20 @@ async function adoptReminderMark(session, sessionId, seedMark, spawnMark, onErro
|
|
|
458
473
|
}
|
|
459
474
|
return { reminderMark, reminderDisclosureCounts: {} };
|
|
460
475
|
}
|
|
476
|
+
async function derivedRouteFallsBack(args) {
|
|
477
|
+
try {
|
|
478
|
+
if (sameRouteIdentity(args.derived, args.primary))
|
|
479
|
+
return false;
|
|
480
|
+
const verdict = await adjudicateDerivedRoute({ brain: args.brain, model: args.derived, getApiKeyAndHeaders: args.getApiKeyAndHeaders });
|
|
481
|
+
if (verdict === undefined || verdict.ok)
|
|
482
|
+
return false;
|
|
483
|
+
deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict }));
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
461
490
|
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
|
|
462
491
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
463
492
|
spec = doors.spec;
|
|
@@ -475,7 +504,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
475
504
|
const sessionId = acquired.sessionId;
|
|
476
505
|
const hostTaskId = spec.taskId ?? sessionId;
|
|
477
506
|
const delegation = effectiveDelegationFacts(internals, resume?.seed.isDelegatedChild);
|
|
478
|
-
|
|
507
|
+
let effectiveCompModel = compModel;
|
|
508
|
+
if (spec.compactionModel === undefined &&
|
|
509
|
+
compModel !== undefined &&
|
|
510
|
+
(await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice }))) {
|
|
511
|
+
effectiveCompModel = undefined;
|
|
512
|
+
}
|
|
513
|
+
warnCompactionWindowHazard(deps.tracer, spec, model, effectiveCompModel, hostTaskId);
|
|
479
514
|
const taskScope = internals?.registryScope ?? spec.principal ?? "default";
|
|
480
515
|
internals?.peerSelfRef?.addAxis("s", sessionId);
|
|
481
516
|
internals?.peerSelfRef?.addAxis("t", hostTaskId);
|
|
@@ -859,7 +894,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
859
894
|
pc.autoMode?.decider === autoModeDecider)
|
|
860
895
|
? [
|
|
861
896
|
{
|
|
862
|
-
policy: createPreToolUseConstraintPolicy(ownPreToolUse, hookEnvFace, notifyOwnHookCrash),
|
|
897
|
+
policy: createPreToolUseConstraintPolicy(ownPreToolUse, hookEnvFace, notifyOwnHookCrash, hookTimeoutMs),
|
|
863
898
|
preToolUse: ownPreToolUse,
|
|
864
899
|
...(hookEnvSource !== undefined ? { hookEnv: hookEnvSource } : {}),
|
|
865
900
|
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
@@ -1059,6 +1094,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1059
1094
|
catch {
|
|
1060
1095
|
classifierModel = model;
|
|
1061
1096
|
}
|
|
1097
|
+
if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice })) {
|
|
1098
|
+
classifierModel = model;
|
|
1099
|
+
}
|
|
1062
1100
|
const classifierSystemPrompt = buildAutoModePrompt(am);
|
|
1063
1101
|
const classifierRuntime = brainToRuntime(deps.brain);
|
|
1064
1102
|
autoModeDecider = createAutoModeDecider({
|
|
@@ -1069,7 +1107,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1069
1107
|
const ctx = await session.buildContext();
|
|
1070
1108
|
const known = ctx.messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult");
|
|
1071
1109
|
const userPrompt = renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
|
|
1072
|
-
const
|
|
1110
|
+
const classifierAuth = await spec.getApiKeyAndHeaders?.(classifierModel);
|
|
1111
|
+
const response = await classifierRuntime.completeSimple(classifierModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
|
|
1112
|
+
signal,
|
|
1113
|
+
...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
|
|
1114
|
+
...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
|
|
1115
|
+
});
|
|
1073
1116
|
return response.content
|
|
1074
1117
|
.filter((c) => c.type === "text")
|
|
1075
1118
|
.map((c) => c.text)
|
|
@@ -1398,7 +1441,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1398
1441
|
owner: hostTaskId,
|
|
1399
1442
|
scope: taskScope,
|
|
1400
1443
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1401
|
-
...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}),
|
|
1444
|
+
...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}), ...(spec.oneShot !== undefined ? { oneShot: spec.oneShot } : {}),
|
|
1402
1445
|
...(internals?.onSubagentSpawn !== undefined ? { sink: internals.onSubagentSpawn } : {}),
|
|
1403
1446
|
...(internals?.parentNotify !== undefined
|
|
1404
1447
|
? { uplink: internals.parentNotify, ...(internals.parentPeerRef !== undefined ? { uplinkRecipient: internals.parentPeerRef } : {}) }
|
|
@@ -2711,7 +2754,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2711
2754
|
for (const n of list ?? []) {
|
|
2712
2755
|
if (known.has(n))
|
|
2713
2756
|
continue;
|
|
2714
|
-
if (
|
|
2757
|
+
if (isNamespacedCoveringRuleName(n) && [...known].some((k) => namespacedRuleNameCovers(n, k)))
|
|
2715
2758
|
continue;
|
|
2716
2759
|
const retired = RETIRED_TOOL_NAMES.get(n);
|
|
2717
2760
|
if (retired !== undefined) {
|
|
@@ -2795,6 +2838,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2795
2838
|
const denyNarrowingPolicy = narrowingLayers.length === 0 ? undefined : narrowingLayers.length === 1 ? narrowingLayers[0] : combinePolicies(...narrowingLayers);
|
|
2796
2839
|
const basePolicyForResumeEdit = lockedPreflight.basePolicyForResumeEdit ?? lockedPreflight.toolPolicy;
|
|
2797
2840
|
const hooks = spec.hooks ?? deps.hooks;
|
|
2841
|
+
const hookTimeoutMs = resolveHookTimeoutMs(hooks?.timeoutMs, notifyOwnHookCrash, hooks);
|
|
2798
2842
|
const preToolUseObservational = hooks?.preToolUse !== undefined && hooks.preToolUseObservational === true;
|
|
2799
2843
|
const ownGatePreToolUse = !preToolUseObservational
|
|
2800
2844
|
? hooks?.preToolUse
|
|
@@ -3571,21 +3615,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3571
3615
|
}
|
|
3572
3616
|
return resolved;
|
|
3573
3617
|
};
|
|
3574
|
-
const
|
|
3575
|
-
const notifyPermissionDenied = hooksWithPermissionDenied
|
|
3576
|
-
? async (payload) => {
|
|
3577
|
-
try {
|
|
3578
|
-
await hooksWithPermissionDenied.permissionDenied(payload);
|
|
3579
|
-
}
|
|
3580
|
-
catch (err) {
|
|
3581
|
-
try {
|
|
3582
|
-
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
3583
|
-
}
|
|
3584
|
-
catch {
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
: undefined;
|
|
3618
|
+
const notifyPermissionDenied = createDenyObserverNotifier(hooks, hookTimeoutMs, abortController.signal, notifyOwnHookCrash);
|
|
3589
3619
|
const notifyHookError = (err) => {
|
|
3590
3620
|
try {
|
|
3591
3621
|
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
@@ -4302,6 +4332,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4302
4332
|
identity: hookIdentity,
|
|
4303
4333
|
reminderMark,
|
|
4304
4334
|
preToolUse: ownGatePreToolUse,
|
|
4335
|
+
hookTimeoutMs,
|
|
4336
|
+
...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
|
|
4305
4337
|
...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
|
|
4306
4338
|
adjudicate,
|
|
4307
4339
|
resolveAsk: resolveAskBound,
|
|
@@ -4418,7 +4450,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4418
4450
|
}
|
|
4419
4451
|
catch {
|
|
4420
4452
|
}
|
|
4421
|
-
const
|
|
4453
|
+
const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(e.toolName, e.input, {
|
|
4422
4454
|
error: e.content
|
|
4423
4455
|
.filter((c) => c.type === "text")
|
|
4424
4456
|
.map((c) => c.text)
|
|
@@ -4426,7 +4458,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4426
4458
|
isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
|
|
4427
4459
|
content: e.content.map((c) => ({ ...c })),
|
|
4428
4460
|
details: clonedDetails,
|
|
4429
|
-
}, { toolCallId: e.toolCallId, toolName: e.toolName, ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity });
|
|
4461
|
+
}, { toolCallId: e.toolCallId, toolName: e.toolName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
|
|
4462
|
+
if (failureSeat.expired) {
|
|
4463
|
+
notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
|
|
4464
|
+
}
|
|
4465
|
+
const patch = failureSeat.expired ? undefined : failureSeat.value;
|
|
4430
4466
|
if (patch?.additionalContext) {
|
|
4431
4467
|
content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
|
|
4432
4468
|
changed = true;
|
|
@@ -4434,7 +4470,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4434
4470
|
}
|
|
4435
4471
|
}
|
|
4436
4472
|
else if (hooks?.postToolUse) {
|
|
4437
|
-
const
|
|
4473
|
+
const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
|
|
4474
|
+
if (successSeat.expired) {
|
|
4475
|
+
notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
|
|
4476
|
+
}
|
|
4477
|
+
const patch = successSeat.expired ? undefined : successSeat.value;
|
|
4438
4478
|
if (patch?.updatedOutput) {
|
|
4439
4479
|
content = patch.updatedOutput;
|
|
4440
4480
|
changed = true;
|
|
@@ -4722,7 +4762,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4722
4762
|
const effectiveReadFaceObserved = carrierReadFace();
|
|
4723
4763
|
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
|
|
4724
4764
|
const preparedHolder = {};
|
|
4725
|
-
const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4765
|
+
const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4726
4766
|
const prepared = buildPrepared();
|
|
4727
4767
|
preparedHolder.current = prepared;
|
|
4728
4768
|
return prepared;
|
|
@@ -219,7 +219,10 @@ export declare class Runner {
|
|
|
219
219
|
* - `tiers` omitted ⇒ the current tier bindings are kept (and re-applied over the new models);
|
|
220
220
|
* explicitly passed (including `undefined`) ⇒ replaced.
|
|
221
221
|
* - Success is announced via `config.models_swapped` (models/tiers counts — key material only,
|
|
222
|
-
* never the catalog itself).
|
|
222
|
+
* never the catalog itself). A swap may additionally emit the advisory
|
|
223
|
+
* `route.base_url_changed_key_unchanged` notice (deliberately conservative — see its entry in
|
|
224
|
+
* the notice directory) for same-name entries whose URL moved while the Model-visible
|
|
225
|
+
* credential fingerprint did not.
|
|
223
226
|
*/
|
|
224
227
|
swapModels(next: {
|
|
225
228
|
models: Record<string, Model>;
|