pi-cohort 5.0.0 → 5.1.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
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.1.0] - 2026-08-06
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- subagent tool schema shrunk from 18.0KB to 12.4KB serialized: the acceptance
|
|
8
|
+
override shape (previously inlined 5x, 44% of the schema) now appears in full
|
|
9
|
+
only at top-level `acceptance`; the 4 nested sites (tasks items, chain steps,
|
|
10
|
+
parallel members, dynamic fanout template) carry a compact stub. Gate-level
|
|
11
|
+
`evidence`/`severity` dropped from the schema (still accepted at runtime).
|
|
12
|
+
Deep acceptance validation moved to the executor boundary with per-site path
|
|
13
|
+
labels. Nested `acceptance: false` is no longer schema-accepted - use
|
|
14
|
+
`level: "none"`. Unknown acceptance keys are now rejected everywhere,
|
|
15
|
+
including saved `.chain.json` chains that previously loaded with the key
|
|
16
|
+
silently ignored. ([#6](https://github.com/jjuraszek/pi-cohort/issues/6))
|
|
17
|
+
|
|
18
|
+
## [5.0.1] - 2026-08-06
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- subagent tool schema dieted from 23.2KB to <=18KB serialized (description
|
|
23
|
+
trimming only; all accepted input shapes unchanged, guarded by a baseline
|
|
24
|
+
shape fixture). `config` field reference moved to the pi-cohort skill's
|
|
25
|
+
`reference/config-fields.md`. Schema size-budget test added.
|
|
26
|
+
|
|
3
27
|
## [5.0.0] - 2026-08-06
|
|
4
28
|
|
|
5
29
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cohort",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"license": "MIT",
|
|
@@ -503,6 +503,8 @@ If intercom messages do not show up, run `subagent({ action: "doctor" })` or `/c
|
|
|
503
503
|
|
|
504
504
|
The `subagent(...)` tool also supports management actions.
|
|
505
505
|
|
|
506
|
+
Full config field reference: `reference/config-fields.md` (sibling of this file).
|
|
507
|
+
|
|
506
508
|
### List available agents and chains
|
|
507
509
|
|
|
508
510
|
```typescript
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# subagent `config` field reference
|
|
2
|
+
|
|
3
|
+
Fields accepted by `subagent({ action: "create" | "update", config: {...} })`.
|
|
4
|
+
`config` may be an object or a JSON string. Presence of `steps` makes it a chain.
|
|
5
|
+
Scope: this is the management create/update path (`parseStepList`), which accepts exactly the step fields below and requires `outputSchema` to be a file path; file-authored `.chain.md` chains support additional step fields (parallel, expand, collect, concurrency, failFast, worktree, acceptance, inline outputSchema) - see SKILL.md chain authoring.
|
|
6
|
+
|
|
7
|
+
## Agent config
|
|
8
|
+
|
|
9
|
+
| Field | Type | Meaning | Default |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `name` | string | Required (create). Letters, numbers, spaces, hyphens. | - |
|
|
12
|
+
| `description` | string | Required (create). | - |
|
|
13
|
+
| `package` | string | Optional namespace; runtime name becomes `package.name`. | none |
|
|
14
|
+
| `scope` | `"user"` \| `"project"` | Where the definition file lands. | `"user"` |
|
|
15
|
+
| `systemPrompt` | string \| false | Persona body; false or "" clears. | `""` |
|
|
16
|
+
| `systemPromptMode` | `"append"` \| `"replace"` | How systemPrompt combines with the base prompt. | per-agent default |
|
|
17
|
+
| `model` | string \| false | Model override; false/"" clears. | inherit |
|
|
18
|
+
| `fallbackModels` | string (csv) \| string[] \| false | Models tried in order on failure; false/"" clears. | none |
|
|
19
|
+
| `tools` | string (csv) \| false | Tool allowlist; false/"" clears. MCP-direct tools rejected. | all |
|
|
20
|
+
| `skills` | string (csv) \| false | Skills injected into the agent; false/"" clears. | none |
|
|
21
|
+
| `extensions` | string (csv) \| "" \| false | Extension list; "" means empty list, false clears. | inherit |
|
|
22
|
+
| `thinking` | string \| false | Thinking level; false/"" clears. | inherit |
|
|
23
|
+
| `inheritProjectContext` | boolean | Inject project AGENTS.md context. | per-agent default |
|
|
24
|
+
| `inheritSkills` | boolean | Inherit parent-visible skills. | per-agent default |
|
|
25
|
+
| `defaultContext` | `"fresh"` \| `"fork"` \| false | Default context mode; false/"" clears. | `"fresh"` |
|
|
26
|
+
| `output` | string \| false | Default output filename; false/"" clears. | none |
|
|
27
|
+
| `reads` | string (csv) \| false | Default files read before running; false/"" clears. | none |
|
|
28
|
+
| `progress` | boolean | Default progress.md tracking. | off |
|
|
29
|
+
| `maxSubagentDepth` | integer >= 0 \| false | Nested subagent depth cap; false/"" clears. | inherit |
|
|
30
|
+
| `completionGuard` | boolean | Enable completion guard for this agent. | inherit |
|
|
31
|
+
|
|
32
|
+
## Chain config
|
|
33
|
+
|
|
34
|
+
Top-level: `name`, `description`, `package`, `scope` as above, plus required `steps` (non-empty array).
|
|
35
|
+
|
|
36
|
+
Per step:
|
|
37
|
+
|
|
38
|
+
| Field | Type | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `agent` | string | Required, non-empty. |
|
|
41
|
+
| `task` | string | Task template; defaults to `""`. |
|
|
42
|
+
| `phase` | string | Phase/group label. |
|
|
43
|
+
| `label` | string | User-facing label. |
|
|
44
|
+
| `as` | string | Identifier for `{outputs.name}` in later steps. |
|
|
45
|
+
| `outputSchema` | string | Schema FILE PATH (saved chains take a path, not an inline object). |
|
|
46
|
+
| `output` | string \| false | Output file path, or false to disable. |
|
|
47
|
+
| `outputMode` | `"inline"` \| `"file-only"` | Output return mode. |
|
|
48
|
+
| `reads` | string[] \| false | Files to read before the step. |
|
|
49
|
+
| `model` | string | Model override. |
|
|
50
|
+
| `skills` | string[] \| false | Skills to inject (NOTE: plural `skills` here, unlike the execution-time `skill` param). |
|
|
51
|
+
| `progress` | boolean | progress.md tracking. |
|
package/src/extension/schemas.ts
CHANGED
|
@@ -5,13 +5,23 @@
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { SUBAGENT_ACTIONS } from "../shared/types.ts";
|
|
7
7
|
|
|
8
|
+
// Descriptor-preserving clone: typebox 1.3.11 metadata ("~kind", "~unsafe",
|
|
9
|
+
// "~optional") lives in NON-ENUMERABLE string-keyed properties. Object spread
|
|
10
|
+
// would drop them, and Type.Optional on a plain clone would re-add "~optional"
|
|
11
|
+
// as an enumerable key, leaking "~optional":true into the serialized JSON.
|
|
12
|
+
const brief = <T extends object>(schema: T, description: string): T =>
|
|
13
|
+
Object.defineProperties(
|
|
14
|
+
Object.defineProperties(Object.create(null), Object.getOwnPropertyDescriptors(schema)),
|
|
15
|
+
{ description: { value: description, enumerable: true, writable: true, configurable: true } },
|
|
16
|
+
) as T;
|
|
17
|
+
|
|
8
18
|
const SkillOverride = Type.Unsafe({
|
|
9
19
|
anyOf: [
|
|
10
20
|
{ type: "array", items: { type: "string" } },
|
|
11
21
|
{ type: "boolean" },
|
|
12
22
|
{ type: "string" },
|
|
13
23
|
],
|
|
14
|
-
description: "
|
|
24
|
+
description: "CSV/array/boolean (false disables, true default).",
|
|
15
25
|
});
|
|
16
26
|
|
|
17
27
|
const OutputOverride = Type.Unsafe({
|
|
@@ -24,7 +34,7 @@ const OutputOverride = Type.Unsafe({
|
|
|
24
34
|
|
|
25
35
|
const OutputModeOverride = Type.String({
|
|
26
36
|
enum: ["inline", "file-only"],
|
|
27
|
-
description: "
|
|
37
|
+
description: "inline (default) or file-only (needs output path).",
|
|
28
38
|
});
|
|
29
39
|
|
|
30
40
|
const ReadsOverride = Type.Unsafe({
|
|
@@ -38,7 +48,7 @@ const ReadsOverride = Type.Unsafe({
|
|
|
38
48
|
const JsonSchemaObject = Type.Unsafe({
|
|
39
49
|
type: "object",
|
|
40
50
|
additionalProperties: true,
|
|
41
|
-
description: "
|
|
51
|
+
description: "Structured output; non-object rejected.",
|
|
42
52
|
});
|
|
43
53
|
|
|
44
54
|
const AcceptanceEvidenceKind = Type.String({
|
|
@@ -55,12 +65,13 @@ const AcceptanceEvidenceKind = Type.String({
|
|
|
55
65
|
],
|
|
56
66
|
});
|
|
57
67
|
|
|
68
|
+
// Gate-level `evidence`/`severity` are accepted at runtime (normalizeCriteria) but omitted
|
|
69
|
+
// from the schema: dead flexibility from a tool call, and the evidence enum is heavy.
|
|
70
|
+
// additionalProperties stays open so inputs carrying them are not provider-rejected.
|
|
58
71
|
const AcceptanceGateSchema = Type.Object({
|
|
59
72
|
id: Type.String(),
|
|
60
73
|
must: Type.String(),
|
|
61
|
-
|
|
62
|
-
severity: Type.Optional(Type.String({ enum: ["required", "recommended"] })),
|
|
63
|
-
}, { additionalProperties: false });
|
|
74
|
+
}, { additionalProperties: true });
|
|
64
75
|
|
|
65
76
|
const AcceptanceVerifyCommandSchema = Type.Object({
|
|
66
77
|
id: Type.String(),
|
|
@@ -108,108 +119,123 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
108
119
|
additionalProperties: false,
|
|
109
120
|
},
|
|
110
121
|
],
|
|
111
|
-
description: "
|
|
122
|
+
description: "Acceptance: auto if omitted; verified needs cmds.",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Nested acceptance sites carry a compact stub instead of the full AcceptanceOverride:
|
|
126
|
+
// inlined 5x it was 44% of the serialized schema. The full shape is documented once at
|
|
127
|
+
// top-level `acceptance`; deep validation happens at the executor boundary
|
|
128
|
+
// (validateAcceptanceInput), not provider-side. The object branch keeps a non-empty
|
|
129
|
+
// `properties` because some providers reject bare {type:"object"} in function
|
|
130
|
+
// declarations - the same wall that rules out $defs/$ref (see issue #6).
|
|
131
|
+
// {const:false} is intentionally absent: deprecated shorthand, use level:"none".
|
|
132
|
+
const AcceptanceOverrideStub = Type.Unsafe({
|
|
133
|
+
anyOf: [
|
|
134
|
+
{ type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
135
|
+
{ type: "object", additionalProperties: true, properties: { level: { type: "string" } } },
|
|
136
|
+
],
|
|
137
|
+
description: "Acceptance override; same shape as top-level acceptance.",
|
|
112
138
|
});
|
|
113
139
|
|
|
114
140
|
const TaskItem = Type.Object({
|
|
115
141
|
agent: Type.String(),
|
|
116
142
|
task: Type.String(),
|
|
117
143
|
cwd: Type.Optional(Type.String()),
|
|
118
|
-
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat
|
|
119
|
-
output: Type.Optional(OutputOverride),
|
|
120
|
-
outputMode: Type.Optional(OutputModeOverride),
|
|
144
|
+
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat N times (same settings)." })),
|
|
145
|
+
output: Type.Optional(brief(OutputOverride, "Output file path, or false.")),
|
|
146
|
+
outputMode: Type.Optional(brief(OutputModeOverride, "Default: inline.")),
|
|
121
147
|
reads: Type.Optional(ReadsOverride),
|
|
122
|
-
progress: Type.Optional(Type.Boolean({ description: "
|
|
123
|
-
model: Type.Optional(Type.String(
|
|
124
|
-
skill: Type.Optional(SkillOverride),
|
|
125
|
-
acceptance: Type.Optional(
|
|
148
|
+
progress: Type.Optional(Type.Boolean({ description: "true enables progress.md tracking; omit or false disables." })),
|
|
149
|
+
model: Type.Optional(Type.String()),
|
|
150
|
+
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
151
|
+
acceptance: Type.Optional(AcceptanceOverrideStub),
|
|
126
152
|
});
|
|
127
153
|
|
|
128
154
|
// Parallel task item (within a parallel step)
|
|
129
155
|
const ParallelTaskSchema = Type.Object({
|
|
130
156
|
agent: Type.String(),
|
|
131
|
-
task: Type.Optional(Type.String({ description: "
|
|
132
|
-
phase: Type.Optional(Type.String(
|
|
133
|
-
label: Type.Optional(Type.String(
|
|
134
|
-
as: Type.Optional(Type.String({ description: "
|
|
135
|
-
outputSchema: Type.Optional(JsonSchemaObject),
|
|
157
|
+
task: Type.Optional(Type.String({ description: "{task},{previous},{chain_dir} template; defaults to {previous}." })),
|
|
158
|
+
phase: Type.Optional(Type.String()),
|
|
159
|
+
label: Type.Optional(Type.String()),
|
|
160
|
+
as: Type.Optional(Type.String({ description: "Identifier for {outputs.name}." })),
|
|
161
|
+
outputSchema: Type.Optional(brief(JsonSchemaObject, "Output JSON Schema.")),
|
|
136
162
|
cwd: Type.Optional(Type.String()),
|
|
137
|
-
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat
|
|
138
|
-
output: Type.Optional(OutputOverride),
|
|
139
|
-
outputMode: Type.Optional(OutputModeOverride),
|
|
140
|
-
reads: Type.Optional(ReadsOverride),
|
|
141
|
-
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
acceptance: Type.Optional(
|
|
163
|
+
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat N times." })),
|
|
164
|
+
output: Type.Optional(brief(OutputOverride, "Output file path, or false.")),
|
|
165
|
+
outputMode: Type.Optional(brief(OutputModeOverride, "Default: inline.")),
|
|
166
|
+
reads: Type.Optional(brief(ReadsOverride, "Reads first, or false.")),
|
|
167
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking." })),
|
|
168
|
+
model: Type.Optional(Type.String()),
|
|
169
|
+
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
170
|
+
acceptance: Type.Optional(AcceptanceOverrideStub),
|
|
145
171
|
});
|
|
146
172
|
|
|
147
173
|
const DynamicExpandSchema = Type.Object({
|
|
148
174
|
from: Type.Object({
|
|
149
|
-
output: Type.String({ description: "
|
|
150
|
-
path: Type.String({ description: "JSON Pointer into
|
|
175
|
+
output: Type.String({ description: "Named prior output." }),
|
|
176
|
+
path: Type.String({ description: "JSON Pointer into it." }),
|
|
151
177
|
}, { additionalProperties: false }),
|
|
152
|
-
item: Type.Optional(Type.String({ description: "
|
|
153
|
-
key: Type.Optional(Type.String({ description: "
|
|
154
|
-
maxItems: Type.Optional(Type.Integer({ minimum: 0, description: "Required
|
|
155
|
-
onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "
|
|
178
|
+
item: Type.Optional(Type.String({ description: "Defaults to item." })),
|
|
179
|
+
key: Type.Optional(Type.String({ description: "Pointer per item; stable child id." })),
|
|
180
|
+
maxItems: Type.Optional(Type.Integer({ minimum: 0, description: "Required unless chain.dynamicFanout.maxItems is configured." })),
|
|
181
|
+
onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "Defaults to skip." })),
|
|
156
182
|
}, { additionalProperties: false });
|
|
157
183
|
|
|
158
184
|
const DynamicParallelTemplateSchema = Type.Object({
|
|
159
185
|
agent: Type.String(),
|
|
160
|
-
task: Type.Optional(Type.String({ description: "
|
|
161
|
-
phase: Type.Optional(Type.String(
|
|
162
|
-
label: Type.Optional(Type.String({ description: "
|
|
163
|
-
outputSchema: Type.Optional(JsonSchemaObject),
|
|
186
|
+
task: Type.Optional(Type.String({ description: "{item},{item.path},{task},{previous},{chain_dir},{outputs.name} template." })),
|
|
187
|
+
phase: Type.Optional(Type.String()),
|
|
188
|
+
label: Type.Optional(Type.String({ description: "Label; item templates supported." })),
|
|
189
|
+
outputSchema: Type.Optional(brief(JsonSchemaObject, "Output JSON Schema.")),
|
|
164
190
|
cwd: Type.Optional(Type.String()),
|
|
165
|
-
output: Type.Optional(OutputOverride),
|
|
166
|
-
outputMode: Type.Optional(OutputModeOverride),
|
|
167
|
-
reads: Type.Optional(ReadsOverride),
|
|
168
|
-
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
acceptance: Type.Optional(
|
|
191
|
+
output: Type.Optional(brief(OutputOverride, "Output file path, or false.")),
|
|
192
|
+
outputMode: Type.Optional(brief(OutputModeOverride, "Default: inline.")),
|
|
193
|
+
reads: Type.Optional(brief(ReadsOverride, "Reads first, or false.")),
|
|
194
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking." })),
|
|
195
|
+
model: Type.Optional(Type.String()),
|
|
196
|
+
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
197
|
+
acceptance: Type.Optional(AcceptanceOverrideStub),
|
|
172
198
|
}, { additionalProperties: false });
|
|
173
199
|
|
|
174
200
|
const DynamicCollectSchema = Type.Object({
|
|
175
|
-
as: Type.String({ description: "
|
|
176
|
-
outputSchema: Type.Optional(JsonSchemaObject),
|
|
201
|
+
as: Type.String({ description: "Collected result array name." }),
|
|
202
|
+
outputSchema: Type.Optional(brief(JsonSchemaObject, "Output JSON Schema.")),
|
|
177
203
|
}, { additionalProperties: false });
|
|
178
204
|
|
|
179
205
|
// Flattened so chain steps do not need an object-shape anyOf/oneOf union.
|
|
180
206
|
const ChainItem = Type.Object({
|
|
181
|
-
agent: Type.Optional(Type.String(
|
|
207
|
+
agent: Type.Optional(Type.String()),
|
|
182
208
|
task: Type.Optional(Type.String({
|
|
183
|
-
description: "
|
|
209
|
+
description: "{task},{previous},{chain_dir},{outputs.name}; required first else '{previous}'."
|
|
184
210
|
})),
|
|
185
|
-
phase: Type.Optional(Type.String({ description: "
|
|
186
|
-
label: Type.Optional(Type.String({ description: "
|
|
187
|
-
as: Type.Optional(Type.String({ description: "
|
|
211
|
+
phase: Type.Optional(Type.String({ description: "Phase/group label (status/graph)." })),
|
|
212
|
+
label: Type.Optional(Type.String({ description: "Chain step label." })),
|
|
213
|
+
as: Type.Optional(Type.String({ description: "Id for {outputs.name} in later steps." })),
|
|
188
214
|
outputSchema: Type.Optional(JsonSchemaObject),
|
|
189
215
|
cwd: Type.Optional(Type.String()),
|
|
190
|
-
output: Type.Optional(OutputOverride),
|
|
191
|
-
outputMode: Type.Optional(OutputModeOverride),
|
|
192
|
-
reads: Type.Optional(ReadsOverride),
|
|
216
|
+
output: Type.Optional(brief(OutputOverride, "Output file path, or false.")),
|
|
217
|
+
outputMode: Type.Optional(brief(OutputModeOverride, "Default: inline.")),
|
|
218
|
+
reads: Type.Optional(brief(ReadsOverride, "Reads first, or false.")),
|
|
193
219
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
acceptance: Type.Optional(
|
|
220
|
+
model: Type.Optional(Type.String()),
|
|
221
|
+
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
222
|
+
acceptance: Type.Optional(AcceptanceOverrideStub),
|
|
197
223
|
parallel: Type.Optional(Type.Unsafe({
|
|
198
224
|
anyOf: [
|
|
199
|
-
Type.Array(ParallelTaskSchema, { minItems: 1
|
|
225
|
+
Type.Array(ParallelTaskSchema, { minItems: 1 }),
|
|
200
226
|
DynamicParallelTemplateSchema,
|
|
201
227
|
],
|
|
202
|
-
description: "Static
|
|
228
|
+
description: "Static array, or dynamic fanout template (expand/collect).",
|
|
203
229
|
})),
|
|
204
230
|
expand: Type.Optional(DynamicExpandSchema),
|
|
205
231
|
collect: Type.Optional(DynamicCollectSchema),
|
|
206
232
|
concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
|
|
207
233
|
failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
|
|
208
234
|
worktree: Type.Optional(Type.Boolean({
|
|
209
|
-
description: "
|
|
235
|
+
description: "Isolated worktree per task."
|
|
210
236
|
})),
|
|
211
237
|
}, {
|
|
212
|
-
description: "
|
|
238
|
+
description: "{agent,task?} seq; {parallel:[..]} concurrent; {expand,parallel,collect} fanout.",
|
|
213
239
|
additionalProperties: false,
|
|
214
240
|
allOf: [
|
|
215
241
|
{ if: { required: ["expand"] }, then: { required: ["parallel", "collect"], properties: { parallel: { type: "object" } } } },
|
|
@@ -219,44 +245,44 @@ const ChainItem = Type.Object({
|
|
|
219
245
|
});
|
|
220
246
|
|
|
221
247
|
const ControlOverrides = Type.Object({
|
|
222
|
-
enabled: Type.Optional(Type.Boolean({ description: "
|
|
223
|
-
needsAttentionAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "No-
|
|
224
|
-
activeNoticeAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
225
|
-
inFlightSilenceCeilingMs: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
226
|
-
inFlightSilenceKillMs: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
227
|
-
activeNoticeAfterTurns: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
228
|
-
activeNoticeAfterTokens: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
229
|
-
failedToolAttemptsBeforeAttention: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
248
|
+
enabled: Type.Optional(Type.Boolean({ description: "Toggle attention tracking." })),
|
|
249
|
+
needsAttentionAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "No-activity window before needs_attention." })),
|
|
250
|
+
activeNoticeAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "Elapsed-ms notice threshold (default: 240000)." })),
|
|
251
|
+
inFlightSilenceCeilingMs: Type.Optional(Type.Integer({ minimum: 1, description: "Silent ms before escalation (default: 600000)." })),
|
|
252
|
+
inFlightSilenceKillMs: Type.Optional(Type.Integer({ minimum: 1, description: "SIGTERM child (default: 1800000, clamped above needs_attention)." })),
|
|
253
|
+
activeNoticeAfterTurns: Type.Optional(Type.Integer({ minimum: 1, description: "Notice by assistant turns (off by default)." })),
|
|
254
|
+
activeNoticeAfterTokens: Type.Optional(Type.Integer({ minimum: 1, description: "Notice by total tokens (off by default)." })),
|
|
255
|
+
failedToolAttemptsBeforeAttention: Type.Optional(Type.Integer({ minimum: 1, description: "Mutating-tool failures before needs_attention (default: 3)." })),
|
|
230
256
|
notifyOn: Type.Optional(Type.Array(Type.String({ enum: ["active_long_running", "needs_attention"] }), {
|
|
231
|
-
description: "
|
|
257
|
+
description: "To parent (default: active_long_running, needs_attention).",
|
|
232
258
|
})),
|
|
233
259
|
notifyChannels: Type.Optional(Type.Array(Type.String({ enum: ["event", "async", "intercom"] }), {
|
|
234
|
-
description: "
|
|
260
|
+
description: "Default: event, async, intercom.",
|
|
235
261
|
})),
|
|
236
262
|
});
|
|
237
263
|
|
|
238
264
|
export const SubagentParams = Type.Object({
|
|
239
|
-
agent: Type.Optional(Type.String({ description: "
|
|
240
|
-
task: Type.Optional(Type.String({ description: "Task (SINGLE mode
|
|
265
|
+
agent: Type.Optional(Type.String({ description: "SINGLE mode agent, or management target." })),
|
|
266
|
+
task: Type.Optional(Type.String({ description: "Task (SINGLE mode; optional if self-contained)." })),
|
|
241
267
|
// Management action (when present, tool operates in management mode)
|
|
242
268
|
action: Type.Optional(Type.String({
|
|
243
269
|
enum: [...SUBAGENT_ACTIONS],
|
|
244
270
|
description: "Management/control action. Omit for execution mode."
|
|
245
271
|
})),
|
|
246
272
|
id: Type.Optional(Type.String({
|
|
247
|
-
description: "Run id
|
|
273
|
+
description: "Run id/prefix for status/interrupt/resume."
|
|
248
274
|
})),
|
|
249
275
|
runId: Type.Optional(Type.String({
|
|
250
|
-
description: "
|
|
276
|
+
description: "Run ID (interrupt/resume); defaults to latest. Prefer id."
|
|
251
277
|
})),
|
|
252
278
|
dir: Type.Optional(Type.String({
|
|
253
|
-
description: "Async run
|
|
279
|
+
description: "Async run dir for status/resume."
|
|
254
280
|
})),
|
|
255
|
-
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based
|
|
256
|
-
message: Type.Optional(Type.String({ description: "Follow-up message for
|
|
281
|
+
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based index for per-child actions." })),
|
|
282
|
+
message: Type.Optional(Type.String({ description: "Follow-up message for resume." })),
|
|
257
283
|
// Chain identifier for management (can't reuse 'chain' — that's the execution array)
|
|
258
284
|
chainName: Type.Optional(Type.String({
|
|
259
|
-
description: "Chain name
|
|
285
|
+
description: "Chain name."
|
|
260
286
|
})),
|
|
261
287
|
// Agent/chain configuration for create/update (nested to avoid conflicts with execution fields)
|
|
262
288
|
config: Type.Optional(Type.Unsafe({
|
|
@@ -264,32 +290,30 @@ export const SubagentParams = Type.Object({
|
|
|
264
290
|
{ type: "object", additionalProperties: true },
|
|
265
291
|
{ type: "string" },
|
|
266
292
|
],
|
|
267
|
-
description: "
|
|
293
|
+
description: "Create/update config; fields: pi-cohort skill reference/config-fields.md; string=JSON."
|
|
268
294
|
})),
|
|
269
|
-
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task,
|
|
270
|
-
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
295
|
+
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode tasks: [{agent, task, ...}]." })),
|
|
296
|
+
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Default: config.parallel.concurrency or 4." })),
|
|
271
297
|
worktree: Type.Optional(Type.Boolean({
|
|
272
|
-
description: "
|
|
273
|
-
"Prevents filesystem conflicts. Requires clean git state. " +
|
|
274
|
-
"Per-worktree diffs included in output."
|
|
298
|
+
description: "Isolated worktree per task; clean git; diffs in output."
|
|
275
299
|
})),
|
|
276
|
-
chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN
|
|
300
|
+
chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN: sequential; step response becomes {previous}." })),
|
|
277
301
|
context: Type.Optional(Type.String({
|
|
278
302
|
enum: ["fresh", "fork"],
|
|
279
|
-
description: "
|
|
303
|
+
description: "fresh (default) or fork; defaultContext:fork forces whole invocation.",
|
|
280
304
|
})),
|
|
281
|
-
chainDir: Type.Optional(Type.String({ description: "
|
|
282
|
-
async: Type.Optional(Type.Boolean({ description: "
|
|
283
|
-
agentScope: Type.Optional(Type.String({ description: "
|
|
305
|
+
chainDir: Type.Optional(Type.String({ description: "Default temp <tmpdir>/, 24h clean." })),
|
|
306
|
+
async: Type.Optional(Type.Boolean({ description: "Background run (default: false, or per config)." })),
|
|
307
|
+
agentScope: Type.Optional(Type.String({ description: "user/project/both (default both, project wins)." })),
|
|
284
308
|
cwd: Type.Optional(Type.String()),
|
|
285
|
-
artifacts: Type.Optional(Type.Boolean({ description: "
|
|
286
|
-
includeProgress: Type.Optional(Type.Boolean({ description: "
|
|
287
|
-
share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist
|
|
309
|
+
artifacts: Type.Optional(Type.Boolean({ description: "(default: true)." })),
|
|
310
|
+
includeProgress: Type.Optional(Type.Boolean({ description: "(default: false)." })),
|
|
311
|
+
share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist (default: false)." })),
|
|
288
312
|
sessionDir: Type.Optional(
|
|
289
|
-
Type.String({ description: "
|
|
313
|
+
Type.String({ description: "Default temp; enables sessions even if share=false." }),
|
|
290
314
|
),
|
|
291
315
|
// Clarification TUI
|
|
292
|
-
clarify: Type.Optional(Type.Boolean({ description: "
|
|
316
|
+
clarify: Type.Optional(Type.Boolean({ description: "Preview/edit TUI; omitted/false runs directly; true=foreground (if supported); parallel-step chains skip it." })),
|
|
293
317
|
control: Type.Optional(ControlOverrides),
|
|
294
318
|
// Solo agent overrides
|
|
295
319
|
output: Type.Optional(Type.Unsafe({
|
|
@@ -297,10 +321,10 @@ export const SubagentParams = Type.Object({
|
|
|
297
321
|
{ type: "string" },
|
|
298
322
|
{ type: "boolean" },
|
|
299
323
|
],
|
|
300
|
-
description: "Omit or
|
|
324
|
+
description: "Omit or false disables; true=agent-configured filename; string=path (rel. cwd).",
|
|
301
325
|
})),
|
|
302
326
|
outputMode: Type.Optional(OutputModeOverride),
|
|
303
327
|
skill: Type.Optional(SkillOverride),
|
|
304
|
-
model: Type.Optional(Type.String({ description: "
|
|
328
|
+
model: Type.Optional(Type.String({ description: "Model override, e.g. 'anthropic/claude-sonnet-4'." })),
|
|
305
329
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
306
330
|
});
|
|
@@ -17,6 +17,7 @@ import { recordSyncCost, renderGrandTotal, sumNestedCost } from "../../extension
|
|
|
17
17
|
import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
18
18
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
19
19
|
import { recordRun } from "../shared/run-history.ts";
|
|
20
|
+
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
20
21
|
import {
|
|
21
22
|
buildChainInstructions,
|
|
22
23
|
writeInitialProgressFile,
|
|
@@ -858,6 +859,33 @@ function validateExecutionInput(
|
|
|
858
859
|
}
|
|
859
860
|
}
|
|
860
861
|
|
|
862
|
+
// Nested acceptance sites only carry a compact stub in the tool schema (see
|
|
863
|
+
// AcceptanceOverrideStub in extension/schemas.ts); this is the deep-validation boundary.
|
|
864
|
+
const acceptanceErrors: string[] = [
|
|
865
|
+
...validateAcceptanceInput(params.acceptance, "acceptance"),
|
|
866
|
+
];
|
|
867
|
+
for (const [i, task] of (params.tasks ?? []).entries()) {
|
|
868
|
+
acceptanceErrors.push(...validateAcceptanceInput(task.acceptance, `tasks[${i}].acceptance`));
|
|
869
|
+
}
|
|
870
|
+
for (const [i, rawStep] of (params.chain ?? []).entries()) {
|
|
871
|
+
const step = rawStep as ChainStep;
|
|
872
|
+
acceptanceErrors.push(...validateAcceptanceInput((rawStep as { acceptance?: unknown }).acceptance, `chain[${i}].acceptance`));
|
|
873
|
+
if (isParallelStep(step)) {
|
|
874
|
+
for (const [j, member] of step.parallel.entries()) {
|
|
875
|
+
acceptanceErrors.push(...validateAcceptanceInput(member.acceptance, `chain[${i}].parallel[${j}].acceptance`));
|
|
876
|
+
}
|
|
877
|
+
} else if (isDynamicParallelStep(step)) {
|
|
878
|
+
acceptanceErrors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${i}].parallel.acceptance`));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (acceptanceErrors.length > 0) {
|
|
882
|
+
return {
|
|
883
|
+
content: [{ type: "text", text: `Invalid acceptance input:\n${acceptanceErrors.map((error) => `- ${error}`).join("\n")}` }],
|
|
884
|
+
isError: true,
|
|
885
|
+
details: { mode: hasChain ? "chain" as const : hasTasks ? "parallel" as const : "single" as const, results: [] },
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
861
889
|
return null;
|
|
862
890
|
}
|
|
863
891
|
|
|
@@ -131,6 +131,8 @@ function explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {
|
|
|
131
131
|
return explicit.level === "none" && typeof explicit.reason === "string" && explicit.reason.trim().length > 0;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
const KNOWN_ACCEPTANCE_KEYS = new Set(["level", "criteria", "evidence", "verify", "review", "stopRules", "reason"]);
|
|
135
|
+
|
|
134
136
|
export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"): string[] {
|
|
135
137
|
const errors: string[] = [];
|
|
136
138
|
if (input === undefined) return errors;
|
|
@@ -144,6 +146,11 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
144
146
|
return errors;
|
|
145
147
|
}
|
|
146
148
|
const value = input as Record<string, unknown>;
|
|
149
|
+
for (const key of Object.keys(value)) {
|
|
150
|
+
if (!KNOWN_ACCEPTANCE_KEYS.has(key)) {
|
|
151
|
+
errors.push(`${pathLabel}.${key} is not a recognized acceptance field (known: level, criteria, evidence, verify, review, stopRules, reason).`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
147
154
|
if (value.level !== undefined && (typeof value.level !== "string" || !VALID_LEVELS.has(value.level as AcceptanceLevel))) {
|
|
148
155
|
errors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified, reviewed.`);
|
|
149
156
|
}
|