@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.
Files changed (159) hide show
  1. package/CHANGELOG.md +418 -1
  2. package/dist/agents/extra-tool-names.d.ts +65 -0
  3. package/dist/agents/extra-tool-names.js +124 -0
  4. package/dist/agents/subagent.d.ts +17 -5
  5. package/dist/agents/subagent.js +65 -38
  6. package/dist/agents/tool-filter.d.ts +82 -5
  7. package/dist/agents/tool-filter.js +134 -4
  8. package/dist/core/agent-config-dir.d.ts +20 -0
  9. package/dist/core/agent-config-dir.js +49 -0
  10. package/dist/core/agent-definition.d.ts +2 -2
  11. package/dist/core/allow-entry.d.ts +30 -4
  12. package/dist/core/allow-entry.js +32 -5
  13. package/dist/core/ask-origin.d.ts +23 -4
  14. package/dist/core/ask-origin.js +11 -5
  15. package/dist/core/checkpoint-store.d.ts +41 -4
  16. package/dist/core/decision-receipt.js +1 -1
  17. package/dist/core/effective-path-target.d.ts +143 -10
  18. package/dist/core/effective-path-target.js +113 -9
  19. package/dist/core/engine-notice.d.ts +25 -3
  20. package/dist/core/engine-notice.js +12 -6
  21. package/dist/core/gate-fold.js +3 -0
  22. package/dist/core/gate-lanes.js +11 -3
  23. package/dist/core/gate-outcome.d.ts +5 -0
  24. package/dist/core/gate-outcome.js +2 -0
  25. package/dist/core/governance-codes.d.ts +3 -1
  26. package/dist/core/governance-codes.js +3 -0
  27. package/dist/core/hooks.d.ts +5 -0
  28. package/dist/core/memory-engine/content-origin.d.ts +0 -34
  29. package/dist/core/memory-engine/content-origin.js +9 -4
  30. package/dist/core/memory-engine/layout.d.ts +5 -1
  31. package/dist/core/memory-engine/layout.js +1 -0
  32. package/dist/core/path-rule-binding.d.ts +65 -0
  33. package/dist/core/path-rule-binding.js +99 -0
  34. package/dist/core/permission-rule-model.d.ts +47 -18
  35. package/dist/core/permission-rule-model.js +38 -42
  36. package/dist/core/permission-rule-provider.d.ts +7 -0
  37. package/dist/core/permission-rule-provider.js +10 -3
  38. package/dist/core/permission-rule-store.d.ts +8 -0
  39. package/dist/core/permission-rule-store.js +6 -2
  40. package/dist/core/permission-rule-sync.js +2 -2
  41. package/dist/core/permission-rules.d.ts +22 -36
  42. package/dist/core/permission-rules.js +50 -40
  43. package/dist/core/real-approval.d.ts +9 -6
  44. package/dist/core/real-approval.js +5 -5
  45. package/dist/core/runner/child-tree.d.ts +110 -0
  46. package/dist/core/runner/child-tree.js +107 -0
  47. package/dist/core/runner/construction-doors.d.ts +5 -0
  48. package/dist/core/runner/construction-doors.js +6 -0
  49. package/dist/core/runner/contracts.d.ts +54 -2
  50. package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
  51. package/dist/core/runner/permission-rule-lanes.js +18 -12
  52. package/dist/core/runner/persisted-path-rows.d.ts +31 -7
  53. package/dist/core/runner/persisted-path-rows.js +31 -12
  54. package/dist/core/runner/prepare-ask-lane.d.ts +6 -0
  55. package/dist/core/runner/prepare-ask-lane.js +4 -2
  56. package/dist/core/runner/prepare-config-doors.js +3 -1
  57. package/dist/core/runner/prepare-execution-env.d.ts +5 -2
  58. package/dist/core/runner/prepare-execution-env.js +11 -3
  59. package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
  60. package/dist/core/runner/prepare-gate-stations.js +3 -1
  61. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  62. package/dist/core/runner/prepare-hands-readface.js +6 -6
  63. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  64. package/dist/core/runner/prepare-inherited-gate.js +8 -2
  65. package/dist/core/runner/prepare-listings.d.ts +4 -4
  66. package/dist/core/runner/prepare-listings.js +2 -22
  67. package/dist/core/runner/prepare-park-ask.d.ts +5 -0
  68. package/dist/core/runner/prepare-park-ask.js +6 -4
  69. package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
  70. package/dist/core/runner/prepare-policy-chain.js +14 -5
  71. package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
  72. package/dist/core/runner/prepare-suspend-saga.js +7 -1
  73. package/dist/core/runner/prepare-task.js +41 -18
  74. package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
  75. package/dist/core/runner/prepare-turn-wiring.js +2 -1
  76. package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
  77. package/dist/core/runner/prepare-workspace-restore.js +1 -0
  78. package/dist/core/runner/resume-apply.js +17 -3
  79. package/dist/core/runner/resume-checkpoint-screen.js +13 -0
  80. package/dist/core/runner/resume-policy-outcome.js +2 -1
  81. package/dist/core/runner/run-leg.js +5 -1
  82. package/dist/core/runner/run-notification-lane.js +2 -2
  83. package/dist/core/runner/runtask.d.ts +2 -0
  84. package/dist/core/runner/runtask.js +4 -2
  85. package/dist/core/runner/stream-steer-verb.js +2 -2
  86. package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
  87. package/dist/core/runner/sub-agent-deny-lists.js +121 -0
  88. package/dist/core/runner/synthetic-tools.d.ts +1 -1
  89. package/dist/core/runner/synthetic-tools.js +3 -2
  90. package/dist/core/runner/write-protection-station.d.ts +3 -2
  91. package/dist/core/runner/write-protection-station.js +1 -0
  92. package/dist/core/runner-deps.d.ts +14 -0
  93. package/dist/core/sensitive-path-policy.d.ts +8 -0
  94. package/dist/core/sensitive-path-policy.js +18 -2
  95. package/dist/core/session-policy-store.d.ts +6 -2
  96. package/dist/core/session-policy-store.js +2 -1
  97. package/dist/core/shell-wrapper-projection.d.ts +11 -0
  98. package/dist/core/shell-wrapper-projection.js +56 -0
  99. package/dist/core/shell-wrapper-table.d.ts +144 -34
  100. package/dist/core/shell-wrapper-table.js +40 -20
  101. package/dist/core/skill-grant.d.ts +10 -2
  102. package/dist/core/skill-grant.js +20 -4
  103. package/dist/core/synthetic-read-verdict.d.ts +3 -0
  104. package/dist/core/synthetic-read-verdict.js +3 -1
  105. package/dist/core/task-notification.d.ts +12 -1
  106. package/dist/core/task-notification.js +13 -1
  107. package/dist/core/task-spec.d.ts +5 -3
  108. package/dist/core/tool-catalog-entries.d.ts +21 -0
  109. package/dist/core/tool-catalog-entries.js +9 -0
  110. package/dist/core/tool-policy.d.ts +45 -12
  111. package/dist/core/tool-policy.js +18 -20
  112. package/dist/core/tool-spec.d.ts +23 -4
  113. package/dist/core/types.d.ts +1 -1
  114. package/dist/core/types.js +1 -1
  115. package/dist/core/write-protect.d.ts +4 -0
  116. package/dist/core/write-protect.js +30 -9
  117. package/dist/index.d.ts +10 -6
  118. package/dist/index.js +9 -5
  119. package/dist/orchestration/run-spec.js +8 -7
  120. package/dist/orchestration/workflow.js +3 -2
  121. package/dist/shell/lex.d.ts +7 -0
  122. package/dist/shell/lex.js +17 -0
  123. package/dist/tools/fs/bash-program-position.d.ts +8 -87
  124. package/dist/tools/fs/bash-program-position.js +33 -69
  125. package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
  126. package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
  127. package/dist/tools/fs/index.d.ts +3 -2
  128. package/dist/tools/fs/program-read-model.d.ts +694 -0
  129. package/dist/tools/fs/program-read-model.js +1252 -0
  130. package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
  131. package/dist/tools/fs/program-read-model.rows.js +613 -0
  132. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  133. package/dist/tools/fs/read-deny-home.js +62 -24
  134. package/dist/tools/fs/read-deny.d.ts +54 -14
  135. package/dist/tools/fs/read-deny.js +104 -50
  136. package/dist/tools/fs/removal-targets.d.ts +83 -0
  137. package/dist/tools/fs/removal-targets.js +236 -0
  138. package/dist/tools/fs/safety.d.ts +3 -0
  139. package/dist/tools/fs/safety.js +10 -3
  140. package/dist/tools/fs/shell-read-argv.d.ts +85 -0
  141. package/dist/tools/fs/shell-read-argv.js +386 -0
  142. package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
  143. package/dist/tools/fs/shell-read-bindings.js +746 -0
  144. package/dist/tools/fs/shell-read-check.js +23 -15
  145. package/dist/tools/fs/shell-read-establish.js +79 -5
  146. package/dist/tools/fs/shell-read-judge.d.ts +11 -1
  147. package/dist/tools/fs/shell-read-judge.js +9 -1
  148. package/dist/tools/fs/shell-read-plan.d.ts +31 -7
  149. package/dist/tools/fs/shell-read-plan.js +299 -393
  150. package/dist/tools/fs/shell-read-word.d.ts +29 -26
  151. package/dist/tools/fs/shell-read-word.js +30 -38
  152. package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
  153. package/dist/tools/fs/shell-read-write-targets.js +83 -136
  154. package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
  155. package/dist/tools/fs/shell-reentry-text.js +90 -53
  156. package/package.json +1 -1
  157. package/test/export-surface.snapshot.json +49 -3
  158. package/wire-changes/core@7.45.0.json +1084 -0
  159. 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 tools from `tools`
