@sema-agent/core 7.44.0 → 7.46.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 +418 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +65 -38
- package/dist/agents/tool-filter.d.ts +82 -5
- package/dist/agents/tool-filter.js +134 -4
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +30 -4
- package/dist/core/allow-entry.js +32 -5
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +11 -5
- package/dist/core/checkpoint-store.d.ts +41 -4
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +143 -10
- package/dist/core/effective-path-target.js +113 -9
- package/dist/core/engine-notice.d.ts +25 -3
- package/dist/core/engine-notice.js +12 -6
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/layout.d.ts +5 -1
- package/dist/core/memory-engine/layout.js +1 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -36
- package/dist/core/permission-rules.js +50 -40
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +110 -0
- package/dist/core/runner/child-tree.js +107 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +54 -2
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +6 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +11 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +5 -0
- package/dist/core/runner/prepare-park-ask.js +6 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
- package/dist/core/runner/prepare-policy-chain.js +14 -5
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +41 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +4 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +45 -12
- package/dist/core/tool-policy.js +18 -20
- package/dist/core/tool-spec.d.ts +23 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +3 -2
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +83 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +49 -3
- package/wire-changes/core@7.45.0.json +1084 -0
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The roster a host declares for a delegation tool's `extraTools` factory (`SubagentToolOptions.extraToolNames`): every tool
|
|
3
|
+
* the factory may inject at a spawn, by the names it answers to and its content class. Declared, the delegation tool's pool can
|
|
4
|
+
* be listed again — the judgment of a denied-tools list and the memory content-origin reader read it exactly as they read the
|
|
5
|
+
* static pool. Each spawn then checks the factory's products against it: a product the roster does not declare (a name or an
|
|
6
|
+
* alias no entry carries), or declares nearer to home than the product says it is (its content class further out than the
|
|
7
|
+
* entry's), refuses that spawn. The roster is an upper bound: a declared tool the factory did not inject this time is fine.
|
|
8
|
+
*
|
|
9
|
+
* One code for every way the declaration fails, `config.extra_tool_names`, with the arm on `detail.reason`: `shape` (the value
|
|
10
|
+
* is not a roster — refused where the delegation tool is built), `undeclared_name` / `undeclared_origin` (a product the roster
|
|
11
|
+
* does not cover — refused at the spawn). A host value is never printed or converted: a sentence names an entry's place and
|
|
12
|
+
* the TYPE of what it found there.
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolContentOrigin, ToolSpec } from "../core/types.js";
|
|
15
|
+
export declare const EXTRA_TOOL_NAMES_CODE = "config.extra_tool_names";
|
|
16
|
+
/** Which way a declaration failed (`detail.reason`). */
|
|
17
|
+
export type ExtraToolNamesReason = "shape" | "undeclared_name" | "undeclared_origin";
|
|
18
|
+
/** One declared tool, as the delegation tool keeps it (a frozen copy of the host's entry). */
|
|
19
|
+
export interface ExtraToolDeclaration {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly aliases?: readonly string[];
|
|
22
|
+
readonly contentOrigin?: ToolContentOrigin;
|
|
23
|
+
}
|
|
24
|
+
/** The error a failed declaration is refused with: the code, and the arm on `detail.reason`. */
|
|
25
|
+
export declare function extraToolNamesError(message: string, reason: ExtraToolNamesReason): Error & {
|
|
26
|
+
code: string;
|
|
27
|
+
detail: {
|
|
28
|
+
reason: ExtraToolNamesReason;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The host's declaration, read ONCE into frozen copies (a later change to the host's array does not reach the pool), or a
|
|
33
|
+
* refusal (`shape`): a roster on a delegation tool with no factory, a value that is not a list, an entry that is not an object, a
|
|
34
|
+
* name that is not a non-empty string, aliases that are not a list of non-empty strings, a content class outside the vocabulary,
|
|
35
|
+
* or one spelling in two entries.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readExtraToolNames(value: unknown, hasFactory: boolean): readonly ExtraToolDeclaration[];
|
|
38
|
+
/**
|
|
39
|
+
* Whether one factory product is a tool the roster declares, as declared — answering to exactly one entry's names, its class no
|
|
40
|
+
* further out than the entry's: undefined when it is, else the arm and the sentence (the delegation tool's name, the product's
|
|
41
|
+
* offending name or class, the roster). A product's content class is its own
|
|
42
|
+
* declaration when that is a vocabulary member, else external — the class an undeclared host tool reads as; an entry that
|
|
43
|
+
* states no class allows any.
|
|
44
|
+
*/
|
|
45
|
+
export declare function undeclaredExtraTool(roster: readonly ExtraToolDeclaration[], product: ToolSpec, toolName: string): {
|
|
46
|
+
reason: Exclude<ExtraToolNamesReason, "shape">;
|
|
47
|
+
sentence: string;
|
|
48
|
+
} | undefined;
|
|
49
|
+
/** One row of a delegation tool's pool face (`ToolSpec.agentToolPool`): a tool's names and its declared content class. */
|
|
50
|
+
export interface DelegationPoolRow {
|
|
51
|
+
name: string;
|
|
52
|
+
aliases?: readonly string[];
|
|
53
|
+
contentOrigin?: ToolContentOrigin;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The pool a delegation tool's agent faces narrow FROM (`ToolSpec.agentToolPool`, read by the judgment of a denied-tools list and
|
|
57
|
+
* by the memory content-origin reader): what the tool hands its children — its own captured set, not the parent's roster (the two
|
|
58
|
+
* may differ, and a parent mounting only the delegation tool can still offer children a network tool) — plus the tools its factory
|
|
59
|
+
* is declared to inject. Absent when a factory has no declared roster: that pool does not exist until the call happens, and the
|
|
60
|
+
* readers take an absent pool as unknowable (the memory reader: external). Without an explicit pool the inherited surface is the
|
|
61
|
+
* face — its spawn-time narrowing only removes entries.
|
|
62
|
+
*/
|
|
63
|
+
export declare function delegationPoolFace(staticPool: readonly ToolSpec[], hasFactory: boolean, roster: readonly ExtraToolDeclaration[] | undefined): DelegationPoolRow[] | undefined;
|
|
64
|
+
/** The first factory product the roster does not declare (see {@link undeclaredExtraTool}), or undefined when every one is declared. */
|
|
65
|
+
export declare function firstUndeclaredExtraTool(roster: readonly ExtraToolDeclaration[], products: readonly ToolSpec[], toolName: string): ReturnType<typeof undeclaredExtraTool>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { isToolContentOrigin } from "../core/memory-engine/content-origin.js";
|
|
2
|
+
import { inlineUntrusted } from "../core/untrusted-text.js";
|
|
3
|
+
import { readToolNameList, valueTypeLabel } from "./tool-filter.js";
|
|
4
|
+
export const EXTRA_TOOL_NAMES_CODE = "config.extra_tool_names";
|
|
5
|
+
export function extraToolNamesError(message, reason) {
|
|
6
|
+
const e = new Error(message);
|
|
7
|
+
e.code = EXTRA_TOOL_NAMES_CODE;
|
|
8
|
+
e.detail = { reason };
|
|
9
|
+
return e;
|
|
10
|
+
}
|
|
11
|
+
const shape = (what) => extraToolNamesError(`createSubagentTool: extraToolNames ${what} — it declares the tools the extraTools factory may inject, each { name, aliases?, contentOrigin? }. Fix the declaration.`, "shape");
|
|
12
|
+
function own(entry, key) {
|
|
13
|
+
try {
|
|
14
|
+
return { ok: true, value: entry[key] };
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { ok: false };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function readExtraToolNames(value, hasFactory) {
|
|
21
|
+
if (!hasFactory)
|
|
22
|
+
throw extraToolNamesError("createSubagentTool: extraToolNames declares the tools an extraTools factory may inject, and this delegation tool has no extraTools factory. Remove the declaration or add the factory.", "shape");
|
|
23
|
+
const list = readToolNameList(value);
|
|
24
|
+
if (list.kind === "not_a_list")
|
|
25
|
+
throw shape(`is a value that is ${list.type}, not a list`);
|
|
26
|
+
if (list.kind === "unreadable")
|
|
27
|
+
throw shape("is a list whose entries cannot be read (an accessor or a proxy trap threw)");
|
|
28
|
+
const out = [];
|
|
29
|
+
const owner = new Map();
|
|
30
|
+
list.entries.forEach((entry, index) => {
|
|
31
|
+
const at = `entry ${index + 1}`;
|
|
32
|
+
if (entry === null || typeof entry !== "object" || valueTypeLabel(entry) !== "an object")
|
|
33
|
+
throw shape(`${at} is a value that is ${valueTypeLabel(entry)}, not an object`);
|
|
34
|
+
const name = own(entry, "name");
|
|
35
|
+
if (!name.ok)
|
|
36
|
+
throw shape(`${at}: its name cannot be read`);
|
|
37
|
+
if (typeof name.value !== "string" || name.value === "")
|
|
38
|
+
throw shape(`${at}: its name is ${typeof name.value === "string" ? "empty" : `a value that is ${valueTypeLabel(name.value)}`}, not a tool name`);
|
|
39
|
+
const aliasesRead = own(entry, "aliases");
|
|
40
|
+
if (!aliasesRead.ok)
|
|
41
|
+
throw shape(`${at}: its aliases cannot be read`);
|
|
42
|
+
let aliases;
|
|
43
|
+
if (aliasesRead.value !== undefined) {
|
|
44
|
+
const al = readToolNameList(aliasesRead.value);
|
|
45
|
+
if (al.kind !== "entries")
|
|
46
|
+
throw shape(`${at}: its aliases are ${al.kind === "not_a_list" ? `a value that is ${al.type}` : "a list that cannot be read"}, not a list of names`);
|
|
47
|
+
aliases = [];
|
|
48
|
+
for (const a of al.entries) {
|
|
49
|
+
if (typeof a !== "string" || a === "")
|
|
50
|
+
throw shape(`${at}: an alias is ${typeof a === "string" ? "empty" : `a value that is ${valueTypeLabel(a)}`}, not a tool name`);
|
|
51
|
+
aliases.push(a);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const originRead = own(entry, "contentOrigin");
|
|
55
|
+
if (!originRead.ok)
|
|
56
|
+
throw shape(`${at}: its contentOrigin cannot be read`);
|
|
57
|
+
if (originRead.value !== undefined && !isToolContentOrigin(originRead.value))
|
|
58
|
+
throw shape(`${at}: its contentOrigin is not one of "local", "execution", "external"`);
|
|
59
|
+
for (const spelling of new Set([name.value, ...(aliases ?? [])])) {
|
|
60
|
+
const first = owner.get(spelling);
|
|
61
|
+
if (first !== undefined)
|
|
62
|
+
throw shape(`entries ${first + 1} and ${index + 1} both answer to ${JSON.stringify(inlineUntrusted(spelling, 80))}`);
|
|
63
|
+
owner.set(spelling, index);
|
|
64
|
+
}
|
|
65
|
+
out.push(Object.freeze({
|
|
66
|
+
name: name.value,
|
|
67
|
+
...(aliases !== undefined ? { aliases: Object.freeze(aliases) } : {}),
|
|
68
|
+
...(originRead.value !== undefined ? { contentOrigin: originRead.value } : {}),
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
return Object.freeze(out);
|
|
72
|
+
}
|
|
73
|
+
const REACH = { local: 0, execution: 1, external: 2 };
|
|
74
|
+
const quoted = (name) => `"${inlineUntrusted(name, 80)}"`;
|
|
75
|
+
function rosterPhrase(roster) {
|
|
76
|
+
const shown = roster.slice(0, 8).map((e) => quoted(e.name));
|
|
77
|
+
return roster.length === 0 ? "none" : shown.join(", ") + (roster.length > shown.length ? `, … (${roster.length - shown.length} more)` : "");
|
|
78
|
+
}
|
|
79
|
+
export function undeclaredExtraTool(roster, product, toolName) {
|
|
80
|
+
const where = `the extraTools factory of delegation tool ${quoted(toolName)}`;
|
|
81
|
+
const tail = "the declaration is the upper bound of what the factory may inject. Fix the declaration or the factory.";
|
|
82
|
+
const name = product.name;
|
|
83
|
+
if (typeof name !== "string")
|
|
84
|
+
return { reason: "undeclared_name", sentence: `${where} injected a tool whose name is a value that is ${valueTypeLabel(name)}, which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) cannot name — ${tail}` };
|
|
85
|
+
const entry = roster.find((e) => e.name === name || (e.aliases?.includes(name) ?? false));
|
|
86
|
+
if (entry === undefined)
|
|
87
|
+
return { reason: "undeclared_name", sentence: `${where} injected tool ${quoted(name)}, which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) do not name — ${tail}` };
|
|
88
|
+
const spellings = new Set([entry.name, ...(entry.aliases ?? [])]);
|
|
89
|
+
const aliases = product.aliases;
|
|
90
|
+
const aliasList = Array.isArray(aliases) ? aliases : [];
|
|
91
|
+
for (const alias of aliasList) {
|
|
92
|
+
if (typeof alias !== "string" || !spellings.has(alias)) {
|
|
93
|
+
const said = typeof alias === "string" ? quoted(alias) : `a value that is ${valueTypeLabel(alias)}`;
|
|
94
|
+
return { reason: "undeclared_name", sentence: `${where} injected tool ${quoted(name)} answering to ${said}, which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) do not name — ${tail}` };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const answers = new Set([name, ...aliasList]);
|
|
98
|
+
const missing = [...spellings].find((n) => !answers.has(n));
|
|
99
|
+
if (missing !== undefined)
|
|
100
|
+
return { reason: "undeclared_name", sentence: `${where} injected tool ${quoted(name)}, which does not answer to ${quoted(missing)} as its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) say it does — ${tail}` };
|
|
101
|
+
const stated = product.contentOrigin;
|
|
102
|
+
const productClass = isToolContentOrigin(stated) ? stated : "external";
|
|
103
|
+
const declaredClass = entry.contentOrigin ?? "external";
|
|
104
|
+
if (REACH[productClass] > REACH[declaredClass]) {
|
|
105
|
+
return { reason: "undeclared_origin", sentence: `${where} injected tool ${quoted(name)} of content class "${productClass}", which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) state as "${declaredClass}" — ${tail}` };
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
export function delegationPoolFace(staticPool, hasFactory, roster) {
|
|
110
|
+
if (hasFactory && roster === undefined)
|
|
111
|
+
return undefined;
|
|
112
|
+
return [...staticPool, ...(roster ?? [])].map((t) => {
|
|
113
|
+
const origin = t.contentOrigin;
|
|
114
|
+
return { name: t.name, ...(t.aliases !== undefined ? { aliases: t.aliases } : {}), ...(origin !== undefined ? { contentOrigin: origin } : {}) };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export function firstUndeclaredExtraTool(roster, products, toolName) {
|
|
118
|
+
for (const product of products) {
|
|
119
|
+
const flaw = undeclaredExtraTool(roster, product, toolName);
|
|
120
|
+
if (flaw !== undefined)
|
|
121
|
+
return flaw;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
@@ -704,13 +704,13 @@ export interface SubagentToolOptions {
|
|
|
704
704
|
inheritedTools?: ToolSpec[];
|
|
705
705
|
/**
|
|
706
706
|
* Tool-level allowlist (design/38 1A), used when NO `agent` is selected (a selected agent definition's
|
|
707
|
-
* own `allowTools`/`denyTools` REPLACE these). When set, the child sees only these
|
|
708
|
-
* (`["*"]` = all). Omit for
|
|
707
|
+
* own `allowTools`/`denyTools` REPLACE these). When set, the child sees only these of its pool and of the engine's hand
|
|
708
|
+
* tools its run mounts (Read / Write / Bash …) (`["*"]` = all). Omit for everything. Allowlist is the security-recommended shape.
|
|
709
709
|
* NOTE: this filters only the WORK tools — the nested delegation tool is governed by `maxDepth`, not
|
|
710
710
|
* allow/deny. To deny the child any further delegation, set `maxDepth: 1` (not a `denyTools` entry).
|
|
711
711
|
*/
|
|
712
712
|
allowTools?: string[];
|
|
713
|
-
/** Tool-level denylist (design/38 1A), used when no `agent` is selected: names removed (a deny wins). */
|
|
713
|
+
/** Tool-level denylist (design/38 1A), used when no `agent` is selected: names removed from the pool and the hand tools (a deny wins). */
|
|
714
714
|
denyTools?: string[];
|
|
715
715
|
/** System prompt / persona for the child. Default (1.259.0, CC 198 parity): the LEAN
|
|
716
716
|
* {@link SUBAGENT_PROMPT} (agent persona + return contract), NOT the full default constitution role
|
|
@@ -764,10 +764,11 @@ export interface SubagentToolOptions {
|
|
|
764
764
|
* call's parent coordinates, to produce tool instances that cannot sit in the mount-time static
|
|
765
765
|
* `tools` pool (the deployment case: per-task tools issued by a task-env registry — the static pool
|
|
766
766
|
* is fixed at `createSubagentTool({...})` time, before any task exists). The products are MERGED
|
|
767
|
-
* INTO the `tools` pool and then flow through the EXACT SAME
|
|
767
|
+
* INTO the `tools` pool and then flow through the EXACT SAME subset reading as the
|
|
768
768
|
* static tools — an agent definition's `allowTools`/`denyTools` (or the tool-level ones when no
|
|
769
769
|
* agent is selected) govern them verbatim: an explicit allowlist must NAME an injected tool for the
|
|
770
|
-
* child to see it; `"*"`/absent allowlist = all.
|
|
770
|
+
* child to see it; `"*"`/absent allowlist = all. Without {@link extraToolNames} the pool cannot be listed before a spawn.
|
|
771
|
+
* Nested delegation threads the SAME factory down
|
|
771
772
|
* (`createSubagentToolNode(opts, depth+1)` carries it), so a grandchild spawn re-evaluates it — per-spawn,
|
|
772
773
|
* at every level; each child gets its own instances bound to its own spawn moment (the
|
|
773
774
|
* server-side factory is execute-time late-bound, so per-spawn evaluation is the confirmed shape).
|
|
@@ -788,6 +789,17 @@ export interface SubagentToolOptions {
|
|
|
788
789
|
* tightening layer on the supported paths, never the only line of defense.
|
|
789
790
|
*/
|
|
790
791
|
extraTools?: (ctx: SubagentSpawnContext) => ToolSpec[] | Promise<ToolSpec[]>;
|
|
792
|
+
/**
|
|
793
|
+
* The roster of {@link extraTools} — every tool it may inject: names, aliases, content class (absent = external). The declared
|
|
794
|
+
* tools join the pool, so the pool is listable before a spawn; a product outside the roster refuses that spawn
|
|
795
|
+
* (`config.extra_tool_names`, `detail.reason` `undeclared_name` / `undeclared_origin`, also to {@link onExtraToolsError});
|
|
796
|
+
* a malformed roster, or one with no factory, throws where the tool is built (`shape`). Absent ⇒ as before. See extra-tool-names.ts.
|
|
797
|
+
*/
|
|
798
|
+
extraToolNames?: ReadonlyArray<{
|
|
799
|
+
name: string;
|
|
800
|
+
aliases?: readonly string[];
|
|
801
|
+
contentOrigin?: import("../core/types.js").ToolContentOrigin;
|
|
802
|
+
}>;
|
|
791
803
|
/**
|
|
792
804
|
* extraTools 快审 S3 — host-side sink for the ORIGINAL `extraTools` factory error (and the S4
|
|
793
805
|
* budget-exceeded signal). The model-facing report deliberately carries only the fixed
|
package/dist/agents/subagent.js
CHANGED
|
@@ -15,7 +15,8 @@ export { childThinkingSeat, deploymentSubagentThinking, deploymentSubagentTierMo
|
|
|
15
15
|
import { OUTPUT_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME } from "../core/runner/synthetic-tools.js";
|
|
16
16
|
import { TOOL_SEARCH_NAME } from "../core/runner/tool-disclosure.js";
|
|
17
17
|
import { OFFLOAD_TOOL_NAME } from "../core/tool-result-store.js";
|
|
18
|
-
import {
|
|
18
|
+
import { readToolNameList, resolveAgentToolSubset, snapshotDefinitionLists, snapshotToolNameList, toolNameAllowed, toolNameListFlaw } from "./tool-filter.js";
|
|
19
|
+
import { EXTRA_TOOL_NAMES_CODE, delegationPoolFace, extraToolNamesError, firstUndeclaredExtraTool, readExtraToolNames } from "./extra-tool-names.js";
|
|
19
20
|
import { builtinAgentDefinitions } from "./builtin-agents.js";
|
|
20
21
|
import { SUBAGENT_PROMPT } from "../prompts/default.js";
|
|
21
22
|
import { hasSessionFork } from "../core/session.js";
|
|
@@ -267,6 +268,19 @@ export function forkGovernanceDenial(enableFork, allowFork) {
|
|
|
267
268
|
return "principal";
|
|
268
269
|
return undefined;
|
|
269
270
|
}
|
|
271
|
+
const worktreeUnavailableSentence = (reason) => `Sub-agent not started: isolation "worktree" is unavailable on this run (${reason}). Retry without the isolation parameter.`;
|
|
272
|
+
const WORKTREE_UNAVAILABLE = {
|
|
273
|
+
readOnly: worktreeUnavailableSentence("no worktree capability — a read-only run mints no worktree"),
|
|
274
|
+
needsFactory: worktreeUnavailableSentence("this deployment has no executionEnvFactory — a static execution environment has one shared working directory, so the child would run in this run's own root instead of a worktree; deploy an executionEnvFactory to enable worktree isolation"),
|
|
275
|
+
notHonored: worktreeUnavailableSentence("the execution environment factory placed the child inside this run's own tree instead of the worktree it was offered — it would have read and written this checkout; the worktree was removed and the child was not started"),
|
|
276
|
+
seatSilent: worktreeUnavailableSentence("the runner this tool runs children on does not declare whether it can place a child in a tree other than the caller's (its seat has no canRerootChild), and an undeclared answer is read as no; use a Runner, or add that method to the seat"),
|
|
277
|
+
};
|
|
278
|
+
const placementRefused = (terminal) => terminal?.kind === "failed" && terminal.code === "isolation.unavailable";
|
|
279
|
+
const isolationUnavailable = (content) => ({
|
|
280
|
+
isError: true,
|
|
281
|
+
content,
|
|
282
|
+
details: { error: "isolation.unavailable" },
|
|
283
|
+
});
|
|
270
284
|
export function createSubagentWorktreeHelper(baseEnv, repoRoot) {
|
|
271
285
|
const handles = new Map();
|
|
272
286
|
return {
|
|
@@ -1259,13 +1273,17 @@ export function createSubagentTool(opts) {
|
|
|
1259
1273
|
opts = { ...opts, builtinAgents: catalog.builtinAgents };
|
|
1260
1274
|
}
|
|
1261
1275
|
const assemblyModels = opts.models ?? catalog?.models;
|
|
1276
|
+
if (opts.extraToolNames !== undefined)
|
|
1277
|
+
opts = { ...opts, extraToolNames: readExtraToolNames(opts.extraToolNames, opts.extraTools !== undefined) };
|
|
1278
|
+
if (opts.denyTools !== undefined)
|
|
1279
|
+
opts = { ...opts, denyTools: snapshotToolNameList(opts.denyTools) };
|
|
1280
|
+
if (opts.allowTools !== undefined)
|
|
1281
|
+
opts = { ...opts, allowTools: snapshotToolNameList(opts.allowTools) };
|
|
1262
1282
|
if (opts.agents?.length) {
|
|
1263
1283
|
opts = {
|
|
1264
1284
|
...opts,
|
|
1265
1285
|
agents: opts.agents.map((a) => ({
|
|
1266
|
-
...a,
|
|
1267
|
-
...(a.allowTools !== undefined ? { allowTools: [...a.allowTools] } : {}),
|
|
1268
|
-
...(a.denyTools !== undefined ? { denyTools: [...a.denyTools] } : {}),
|
|
1286
|
+
...snapshotDefinitionLists(a),
|
|
1269
1287
|
...(a.skills !== undefined ? { skills: [...a.skills] } : {}),
|
|
1270
1288
|
})),
|
|
1271
1289
|
};
|
|
@@ -1302,9 +1320,11 @@ export function createSubagentTool(opts) {
|
|
|
1302
1320
|
return built;
|
|
1303
1321
|
}
|
|
1304
1322
|
export function agentToolsNote(def) {
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
const
|
|
1323
|
+
const allowRead = readToolNameList(def.allowTools);
|
|
1324
|
+
const allow = allowRead.kind === "entries" ? allowRead.entries.filter((entry) => typeof entry === "string") : [];
|
|
1325
|
+
const hasAllow = allow.length > 0 && !allow.includes("*");
|
|
1326
|
+
const denyRead = readToolNameList(def.denyTools);
|
|
1327
|
+
const deny = denyRead.kind === "entries" ? denyRead.entries.filter((entry) => typeof entry === "string") : [];
|
|
1308
1328
|
const hasDeny = deny.length > 0;
|
|
1309
1329
|
if (hasAllow && hasDeny) {
|
|
1310
1330
|
const denied = new Set(deny);
|
|
@@ -1367,23 +1387,18 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1367
1387
|
...(a.allowTools !== undefined ? { allowTools: a.allowTools } : {}),
|
|
1368
1388
|
...(a.denyTools !== undefined ? { denyTools: a.denyTools } : {}),
|
|
1369
1389
|
...(depthPermitsNesting && toolNameAllowed(delegationToolName, a.allowTools, a.denyTools) ? { canRedelegate: true } : {}),
|
|
1390
|
+
...(depth === 0 && !builtins.includes(a) ? { authored: true } : {}),
|
|
1370
1391
|
})),
|
|
1371
1392
|
...(forkOffered ? [{ name: FORK_SUBAGENT_TYPE, ...(depthPermitsNesting ? { canRedelegate: true } : {}) }] : []),
|
|
1372
1393
|
];
|
|
1373
|
-
const agentToolPool = opts.extraTools !== undefined
|
|
1374
|
-
? undefined
|
|
1375
|
-
: (opts.tools ?? opts.inheritedTools ?? []).map((t) => ({
|
|
1376
|
-
name: t.name,
|
|
1377
|
-
...(t.aliases !== undefined ? { aliases: t.aliases } : {}),
|
|
1378
|
-
...(t.contentOrigin !== undefined
|
|
1379
|
-
? { contentOrigin: t.contentOrigin }
|
|
1380
|
-
: {}),
|
|
1381
|
-
}));
|
|
1394
|
+
const agentToolPool = delegationPoolFace(opts.tools ?? opts.inheritedTools ?? [], opts.extraTools !== undefined, opts.extraToolNames);
|
|
1382
1395
|
return {
|
|
1383
1396
|
name: opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME,
|
|
1384
1397
|
...toolFace("agent"),
|
|
1385
1398
|
agentListing,
|
|
1386
1399
|
agentToolFaces,
|
|
1400
|
+
...(depth === 0 && opts.allowTools !== undefined ? { agentToolLevelAllowTools: opts.allowTools } : {}),
|
|
1401
|
+
...(depth === 0 && opts.denyTools !== undefined ? { agentToolLevelDenyTools: opts.denyTools } : {}),
|
|
1387
1402
|
...(agentToolPool !== undefined ? { agentToolPool } : {}),
|
|
1388
1403
|
...(rosterNames !== undefined ? { agentModels: rosterNames } : {}),
|
|
1389
1404
|
executionMode: "parallel",
|
|
@@ -1527,6 +1542,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1527
1542
|
details: { error: "unknown subagent_type" },
|
|
1528
1543
|
};
|
|
1529
1544
|
}
|
|
1545
|
+
const allowFlaw = toolNameListFlaw(`delegation tool "${toolName}": ${def !== undefined ? `agent definition "${def.name}"` : "the tool-level list"}`, "allowed", def !== undefined ? def.allowTools : opts.allowTools);
|
|
1546
|
+
if (allowFlaw !== undefined)
|
|
1547
|
+
return { isError: true, content: `Sub-agent not started: ${inlineUntrusted(allowFlaw, 480)}`, details: { error: "config.agent.unknown_tool" } };
|
|
1530
1548
|
if (a.name !== undefined && typeof a.name !== "string") {
|
|
1531
1549
|
return { isError: true, content: "Sub-agent not started: name must be a string.", details: { error: "name.invalid" } };
|
|
1532
1550
|
}
|
|
@@ -1717,13 +1735,11 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1717
1735
|
let worktreeDir;
|
|
1718
1736
|
if (reviveClaim === undefined && (a.isolation ?? def?.isolation) === "worktree") {
|
|
1719
1737
|
const iso = ctx.worktreeIsolation;
|
|
1720
|
-
if (!iso)
|
|
1721
|
-
return
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
};
|
|
1726
|
-
}
|
|
1738
|
+
if (!iso)
|
|
1739
|
+
return isolationUnavailable(WORKTREE_UNAVAILABLE.readOnly);
|
|
1740
|
+
const reroots = opts.runner.canRerootChild?.();
|
|
1741
|
+
if (reroots !== true)
|
|
1742
|
+
return isolationUnavailable(reroots === undefined ? WORKTREE_UNAVAILABLE.seatSilent : WORKTREE_UNAVAILABLE.needsFactory);
|
|
1727
1743
|
try {
|
|
1728
1744
|
worktreeDir = (await iso.mint(ctx.toolCallId)).worktreeDir;
|
|
1729
1745
|
}
|
|
@@ -1736,11 +1752,16 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1736
1752
|
}
|
|
1737
1753
|
}
|
|
1738
1754
|
let worktreeKeptPath;
|
|
1739
|
-
|
|
1755
|
+
let childPlacement;
|
|
1756
|
+
const finishWorktree = async (terminal) => {
|
|
1740
1757
|
if (worktreeDir === undefined)
|
|
1741
1758
|
return undefined;
|
|
1742
1759
|
try {
|
|
1743
1760
|
const outcome = await ctx.worktreeIsolation.finish(worktreeDir);
|
|
1761
|
+
if (placementRefused(terminal))
|
|
1762
|
+
return undefined;
|
|
1763
|
+
if (childPlacement?.placement === "elsewhere")
|
|
1764
|
+
return `worktree: none — the execution environment ran the agent at ${inlineUntrusted(childPlacement.cwd, REPORT_FIELD_MAX)}, not in the worktree it was offered (${outcome === "pruned" ? "removed unused" : `kept at ${worktreeDir}: it changed though the agent never worked in it`})`;
|
|
1744
1765
|
if (outcome === "kept")
|
|
1745
1766
|
worktreeKeptPath = worktreeDir;
|
|
1746
1767
|
return outcome === "pruned"
|
|
@@ -1789,14 +1810,15 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1789
1810
|
reportExtraToolsError(e);
|
|
1790
1811
|
}
|
|
1791
1812
|
}
|
|
1792
|
-
|
|
1813
|
+
const notStarted = async (why, details) => {
|
|
1793
1814
|
const wt = await finishWorktree();
|
|
1794
|
-
return {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1815
|
+
return { isError: true, content: `Sub-agent not started: ${why.slice(0, 1600)}${wt ? `\n${wt}` : ""}`, details };
|
|
1816
|
+
};
|
|
1817
|
+
if (ctx.signal?.aborted)
|
|
1818
|
+
return notStarted("the parent task was aborted while extraTools was being evaluated.", { error: "aborted" });
|
|
1819
|
+
const undeclared = opts.extraToolNames !== undefined ? firstUndeclaredExtraTool(opts.extraToolNames, injectedTools, toolName) : undefined;
|
|
1820
|
+
if (undeclared !== undefined)
|
|
1821
|
+
return (reportExtraToolsError(extraToolNamesError(undeclared.sentence, undeclared.reason)), notStarted(undeclared.sentence, { error: EXTRA_TOOL_NAMES_CODE, reason: undeclared.reason }));
|
|
1800
1822
|
}
|
|
1801
1823
|
const inherited = opts.tools === undefined && extraToolsNote === undefined ? inheritedChildPool(opts.inheritedTools ?? [], ctx.mountsCallerTool, wantsBackground && agentName === undefined && !wantsFork) : [];
|
|
1802
1824
|
if (extraToolsNote !== undefined && opts.tools === undefined && (opts.inheritedTools?.length ?? 0) > 0)
|
|
@@ -1823,8 +1845,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1823
1845
|
}
|
|
1824
1846
|
childPool = [...(opts.tools ?? inherited.filter((t) => ![t.name, ...(t.aliases ?? [])].some((n) => taken.has(n)))), ...accepted];
|
|
1825
1847
|
}
|
|
1826
|
-
const
|
|
1848
|
+
const subset = resolveAgentToolSubset(childPool, def ? def.allowTools : opts.allowTools, def ? def.denyTools : opts.denyTools);
|
|
1849
|
+
const allowed = new Set([...subset.handed, ...(agentName !== undefined ? teammateInheritedFamily(childPool.filter((t) => inherited.includes(t)), def ? def.denyTools : opts.denyTools) : [])]);
|
|
1827
1850
|
const childTools = childPool.filter((t) => allowed.has(t));
|
|
1851
|
+
const childExcludeTools = ctx.excludeTools !== undefined || subset.withheldHands.length > 0 ? [...new Set([...(ctx.excludeTools ?? []), ...subset.withheldHands])] : undefined;
|
|
1828
1852
|
const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
1829
1853
|
const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
|
|
1830
1854
|
const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
|
|
@@ -1902,7 +1926,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1902
1926
|
...(ctx.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: ctx.getApiKeyAndHeaders } : {}),
|
|
1903
1927
|
...(ctx.checkpointStoreForChildren !== undefined ? { checkpointStore: ctx.checkpointStoreForChildren } : {}),
|
|
1904
1928
|
tools: [reportTool],
|
|
1905
|
-
...(
|
|
1929
|
+
...(childExcludeTools !== undefined ? { excludeTools: [...childExcludeTools] } : {}),
|
|
1906
1930
|
...(ctx.deferTools !== undefined ? { deferTools: [...ctx.deferTools] } : {}),
|
|
1907
1931
|
...(ctx.alwaysLoadTools !== undefined ? { alwaysLoadTools: [...ctx.alwaysLoadTools] } : {}),
|
|
1908
1932
|
...(ctx.restoreGatedTools !== undefined ? { restoreGatedTools: ctx.restoreGatedTools === true ? true : [...ctx.restoreGatedTools] } : {}),
|
|
@@ -2148,8 +2172,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2148
2172
|
})(),
|
|
2149
2173
|
...(ctx.subagentRetain !== undefined ? { parentRetainLedger: ctx.subagentRetain } : {}),
|
|
2150
2174
|
onForwardEvent: recordingForward,
|
|
2175
|
+
...(ctx.parentCwd !== undefined ? { callerRoot: ctx.parentCwd } : {}),
|
|
2151
2176
|
...(worktreeDir !== undefined
|
|
2152
|
-
? { parentCwd: worktreeDir }
|
|
2177
|
+
? { parentCwd: worktreeDir, worktreeOffered: true, onWorkspaceResolved: (ws) => void (childPlacement = ws) }
|
|
2153
2178
|
: childCwd !== undefined
|
|
2154
2179
|
?
|
|
2155
2180
|
{ parentCwd: childCwd, requestedCwd: childCwd }
|
|
@@ -2182,7 +2207,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2182
2207
|
...(ctx.checkpointStoreForChildren !== undefined ? { checkpointStore: ctx.checkpointStoreForChildren } : {}),
|
|
2183
2208
|
...(childOnAsk !== undefined ? { onAsk: childOnAsk } : {}),
|
|
2184
2209
|
...childClampSeats({ handsReadOnly: ctx.handsReadOnly, interactiveTools: ctx.interactiveTools, approvalReach: ctx.approvalReach, readFace: ctx.readFace, writeFace: ctx.writeFace, readDenyPatterns: ctx.readDenyPatterns }),
|
|
2185
|
-
...(
|
|
2210
|
+
...(childExcludeTools !== undefined ? { excludeTools: [...childExcludeTools] } : {}),
|
|
2186
2211
|
...(ctx.deferTools !== undefined ? { deferTools: [...ctx.deferTools] } : {}),
|
|
2187
2212
|
...(ctx.alwaysLoadTools !== undefined ? { alwaysLoadTools: [...ctx.alwaysLoadTools] } : {}),
|
|
2188
2213
|
...(ctx.restoreGatedTools !== undefined ? { restoreGatedTools: ctx.restoreGatedTools === true ? true : [...ctx.restoreGatedTools] } : {}),
|
|
@@ -2667,7 +2692,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2667
2692
|
const childFace = terminalProjection(child.terminal);
|
|
2668
2693
|
settleForkRow(child.terminal);
|
|
2669
2694
|
dropHostAbortListener();
|
|
2670
|
-
await finishWorktree();
|
|
2695
|
+
await finishWorktree(child.terminal);
|
|
2671
2696
|
try {
|
|
2672
2697
|
const nsb = child.stats.nested;
|
|
2673
2698
|
ctx.reportUsage?.({
|
|
@@ -3445,7 +3470,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3445
3470
|
settleDelegationRow(child.terminal);
|
|
3446
3471
|
closeObserverWindow(childFace.status);
|
|
3447
3472
|
dropHostAbortListener();
|
|
3448
|
-
await finishWorktree();
|
|
3473
|
+
await finishWorktree(child.terminal);
|
|
3449
3474
|
try {
|
|
3450
3475
|
const ns2 = child.stats.nested;
|
|
3451
3476
|
ctx.reportUsage?.({
|
|
@@ -3850,7 +3875,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3850
3875
|
}
|
|
3851
3876
|
}
|
|
3852
3877
|
}
|
|
3853
|
-
const worktreeLine = await finishWorktree();
|
|
3878
|
+
const worktreeLine = await finishWorktree(child.terminal);
|
|
3879
|
+
if (worktreeDir !== undefined && placementRefused(child.terminal))
|
|
3880
|
+
return isolationUnavailable(WORKTREE_UNAVAILABLE.notHonored);
|
|
3854
3881
|
const errClass = classifySubagentError(child.terminal);
|
|
3855
3882
|
const lines = [
|
|
3856
3883
|
...(handbackWarning ? [handbackWarning, ""] : []),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ToolSpec } from "../core/types.js";
|
|
1
|
+
import type { AgentDefinition, ToolSpec } from "../core/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Per-agent tool allow/deny (design/38 1A). Statically narrows a tool POOL to the subset a delegated
|
|
4
4
|
* sub-agent / team member may use — assembly-time filtering (the tool simply never appears in the
|
|
@@ -16,13 +16,74 @@ import type { ToolSpec } from "../core/types.js";
|
|
|
16
16
|
* Anchored (dual-leg verified): this spawn-time item-level filter IS the upstream shape for allow
|
|
17
17
|
* entries — CC's resolveAgentTools partitions unknown names into an `invalidTools` bucket nobody
|
|
18
18
|
* consumes at runtime, and a live 2.1.207 probe shows the agent stays listed/delegable with the
|
|
19
|
-
* unknown item silently dropped, zero warnings.
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
19
|
+
* unknown item silently dropped, zero warnings. A DENY entry is read as the reference reads it
|
|
20
|
+
* ({@link readDeniedEntry}: `Tool(content)` names `Tool`, the content is not read, the whole tool is hidden). The
|
|
21
|
+
* list is judged once before the run, from every place a deployment writes one (prepare's
|
|
22
|
+
* `judgeDeniedToolNames`): a spelling that can name no tool is refused, the rest is told to the host.
|
|
22
23
|
*
|
|
23
24
|
* Note: `"*"` in `allowTools` is the "allow all" sentinel, so a tool literally NAMED `"*"` cannot be
|
|
24
25
|
* the sole allowed tool via this path (tool names are conventionally identifiers, so this is moot).
|
|
25
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* How ONE denied-tools entry is read — the reference reads each as a rule string, without trimming: `Tool(content)` names
|
|
29
|
+
* `Tool` (the content is dropped, so the whole tool is hidden), `Tool()` and `Tool(*)` are the bare name, and anything else
|
|
30
|
+
* — a malformed paren form, an entry with surrounding whitespace — is the whole string, which names no tool. A value that is
|
|
31
|
+
* not a string (a list a host read from a file can hold anything) names no tool either: it reads as `not_a_string` with a
|
|
32
|
+
* fixed label of its TYPE, and nothing about the value is read, converted or printed — `typeof` and the array test only.
|
|
33
|
+
*/
|
|
34
|
+
export type DeniedEntryReading = {
|
|
35
|
+
readonly kind: "name";
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly content: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: "not_a_string";
|
|
40
|
+
readonly type: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function readDeniedEntry(entry: unknown): DeniedEntryReading;
|
|
43
|
+
/** A fixed label of a host value's TYPE for a sentence that must not print or convert the value (`typeof` and the array test only). */
|
|
44
|
+
export declare function valueTypeLabel(value: unknown): string;
|
|
45
|
+
/**
|
|
46
|
+
* How a tool-name list's VALUE (an agent definition's allowed-tools or denied-tools list) reads as a list. A host that reads its
|
|
47
|
+
* definitions from a file can hand over anything, and a list can be hostile as a container (an accessor or a proxy trap that
|
|
48
|
+
* throws, an overridden iterator, holes): it is read BY INDEX over its length, once, into a dense copy — no iterator of the host
|
|
49
|
+
* runs, a hole reads as `undefined` (an entry that is not a string) — and a value that is not an array, or an array whose read
|
|
50
|
+
* throws, is reported as such and never printed or converted.
|
|
51
|
+
*/
|
|
52
|
+
export type ToolNameListReading = {
|
|
53
|
+
readonly kind: "entries";
|
|
54
|
+
readonly entries: readonly unknown[];
|
|
55
|
+
} | {
|
|
56
|
+
readonly kind: "not_a_list";
|
|
57
|
+
readonly type: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: "unreadable";
|
|
60
|
+
};
|
|
61
|
+
export declare function readToolNameList(value: unknown): ToolNameListReading;
|
|
62
|
+
/** A tool-name list as the mount keeps it: the dense copy when it reads as a list, the failure marker when it cannot be read, else
|
|
63
|
+
* the value itself (not a list: the judge refuses it, and its type never changes). */
|
|
64
|
+
export declare function snapshotToolNameList(value: unknown): unknown;
|
|
65
|
+
/** The two tool-name lists of a definition, and the words a sentence calls each. */
|
|
66
|
+
export type ToolNameListSide = "allowed" | "denied";
|
|
67
|
+
/** The sentence for an entry that is not a string: it can be no tool name; the sentence names its place and its type, never its value. */
|
|
68
|
+
export declare function notAStringSentence(where: string, side: ToolNameListSide, index: number, type: string): string;
|
|
69
|
+
/** The sentence for a value that is not a list, or a list that cannot be read: it names the type, never the value. */
|
|
70
|
+
export declare function notAListSentence(where: string, side: ToolNameListSide, list: Exclude<ToolNameListReading, {
|
|
71
|
+
kind: "entries";
|
|
72
|
+
}>): string;
|
|
73
|
+
/**
|
|
74
|
+
* Why a host-written tool-name list cannot be one, as a sentence — or `undefined` when it is absent (`undefined` / `null`) or a list
|
|
75
|
+
* of strings. The one reading the judgment before the run and the spawn-time check of a tool that never went through prepare share.
|
|
76
|
+
*/
|
|
77
|
+
export declare function toolNameListFlaw(where: string, side: ToolNameListSide, value: unknown): string | undefined;
|
|
78
|
+
/** A definition with its two tool-name lists replaced by their one copy — the copy every later reader (the judgment before the
|
|
79
|
+
* run, the roster, the filter at each spawn) takes instead of the host's container. */
|
|
80
|
+
export declare function snapshotDefinitionLists(def: AgentDefinition): AgentDefinition;
|
|
81
|
+
/**
|
|
82
|
+
* The names an allowed-tools value admits, or `undefined` when it names no allowlist: absent, `null`, or a list holding the
|
|
83
|
+
* wildcard. A list admits its string entries (an entry that is not a string admits nothing); a value that is not a list admits
|
|
84
|
+
* NOTHING — the judge before the run refuses it, and a reader that meets it first fails closed.
|
|
85
|
+
*/
|
|
86
|
+
export declare function allowedToolNames(allowTools: unknown): ReadonlySet<string> | undefined;
|
|
26
87
|
/**
|
|
27
88
|
* Membership check for ONE tool name under the same allow/deny semantics as {@link resolveToolSubset}
|
|
28
89
|
* (canonicalized both sides; `"*"` allow sentinel; deny wins). Used by the delegation tool to decide
|
|
@@ -32,4 +93,20 @@ import type { ToolSpec } from "../core/types.js";
|
|
|
32
93
|
* TOOL-level allow/deny keeps its own semantics (delegation governed by `maxDepth`).
|
|
33
94
|
*/
|
|
34
95
|
export declare function toolNameAllowed(name: string, allowTools?: ReadonlyArray<string>, denyTools?: ReadonlyArray<string>): boolean;
|
|
35
|
-
export declare function resolveToolSubset
|
|
96
|
+
export declare function resolveToolSubset<T extends {
|
|
97
|
+
readonly name: string;
|
|
98
|
+
readonly aliases?: readonly string[];
|
|
99
|
+
} = ToolSpec>(pool: ReadonlyArray<T>, allowTools?: ReadonlyArray<string>, denyTools?: ReadonlyArray<string>): T[];
|
|
100
|
+
/**
|
|
101
|
+
* What ONE agent's tool-name lists leave it, read once over everything it can get: the tools the delegation tool hands it
|
|
102
|
+
* (`handed` — its pool and the factory's products, the subset in pool order) and the engine's hand tools its own run mounts
|
|
103
|
+
* over an execution environment ({@link HAND_TOOLS} — `withheldHands`, the wire names the lists take away, which the spawn
|
|
104
|
+
* adds to the child's exclusion list: the one fact every reader of "is this tool mounted" consults). One
|
|
105
|
+
* {@link resolveToolSubset} call over both halves, so a name reads the same whichever half carries it. The exclusion list
|
|
106
|
+
* unmounts by wire name, so a withheld hand name that a handed tool is mounted under stays off it (it would take the kept
|
|
107
|
+
* tool with it); a handed tool's alias does not count — the exclusion never reaches an alias, so the hand tool stays withheld.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveAgentToolSubset(pool: ReadonlyArray<ToolSpec>, allowTools?: ReadonlyArray<string>, denyTools?: ReadonlyArray<string>): {
|
|
110
|
+
handed: ToolSpec[];
|
|
111
|
+
withheldHands: string[];
|
|
112
|
+
};
|