@sema-agent/core 7.7.0 → 7.8.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 +37 -0
- package/dist/agents/agent-transcript-tool.js +2 -2
- package/dist/agents/list-agents-tool.js +2 -3
- package/dist/agents/observer.js +2 -2
- package/dist/agents/send-message-tool.js +2 -1
- package/dist/agents/subagent-steps.js +5 -4
- package/dist/agents/subagent.js +2 -3
- package/dist/core/ask-question.js +2 -2
- package/dist/core/checkpoint-store.d.ts +38 -4
- package/dist/core/context-edit.d.ts +0 -22
- package/dist/core/context-edit.js +2 -11
- package/dist/core/engine-notice.d.ts +6 -0
- package/dist/core/fs-write-gate-policy.d.ts +7 -1
- package/dist/core/fs-write-gate-policy.js +7 -7
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +4 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +8 -2
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +17 -0
- package/dist/core/mcp.js +3 -1
- package/dist/core/memory-engine/tools.js +4 -12
- package/dist/core/permission-rules.d.ts +32 -2
- package/dist/core/permission-rules.js +43 -37
- package/dist/core/present-plan-tool.js +3 -4
- package/dist/core/runner/active-skill-scope.js +7 -4
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/contracts.d.ts +27 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +16 -11
- package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
- package/dist/core/runner/prepare-defer-classify.js +10 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
- package/dist/core/runner/prepare-delegation-surface.js +15 -17
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-lsp.d.ts +4 -3
- package/dist/core/runner/prepare-lsp.js +3 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
- package/dist/core/runner/prepare-memory-engine-session.js +4 -2
- package/dist/core/runner/prepare-offload-wrappers.js +7 -5
- package/dist/core/runner/prepare-park-ask.d.ts +4 -0
- package/dist/core/runner/prepare-park-ask.js +4 -1
- package/dist/core/runner/prepare-project-context.d.ts +5 -23
- package/dist/core/runner/prepare-project-context.js +7 -5
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
- package/dist/core/runner/prepare-protocol-tools.js +38 -21
- package/dist/core/runner/prepare-question-face.d.ts +4 -3
- package/dist/core/runner/prepare-question-face.js +5 -3
- package/dist/core/runner/prepare-run-refs.d.ts +16 -0
- package/dist/core/runner/prepare-run-refs.js +6 -0
- package/dist/core/runner/prepare-safety-scan.js +42 -2
- package/dist/core/runner/prepare-task.js +37 -19
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
- package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +3 -8
- package/dist/core/runner/run-harness-handlers.d.ts +46 -0
- package/dist/core/runner/run-harness-handlers.js +345 -0
- package/dist/core/runner/run-turn-boundary.d.ts +69 -0
- package/dist/core/runner/run-turn-boundary.js +694 -0
- package/dist/core/runner/runtask.js +47 -1043
- package/dist/core/runner/session-rule-policy.js +9 -4
- package/dist/core/runner/synthetic-tools.js +5 -8
- package/dist/core/runner/tool-disclosure.d.ts +4 -0
- package/dist/core/runner/tool-disclosure.js +9 -5
- package/dist/core/runner/tool-end-body.js +3 -2
- package/dist/core/runner/tool-face-overlay.d.ts +40 -0
- package/dist/core/runner/tool-face-overlay.js +125 -0
- package/dist/core/runner/tool-output-projection.d.ts +10 -0
- package/dist/core/runner/tool-output-projection.js +33 -10
- package/dist/core/sensitive-path-policy.d.ts +3 -0
- package/dist/core/sensitive-path-policy.js +6 -5
- package/dist/core/shared-memory/tools.js +3 -6
- package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
- package/dist/core/task-event.d.ts +10 -0
- package/dist/core/task-registry.js +4 -5
- package/dist/core/task-spec.d.ts +8 -0
- package/dist/core/tool-catalog-entries.d.ts +48 -0
- package/dist/core/tool-catalog-entries.js +174 -0
- package/dist/core/tool-conformance.d.ts +22 -0
- package/dist/core/tool-conformance.js +37 -0
- package/dist/core/tool-face.d.ts +171 -0
- package/dist/core/tool-face.js +25 -0
- package/dist/core/tool-policy.d.ts +24 -0
- package/dist/core/tool-policy.js +4 -2
- package/dist/core/tool-registry.d.ts +114 -0
- package/dist/core/tool-registry.js +114 -0
- package/dist/core/tool-result-budget.d.ts +0 -3
- package/dist/core/tool-result-budget.js +4 -3
- package/dist/core/tool-result-store.d.ts +0 -18
- package/dist/core/tool-result-store.js +2 -18
- package/dist/core/tool-roster.d.ts +450 -0
- package/dist/core/tool-roster.js +497 -0
- package/dist/core/tool-spec.d.ts +54 -3
- package/dist/core/tools.js +7 -0
- package/dist/core/wiring-manifest.d.ts +13 -0
- package/dist/core/wiring-manifest.js +2 -1
- package/dist/core/write-protect.d.ts +2 -1
- package/dist/core/write-protect.js +5 -4
- package/dist/engine/compaction/utils.d.ts +6 -0
- package/dist/engine/compaction/utils.js +19 -19
- package/dist/engine/loop/types.d.ts +6 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +15 -4
- package/dist/orchestration/goal.js +2 -1
- package/dist/orchestration/run-spec.js +3 -5
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +7 -11
- package/dist/tools/fs/fs-read.js +2 -2
- package/dist/tools/fs/fs-search-tools.d.ts +0 -7
- package/dist/tools/fs/fs-search-tools.js +3 -16
- package/dist/tools/fs/fs-write.js +4 -6
- package/dist/tools/fs/repo-map.js +2 -2
- package/dist/tools/fs/safety.d.ts +2 -1
- package/dist/tools/fs/safety.js +6 -5
- package/dist/tools/monitor.js +2 -2
- package/dist/tools/scheduler-tools.js +5 -8
- package/dist/tools/task-list.js +5 -8
- package/dist/tools/todo.js +2 -2
- package/dist/tools/web.js +3 -6
- package/dist/tools/worktree.js +3 -4
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +147 -7
package/dist/core/mcp.js
CHANGED
|
@@ -18,6 +18,7 @@ import { discloseReminderShaped, observeReminderMarkEcho } from "./reminder-disc
|
|
|
18
18
|
import { withContentOrigin } from "./memory-engine/content-origin.js";
|
|
19
19
|
import { snapshotMcpEntries } from "./locked-config.js";
|
|
20
20
|
import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
|
|
21
|
+
import { schemaKeyBoundProblem } from "./tool-roster.js";
|
|
21
22
|
export const MCP_PREFIX = MCP_NAMESPACE.prefix;
|
|
22
23
|
const MCP_OUTPUT_TOKENS_DEFAULT = 25_000;
|
|
23
24
|
const MCP_CHARS_PER_TOKEN = 4;
|
|
@@ -1513,7 +1514,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer, reminderD
|
|
|
1513
1514
|
effectiveInputSchema = normalized.schema;
|
|
1514
1515
|
effectiveDescription = normalized.note + (effectiveDescription ? `\n\n${effectiveDescription}` : "");
|
|
1515
1516
|
}
|
|
1516
|
-
const schemaProblem = mcpToolSchemaProblem(effectiveInputSchema);
|
|
1517
|
+
const schemaProblem = mcpToolSchemaProblem(effectiveInputSchema) ?? schemaKeyBoundProblem(effectiveInputSchema);
|
|
1517
1518
|
if (schemaProblem !== undefined) {
|
|
1518
1519
|
dropped.push({ tool: inlineUntrusted(t.name), reason: inlineUntrusted(schemaProblem, 240) });
|
|
1519
1520
|
continue;
|
|
@@ -1551,6 +1552,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer, reminderD
|
|
|
1551
1552
|
parameters: (effectiveInputSchema ?? { type: "object" }),
|
|
1552
1553
|
...(mcpMaxResultSizeChars !== undefined ? { mcpMaxResultSizeChars } : {}),
|
|
1553
1554
|
...(mcpAlwaysLoad ? { mcpAlwaysLoad: true } : {}),
|
|
1555
|
+
mcpRemoteName: remoteName,
|
|
1554
1556
|
execute: async (_toolCallId, params, signal) => {
|
|
1555
1557
|
const what = `The call to tool ${inlineUntrusted(remoteName)}`;
|
|
1556
1558
|
if (isServerRevoked?.(spec.name) === true) {
|
|
@@ -6,6 +6,7 @@ import { formatMemoryAge } from "../memory-recall.js";
|
|
|
6
6
|
import { committedOriginOf } from "./frontmatter.js";
|
|
7
7
|
import { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_INDEX_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, MEMORY_SEARCH_BAND_ORDER_HEADER } from "./provenance-wording.js";
|
|
8
8
|
import { deriveSupersededSet, memorySupersededNote, MEMORY_SEARCH_SUPERSEDED_TAG } from "./consolidation.js";
|
|
9
|
+
import { toolFace } from "../tool-catalog-entries.js";
|
|
9
10
|
export const MEMORY_SEARCH_TOOL_NAME = "memory_search";
|
|
10
11
|
export const MEMORY_GET_TOOL_NAME = "memory_get";
|
|
11
12
|
export const MEMORY_INDEX_TOOL_NAME = "memory_index";
|
|
@@ -175,11 +176,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
175
176
|
};
|
|
176
177
|
const searchTool = {
|
|
177
178
|
name: MEMORY_SEARCH_TOOL_NAME,
|
|
179
|
+
...toolFace("memory-search"),
|
|
178
180
|
description: carry ? `${SEARCH_DESCRIPTION}\n\n${MEMORY_PROVENANCE_SEARCH_SENTENCE}` : SEARCH_DESCRIPTION,
|
|
179
|
-
effect: "read",
|
|
180
|
-
offload: false,
|
|
181
|
-
contentOrigin: "local",
|
|
182
|
-
contract: { contractId: "core.memory_search@1", implementationRevision: "6" },
|
|
183
181
|
parameters: Type.Object({
|
|
184
182
|
query: Type.String({ description: "Keywords to look for (lexical match against entry names, descriptions and bodies)." }),
|
|
185
183
|
limit: Type.Optional(Type.Number({ description: `Maximum hits to return (default ${MEMORY_SEARCH_DEFAULT_LIMIT}, max ${MEMORY_SEARCH_MAX_LIMIT}).` })),
|
|
@@ -319,11 +317,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
319
317
|
};
|
|
320
318
|
const getTool = {
|
|
321
319
|
name: MEMORY_GET_TOOL_NAME,
|
|
320
|
+
...toolFace("memory-get"),
|
|
322
321
|
description: GET_DESCRIPTION,
|
|
323
|
-
effect: "read",
|
|
324
|
-
offload: false,
|
|
325
|
-
contentOrigin: "local",
|
|
326
|
-
contract: { contractId: "core.memory_get@1", implementationRevision: "6" },
|
|
327
322
|
parameters: Type.Object({
|
|
328
323
|
id: Type.Optional(Type.String({ description: "Entry id (exact lookup). Pass either id or slug, not both." })),
|
|
329
324
|
slug: Type.Optional(Type.String({ description: "Entry slug (its file path without .md). Ambiguous across scopes unless scope is also passed." })),
|
|
@@ -545,11 +540,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
545
540
|
};
|
|
546
541
|
const indexTool = {
|
|
547
542
|
name: MEMORY_INDEX_TOOL_NAME,
|
|
543
|
+
...toolFace("memory-index"),
|
|
548
544
|
description: carry ? `${INDEX_DESCRIPTION}\n\n${MEMORY_PROVENANCE_INDEX_SENTENCE}` : INDEX_DESCRIPTION,
|
|
549
|
-
effect: "read",
|
|
550
|
-
offload: false,
|
|
551
|
-
contentOrigin: "local",
|
|
552
|
-
contract: { contractId: "core.memory_index@1", implementationRevision: "1" },
|
|
553
545
|
parameters: Type.Object({
|
|
554
546
|
offset: Type.Optional(Type.Number({ description: "Zero-based row offset into the listing (default 0) — pass the previous page's footer value to continue." })),
|
|
555
547
|
limit: Type.Optional(Type.Number({ description: `Maximum rows for this page (default ${MEMORY_INDEX_PAGE_ROWS}, max ${MEMORY_INDEX_MAX_PAGE_ROWS}).` })),
|
|
@@ -43,6 +43,11 @@ export interface PermissionRuleIssue {
|
|
|
43
43
|
/** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(独立
|
|
44
44
|
* 分类,绝不静默 no-op —— `Bash(npm run:*)` 被当 generic 参数规则接受后永不匹配是最危险的失败形态)。 */
|
|
45
45
|
code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded" | "unsupported.bash_prefix"
|
|
46
|
+
/** design/388 B15 — the closed rule-lint set over a tool's RULE FACE (derived scalar vocabulary; a
|
|
47
|
+
* declaration only narrows it): the named parameter is not one the tool carries; it is carried but
|
|
48
|
+
* is an object/array (a rule on it can never match); the tool is unknown to the resolver a leg
|
|
49
|
+
* supplied (absent resolver ⇒ the catalog alone judges and an unknown tool stays permissive). */
|
|
50
|
+
| "unsupported.param_unknown" | "unsupported.param_non_scalar" | "unsupported.tool_unknown"
|
|
46
51
|
/** design/179 D7: a Bash prefix rule under `bashPrefixLane:"rules-store"` — reported so a migration
|
|
47
52
|
* report can tell "route this through the rule store" apart from "this is a mistake". */
|
|
48
53
|
| "unsupported.bash_prefix_rules_store" | "unsupported.file_glob" | "unsupported.mcp_paren"
|
|
@@ -98,6 +103,13 @@ export interface PermissionRulePolicyOptions {
|
|
|
98
103
|
*/
|
|
99
104
|
bashPrefixLane?: "reject" | "rules-store";
|
|
100
105
|
caps?: Partial<PermissionRuleCaps>;
|
|
106
|
+
/**
|
|
107
|
+
* design/388 B15 — the leg's RULE-FACE resolver (typically `ruleFacesFromRoster(prepared.toolRoster)`): with it
|
|
108
|
+
* every tool on the leg is judged — MCP and caller tools included — and a rule naming a tool the leg does not
|
|
109
|
+
* mount is `unsupported.tool_unknown`. Without it the static catalog alone judges; a tool the catalog does not
|
|
110
|
+
* carry stays permissive (a rule for it compiles unjudged).
|
|
111
|
+
*/
|
|
112
|
+
ruleFaces?: (toolName: string) => RuleFaceView | undefined;
|
|
101
113
|
}
|
|
102
114
|
/**
|
|
103
115
|
* Parse one rule string — CC `Lh` :55214 parity, including its lenient downgrades (malformed parens /
|
|
@@ -134,11 +146,29 @@ export declare function namespacedRuleNameCovers(ruleName: string, toolName: str
|
|
|
134
146
|
* — i.e. the shapes {@link namespacedRuleNameCovers} answers and an exact name table cannot. Every
|
|
135
147
|
* lane that keys rules by exact tool name needs this to know which entries it must NOT key that way. */
|
|
136
148
|
export declare function isNamespacedCoveringRuleName(name: string): boolean;
|
|
137
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* design/388 A-6/7/8/9 (D-3): the per-tool RULE FACE — the parameters a `Tool(param:pattern)` rule may name
|
|
151
|
+
* (`params`, the SCALAR top-level keys ∪ the path target's aliases), the ones the generic lexical matcher must
|
|
152
|
+
* not be pointed at (`primaryParams`), the carried-but-non-scalar keys a rule can never match (`nonScalar`), and
|
|
153
|
+
* whether the tool names a path target at all (the file-tool glob lane). Resolved through
|
|
154
|
+
* {@link PermissionRulePolicyOptions.ruleFaces} when a leg supplies its roster, else from the static catalog
|
|
155
|
+
* — the three hand-kept tables (`PRIMARY_FIELDS` / `FILE_TOOLS` / `FILE_TOOL_PARAMS`) this replaced are gone.
|
|
156
|
+
*/
|
|
157
|
+
export interface RuleFaceView {
|
|
158
|
+
primaryParams: readonly string[];
|
|
159
|
+
/** The full scalar vocabulary; ABSENT when the resolver does not know it (the static catalog for a
|
|
160
|
+
* definition whose schema is factory-built) — then only the primary-field check applies. */
|
|
161
|
+
params?: readonly string[];
|
|
162
|
+
nonScalar?: readonly string[];
|
|
163
|
+
pathTargeting?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** The static catalog's answer for a wire name (undefined ⇒ the catalog does not carry it). */
|
|
166
|
+
export declare function catalogRuleFaceView(toolName: string): RuleFaceView | undefined;
|
|
167
|
+
/** Exported for the lockstep guard only (the scalar vocabulary of a catalog tool) — not part of the rule DSL. */
|
|
138
168
|
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
139
169
|
/** Dry-run 校验/迁移报告:不 throw,返回全部 issues(含 `unsupported.*` 分类,
|
|
140
170
|
* 供从 CC settings 迁移的部署逐条对照)。 */
|
|
141
|
-
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "bashPrefixLane">): PermissionRuleIssue[];
|
|
171
|
+
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "bashPrefixLane" | "ruleFaces">): PermissionRuleIssue[];
|
|
142
172
|
/**
|
|
143
173
|
* Compile declarative permission rules into ONE {@link ToolPolicy} (design/127). Evaluation order
|
|
144
174
|
* inside the policy (CC `G1e` :595272 isomorph): bare deny → param deny → bare ask → param ask →
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MCP_NAMESPACE, protocolOf } from "./protocol-table.js";
|
|
2
|
-
|
|
2
|
+
import { catalogRuleFaceOf, pathTargetOf } from "./tool-registry.js";
|
|
3
|
+
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
3
4
|
const DEFAULT_CAPS = {
|
|
4
5
|
maxRules: 256,
|
|
5
6
|
maxRuleLength: 512,
|
|
@@ -107,31 +108,16 @@ export function isNamespacedCoveringRuleName(name) {
|
|
|
107
108
|
const parsed = parseNamespacedRuleName(name);
|
|
108
109
|
return parsed !== undefined && (parsed.tool === undefined || parsed.tool.includes("*"));
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
WebFetch: new Set(["url"]),
|
|
120
|
-
WebSearch: new Set(["query"]),
|
|
121
|
-
};
|
|
122
|
-
const FILE_TOOLS = new Set(["Read", "Edit", "Write", "MultiEdit", "NotebookEdit", "Grep", "Glob"]);
|
|
123
|
-
const FILE_TOOL_PARAMS = {
|
|
124
|
-
Read: new Set(["file_path", "path", "offset", "limit", "pages"]),
|
|
125
|
-
Edit: new Set(["file_path", "path", "old_string", "new_string", "replace_all"]),
|
|
126
|
-
Write: new Set(["file_path", "path", "content"]),
|
|
127
|
-
NotebookEdit: new Set(["notebook_path", "path", "cell_id", "cell_type", "edit_mode", "new_source"]),
|
|
128
|
-
Grep: new Set([
|
|
129
|
-
"pattern", "path", "glob", "type", "output_mode", "head_limit", "offset",
|
|
130
|
-
"multiline", "context", "ignore_case", "-i", "-n", "-o", "-A", "-B", "-C",
|
|
131
|
-
]),
|
|
132
|
-
Glob: new Set(["pattern", "path", "max_results"]),
|
|
111
|
+
export function catalogRuleFaceView(toolName) {
|
|
112
|
+
const face = catalogRuleFaceOf(toolName);
|
|
113
|
+
if (face === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
return { ...face, pathTargeting: pathTargetOf(toolName) !== undefined };
|
|
116
|
+
}
|
|
117
|
+
export const fileToolParamVocabulary = (canonicalTool) => {
|
|
118
|
+
const face = catalogRuleFaceOf(canonicalTool);
|
|
119
|
+
return face?.params === undefined || pathTargetOf(canonicalTool) === undefined ? undefined : new Set(face.params);
|
|
133
120
|
};
|
|
134
|
-
export const fileToolParamVocabulary = (canonicalTool) => FILE_TOOL_PARAMS[canonicalTool];
|
|
135
121
|
function countStars(s) {
|
|
136
122
|
let n = 0;
|
|
137
123
|
for (const c of s)
|
|
@@ -139,7 +125,15 @@ function countStars(s) {
|
|
|
139
125
|
n++;
|
|
140
126
|
return n;
|
|
141
127
|
}
|
|
142
|
-
function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
128
|
+
function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject", ruleFaces) {
|
|
129
|
+
const faceOf = (toolName) => {
|
|
130
|
+
if (ruleFaces !== undefined) {
|
|
131
|
+
const face = ruleFaces(toolName);
|
|
132
|
+
return { face, judged: true };
|
|
133
|
+
}
|
|
134
|
+
const face = catalogRuleFaceView(toolName);
|
|
135
|
+
return { face, judged: face !== undefined };
|
|
136
|
+
};
|
|
143
137
|
const byTool = new Map();
|
|
144
138
|
const namespacedCovering = { deny: [], ask: [], allow: [] };
|
|
145
139
|
const issues = [];
|
|
@@ -209,18 +203,30 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
|
209
203
|
continue;
|
|
210
204
|
}
|
|
211
205
|
}
|
|
212
|
-
else
|
|
213
|
-
|
|
214
|
-
|
|
206
|
+
else {
|
|
207
|
+
const resolved = faceOf(toolName);
|
|
208
|
+
if (resolved.judged && resolved.face === undefined) {
|
|
209
|
+
bad(text, "unsupported.tool_unknown", `"${toolName}" is not a tool on this leg's roster — the rule would compile and never match`);
|
|
215
210
|
continue;
|
|
216
211
|
}
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
bad(text, "unsupported.unknown_param", `"${paramPart}" is not a parameter of ${toolName} (it carries ${[...vocabulary].sort().join(", ")}) — the rule would compile and never match. ` +
|
|
221
|
-
`For a path glob use the tool-specific lane (\`${toolName}(src/**)\` is not the generic param DSL); a Windows path such as \`${toolName}(C:/foo)\` parses as the param "C".`);
|
|
212
|
+
const face = resolved.face;
|
|
213
|
+
if (face !== undefined && face.pathTargeting === true && colon <= 0) {
|
|
214
|
+
bad(text, "unsupported.file_glob", "file-tool glob rules (`Edit(src/**)`) are the tool-specific lane — not supported by the generic param DSL");
|
|
222
215
|
continue;
|
|
223
216
|
}
|
|
217
|
+
if (face?.params !== undefined && colon > 0) {
|
|
218
|
+
const paramPart = content.slice(0, colon).trim();
|
|
219
|
+
if (!face.params.includes(paramPart)) {
|
|
220
|
+
if (face.nonScalar?.includes(paramPart)) {
|
|
221
|
+
bad(text, "unsupported.param_non_scalar", `"${paramPart}" is a parameter of ${toolName} but its value is an object or an array — the generic matcher compares scalars only; the rule would compile and never match`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
bad(text, "unsupported.param_unknown", `"${paramPart}" is not a parameter of ${toolName} (it carries ${[...face.params].sort().join(", ")}) — the rule would compile and never match. ` +
|
|
225
|
+
`For a path glob use the tool-specific lane (\`${toolName}(src/**)\` is not the generic param DSL); a Windows path such as \`${toolName}(C:/foo)\` parses as the param "C".`);
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
224
230
|
}
|
|
225
231
|
if (colon <= 0) {
|
|
226
232
|
bad(text, "invalid.param_split", "param-level ruleContent must be `param:pattern`");
|
|
@@ -240,7 +246,7 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
|
240
246
|
bad(text, "invalid.cap_exceeded", `pattern exceeds maxPatternLength ${caps.maxPatternLength} or maxStars ${caps.maxStars}`);
|
|
241
247
|
continue;
|
|
242
248
|
}
|
|
243
|
-
if (
|
|
249
|
+
if (faceOf(toolName).face?.primaryParams.includes(param) === true && primaryFieldGeneric === "reject") {
|
|
244
250
|
bad(text, "invalid.primary_field", `"${param}" is ${toolName}'s primary field — the generic matcher is a trim+FULL-STRING lexical match (no shell/path semantics); '${toolName}(${param}:git push*)' would NOT match 'cd /x && git push'. Opt in with primaryFieldGeneric:"allow-lexical" if you understand this.`);
|
|
245
251
|
continue;
|
|
246
252
|
}
|
|
@@ -251,7 +257,7 @@ function compile(rules, caps, primaryFieldGeneric, bashPrefixLane = "reject") {
|
|
|
251
257
|
}
|
|
252
258
|
export function validatePermissionRules(rules, opts) {
|
|
253
259
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
254
|
-
return compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.bashPrefixLane ?? "reject").issues;
|
|
260
|
+
return compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.bashPrefixLane ?? "reject", opts?.ruleFaces).issues;
|
|
255
261
|
}
|
|
256
262
|
function isPlainObjectArgs(args) {
|
|
257
263
|
return typeof args === "object" && args !== null && !Array.isArray(args);
|
|
@@ -281,7 +287,7 @@ function ruleMessage(kind, ruleText, source) {
|
|
|
281
287
|
}
|
|
282
288
|
export function createPermissionRulePolicy(rules, opts) {
|
|
283
289
|
const caps = { ...DEFAULT_CAPS, ...opts?.caps };
|
|
284
|
-
const { byTool, namespacedCovering, issues } = compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.bashPrefixLane ?? "reject");
|
|
290
|
+
const { byTool, namespacedCovering, issues } = compile(rules, caps, opts?.primaryFieldGeneric ?? "reject", opts?.bashPrefixLane ?? "reject", opts?.ruleFaces);
|
|
285
291
|
const setLevelCap = issues.some((i) => i.code === "invalid.cap_exceeded" && i.rule === "");
|
|
286
292
|
if (issues.length > 0 && ((opts?.onInvalidRule ?? "throw") === "throw" || setLevelCap)) {
|
|
287
293
|
const e = new Error(`createPermissionRulePolicy: ${issues.length} invalid/unsupported rule(s):\n` +
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
3
|
+
import { toolFace } from "./tool-catalog-entries.js";
|
|
3
4
|
export const PRESENT_PLAN_TOOL_NAME = "ExitPlanMode";
|
|
4
5
|
export const ENTER_PLAN_MODE_TOOL_NAME = "EnterPlanMode";
|
|
5
6
|
const ENTER_PLAN_MODE_DESCRIPTION = `Enter PLAN MODE (read-only): stop making changes and switch to exploration/planning. For the rest of this run, write/edit/mutating tool calls are REJECTED while read-only tools (Read, Grep, Glob, etc.) still work. When your plan is ready, call ${PRESENT_PLAN_TOOL_NAME} with it — the run PAUSES for a human to approve it, then continues (read-only lifted) so you can execute.
|
|
@@ -31,10 +32,9 @@ BAD: "Fix the typo in the README" (trivial; nothing to align on). "Remove the le
|
|
|
31
32
|
export function createEnterPlanModeTool(enterPlanMode) {
|
|
32
33
|
return {
|
|
33
34
|
name: ENTER_PLAN_MODE_TOOL_NAME,
|
|
34
|
-
|
|
35
|
+
...toolFace("enter-plan-mode"),
|
|
35
36
|
description: ENTER_PLAN_MODE_DESCRIPTION,
|
|
36
37
|
parameters: Type.Object({}),
|
|
37
|
-
effect: "read",
|
|
38
38
|
executionMode: "sequential",
|
|
39
39
|
execute: async () => {
|
|
40
40
|
enterPlanMode();
|
|
@@ -61,14 +61,13 @@ const PRESENT_PLAN_DESCRIPTION = [
|
|
|
61
61
|
export function createPresentPlanTool(requestReview) {
|
|
62
62
|
return {
|
|
63
63
|
name: PRESENT_PLAN_TOOL_NAME,
|
|
64
|
-
|
|
64
|
+
...toolFace("exit-plan-mode"),
|
|
65
65
|
description: PRESENT_PLAN_DESCRIPTION,
|
|
66
66
|
parameters: Type.Object({
|
|
67
67
|
plan: Type.String({
|
|
68
68
|
description: "Your full proposed plan, in markdown. The human reviewer approves / edits / rejects it before you act.",
|
|
69
69
|
}),
|
|
70
70
|
}),
|
|
71
|
-
effect: "read",
|
|
72
71
|
execute: async (args) => {
|
|
73
72
|
const { plan } = args;
|
|
74
73
|
requestReview({ reason: "ExitPlanMode" });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { canonicalizeTarget
|
|
1
|
+
import { canonicalizeTarget } from "../../tools/fs/safety.js";
|
|
2
|
+
import { pathTargetValue } from "../tool-registry.js";
|
|
2
3
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
3
4
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
4
5
|
export class ActiveSkillScope {
|
|
@@ -74,7 +75,9 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
const pathConstrainingActive = manifests.some((m) => m.allowPaths && m.allowPaths.length > 0);
|
|
77
|
-
|
|
78
|
+
const declaredTarget = req.face?.pathTarget;
|
|
79
|
+
const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
|
|
80
|
+
if (pathConstrainingActive && !skillScopeEligible) {
|
|
78
81
|
const eff = toolEffects?.get(toolName) ?? "write";
|
|
79
82
|
if (eff !== "read") {
|
|
80
83
|
const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
|
|
@@ -85,10 +88,10 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
|
-
if (
|
|
91
|
+
if (skillScopeEligible) {
|
|
89
92
|
const constraining = manifests.filter((m) => m.allowPaths && m.allowPaths.length > 0);
|
|
90
93
|
if (constraining.length > 0) {
|
|
91
|
-
const path =
|
|
94
|
+
const path = pathTargetValue(req.args, declaredTarget);
|
|
92
95
|
if (typeof path !== "string" || path.length === 0) {
|
|
93
96
|
return {
|
|
94
97
|
action: "deny",
|
|
@@ -15,7 +15,7 @@ import type { Prepared } from "./contracts.js";
|
|
|
15
15
|
* end-of-task lane's deliberate ABSENCE of `signal`) is a legitimate per-lane difference and stays at
|
|
16
16
|
* the call site, where its reason is written down.
|
|
17
17
|
*
|
|
18
|
-
* Every export is a free function: the turn-boundary lane assembles its call inside `
|
|
18
|
+
* Every export is a free function: the turn-boundary lane assembles its call inside `createTurnBoundary`
|
|
19
19
|
* (a module-level function with no `this`), so a method would not be reachable from all three sites.
|
|
20
20
|
* Nothing here reads or writes runner state — pure assembly over `spec` + `Prepared`.
|
|
21
21
|
*/
|
|
@@ -718,7 +718,10 @@ export interface Prepared {
|
|
|
718
718
|
* the human already adjudicated it). Same wrapping (offload + ctx) the harness runs with.
|
|
719
719
|
*
|
|
720
720
|
* ONE array, mutated in place; identity is the contract (every closure that resolves a name at run time
|
|
721
|
-
* reads this exact array).
|
|
721
|
+
* reads this exact array). Since design/388 the array is PRIVATE to the `RosterBuilder` (tool-roster.ts) —
|
|
722
|
+
* every station below writes through its verbs (`mount` / `mountAll` / `unmountWhere` / `replaceAt` /
|
|
723
|
+
* `replaceDomain`) and this field is the builder's read face. WRITER TABLE (design/238 R-3) — the only
|
|
724
|
+
* stations that write it, in prepare order:
|
|
722
725
|
* · caps-and-workflow — MINTS it (`spec.tools` through the ctx wrap + the large-result wrapper), then pushes
|
|
723
726
|
* ReportBlocked (enableBlockedReport ≠ false), ReportFindings (no caller tool of that name/alias),
|
|
724
727
|
* ExitPlanMode + EnterPlanMode (enablePlanMode ∧ the plan-review face), Workflow (self-orchestration active
|
|
@@ -740,7 +743,28 @@ export interface Prepared {
|
|
|
740
743
|
* memory session or a recorder channel); the refresh seam re-runs the same pass through `contentOriginWrapRef`
|
|
741
744
|
* after a re-splice (identity-tracked, so a wrapper is never wrapped again).
|
|
742
745
|
* tool-disclosure and listings only READ it (`harnessTools` is a derived list; placeholders never enter here). */
|
|
743
|
-
tools: AgentTool[];
|
|
746
|
+
tools: readonly AgentTool[];
|
|
747
|
+
/**
|
|
748
|
+
* design/388 §2.5 — this leg's minted TOOL ROSTER (the same object `wiringManifest.tools` carries):
|
|
749
|
+
* one row per mounted instance in wire order. Minted ONCE at the prompt-assembly station (the same
|
|
750
|
+
* pass that projects the prompt manifest's tool rows, so the two agree by construction); the park
|
|
751
|
+
* station reads the gated call's identity off it, the exemption probe hands it to the host.
|
|
752
|
+
*/
|
|
753
|
+
toolRoster: import("../tool-roster.js").ToolRoster;
|
|
754
|
+
/**
|
|
755
|
+
* design/388 §2.5 — the run-time delta seat: every roster change after prepare (the RefreshMcpTools
|
|
756
|
+
* seam) re-mints the roster here and publishes a `ToolRosterDelta`. The run loop subscribes at the
|
|
757
|
+
* point it emits `wiring_manifest` and forwards each delta as a `tool_roster_delta` event; until it
|
|
758
|
+
* does, deltas are buffered in order.
|
|
759
|
+
*/
|
|
760
|
+
toolRosterDeltas: import("../tool-roster.js").ToolRosterDeltaSeat;
|
|
761
|
+
/**
|
|
762
|
+
* design/388 L4 — the leg's `tool_end.structured` projector: the engine card set ∪ the caller rows' declared
|
|
763
|
+
* `renderHints.resultCards`; an undeclared `details.type` is dropped and announced ONCE per leg
|
|
764
|
+
* (`config.tool_card_undeclared`). The run loop's tool_end body projection reads it in place of the
|
|
765
|
+
* module-level `structuredFrom` (which has no announce seat).
|
|
766
|
+
*/
|
|
767
|
+
structuredProjector: (result: unknown) => unknown;
|
|
744
768
|
/** Name→effect map for every tool this task can call (design/44 §3). Used by the abort-path orphan
|
|
745
769
|
* reconcile (design/64 §9) to make interrupted tool_results effect-aware (read/idempotent = safe to
|
|
746
770
|
* repeat; write/unknown = outcome unknown). Unknown names default to `write` (conservative). */
|
|
@@ -1985,7 +2009,7 @@ export interface ParkAsk {
|
|
|
1985
2009
|
* Grouped by lifecycle semantics per the B15 pre-recon partition. Function members start as inert
|
|
1986
2010
|
* placeholders and are assigned at their ORIGINAL declaration sites (the original code could not
|
|
1987
2011
|
* call them earlier either — TDZ proved it). Not on the npm face; this extraction is what enabled
|
|
1988
|
-
*
|
|
2012
|
+
* createTurnBoundary / createHarnessHandlers (三期, both in runtask.ts).
|
|
1989
2013
|
*/
|
|
1990
2014
|
export interface RunState {
|
|
1991
2015
|
telemetry: {
|
|
@@ -46,7 +46,7 @@ export interface PrepareAskLaneInput {
|
|
|
46
46
|
handsCwdRef: CwdRef | undefined;
|
|
47
47
|
/** borrowed-readonly — the shared mount roster; read at RUN time by the approval-preview projection (alias-aware
|
|
48
48
|
* lookup over whatever the roster holds when the ask is minted). Never mutated here. */
|
|
49
|
-
tools: AgentTool[];
|
|
49
|
+
tools: readonly AgentTool[];
|
|
50
50
|
/** borrowed-mutable — the inherited-unavailable marker set. Writer here: `resolveAskBound` consumes (`delete`) a
|
|
51
51
|
* marked call at its fail-closed intercept; the policy-chain phase records, the gate station sweeps. */
|
|
52
52
|
inheritedUnavailableAsks: Set<string>;
|
|
@@ -34,6 +34,7 @@ import type { AutoModeArmReason } from "../wiring-manifest.js";
|
|
|
34
34
|
import type { ReadFace } from "../../tools/fs/index.js";
|
|
35
35
|
import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
|
|
36
36
|
import { type BlockedRef } from "./synthetic-tools.js";
|
|
37
|
+
import { RosterBuilder } from "../tool-roster.js";
|
|
37
38
|
export interface PrepareCapsAndWorkflowInput {
|
|
38
39
|
/** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
|
|
39
40
|
* `enablePlanMode`, `interactiveTools`, `checkpointStore`; the caps/compliance stations read `principal`, `selfOrchestration`,
|
|
@@ -124,10 +125,10 @@ export interface PrepareCapsAndWorkflowInput {
|
|
|
124
125
|
}>;
|
|
125
126
|
}
|
|
126
127
|
export interface PrepareCapsAndWorkflowResult {
|
|
127
|
-
/** owned — THE run's roster
|
|
128
|
-
*
|
|
129
|
-
* ReportFindings, ExitPlanMode, EnterPlanMode and Workflow
|
|
130
|
-
|
|
128
|
+
/** owned — THE run's roster BUILDER (design/388: the mount array is private to it; `Prepared.tools` is its read face,
|
|
129
|
+
* whose JSDoc carries the writer table). Minted here from the caller's tools; every later mount goes through its verbs.
|
|
130
|
+
* Writers here: the caller mounts, then the ReportBlocked, ReportFindings, ExitPlanMode, EnterPlanMode and Workflow mounts. */
|
|
131
|
+
roster: RosterBuilder;
|
|
131
132
|
/** borrowed-mutable — the ReportBlocked tool's write seat (`Prepared.blockedRef`). Writer: the tool at run time; read at
|
|
132
133
|
* result assembly. */
|
|
133
134
|
blockedRef: BlockedRef;
|
|
@@ -2,7 +2,7 @@ import { isSyntheticApiErrorMessage } from "../../internal/harness.js";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { forkGovernanceDenial } from "../../agents/subagent.js";
|
|
4
4
|
import { CROSS_SESSION_CLASSIFIER_RULE } from "../../agents/cross-session-envelope.js";
|
|
5
|
-
import { createRunWorkflowTool
|
|
5
|
+
import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
|
|
6
6
|
import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-runner.js";
|
|
7
7
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
8
8
|
import { thinkingOffExpressible } from "../../brain/reasoning.js";
|
|
@@ -20,6 +20,7 @@ import { emitTrace } from "../trace.js";
|
|
|
20
20
|
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
21
21
|
import { derivedRouteFallsBack } from "./derived-route-fallback.js";
|
|
22
22
|
import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
|
|
23
|
+
import { RosterBuilder, callerMountSource } from "../tool-roster.js";
|
|
23
24
|
function assembleParentCaptureState(o, i, ctl, ancestors) {
|
|
24
25
|
const build = (optedOut, indeterminate) => ({
|
|
25
26
|
optedOut: optedOut === true,
|
|
@@ -65,7 +66,12 @@ function autoModeArmReasonOf(intent, facePresent, capsAutoMode, capsFaulted) {
|
|
|
65
66
|
}
|
|
66
67
|
export async function prepareCapsAndWorkflow(input) {
|
|
67
68
|
const { spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
|
|
68
|
-
const
|
|
69
|
+
const roster = new RosterBuilder(toolEffects);
|
|
70
|
+
for (const t of spec.tools ?? []) {
|
|
71
|
+
const mounted = mountCallerTool(t);
|
|
72
|
+
roster.mount(mounted, { source: callerMountSource(mounted), mountedBy: "scenario" });
|
|
73
|
+
}
|
|
74
|
+
function mountCallerTool(t) {
|
|
69
75
|
if (isDefineToolProduct(t)) {
|
|
70
76
|
return maybeOffload(t, t);
|
|
71
77
|
}
|
|
@@ -73,20 +79,20 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
73
79
|
...t,
|
|
74
80
|
execute: (args, ctx) => t.execute(args, enrichSpecToolCtx(ctx)),
|
|
75
81
|
}), t);
|
|
76
|
-
}
|
|
82
|
+
}
|
|
77
83
|
const blockedRef = {};
|
|
78
84
|
if (spec.enableBlockedReport !== false) {
|
|
79
|
-
|
|
85
|
+
roster.mount(createReportBlockedTool(blockedRef), { source: "builtin", mountedBy: "always" });
|
|
80
86
|
}
|
|
81
87
|
if (!(spec.tools ?? []).some((t) => t.name === REPORT_FINDINGS_TOOL_NAME || t.aliases?.includes(REPORT_FINDINGS_TOOL_NAME))) {
|
|
82
|
-
|
|
88
|
+
roster.mount(createReportFindingsTool(), { source: "builtin", mountedBy: "always" });
|
|
83
89
|
}
|
|
84
90
|
if (spec.enablePlanMode === true && spec.interactiveTools !== false) {
|
|
85
91
|
const planReviewFace = resolveCheckpointStore(spec, deps) !== undefined;
|
|
86
92
|
if (spec.interactiveTools === true || planReviewFace) {
|
|
87
|
-
|
|
93
|
+
roster.mount(defineTool(createPresentPlanTool(requestReview)), { source: "builtin", mountedBy: "optional" });
|
|
88
94
|
if (planReviewFace) {
|
|
89
|
-
|
|
95
|
+
roster.mount(defineTool(createEnterPlanModeTool(enterPlanMode)), { source: "builtin", mountedBy: "optional" });
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
}
|
|
@@ -210,8 +216,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
210
216
|
workflowToolsActive = true;
|
|
211
217
|
const currentSizeGuideline = () => resolveWorkflowSizeGuideline(deps.workflowLimits?.sizeGuideline).size;
|
|
212
218
|
workflowSizeGuideline = { legGuideline: currentSizeGuideline(), current: currentSizeGuideline };
|
|
213
|
-
|
|
214
|
-
tools.push(await createRunWorkflowTool({
|
|
219
|
+
roster.mount(await createRunWorkflowTool({
|
|
215
220
|
runner: runnerSelf,
|
|
216
221
|
scriptRunner: deps.workflowScriptRunner,
|
|
217
222
|
governanceBaseline: deps.workflowGovernanceBaseline,
|
|
@@ -266,7 +271,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
266
271
|
parentCenterSourceRevision: () => input.centerAdoption()?.sourceRevision,
|
|
267
272
|
...(forwardEvent ? { forwardEvent } : {}),
|
|
268
273
|
inheritedGateForChildren,
|
|
269
|
-
}));
|
|
274
|
+
}), { source: "builtin", mountedBy: "workflow" });
|
|
270
275
|
}
|
|
271
|
-
return {
|
|
276
|
+
return { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline };
|
|
272
277
|
}
|
|
@@ -1,43 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/390 M11 — prepareTask's TOOL-FACE FOLD + DEFERRED CLASSIFICATION phase: the exclusion valve's
|
|
3
|
-
* true unmount (in place, on the shared mount array), the full-shell anti-drift invariant, the classic-
|
|
4
|
-
* profile description swap, `classifyDeferredOverFace` (the one classifier both counterfactual passes
|
|
5
|
-
* run), the built-in memory pairs' support-name pre-check (sole-cause counterfactual, whole-group
|
|
6
|
-
* retraction, the engine pair's recall paragraph stripped back out of the memory block) and the final
|
|
7
|
-
* `deferred` set. The body is the driver's stretch moved verbatim (one exception, named below); the
|
|
8
|
-
* interface is the dependency list it had implicitly (design/238 R-1).
|
|
9
|
-
*
|
|
10
|
-
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await, so the phase adds no yield between
|
|
11
|
-
* the git-status probe before it and the content-origin wrap after it (design/390 §1.5 S1: a stretch
|
|
12
|
-
* with no await is extracted as a sync function — an `async` wrapper would open a microtask window).
|
|
13
|
-
*
|
|
14
|
-
* The one non-move: the stretch used to write `sharedMemoryPairMounted = false` /
|
|
15
|
-
* `memoryEnginePairMounted = false` on the retraction arm. Nothing reads either flag after this stretch
|
|
16
|
-
* (the driver's next reader was this stretch's own pair derivation, which runs first), so the two dead
|
|
17
|
-
* writes are not carried into the Result — the flags arrive as borrowed-readonly inputs.
|
|
18
|
-
*
|
|
19
|
-
* Throws pass through unchanged: `internal.full_shell_reachable_mismatch` (an engine invariant, never a
|
|
20
|
-
* configuration error). The reserved-name refusal for a standing ToolSearch collision is NOT here — it
|
|
21
|
-
* belongs to the disclosure mount downstream, which reads the `deferred` set this phase returns.
|
|
22
|
-
*/
|
|
23
|
-
import type { AgentTool } from "../../internal/harness.js";
|
|
24
1
|
import type { Model } from "../../internal/llm.js";
|
|
25
2
|
import type { MaterializedA2a } from "../a2a.js";
|
|
26
3
|
import type { MaterializedMcp } from "../mcp.js";
|
|
27
4
|
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
28
5
|
import type { AnnounceOnceSink, ToolFaceSnapshot } from "./contracts.js";
|
|
6
|
+
import { RosterBuilder } from "../tool-roster.js";
|
|
29
7
|
export interface PrepareDeferClassifyInput {
|
|
30
8
|
/** borrowed-readonly — the REBOUND spec (`spec′`); only `tools` is read (the caller roster the
|
|
31
9
|
* classifier judges: names for the full-schema filter, specs for the `defer` declarations). */
|
|
32
|
-
spec: Pick<TaskSpec, "tools">;
|
|
10
|
+
spec: Pick<TaskSpec, "tools" | "excludeAllTools">;
|
|
33
11
|
/** borrowed-readonly — deployment deps; only `deferMode` is read (the classifier's mode knob). */
|
|
34
12
|
deps: Pick<RunnerDeps, "deferMode">;
|
|
35
13
|
/** borrowed-mutable — the shared mount array (later refs alias it; in-place contract, never
|
|
36
14
|
* re-created). Mutated here, in order: `splice` of every excluded name; element REPLACEMENT with a
|
|
37
15
|
* shallow copy for the classic-profile description swap (the caller's ToolSpec object is never
|
|
38
16
|
* mutated); `splice` of a retracted memory group. After this phase: the content-origin wrap
|
|
39
|
-
* (element replacement with the wrapped object), the disclosure mount, the MCP refresh re-splice.
|
|
40
|
-
|
|
17
|
+
* (element replacement with the wrapped object), the disclosure mount, the MCP refresh re-splice. Since
|
|
18
|
+
* design/388 every one of those writes is a builder verb (`unmountWhere` / `replaceAt`). */
|
|
19
|
+
roster: RosterBuilder;
|
|
41
20
|
/** borrowed-readonly — the frozen task-start tool face: `exclude` (the valve), `defer` (operator
|
|
42
21
|
* defers, exact wire names), `alwaysLoad` (the inline pins). */
|
|
43
22
|
toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude" | "defer" | "alwaysLoad">;
|
|
@@ -4,6 +4,7 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
|
|
|
4
4
|
import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
|
|
5
5
|
import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
|
|
6
6
|
import { TOOL_SEARCH_NAME, classifyDeferred } from "./tool-disclosure.js";
|
|
7
|
+
import { RosterBuilder } from "../tool-roster.js";
|
|
7
8
|
function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
|
|
8
9
|
return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
|
|
9
10
|
}
|
|
@@ -11,13 +12,15 @@ function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
|
|
|
11
12
|
return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
|
|
12
13
|
}
|
|
13
14
|
export function prepareDeferClassify(input) {
|
|
14
|
-
const { spec, deps,
|
|
15
|
+
const { spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryRecallSegment, onceLedger, sessionId } = input;
|
|
16
|
+
const tools = roster.tools;
|
|
15
17
|
let memoryBlock = input.memoryBlock;
|
|
16
|
-
if (
|
|
18
|
+
if (spec.excludeAllTools === true) {
|
|
19
|
+
roster.unmountWhere(() => true);
|
|
20
|
+
}
|
|
21
|
+
else if (toolFaceSnapshot.exclude !== undefined && toolFaceSnapshot.exclude.length > 0) {
|
|
17
22
|
const excluded = new Set(toolFaceSnapshot.exclude);
|
|
18
|
-
|
|
19
|
-
if (excluded.has(tools[i].name))
|
|
20
|
-
tools.splice(i, 1);
|
|
23
|
+
roster.unmountWhere((t) => excluded.has(t.name));
|
|
21
24
|
}
|
|
22
25
|
if (handsEnabled) {
|
|
23
26
|
const fullShellOnRoster = tools.some((t) => t.name === "Bash" && getToolContract(t, "core").contractId === FULL_SHELL_CONTRACT_ID);
|
|
@@ -33,7 +36,7 @@ export function prepareDeferClassify(input) {
|
|
|
33
36
|
for (let i = 0; i < tools.length; i++) {
|
|
34
37
|
const t = tools[i];
|
|
35
38
|
if (t.descriptionClassic !== undefined)
|
|
36
|
-
|
|
39
|
+
roster.replaceAt(i, { ...t, description: t.descriptionClassic });
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
const userToolNames = (spec.tools ?? []).map((t) => t.name);
|
|
@@ -73,10 +76,7 @@ export function prepareDeferClassify(input) {
|
|
|
73
76
|
const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
|
|
74
77
|
if (soleCause && supportNameTaken) {
|
|
75
78
|
const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
|
|
76
|
-
|
|
77
|
-
if (retractNames.has(tools[i].name))
|
|
78
|
-
tools.splice(i, 1);
|
|
79
|
-
}
|
|
79
|
+
roster.unmountWhere((t) => retractNames.has(t.name));
|
|
80
80
|
if (sharedMemoryPair.length > 0) {
|
|
81
81
|
onceLedger.onError(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
|
|
82
82
|
`the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
|