@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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { isWinFormPath } from "../../tools/fs/safety.js";
|
|
3
3
|
import { createCoarseCommandNamePolicy, namespacedCoveringEntries, namespacedCoveringHit } from "../tool-policy.js";
|
|
4
|
-
|
|
5
|
-
export const
|
|
4
|
+
import { declaredPathTargetOf, isProtectedWrite, pathConfinableWriteToolNames, skillScopeWriteToolNames } from "../tool-registry.js";
|
|
5
|
+
export const PATH_WRITE_TOOLS = skillScopeWriteToolNames();
|
|
6
|
+
export const PATH_CONFINABLE_WRITE_TOOLS = pathConfinableWriteToolNames();
|
|
6
7
|
export function isWithin(root, p) {
|
|
7
8
|
if (!root)
|
|
8
9
|
return false;
|
|
@@ -49,14 +50,18 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
49
50
|
return d;
|
|
50
51
|
}
|
|
51
52
|
if (allowDirs) {
|
|
52
|
-
if (!
|
|
53
|
+
if (!isProtectedWrite(req)) {
|
|
53
54
|
const eff = toolEffects?.get(toolName) ?? "write";
|
|
54
55
|
if (eff !== "read") {
|
|
55
56
|
return deny(`write-capable tool "${req.toolName}" denied: session rule confines writes to allowDirs but this tool cannot be path-confined`);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
59
|
-
const
|
|
60
|
+
const declared = declaredPathTargetOf(req);
|
|
61
|
+
if (declared === undefined) {
|
|
62
|
+
return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the tool declares no path target to confine`);
|
|
63
|
+
}
|
|
64
|
+
const path = writeTargetPath(req, declared);
|
|
60
65
|
if (typeof path !== "string" || path.length === 0) {
|
|
61
66
|
return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
|
|
62
67
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool } from "../tools.js";
|
|
3
3
|
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
4
|
+
import { toolFace } from "../tool-catalog-entries.js";
|
|
4
5
|
export const OUTPUT_TOOL_NAME = "StructuredOutput";
|
|
5
6
|
export const REPORT_BLOCKED_TOOL_NAME = "ReportBlocked";
|
|
6
7
|
export const REPORT_FINDINGS_TOOL_NAME = "ReportFindings";
|
|
@@ -15,10 +16,9 @@ export function resolveOutputRetries(outputRetries) {
|
|
|
15
16
|
export function createOutputTool(ref, schema, strictSchema) {
|
|
16
17
|
return defineTool({
|
|
17
18
|
name: OUTPUT_TOOL_NAME,
|
|
18
|
-
|
|
19
|
+
...toolFace("structured-output"),
|
|
19
20
|
description: "Call this to submit your FINAL result as a JSON object matching the required schema. The arguments " +
|
|
20
21
|
"ARE the result. Calling this ends the task. Do this once you have the answer (use other tools first if needed).",
|
|
21
|
-
effect: "read",
|
|
22
22
|
parameters: strictSchema ?? schema,
|
|
23
23
|
execute: (args) => {
|
|
24
24
|
ref.value = args;
|
|
@@ -30,9 +30,8 @@ export function createOutputTool(ref, schema, strictSchema) {
|
|
|
30
30
|
export function createReportBlockedTool(ref) {
|
|
31
31
|
return defineTool({
|
|
32
32
|
name: REPORT_BLOCKED_TOOL_NAME,
|
|
33
|
-
|
|
33
|
+
...toolFace("report-blocked"),
|
|
34
34
|
description: "Call this ONLY when you cannot complete the task — missing information, missing permission/credentials, or an ambiguous request you cannot resolve. Provide a concise reason that an external system or operator can act on. Calling this ends the task.",
|
|
35
|
-
effect: "read",
|
|
36
35
|
parameters: Type.Object({ reason: Type.String() }),
|
|
37
36
|
execute: (args) => {
|
|
38
37
|
ref.reason = String(args.reason ?? "").trim() || "blocked";
|
|
@@ -59,14 +58,13 @@ const REPORT_FINDING_SCHEMA = Type.Object({
|
|
|
59
58
|
export function createReportFindingsTool() {
|
|
60
59
|
return defineTool({
|
|
61
60
|
name: REPORT_FINDINGS_TOOL_NAME,
|
|
62
|
-
|
|
61
|
+
...toolFace("report-findings"),
|
|
63
62
|
description: "Report code-review findings as a typed list so the host UI can render them. Use this only when the " +
|
|
64
63
|
"active code-review instructions tell you to report findings with this tool; otherwise follow whatever " +
|
|
65
64
|
"output format those instructions specify. When reporting a review's results, call it once with the " +
|
|
66
65
|
"verified findings ranked most-severe first (empty array if nothing survived verification) and do not " +
|
|
67
66
|
"also print the findings as text. When re-reporting after applying fixes (only if the apply " +
|
|
68
67
|
"instructions ask for it), set `outcome` on each finding to what actually happened.",
|
|
69
|
-
effect: "read",
|
|
70
68
|
parameters: Type.Object({
|
|
71
69
|
level: Type.Optional(Type.Union([Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("xhigh"), Type.Literal("max")], { description: "Effort level the review ran at" })),
|
|
72
70
|
findings: Type.Array(REPORT_FINDING_SCHEMA, {
|
|
@@ -177,7 +175,7 @@ export function createSkillTool(skills, scope) {
|
|
|
177
175
|
const names = skills.map((s) => s.name).join(", ");
|
|
178
176
|
return defineTool({
|
|
179
177
|
name: SKILL_TOOL_NAME,
|
|
180
|
-
|
|
178
|
+
...toolFace("skill"),
|
|
181
179
|
description: `Invoke a skill.\n\n` +
|
|
182
180
|
`A skill is a packaged set of instructions the user or project has set up for a particular kind of task (deploy steps, a review checklist, a repo-specific workflow). ` +
|
|
183
181
|
`Available skills appear in a system-reminder listing with one-line descriptions. ` +
|
|
@@ -192,7 +190,6 @@ export function createSkillTool(skills, scope) {
|
|
|
192
190
|
skill: Type.String({ description: 'The skill name. E.g., "commit", "review-pr", or "pdf"' }),
|
|
193
191
|
args: Type.Optional(Type.String({ description: "Optional arguments for the skill" })),
|
|
194
192
|
}),
|
|
195
|
-
effect: "read",
|
|
196
193
|
execute: (rawArgs) => {
|
|
197
194
|
const a = (rawArgs ?? {});
|
|
198
195
|
const name = String(a.skill ?? "");
|
|
@@ -39,6 +39,9 @@ export interface DeferredToolInfo {
|
|
|
39
39
|
name: string;
|
|
40
40
|
hint: string;
|
|
41
41
|
description: string;
|
|
42
|
+
/** The real tool's aliases — a placeholder stands for the tool under EVERY name it answers to, so dispatch by
|
|
43
|
+
* alias resolves to the same slot before and after activation (and the roster's name→row resolution agrees). */
|
|
44
|
+
aliases?: readonly string[];
|
|
42
45
|
}
|
|
43
46
|
/** First line of a description, trimmed to a single short hint for a placeholder / announcement. */
|
|
44
47
|
export declare function deferHint(description: string, max?: number): string;
|
|
@@ -90,6 +93,7 @@ export declare function classifyDeferred(opts: {
|
|
|
90
93
|
export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, tools: ReadonlyArray<{
|
|
91
94
|
name: string;
|
|
92
95
|
description: string;
|
|
96
|
+
aliases?: readonly string[];
|
|
93
97
|
}>): Map<string, DeferredToolInfo>;
|
|
94
98
|
/**
|
|
95
99
|
* RB-403 — the direct-call lane a placeholder may carry. The upstream defer gate is SHAPE
|
|
@@ -3,6 +3,7 @@ import { Value } from "typebox/value";
|
|
|
3
3
|
import { defineTool, errorResult } from "../tools.js";
|
|
4
4
|
import { formatZodValidationError, truncateError } from "../tool-errors.js";
|
|
5
5
|
import { DEFAULT_CHARS_PER_TOKEN } from "../../internal/harness.js";
|
|
6
|
+
import { toolFace } from "../tool-catalog-entries.js";
|
|
6
7
|
export const TOOL_SEARCH_NAME = "ToolSearch";
|
|
7
8
|
const DEFER_AUTO_FRACTION = 0.1;
|
|
8
9
|
const CHARS_PER_TOKEN = DEFAULT_CHARS_PER_TOKEN;
|
|
@@ -62,7 +63,7 @@ export function buildDeferredRegistry(deferred, tools) {
|
|
|
62
63
|
const reg = new Map();
|
|
63
64
|
for (const t of tools) {
|
|
64
65
|
if (deferred.has(t.name))
|
|
65
|
-
reg.set(t.name, { name: t.name, hint: deferHint(t.description), description: t.description });
|
|
66
|
+
reg.set(t.name, { name: t.name, hint: deferHint(t.description), description: t.description, ...(t.aliases !== undefined && t.aliases.length > 0 ? { aliases: [...t.aliases] } : {}) });
|
|
66
67
|
}
|
|
67
68
|
return reg;
|
|
68
69
|
}
|
|
@@ -127,6 +128,7 @@ export function createPlaceholderTool(info, direct) {
|
|
|
127
128
|
if (direct !== undefined) {
|
|
128
129
|
return {
|
|
129
130
|
name: info.name,
|
|
131
|
+
...(info.aliases !== undefined ? { aliases: [...info.aliases] } : {}),
|
|
130
132
|
label: info.name,
|
|
131
133
|
description: `${info.hint} — deferred: its parameters are not listed here. Call ` +
|
|
132
134
|
`${TOOL_SEARCH_NAME}({"query":"select:${sn}"}) to load them; a call that already matches this ` +
|
|
@@ -156,6 +158,7 @@ export function createPlaceholderTool(info, direct) {
|
|
|
156
158
|
}
|
|
157
159
|
return defineTool({
|
|
158
160
|
name: info.name,
|
|
161
|
+
...(info.aliases !== undefined ? { aliases: [...info.aliases] } : {}),
|
|
159
162
|
description: `${info.hint} — deferred: call ${TOOL_SEARCH_NAME}({"query":"select:${sn}"}) to load its parameters before use.`,
|
|
160
163
|
parameters: EMPTY_PARAMS,
|
|
161
164
|
effect: "read",
|
|
@@ -263,8 +266,10 @@ export function extractDiscoveredToolNames(messages, registry) {
|
|
|
263
266
|
const resolved = resolveToolSearch(part.arguments, registry);
|
|
264
267
|
pendingSearch.set(part.id, prior === undefined ? resolved : [...prior, ...resolved]);
|
|
265
268
|
}
|
|
266
|
-
else
|
|
267
|
-
|
|
269
|
+
else {
|
|
270
|
+
const canonical = registry.has(part.name) ? part.name : [...registry.values()].find((i) => i.aliases?.includes(part.name))?.name;
|
|
271
|
+
if (canonical !== undefined)
|
|
272
|
+
pendingDirect.set(part.id, canonical);
|
|
268
273
|
}
|
|
269
274
|
}
|
|
270
275
|
}
|
|
@@ -299,7 +304,7 @@ export function createToolSearchTool(opts) {
|
|
|
299
304
|
});
|
|
300
305
|
return defineTool({
|
|
301
306
|
name: TOOL_SEARCH_NAME,
|
|
302
|
-
|
|
307
|
+
...toolFace("tool-search"),
|
|
303
308
|
description: "Discover and activate deferred tools. " +
|
|
304
309
|
activationPosture +
|
|
305
310
|
"Query forms: " +
|
|
@@ -317,7 +322,6 @@ export function createToolSearchTool(opts) {
|
|
|
317
322
|
})),
|
|
318
323
|
max_results: Type.Optional(Type.Number({ description: "Maximum number of results to return (default: 5)" })),
|
|
319
324
|
}),
|
|
320
|
-
effect: "read",
|
|
321
325
|
execute: async (raw) => {
|
|
322
326
|
const staleSelect = (raw ?? {});
|
|
323
327
|
if (staleSelect.select !== undefined) {
|
|
@@ -2,6 +2,7 @@ import {} from "../../internal/harness.js";
|
|
|
2
2
|
import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
|
|
3
3
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
4
4
|
import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
|
|
5
|
+
import { TASK_CREATE_TOOL_NAME, TASK_UPDATE_TOOL_NAME, TODO_WRITE_TOOL_NAME } from "../tool-catalog-entries.js";
|
|
5
6
|
const BATCH_RESPONSE_MAX_CHARS = 500;
|
|
6
7
|
export function batchResponseDigest(result) {
|
|
7
8
|
const content = result !== null && typeof result === "object" ? result.content : result;
|
|
@@ -54,9 +55,9 @@ export function reconciledToolEndBody(orphan) {
|
|
|
54
55
|
return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
|
|
55
56
|
}
|
|
56
57
|
export function writeFamilyOfCanonical(name) {
|
|
57
|
-
if (name ===
|
|
58
|
+
if (name === TASK_CREATE_TOOL_NAME || name === TASK_UPDATE_TOOL_NAME)
|
|
58
59
|
return "task";
|
|
59
|
-
if (name ===
|
|
60
|
+
if (name === TODO_WRITE_TOOL_NAME)
|
|
60
61
|
return "todo";
|
|
61
62
|
if (name === TOOL_SEARCH_NAME)
|
|
62
63
|
return "tool_search";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/388 §1.1 / §2.7 — the MCP `toolFaces` OVERLAY: a deployment's per-tool face declaration for a
|
|
3
|
+
* protocol tool that has no `ToolSpec` of its own (`McpServerSpec.toolFaces`, keyed by the server's RAW
|
|
4
|
+
* tool name like `allowTools` / `toolAxes`). Validated against the MATERIALIZED schema at every listing
|
|
5
|
+
* (the first mount and every RefreshMcpTools): `pathTarget.param` must be a top-level property; an
|
|
6
|
+
* `approvalCard` must find the keys the card needs; a `ruleFace` may only name scalar keys; `family`
|
|
7
|
+
* must be a member of the closed set. A failing overlay does NOT mount — the tool mounts BARE (no
|
|
8
|
+
* face) and the leg announces `config.tool_face_invalid` once per tool; a `toolFaces` key naming a
|
|
9
|
+
* tool the listing does not carry announces `config.tool_face_undeclared` once per name. Never a
|
|
10
|
+
* silent partial application: an overlay is one declaration, applied whole or not at all.
|
|
11
|
+
*
|
|
12
|
+
* Prepare-path machinery (layer 1): pure over the tool objects and the server specs; the notice sink
|
|
13
|
+
* is a parameter.
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentTool } from "../../internal/harness-types.js";
|
|
16
|
+
import type { McpServerSpec } from "../mcp-server-spec.js";
|
|
17
|
+
import { type ToolApprovalCard } from "../tool-face.js";
|
|
18
|
+
import { type EngineNotice } from "../engine-notice.js";
|
|
19
|
+
/** The keys each specialised approval card needs on the call (design/388 §2.5 boundary ①: the SHELL owns the
|
|
20
|
+
* card's full schema; this is the pre-filter the overlay is validated against). */
|
|
21
|
+
export declare const APPROVAL_CARD_REQUIRED_KEYS: Readonly<Record<ToolApprovalCard, readonly string[]>>;
|
|
22
|
+
export type McpToolFace = NonNullable<McpServerSpec["toolFaces"]>[string];
|
|
23
|
+
/** Why an overlay is refused — a closed reason list, each a distinct declaration mistake. */
|
|
24
|
+
export declare function toolFaceProblem(face: McpToolFace, schema: AgentTool["parameters"]): string | undefined;
|
|
25
|
+
export interface ApplyToolFacesInput {
|
|
26
|
+
/** The materialized tools of ONE server (namespaced names). */
|
|
27
|
+
tools: readonly AgentTool[];
|
|
28
|
+
server: Pick<McpServerSpec, "name" | "toolFaces">;
|
|
29
|
+
/** The leg's notice sink and its once-per-leg ledger (keys: `invalid:<name>` / `undeclared:<server>/<raw>`). */
|
|
30
|
+
onNotice: ((notice: EngineNotice) => void) | undefined;
|
|
31
|
+
announced: Set<string>;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
runId: string;
|
|
34
|
+
}
|
|
35
|
+
/** Apply the server's overlay to its tools: a validated face is stamped on a COPY of the tool object (the same
|
|
36
|
+
* untyped carry `defineTool` uses, so the roster reads it off the instance). A face that does NOT fit the materialized
|
|
37
|
+
* schema is a bad declaration: the tool is NOT mounted on this leg (announced once, `config.tool_face_invalid`) —
|
|
38
|
+
* mounting it bare would silently drop the write protection the operator declared for it (an MCP name has no catalog
|
|
39
|
+
* floor), and a schema refresh that invalidates only a display hint must fail loud, never open. */
|
|
40
|
+
export declare function applyMcpToolFaces(input: ApplyToolFacesInput): AgentTool[];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { MCP_NAMESPACE } from "../protocol-table.js";
|
|
2
|
+
import { mintNamespacePrefix } from "../protocol-naming.js";
|
|
3
|
+
import { TOOL_APPROVAL_CARDS, TOOL_FAMILIES, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, TOOL_KEY_MAX_CHARS } from "../tool-face.js";
|
|
4
|
+
import { inputKeysOf } from "../tool-roster.js";
|
|
5
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
6
|
+
export const APPROVAL_CARD_REQUIRED_KEYS = Object.freeze({
|
|
7
|
+
"file-edit": ["file_path"],
|
|
8
|
+
"file-write": ["file_path"],
|
|
9
|
+
"notebook-edit": ["notebook_path"],
|
|
10
|
+
shell: ["command"],
|
|
11
|
+
fetch: ["url"],
|
|
12
|
+
plan: [],
|
|
13
|
+
question: [],
|
|
14
|
+
generic: [],
|
|
15
|
+
});
|
|
16
|
+
export function toolFaceProblem(face, schema) {
|
|
17
|
+
const { keys, scalar } = inputKeysOf(schema);
|
|
18
|
+
const top = new Set(keys.map((k) => k.name));
|
|
19
|
+
if (face.family !== undefined && !TOOL_FAMILIES.includes(face.family))
|
|
20
|
+
return `family ${JSON.stringify(face.family)} is outside the closed set`;
|
|
21
|
+
const overlong = (label, values) => {
|
|
22
|
+
const k = values.find((v) => v.length > TOOL_KEY_MAX_CHARS);
|
|
23
|
+
return k === undefined ? undefined : `${label} ${JSON.stringify(k.slice(0, 40) + "…")} is ${k.length} characters; a roster key is at most ${TOOL_KEY_MAX_CHARS}`;
|
|
24
|
+
};
|
|
25
|
+
const vocabulary = new Set(scalar);
|
|
26
|
+
if (face.pathTarget !== undefined) {
|
|
27
|
+
if (typeof face.pathTarget.param !== "string" || !top.has(face.pathTarget.param))
|
|
28
|
+
return `pathTarget.param ${JSON.stringify(face.pathTarget.param)} is not a top-level property of the tool's schema`;
|
|
29
|
+
if (!["read", "create", "edit"].includes(face.pathTarget.access))
|
|
30
|
+
return `pathTarget.access ${JSON.stringify(face.pathTarget.access)} is outside the closed set`;
|
|
31
|
+
const long = overlong("pathTarget.param", [face.pathTarget.param]) ?? overlong("pathTarget.aliases", face.pathTarget.aliases ?? []);
|
|
32
|
+
if (long !== undefined)
|
|
33
|
+
return long;
|
|
34
|
+
vocabulary.add(face.pathTarget.param);
|
|
35
|
+
for (const a of face.pathTarget.aliases ?? [])
|
|
36
|
+
vocabulary.add(a);
|
|
37
|
+
}
|
|
38
|
+
const hints = face.renderHints;
|
|
39
|
+
if (hints !== undefined) {
|
|
40
|
+
const card = hints.approvalCard;
|
|
41
|
+
if (card !== undefined) {
|
|
42
|
+
if (!TOOL_APPROVAL_CARDS.includes(card))
|
|
43
|
+
return `renderHints.approvalCard ${JSON.stringify(card)} is outside the closed set`;
|
|
44
|
+
const missing = APPROVAL_CARD_REQUIRED_KEYS[card].filter((k) => !top.has(k));
|
|
45
|
+
if (missing.length > 0)
|
|
46
|
+
return `renderHints.approvalCard ${JSON.stringify(card)} needs ${missing.join(", ")} on the tool's schema`;
|
|
47
|
+
}
|
|
48
|
+
if (hints.userFacingName !== undefined && hints.userFacingName.length > RENDER_HINT_MAX_CHARS)
|
|
49
|
+
return `renderHints.userFacingName is ${hints.userFacingName.length} characters; the bound is ${RENDER_HINT_MAX_CHARS}`;
|
|
50
|
+
if (hints.activity !== undefined && hints.activity.length > RENDER_HINT_ACTIVITY_MAX_CHARS)
|
|
51
|
+
return `renderHints.activity is ${hints.activity.length} characters; the bound is ${RENDER_HINT_ACTIVITY_MAX_CHARS}`;
|
|
52
|
+
if ((hints.summaryParams?.length ?? 0) > RENDER_HINT_MAX_LIST)
|
|
53
|
+
return `renderHints.summaryParams lists ${hints.summaryParams.length} entries; the bound is ${RENDER_HINT_MAX_LIST}`;
|
|
54
|
+
if ((hints.resultCards?.length ?? 0) > RENDER_HINT_MAX_LIST)
|
|
55
|
+
return `renderHints.resultCards lists ${hints.resultCards.length} entries; the bound is ${RENDER_HINT_MAX_LIST}`;
|
|
56
|
+
const long = overlong("renderHints.summaryParams", hints.summaryParams ?? []) ?? overlong("renderHints.resultCards", hints.resultCards ?? []);
|
|
57
|
+
if (long !== undefined)
|
|
58
|
+
return long;
|
|
59
|
+
const stray = (hints.summaryParams ?? []).find((p) => !top.has(p));
|
|
60
|
+
if (stray !== undefined)
|
|
61
|
+
return `renderHints.summaryParams names ${JSON.stringify(stray)}, which is not a top-level property`;
|
|
62
|
+
}
|
|
63
|
+
for (const p of face.ruleFace?.params ?? [])
|
|
64
|
+
if (!vocabulary.has(p))
|
|
65
|
+
return `ruleFace.params names ${JSON.stringify(p)}, which is neither a scalar top-level property nor the declared path target`;
|
|
66
|
+
for (const p of face.ruleFace?.primaryParams ?? [])
|
|
67
|
+
if (!vocabulary.has(p))
|
|
68
|
+
return `ruleFace.primaryParams names ${JSON.stringify(p)}, which is neither a top-level property nor the declared path target`;
|
|
69
|
+
const long = overlong("ruleFace.params", face.ruleFace?.params ?? []) ?? overlong("ruleFace.primaryParams", face.ruleFace?.primaryParams ?? []);
|
|
70
|
+
if (long !== undefined)
|
|
71
|
+
return long;
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
export function applyMcpToolFaces(input) {
|
|
75
|
+
const faces = input.server.toolFaces;
|
|
76
|
+
if (faces === undefined)
|
|
77
|
+
return [...input.tools];
|
|
78
|
+
const prefix = mintNamespacePrefix(MCP_NAMESPACE, input.server.name);
|
|
79
|
+
const accepted = new Set(input.tools.filter((t) => t.name.startsWith(prefix)).map((t) => t.mcpRemoteName).filter((r) => typeof r === "string"));
|
|
80
|
+
for (const raw of Object.keys(faces)) {
|
|
81
|
+
if (accepted.has(raw))
|
|
82
|
+
continue;
|
|
83
|
+
const key = `undeclared:${input.server.name}/${raw}`;
|
|
84
|
+
if (input.announced.has(key))
|
|
85
|
+
continue;
|
|
86
|
+
input.announced.add(key);
|
|
87
|
+
deliverEngineNotice(input.onNotice, {
|
|
88
|
+
code: "config.tool_face_undeclared",
|
|
89
|
+
message: `MCP server "${input.server.name}": toolFaces declares a face for "${raw}", which the server's tool listing does not carry — the declaration is inert`,
|
|
90
|
+
detail: { server: input.server.name, tool: raw, sessionId: input.sessionId, runId: input.runId },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return input.tools.flatMap((t) => {
|
|
94
|
+
const stamped = stampOne(t);
|
|
95
|
+
return stamped === undefined ? [] : [stamped];
|
|
96
|
+
});
|
|
97
|
+
function stampOne(t) {
|
|
98
|
+
const raw = t.mcpRemoteName;
|
|
99
|
+
const face = raw !== undefined && t.name.startsWith(prefix) ? faces?.[raw] : undefined;
|
|
100
|
+
if (raw === undefined || face === undefined)
|
|
101
|
+
return t;
|
|
102
|
+
const hit = { raw, face };
|
|
103
|
+
const problem = toolFaceProblem(hit.face, t.parameters);
|
|
104
|
+
if (problem !== undefined) {
|
|
105
|
+
const key = `invalid:${t.name}`;
|
|
106
|
+
if (!input.announced.has(key)) {
|
|
107
|
+
input.announced.add(key);
|
|
108
|
+
deliverEngineNotice(input.onNotice, {
|
|
109
|
+
code: "config.tool_face_invalid",
|
|
110
|
+
message: `MCP server "${input.server.name}": the toolFaces overlay for "${hit.raw}" does not fit the tool's materialized schema (${problem}) — the tool is NOT mounted until the overlay fits (mounting it bare would drop the protection the overlay declares)`,
|
|
111
|
+
detail: { server: input.server.name, tool: hit.raw, problem, sessionId: input.sessionId, runId: input.runId },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const f = hit.face;
|
|
117
|
+
return {
|
|
118
|
+
...t,
|
|
119
|
+
...(f.family !== undefined ? { family: f.family } : {}),
|
|
120
|
+
...(f.pathTarget !== undefined ? { pathTarget: f.pathTarget } : {}),
|
|
121
|
+
...(f.renderHints !== undefined ? { renderHints: f.renderHints } : {}),
|
|
122
|
+
...(f.ruleFace !== undefined ? { ruleFace: f.ruleFace } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -9,4 +9,14 @@ export declare const toolOutputFrom: (result: unknown) => {
|
|
|
9
9
|
truncated: boolean;
|
|
10
10
|
totalChars?: number;
|
|
11
11
|
} | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* L4's run-time half (design/388 §2.4): a per-leg projector — the engine card set plus the leg's declared
|
|
14
|
+
* caller cards — that DROPS an undeclared `details.type` exactly as the module-level gate does and announces
|
|
15
|
+
* it ONCE per leg (`config.tool_card_undeclared`, audience operator). The module-level {@link structuredFrom}
|
|
16
|
+
* is this projector over the engine set alone, with no announce seat.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createStructuredProjector(opts: {
|
|
19
|
+
declaredCards?: Iterable<string>;
|
|
20
|
+
onUndeclared?: (type: string) => void;
|
|
21
|
+
}): (result: unknown) => unknown;
|
|
12
22
|
export declare const structuredFrom: (result: unknown) => unknown;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { engineCardTypes } from "../tool-registry.js";
|
|
1
2
|
const MAX_TOOL_END_OUTPUT_CHARS = 16384;
|
|
2
3
|
const safeJsonLength = (v) => {
|
|
3
4
|
try {
|
|
@@ -56,16 +57,38 @@ export const toolOutputFrom = (result) => {
|
|
|
56
57
|
}
|
|
57
58
|
return { output: raw, truncated: true, totalChars };
|
|
58
59
|
};
|
|
59
|
-
const CC_DETAIL_TYPES =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
const CC_DETAIL_TYPES = engineCardTypes();
|
|
61
|
+
export function createStructuredProjector(opts) {
|
|
62
|
+
const allowed = new Set([...CC_DETAIL_TYPES, ...(opts.declaredCards ?? [])]);
|
|
63
|
+
const announced = new Set();
|
|
64
|
+
return (result) => {
|
|
65
|
+
const details = result !== null && typeof result === "object" ? result.details : undefined;
|
|
66
|
+
if (details === null || typeof details !== "object")
|
|
67
|
+
return undefined;
|
|
68
|
+
const t = details.type;
|
|
69
|
+
if (typeof t !== "string")
|
|
70
|
+
return undefined;
|
|
71
|
+
if (!allowed.has(t)) {
|
|
72
|
+
if (opts.onUndeclared !== undefined && !announced.has(t)) {
|
|
73
|
+
announced.add(t);
|
|
74
|
+
opts.onUndeclared(t);
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return boundedCard(details);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function boundedCard(details) {
|
|
82
|
+
try {
|
|
83
|
+
const serialized = JSON.stringify(details) ?? "";
|
|
84
|
+
if (serialized.length > MAX_TOOL_END_OUTPUT_CHARS * 4)
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return details;
|
|
91
|
+
}
|
|
69
92
|
export const structuredFrom = (result) => {
|
|
70
93
|
const details = result !== null && typeof result === "object" ? result.details : undefined;
|
|
71
94
|
if (details === null || typeof details !== "object")
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
2
|
import type { ToolPolicy } from "./tool-policy.js";
|
|
3
|
+
/** fs hand tools that take a `path` and write — the reliably-guardable surface. NotebookEdit joined
|
|
4
|
+
* in [886]①a (mirror of the fs-write-gate's covered set): its target is `notebook_path`, and a
|
|
5
|
+
* guard that ignored notebook writes to the same paths by default was the same hole one tool away. */
|
|
3
6
|
/**
|
|
4
7
|
* RECOMMENDED default pattern set for {@link createSensitivePathPolicy} (additive export).
|
|
5
8
|
* NO upstream verbatim anchor exists (cc-anchors/2.1.209 carries no sensitive/dangerous file-pattern
|
|
@@ -2,7 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { canonicalizeTarget, expandHomeTilde, isAbsolutePathForm, isWinFormPath, writeTargetPath } from "../tools/fs/safety.js";
|
|
4
4
|
import { compileSegmentPattern, matchSegmentPatterns } from "../tools/fs/read-deny.js";
|
|
5
|
-
|
|
5
|
+
import { isProtectedWrite } from "./tool-registry.js";
|
|
6
6
|
export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
7
7
|
".env",
|
|
8
8
|
".env.*",
|
|
@@ -90,13 +90,14 @@ export function createSensitivePathPolicy(opts) {
|
|
|
90
90
|
}
|
|
91
91
|
return keys;
|
|
92
92
|
};
|
|
93
|
-
const
|
|
93
|
+
const guardedByName = opts.tools !== undefined ? new Set(opts.tools) : undefined;
|
|
94
94
|
return {
|
|
95
95
|
async check(req, signal) {
|
|
96
|
-
|
|
97
|
-
if (compiled.length === 0 || !guarded.has(canonical))
|
|
96
|
+
if (compiled.length === 0)
|
|
98
97
|
return { action: "allow" };
|
|
99
|
-
|
|
98
|
+
if (guardedByName !== undefined ? !guardedByName.has(req.toolName) : !isProtectedWrite(req))
|
|
99
|
+
return { action: "allow" };
|
|
100
|
+
const path = writeTargetPath(req);
|
|
100
101
|
if (typeof path !== "string" || path.length === 0)
|
|
101
102
|
return { action: "allow" };
|
|
102
103
|
const canon = await canonicalizeTarget(opts.env, path, signal, req.cwd ?? opts.rootPath);
|
|
@@ -2,6 +2,7 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { errorResult } from "../tools.js";
|
|
3
3
|
import { CONNECTING_MESSAGE, GENERIC_FAILURE_MESSAGE, NO_STORES_MESSAGE, SHARED_STORE_FRAME, UNAVAILABLE_MESSAGE, datePrefixOf, echoUntrusted, foldReaderError, listPrefixArgument, normalizeEntries, normalizeMemoryPath, normalizeReadValue, normalizeSnapshot, pageDocuments, relativePathRefusal, sanitizeSharedContent, validateDocumentPath, validatePathPrefix, } from "./normalize.js";
|
|
4
4
|
import { SHARED_MEMORY_LIST_PAGE_SIZE, SHARED_MEMORY_LIST_TOOL_NAME, SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_READ_TOOL_NAME, } from "./types.js";
|
|
5
|
+
import { toolFace } from "../tool-catalog-entries.js";
|
|
5
6
|
const LIST_HINT = "List shared memory stores and their documents — check here before telling the user you do not have something.";
|
|
6
7
|
const LIST_DESCRIPTION = [
|
|
7
8
|
LIST_HINT,
|
|
@@ -125,11 +126,9 @@ export function createSharedMemoryTools(opts) {
|
|
|
125
126
|
};
|
|
126
127
|
const listTool = {
|
|
127
128
|
name: SHARED_MEMORY_LIST_TOOL_NAME,
|
|
129
|
+
...toolFace("memory-list"),
|
|
128
130
|
description: LIST_DESCRIPTION,
|
|
129
|
-
effect: "read",
|
|
130
131
|
defer: true,
|
|
131
|
-
offload: false,
|
|
132
|
-
contract: { contractId: "core.memory_list@1", implementationRevision: "1" },
|
|
133
132
|
parameters: Type.Object({
|
|
134
133
|
store: Type.Optional(Type.String({
|
|
135
134
|
description: "Id of the connected memory store to list. Omit to list the memory stores connected to this session (id, description, and writable or read-only).",
|
|
@@ -258,11 +257,9 @@ export function createSharedMemoryTools(opts) {
|
|
|
258
257
|
};
|
|
259
258
|
const readTool = {
|
|
260
259
|
name: SHARED_MEMORY_READ_TOOL_NAME,
|
|
260
|
+
...toolFace("memory-read"),
|
|
261
261
|
description: READ_DESCRIPTION,
|
|
262
|
-
effect: "read",
|
|
263
262
|
defer: true,
|
|
264
|
-
offload: false,
|
|
265
|
-
contract: { contractId: "core.memory_read@1", implementationRevision: "1" },
|
|
266
263
|
parameters: Type.Object({
|
|
267
264
|
store: Type.String({
|
|
268
265
|
description: `Id of the connected memory store to read from (call ${SHARED_MEMORY_LIST_TOOL_NAME} with no arguments to see the connected stores).`,
|
|
@@ -301,6 +301,29 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
301
301
|
const stored = (await store.get(bitless.token)).pendingAction;
|
|
302
302
|
assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
|
|
303
303
|
});
|
|
304
|
+
run("design/388 B18 × B6 identity + face group: a row's seven optional members (capabilityId/contractId/shapeDigest/implementationRevision/rosterDigest + family/pathTarget) survive the round-trip verbatim; an absent group stays absent", async () => {
|
|
305
|
+
const store = make();
|
|
306
|
+
const carried = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-carried" });
|
|
307
|
+
const group = {
|
|
308
|
+
capabilityId: "caller::Write@acme.write@2",
|
|
309
|
+
contractId: "acme.write@2",
|
|
310
|
+
shapeDigest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
311
|
+
implementationRevision: "r7",
|
|
312
|
+
rosterDigest: "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
|
|
313
|
+
family: "file-write",
|
|
314
|
+
pathTarget: { param: "destination", aliases: ["path"], access: "create", skillScopeEligible: true },
|
|
315
|
+
};
|
|
316
|
+
Object.assign(carried.pendingAction, group);
|
|
317
|
+
const bare = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-bare" });
|
|
318
|
+
for (const cp of [carried, bare])
|
|
319
|
+
await store.put(cp.token, cp);
|
|
320
|
+
const back = (await store.get(carried.token)).pendingAction;
|
|
321
|
+
for (const [k, v] of Object.entries(group))
|
|
322
|
+
assert.deepEqual(back[k], v, `the row's ${k} must survive the round-trip verbatim (a whitelisting serializer that drops it makes every resume-edit recheck judge by the catalog's slot for the NAME)`);
|
|
323
|
+
const bareBack = (await store.get(bare.token)).pendingAction;
|
|
324
|
+
for (const k of Object.keys(group))
|
|
325
|
+
assert.equal(k in bareBack, false, `an absent ${k} must stay OMITTED (a pre-388 row reads name-only, never a partial face)`);
|
|
326
|
+
});
|
|
304
327
|
run("#457 previewWithheld three-form matrix: a row's spelling survives + projects; absent stays absent; an out-of-contract spelling reads as absent", async () => {
|
|
305
328
|
const store = make();
|
|
306
329
|
const oversize = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "pw-oversize" });
|
|
@@ -885,6 +885,16 @@ export type TaskEvent = ({
|
|
|
885
885
|
*/
|
|
886
886
|
type: "wiring_manifest";
|
|
887
887
|
manifest: import("./wiring-manifest.js").WiringManifest;
|
|
888
|
+
} & TaskEventIdentity) | ({
|
|
889
|
+
/**
|
|
890
|
+
* design/388 §2.5 — a RUN-TIME change to this leg's mounted tool set (today: the RefreshMcpTools
|
|
891
|
+
* seam re-inserting a server's tools in place). Carries the WHOLE post-change roster (positions
|
|
892
|
+
* come from it; a consumer replaces its copy) plus a summary usable only when the consumer holds
|
|
893
|
+
* `fromDigest` — otherwise it adopts the snapshot and records a skew (never rejects). The prepared
|
|
894
|
+
* roster itself rides `wiring_manifest.manifest.tools`; this frame never precedes that one.
|
|
895
|
+
*/
|
|
896
|
+
type: "tool_roster_delta";
|
|
897
|
+
delta: import("./tool-roster.js").ToolRosterDelta;
|
|
888
898
|
} & TaskEventIdentity) | {
|
|
889
899
|
type: "done";
|
|
890
900
|
result: TaskResult;
|
|
@@ -9,8 +9,9 @@ import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-re
|
|
|
9
9
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
10
10
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
11
11
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
12
|
-
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME,
|
|
12
|
+
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
13
13
|
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, activeDelegationHandlesLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
14
|
+
import { toolFace } from "./tool-catalog-entries.js";
|
|
14
15
|
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
15
16
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
16
17
|
const BLOCK_MAX_TIMEOUT_MS = 600_000;
|
|
@@ -1234,10 +1235,9 @@ export function createTaskOutputTool(opts) {
|
|
|
1234
1235
|
const caps = { ...REGISTRY_TASK_TOOL_CAPS, notification: opts.notificationWired !== false };
|
|
1235
1236
|
return defineTool({
|
|
1236
1237
|
name: TASK_OUTPUT_TOOL_NAME,
|
|
1237
|
-
|
|
1238
|
+
...toolFace("task-output"),
|
|
1238
1239
|
description: composeTaskOutputDescription(caps),
|
|
1239
1240
|
parameters: composeTaskOutputParams(caps),
|
|
1240
|
-
effect: "read",
|
|
1241
1241
|
execute: async (rawArgs, ctx) => {
|
|
1242
1242
|
const args = rawArgs;
|
|
1243
1243
|
const id = resolveTaskIdArg(args.task_id);
|
|
@@ -1283,10 +1283,9 @@ export function createTaskOutputTool(opts) {
|
|
|
1283
1283
|
export function createTaskStopTool(opts) {
|
|
1284
1284
|
return defineTool({
|
|
1285
1285
|
name: TASK_STOP_TOOL_NAME,
|
|
1286
|
-
|
|
1286
|
+
...toolFace("task-stop"),
|
|
1287
1287
|
description: composeTaskStopDescription(REGISTRY_TASK_TOOL_CAPS),
|
|
1288
1288
|
parameters: TASK_STOP_PARAMS,
|
|
1289
|
-
effect: "write",
|
|
1290
1289
|
execute: async (rawArgs, ctx) => {
|
|
1291
1290
|
const args = rawArgs;
|
|
1292
1291
|
const id = resolveTaskIdArg(args.task_id, args.shell_id);
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -387,6 +387,14 @@ export interface TaskSpec {
|
|
|
387
387
|
* owns that risk, same trust level as assembling `spec.tools` itself.
|
|
388
388
|
*/
|
|
389
389
|
excludeTools?: string[];
|
|
390
|
+
/**
|
|
391
|
+
* design/388 §2.2 ⑥ — unmount the WHOLE tool face: the leg mounts nothing (no hands, no
|
|
392
|
+
* scaffolds, no MCP or skill tools; the effect roster reads `entries: []` and the model request
|
|
393
|
+
* carries `tools: []`). The utility-task shape a shell used to express with a hand-kept exclusion
|
|
394
|
+
* list. Not spelled `excludeTools: ["*"]` on purpose — `"*"` is a legal caller tool name (only `__`
|
|
395
|
+
* is reserved). `excludeTools` beside it is redundant, not an error.
|
|
396
|
+
*/
|
|
397
|
+
excludeAllTools?: true;
|
|
390
398
|
/**
|
|
391
399
|
* Per-request DEFERRED disclosure for ALREADY-MOUNTED tools (built-ins
|
|
392
400
|
* included — `ToolSpec.defer` only covers caller specs): wire names listed here ship as
|