708
- * (`["*"]` = all). Omit for the whole `tools` pool. Allowlist is the security-recommended shape.
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 `resolveToolSubset` whitelist as the
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. Nested delegation threads the SAME factory down
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
@@ -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 { resolveToolSubset, toolNameAllowed } from "./tool-filter.js";
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 allow = def.allowTools;
1306
- const hasAllow = allow !== undefined && allow.length > 0 && !allow.includes("*");
1307
- const deny = def.denyTools ?? [];
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
- isError: true,
1723
- content: `Sub-agent not started: isolation "worktree" is unavailable on this run (no worktree capability — read-only run or no execution environment). Retry without the isolation parameter.`,
1724
- details: { error: "isolation.unavailable" },
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
- const finishWorktree = async () => {
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
- if (ctx.signal?.aborted) {
1813
+ const notStarted = async (why, details) => {
1793
1814
  const wt = await finishWorktree();
1794
- return {
1795
- isError: true,
1796
- content: `Sub-agent not started: the parent task was aborted while extraTools was being evaluated.${wt ? `\n${wt}` : ""}`,
1797
- details: { error: "aborted" },
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 allowed = new Set([...resolveToolSubset(childPool, def ? def.allowTools : opts.allowTools, def ? def.denyTools : opts.denyTools), ...(agentName !== undefined ? teammateInheritedFamily(childPool.filter((t) => inherited.includes(t)), def ? def.denyTools : opts.denyTools) : [])]);
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
- ...(ctx.excludeTools !== undefined ? { excludeTools: [...ctx.excludeTools] } : {}),
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
- ...(ctx.excludeTools !== undefined ? { excludeTools: [...ctx.excludeTools] } : {}),
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. The asymmetric prepare-time fail-loud for
20
- * `TaskSpec.agents` DENY entries is a deliberate sema extension (no upstream deny-list exists): an
21
- * allow-typo silently narrows (safe direction), a deny-typo silently widens (unacceptable).
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(pool: ReadonlyArray<ToolSpec>, allowTools?: ReadonlyArray<string>, denyTools?: ReadonlyArray<string>): ToolSpec[];
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
+ };