@crewhaus/tool-task 0.5.7 → 0.6.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/dist/index.d.ts +4 -1
- package/dist/index.js +173 -34
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SubAgentDefinition, type SubAgentFailure } from "@crewhaus/agent-context-isolation";
|
|
2
2
|
import { CrewhausError } from "@crewhaus/errors";
|
|
3
3
|
import type { RegisteredTool } from "@crewhaus/tool-catalog";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -25,14 +25,17 @@ declare const taskInputSchema: z.ZodObject<{
|
|
|
25
25
|
description: z.ZodString;
|
|
26
26
|
prompt: z.ZodString;
|
|
27
27
|
subagent_type: z.ZodOptional<z.ZodString>;
|
|
28
|
+
profile: z.ZodOptional<z.ZodString>;
|
|
28
29
|
}, "strip", z.ZodTypeAny, {
|
|
29
30
|
description: string;
|
|
30
31
|
prompt: string;
|
|
31
32
|
subagent_type?: string | undefined;
|
|
33
|
+
profile?: string | undefined;
|
|
32
34
|
}, {
|
|
33
35
|
description: string;
|
|
34
36
|
prompt: string;
|
|
35
37
|
subagent_type?: string | undefined;
|
|
38
|
+
profile?: string | undefined;
|
|
36
39
|
}>;
|
|
37
40
|
export type TaskInput = z.infer<typeof taskInputSchema>;
|
|
38
41
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Catalog R4 `tool-task` — Section 13.
|
|
3
3
|
*
|
|
4
|
-
* The `Task(description, prompt, subagent_type?)` tool. When
|
|
5
|
-
* resolves the named sub-agent definition, builds the child's tool
|
|
6
|
-
* resolves child permissions,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The `Task(description, prompt, subagent_type?, profile?)` tool. When
|
|
5
|
+
* invoked, it resolves the named sub-agent definition, builds the child's tool
|
|
6
|
+
* catalog, resolves child permissions, validates the optional `profile`
|
|
7
|
+
* against the definition's allowlist (0.6.0 §7.7 — a model-filled model
|
|
8
|
+
* argument that can never name a model outside the spec), and dispatches
|
|
9
|
+
* `bridge.spawnSubAgent` with the ONE shared `projectParentHandle(bridge)`
|
|
10
|
+
* projection. Returns the child's final assistant text as the tool result so
|
|
11
|
+
* the parent model can react to the child's verdict.
|
|
9
12
|
*
|
|
10
13
|
* Resolution order for `subagent_type`:
|
|
11
14
|
* 1. `opts.subAgents.get(name)` — inline spec map (codegen-supplied).
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
*/
|
|
38
41
|
import { existsSync, readFileSync } from "node:fs";
|
|
39
42
|
import { join } from "node:path";
|
|
43
|
+
import { projectParentHandle, subAgentProfileAllowlist, } from "@crewhaus/agent-context-isolation";
|
|
40
44
|
import { CrewhausError } from "@crewhaus/errors";
|
|
41
45
|
import { resolveChildPermissions } from "@crewhaus/sub-agent-permission-inheritance";
|
|
42
46
|
import { buildTool } from "@crewhaus/tool-builder";
|
|
@@ -83,7 +87,49 @@ const taskInputSchema = z.object({
|
|
|
83
87
|
.min(1)
|
|
84
88
|
.optional()
|
|
85
89
|
.describe("Name of the sub-agent definition to spawn. Falls back to 'general-purpose' if omitted."),
|
|
90
|
+
// 0.6.0 §7.7 — a model-filled MODEL argument. Validated in `execute` against
|
|
91
|
+
// the definition's allowlist (`allowed_profiles`, else the child's own
|
|
92
|
+
// model/profile) — never resolved, never passed through: it can only ever
|
|
93
|
+
// name a model the spec already declared for that child (§10.1).
|
|
94
|
+
profile: z
|
|
95
|
+
.string()
|
|
96
|
+
.min(1)
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Model profile to run the sub-agent on. Must be one of the sub-agent's allowed profiles; omit to use its default model."),
|
|
86
99
|
});
|
|
100
|
+
/** Frontmatter mirror of a `thinking` block (spec grammar: exactly one of the two). */
|
|
101
|
+
const FRONTMATTER_THINKING = z.union([
|
|
102
|
+
z.object({ budget_tokens: z.number().int().min(1024) }).strict(),
|
|
103
|
+
z.object({ effort: z.enum(["low", "medium", "high"]) }).strict(),
|
|
104
|
+
]);
|
|
105
|
+
const FRONTMATTER_CIRCUIT_BREAKER = z
|
|
106
|
+
.object({
|
|
107
|
+
failureThreshold: z.number().int().positive().optional(),
|
|
108
|
+
windowMs: z.number().int().positive().optional(),
|
|
109
|
+
cooldownMs: z.number().int().positive().optional(),
|
|
110
|
+
})
|
|
111
|
+
.strict();
|
|
112
|
+
const FRONTMATTER_TIER_ROUTING = z
|
|
113
|
+
.object({
|
|
114
|
+
contextTokenThreshold: z.number().int().positive().optional(),
|
|
115
|
+
toolsToDefault: z.boolean().optional(),
|
|
116
|
+
firstTurnToDefault: z.boolean().optional(),
|
|
117
|
+
priorToolDensityThreshold: z.number().nonnegative().optional(),
|
|
118
|
+
})
|
|
119
|
+
.strict();
|
|
120
|
+
/**
|
|
121
|
+
* On-disk `<name>.md` frontmatter. Mirrors the spec's `sub_agents.<n>` keys
|
|
122
|
+
* (0.6.0 §7.7 adds the routing quartet, the params, `budget_share`,
|
|
123
|
+
* `inherit_routing` and `allowed_profiles`). Two honest differences from the
|
|
124
|
+
* spec form, because a file on disk has no `models:` registry to resolve
|
|
125
|
+
* against: `model` and every candidate are plain model strings (no `$ref`),
|
|
126
|
+
* and `allowed_profiles` entries name their model explicitly —
|
|
127
|
+
* `{ profile, model, thinking?, max_tokens?, temperature?, model_fallbacks?,
|
|
128
|
+
* circuit_breaker? }` — the already-resolved shape the compiler produces for a
|
|
129
|
+
* spec-declared sub-agent. `model_pool` is validated for its routing identity
|
|
130
|
+
* (`candidates[].{model, tags}`, `policy`) and otherwise passed through: the
|
|
131
|
+
* runtime owns the per-candidate settings grammar.
|
|
132
|
+
*/
|
|
87
133
|
const FRONTMATTER_SCHEMA = z.object({
|
|
88
134
|
name: z.string().min(1),
|
|
89
135
|
description: z.string().min(1),
|
|
@@ -96,7 +142,50 @@ const FRONTMATTER_SCHEMA = z.object({
|
|
|
96
142
|
])
|
|
97
143
|
.optional(),
|
|
98
144
|
inherit_bypass: z.boolean().optional(),
|
|
145
|
+
overlay: z.string().min(1).optional(),
|
|
146
|
+
thinking: FRONTMATTER_THINKING.optional(),
|
|
147
|
+
max_tokens: z.number().int().positive().optional(),
|
|
148
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
149
|
+
model_fallbacks: z.array(z.string().min(1)).min(1).optional(),
|
|
150
|
+
circuit_breaker: FRONTMATTER_CIRCUIT_BREAKER.optional(),
|
|
151
|
+
model_tiers: z
|
|
152
|
+
.object({
|
|
153
|
+
fast: z.string().min(1),
|
|
154
|
+
default: z.string().min(1),
|
|
155
|
+
routing: FRONTMATTER_TIER_ROUTING.optional(),
|
|
156
|
+
})
|
|
157
|
+
.strict()
|
|
158
|
+
.optional(),
|
|
159
|
+
model_pool: z
|
|
160
|
+
.object({
|
|
161
|
+
candidates: z
|
|
162
|
+
.array(z.object({ model: z.string().min(1), tags: z.array(z.string().min(1)) }).passthrough())
|
|
163
|
+
.min(1),
|
|
164
|
+
policy: z.enum(["static", "heuristic", "learned", "classifier"]).optional(),
|
|
165
|
+
})
|
|
166
|
+
.passthrough()
|
|
167
|
+
.optional(),
|
|
168
|
+
budget_share: z.number().gt(0).max(1).optional(),
|
|
169
|
+
inherit_routing: z.boolean().optional(),
|
|
170
|
+
allowed_profiles: z
|
|
171
|
+
.array(z
|
|
172
|
+
.object({
|
|
173
|
+
profile: z.string().min(1),
|
|
174
|
+
model: z.string().min(1),
|
|
175
|
+
thinking: FRONTMATTER_THINKING.optional(),
|
|
176
|
+
max_tokens: z.number().int().positive().optional(),
|
|
177
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
178
|
+
model_fallbacks: z.array(z.string().min(1)).min(1).optional(),
|
|
179
|
+
circuit_breaker: FRONTMATTER_CIRCUIT_BREAKER.optional(),
|
|
180
|
+
})
|
|
181
|
+
.strict())
|
|
182
|
+
.min(1)
|
|
183
|
+
.optional(),
|
|
99
184
|
});
|
|
185
|
+
/** Spec-spelled thinking block → the runtime `IrThinking` shape. */
|
|
186
|
+
function thinkingFromFrontmatter(t) {
|
|
187
|
+
return "budget_tokens" in t ? { budgetTokens: t.budget_tokens } : { effort: t.effort };
|
|
188
|
+
}
|
|
100
189
|
/**
|
|
101
190
|
* Parse a `<dir>/<name>.md` sub-agent file. Same shape as SKILL.md: a
|
|
102
191
|
* leading `---` frontmatter block; the body is the agent instructions.
|
|
@@ -144,6 +233,45 @@ export function parseSubAgentFile(content, fallbackName) {
|
|
|
144
233
|
...(parsed.data.inherit_bypass !== undefined
|
|
145
234
|
? { inherit_bypass: parsed.data.inherit_bypass }
|
|
146
235
|
: {}),
|
|
236
|
+
// 0.6.0 §7.7 — routing + params, spelled as the runtime definition holds
|
|
237
|
+
// them (the IR's names). Every key is copied only when present. `overlay`
|
|
238
|
+
// is the default-profile prompt prefix a pinned `allowed_profiles` entry
|
|
239
|
+
// replaces (the spawner folds whichever applies; the body stays raw).
|
|
240
|
+
...(parsed.data.overlay !== undefined ? { overlay: parsed.data.overlay } : {}),
|
|
241
|
+
...(parsed.data.thinking !== undefined
|
|
242
|
+
? { thinking: thinkingFromFrontmatter(parsed.data.thinking) }
|
|
243
|
+
: {}),
|
|
244
|
+
...(parsed.data.max_tokens !== undefined ? { maxTokens: parsed.data.max_tokens } : {}),
|
|
245
|
+
...(parsed.data.temperature !== undefined ? { temperature: parsed.data.temperature } : {}),
|
|
246
|
+
...(parsed.data.model_fallbacks !== undefined
|
|
247
|
+
? { modelFallbacks: parsed.data.model_fallbacks }
|
|
248
|
+
: {}),
|
|
249
|
+
...(parsed.data.circuit_breaker !== undefined
|
|
250
|
+
? { circuitBreaker: parsed.data.circuit_breaker }
|
|
251
|
+
: {}),
|
|
252
|
+
...(parsed.data.model_tiers !== undefined ? { modelTiers: parsed.data.model_tiers } : {}),
|
|
253
|
+
...(parsed.data.model_pool !== undefined
|
|
254
|
+
? { modelPool: parsed.data.model_pool }
|
|
255
|
+
: {}),
|
|
256
|
+
...(parsed.data.budget_share !== undefined ? { budgetShare: parsed.data.budget_share } : {}),
|
|
257
|
+
...(parsed.data.inherit_routing !== undefined
|
|
258
|
+
? { inheritRouting: parsed.data.inherit_routing }
|
|
259
|
+
: {}),
|
|
260
|
+
...(parsed.data.allowed_profiles !== undefined
|
|
261
|
+
? {
|
|
262
|
+
allowedProfiles: parsed.data.allowed_profiles.map((o) => ({
|
|
263
|
+
profile: o.profile,
|
|
264
|
+
model: o.model,
|
|
265
|
+
...(o.thinking !== undefined
|
|
266
|
+
? { thinking: thinkingFromFrontmatter(o.thinking) }
|
|
267
|
+
: {}),
|
|
268
|
+
...(o.max_tokens !== undefined ? { maxTokens: o.max_tokens } : {}),
|
|
269
|
+
...(o.temperature !== undefined ? { temperature: o.temperature } : {}),
|
|
270
|
+
...(o.model_fallbacks !== undefined ? { modelFallbacks: o.model_fallbacks } : {}),
|
|
271
|
+
...(o.circuit_breaker !== undefined ? { circuitBreaker: o.circuit_breaker } : {}),
|
|
272
|
+
})),
|
|
273
|
+
}
|
|
274
|
+
: {}),
|
|
147
275
|
};
|
|
148
276
|
return def;
|
|
149
277
|
}
|
|
@@ -210,9 +338,22 @@ function buildChildCatalog(parentTools, def) {
|
|
|
210
338
|
}
|
|
211
339
|
export function createTaskTool(opts = {}) {
|
|
212
340
|
const knownNames = opts.subAgents !== undefined ? [...opts.subAgents.keys()] : [];
|
|
213
|
-
|
|
341
|
+
// 0.6.0 §7.7 — advertise the `profile` argument only when some definition
|
|
342
|
+
// declares an allowlist, so a spec without `allowed_profiles` keeps the
|
|
343
|
+
// exact description it had (the tool list sits in the cached prefix).
|
|
344
|
+
const profileNote = opts.subAgents !== undefined &&
|
|
345
|
+
[...opts.subAgents.values()].some((d) => d.allowedProfiles !== undefined)
|
|
346
|
+
? ` Pass \`profile\` to run a sub-agent on one of its allowed model profiles (${[
|
|
347
|
+
...opts.subAgents.values(),
|
|
348
|
+
]
|
|
349
|
+
.filter((d) => d.allowedProfiles !== undefined)
|
|
350
|
+
.map((d) => `${d.name}: ${(d.allowedProfiles ?? []).map((o) => o.profile).join("/")}`)
|
|
351
|
+
.join("; ")}).`
|
|
352
|
+
: "";
|
|
353
|
+
const description = (knownNames.length === 0
|
|
214
354
|
? "Spawn a sub-agent to handle a delegated task. Pass a `description` (label) and a `prompt` (the task instructions). The sub-agent runs in an isolated context and returns a single final message."
|
|
215
|
-
: `Spawn a sub-agent to handle a delegated task. Pass a \`description\` (label), a \`prompt\` (the task instructions), and optionally a \`subagent_type\` to pick a specialist. Available subagent_type values: ${knownNames.join(", ")}. The sub-agent runs in an isolated context and returns a single final message
|
|
355
|
+
: `Spawn a sub-agent to handle a delegated task. Pass a \`description\` (label), a \`prompt\` (the task instructions), and optionally a \`subagent_type\` to pick a specialist. Available subagent_type values: ${knownNames.join(", ")}. The sub-agent runs in an isolated context and returns a single final message.`) +
|
|
356
|
+
profileNote;
|
|
216
357
|
return buildTool({
|
|
217
358
|
name: "Task",
|
|
218
359
|
description,
|
|
@@ -220,6 +361,13 @@ export function createTaskTool(opts = {}) {
|
|
|
220
361
|
concurrencySafe: true,
|
|
221
362
|
readOnly: false,
|
|
222
363
|
destructive: false,
|
|
364
|
+
// Pillar 3 rule 6 (AGENTS.md): the sub-agent's `finalMessage` is already
|
|
365
|
+
// classified and lineage-tagged at the spawner boundary (origin
|
|
366
|
+
// "subagent"), so the runtime's post-tool pass at origin "tool" is
|
|
367
|
+
// skipped here — a second pass would double-warn and burn the cache.
|
|
368
|
+
// The rule documented this flag for years without a line setting it
|
|
369
|
+
// (0.6.0 plan §7.5 noted the gap); this is that line.
|
|
370
|
+
classifyOutput: false,
|
|
223
371
|
// Per-call parallel eligibility (see the module header). A dispatch is
|
|
224
372
|
// parallel-safe iff the resolved sub-agent's ENTIRE effective tool set
|
|
225
373
|
// is read-only + concurrency-safe + non-destructive. Fail-closed: an
|
|
@@ -259,35 +407,25 @@ export function createTaskTool(opts = {}) {
|
|
|
259
407
|
catch (err) {
|
|
260
408
|
return `[Task error] ${err.message}`;
|
|
261
409
|
}
|
|
410
|
+
// 0.6.0 §7.7 / §10.1 — the model-filled `profile` argument is checked
|
|
411
|
+
// against the definition's allowlist BEFORE anything is spawned. Thrown
|
|
412
|
+
// (not returned) so tool-executor renders it `is_error: true`: the
|
|
413
|
+
// parent model sees a refusal, not a result to build on.
|
|
414
|
+
if (input.profile !== undefined) {
|
|
415
|
+
const allowed = subAgentProfileAllowlist(def, bridge.model);
|
|
416
|
+
if (!allowed.includes(input.profile)) {
|
|
417
|
+
throw new SubAgentResolutionError(`profile "${input.profile}" is not allowed for sub-agent "${def.name}" — allowed: ${allowed.join(", ")}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
262
420
|
const childTools = buildChildCatalog(bridge.tools, def);
|
|
263
421
|
const childPerms = resolveChildPermissions({ mode: bridge.permissionMode, rules: bridge.permissionRules }, def);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
maxTokens: bridge.maxTokens,
|
|
272
|
-
...(bridge.sessionRootDir !== undefined ? { sessionRootDir: bridge.sessionRootDir } : {}),
|
|
273
|
-
// v0.3.0 §7.1 — the four child seams, projected by runtime-core onto
|
|
274
|
-
// the bridge (recall-only memory, skills list, failure taxonomy,
|
|
275
|
-
// read-only continuity) and handed to the spawner verbatim.
|
|
276
|
-
...(bridge.memory !== undefined ? { memory: bridge.memory } : {}),
|
|
277
|
-
...(bridge.skills !== undefined ? { skills: bridge.skills } : {}),
|
|
278
|
-
...(bridge.failureTaxonomy !== undefined
|
|
279
|
-
? { failureTaxonomy: bridge.failureTaxonomy }
|
|
280
|
-
: {}),
|
|
281
|
-
...(bridge.continuity !== undefined ? { continuity: bridge.continuity } : {}),
|
|
282
|
-
// Loop contract 0.4 (G11) — the parent's ask_mode + approval seam, so
|
|
283
|
-
// a child's headless `ask` parks against the same store instead of
|
|
284
|
-
// collapsing to a denial. THIS projection is the mechanism of the bug
|
|
285
|
-
// it fixes: `RuntimeBridge` extends `ParentRunHandle`, but the handle
|
|
286
|
-
// is hand-copied field by field and every seam field is optional — an
|
|
287
|
-
// omission type-checks perfectly and drops the capability silently.
|
|
288
|
-
...(bridge.askMode !== undefined ? { askMode: bridge.askMode } : {}),
|
|
289
|
-
...(bridge.approvals !== undefined ? { approvals: bridge.approvals } : {}),
|
|
290
|
-
};
|
|
422
|
+
// 0.6.0 §10.2 — ONE shared projection replaces the field-by-field hand
|
|
423
|
+
// copy this tool carried (and whose optional-seam omissions were the
|
|
424
|
+
// mechanism of the G11 approval bug): `projectParentHandle` lives beside
|
|
425
|
+
// `ParentRunHandle`, so a new handle field lands in the projection in
|
|
426
|
+
// the same file. The routing projection (served arm, budget cap) rides
|
|
427
|
+
// along for `inherit_routing` / `budget_share`.
|
|
428
|
+
const parentHandle = projectParentHandle(bridge);
|
|
291
429
|
// A fatal child failure (billing/auth) makes `spawnSubAgent` throw
|
|
292
430
|
// `RunFailedError`; it propagates through here and through
|
|
293
431
|
// tool-executor's carve-out so the whole run halts with the child's
|
|
@@ -299,6 +437,7 @@ export function createTaskTool(opts = {}) {
|
|
|
299
437
|
permissionRules: childPerms.rules,
|
|
300
438
|
childTools,
|
|
301
439
|
...(bridge.sessionRootDir !== undefined ? { sessionRootDir: bridge.sessionRootDir } : {}),
|
|
440
|
+
...(input.profile !== undefined ? { profile: input.profile } : {}),
|
|
302
441
|
});
|
|
303
442
|
if (result.failure !== undefined) {
|
|
304
443
|
// Non-fatal classified failure → an is_error tool result carrying
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/tool-task",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Task(description, prompt, subagent_type?) tool — spawns a sub-agent and returns its final message (Section 13)",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
"test": "bun test src"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@crewhaus/agent-context-isolation": "0.
|
|
19
|
-
"@crewhaus/errors": "0.
|
|
20
|
-
"@crewhaus/sub-agent-permission-inheritance": "0.
|
|
21
|
-
"@crewhaus/tool-builder": "0.
|
|
22
|
-
"@crewhaus/tool-catalog": "0.
|
|
18
|
+
"@crewhaus/agent-context-isolation": "0.6.0",
|
|
19
|
+
"@crewhaus/errors": "0.6.0",
|
|
20
|
+
"@crewhaus/sub-agent-permission-inheritance": "0.6.0",
|
|
21
|
+
"@crewhaus/tool-builder": "0.6.0",
|
|
22
|
+
"@crewhaus/tool-catalog": "0.6.0",
|
|
23
23
|
"yaml": "^2.6.0",
|
|
24
24
|
"zod": "^3.23.8"
|
|
25
25
|
},
|