@emiliosp/pi-maestro 0.15.0 → 0.17.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/ADR.md +127 -0
- package/CHANGELOG.md +22 -0
- package/README.md +7 -7
- package/ROADMAP.md +221 -46
- package/agents/builder.md +8 -8
- package/agents/verifier.md +12 -12
- package/docs/configuration.md +21 -27
- package/docs/glossary.md +5 -3
- package/docs/subagent-integration.md +4 -4
- package/docs/workflow.md +40 -32
- package/package.json +3 -3
- package/src/artifacts/builder-handoff/schema.ts +1 -2
- package/src/config/assertConfiguration.ts +0 -6
- package/src/config/defaults.ts +0 -5
- package/src/config/schema.ts +0 -14
- package/src/delegation/SubSession.ts +102 -0
- package/src/delegation/TaskExecutionError.ts +20 -0
- package/src/delegation/createChildServices.ts +110 -0
- package/src/delegation/executeTask.ts +38 -0
- package/src/delegation/readTaskResult.ts +27 -0
- package/src/maestro/instructions/getMaestroInstructions.ts +13 -11
- package/src/tools/main/resolve-findings.ts +3 -4
- package/src/tools/main/run-builder.ts +2 -3
- package/src/tools/main/run-simplifier.ts +2 -3
- package/src/tools/main/run-verifier.ts +2 -3
- package/src/tools/utils/pi-subagent-delegation.ts +3 -0
- package/templates/spec.md +23 -11
package/docs/configuration.md
CHANGED
|
@@ -17,45 +17,39 @@ For example, if Pi starts in `/work/app/src`, `Maestro` reads `/work/app/src/.pi
|
|
|
17
17
|
"builder": {
|
|
18
18
|
"required": true,
|
|
19
19
|
"model": "openai-codex/gpt-5.6-luna",
|
|
20
|
-
"thinking": "max"
|
|
21
|
-
"timeoutMinutes": 60
|
|
20
|
+
"thinking": "max"
|
|
22
21
|
},
|
|
23
22
|
"simplifier": {
|
|
24
23
|
"required": true,
|
|
25
24
|
"model": "openai-codex/gpt-6.1-sol",
|
|
26
|
-
"thinking": "high"
|
|
27
|
-
"timeoutMinutes": 60
|
|
25
|
+
"thinking": "high"
|
|
28
26
|
},
|
|
29
27
|
"verifier": {
|
|
30
28
|
"required": true,
|
|
31
29
|
"model": "openai-codex/gpt-6.1-sol",
|
|
32
|
-
"thinking": "high"
|
|
33
|
-
"timeoutMinutes": 60
|
|
30
|
+
"thinking": "high"
|
|
34
31
|
}
|
|
35
32
|
}
|
|
36
33
|
```
|
|
37
34
|
|
|
38
35
|
## Fields
|
|
39
36
|
|
|
40
|
-
| Field
|
|
41
|
-
|
|
|
42
|
-
| `version`
|
|
43
|
-
| `specDirectory`
|
|
44
|
-
| `builder`
|
|
45
|
-
| `builder.required`
|
|
46
|
-
| `builder.model`
|
|
47
|
-
| `builder.thinking`
|
|
48
|
-
| `
|
|
49
|
-
| `verifier`
|
|
50
|
-
| `verifier.
|
|
51
|
-
| `verifier.
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `simplifier`
|
|
55
|
-
| `simplifier.
|
|
56
|
-
| `simplifier.model` | No | `openai-codex/gpt-6.1-sol` | Model string passed unchanged to Pi. |
|
|
57
|
-
| `simplifier.thinking` | No | `high` | One supported thinking level. |
|
|
58
|
-
| `simplifier.timeoutMinutes` | No | `60` | Integer from `1` to `1440`. Applies to each `simplifier` `run`. |
|
|
37
|
+
| Field | Required | Default | Rules |
|
|
38
|
+
| --------------------- | ------------------------: | --------------------------- | -------------------------------------------------------- |
|
|
39
|
+
| `version` | Yes, when the file exists | `1.0.0` | Configuration format version. Only `1.0.0` is supported. |
|
|
40
|
+
| `specDirectory` | No | `.specs` | Relative path below the project root. |
|
|
41
|
+
| `builder` | No | `Builder` defaults | Overrides supported `builder` fields. |
|
|
42
|
+
| `builder.required` | No | `true` | Boolean selecting the `builder` phase. |
|
|
43
|
+
| `builder.model` | No | `openai-codex/gpt-5.6-luna` | Model string passed unchanged to Pi. |
|
|
44
|
+
| `builder.thinking` | No | `max` | One supported thinking level. |
|
|
45
|
+
| `verifier` | No | `Verifier` defaults | Overrides supported `verifier` fields. |
|
|
46
|
+
| `verifier.required` | No | `true` | Boolean selecting the `verifier` phase. |
|
|
47
|
+
| `verifier.model` | No | `openai-codex/gpt-6.1-sol` | Model string passed unchanged to Pi. |
|
|
48
|
+
| `verifier.thinking` | No | `high` | One supported thinking level. |
|
|
49
|
+
| `simplifier` | No | `Simplifier` defaults | Overrides supported `simplifier` fields. |
|
|
50
|
+
| `simplifier.required` | No | `true` | Boolean selecting the `simplifier` phase. |
|
|
51
|
+
| `simplifier.model` | No | `openai-codex/gpt-6.1-sol` | Model string passed unchanged to Pi. |
|
|
52
|
+
| `simplifier.thinking` | No | `high` | One supported thinking level. |
|
|
59
53
|
|
|
60
54
|
Model values use the full `provider/model` identifier supplied by Pi's model registry, such as `openrouter/~openai/gpt-mini-latest`. Model IDs may contain additional
|
|
61
55
|
slashes. `Maestro` does not apply a separate format rule; it matches the complete identifier against Pi's available models and passes it unchanged through `pi-subagents`.
|
|
@@ -80,12 +74,12 @@ Only `version` is required when the file exists. Each other field overrides its
|
|
|
80
74
|
{
|
|
81
75
|
"version": "1.0.0",
|
|
82
76
|
"builder": {
|
|
83
|
-
"
|
|
77
|
+
"thinking": "high"
|
|
84
78
|
}
|
|
85
79
|
}
|
|
86
80
|
```
|
|
87
81
|
|
|
88
|
-
This example keeps every default except the `builder`
|
|
82
|
+
This example keeps every default except the `builder` thinking level.
|
|
89
83
|
|
|
90
84
|
## Phase selection
|
|
91
85
|
|
package/docs/glossary.md
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7
7
|
| `Owner` | The person who approves the `spec`, decides questions and `findings`, and performs the final review. |
|
|
8
8
|
| `Maestro` | The coordinator that prepares the `spec`, runs the agents, and records `owner` decisions. |
|
|
9
|
-
| `Builder` | The agent that implements the approved `spec` and runs
|
|
9
|
+
| `Builder` | The agent that implements the approved `spec` and runs every `GIVEN`, `WHEN`, and `THEN` scenario. |
|
|
10
10
|
| `Verifier` | The independent agent that checks the implementation and reports technical issues. |
|
|
11
11
|
| `Simplifier` | The agent that refactors accepted code for clarity without changing behavior or introducing check failures. |
|
|
12
12
|
| `Spec` | A `specification` for one change: `proposal.md` for motivation and scope, `spec.md` for behavior and `acceptance criteria`, and optional `design.md` for technical decisions. The present documents are approved and frozen together. |
|
|
13
13
|
| `Acceptance criterion` (`AC`) | One required, observable result defined in the `spec`. |
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
14
|
+
| `GIVEN` | The starting conditions of an `acceptance criterion`. |
|
|
15
|
+
| `WHEN` | The action or observation that checks the behavior of an `acceptance criterion`. |
|
|
16
|
+
| `THEN` | The measurable outcome the `WHEN` must produce. |
|
|
17
|
+
| `Assumption` | A detail the request did not specify or a scope boundary taken as given. A false `assumption` during implementation is an `escalation` trigger. |
|
|
16
18
|
| `Artifact` | A saved workflow file, such as a `spec`, or a `handoff` between agents. |
|
|
17
19
|
| `Handoff` | A saved agent report. `Builder` and `verifier` reports include `probe` outcomes. A `simplifier` report includes refactored files, a summary, and notes. |
|
|
18
20
|
| `Observability` | The owner's ability to inspect workflow progress and reported results. |
|
|
@@ -16,8 +16,8 @@ The package supplies all three roles and the tools that save their results:
|
|
|
16
16
|
Each `run` starts with a fresh conversation, not the main session's conversation history. All three agents inherit project instructions, the owner's global `AGENTS.md`,
|
|
17
17
|
and available skills. The global file normally lives at `~/.pi/agent/AGENTS.md`.
|
|
18
18
|
|
|
19
|
-
`Maestro` supplies the `spec` ID, project directory, model, thinking level
|
|
20
|
-
|
|
19
|
+
`Maestro` supplies the `spec` ID, project directory, model, and thinking level. The agents read all approved documents (`proposal.md`, `spec.md`, and `design.md` when
|
|
20
|
+
present) and saved `artifacts` to understand the work. Earlier `artifacts` provide context, not proof.
|
|
21
21
|
|
|
22
22
|
The `owner` does not call agent tools directly. `Maestro` starts the enabled `builder` after `GREEN FLAG` approval. The enabled `verifier` starts after successful
|
|
23
23
|
`builder` completion. The enabled `simplifier` follows the selected preceding phases. Disabled agents are not delegated. See
|
|
@@ -38,8 +38,8 @@ If a `run` fails or returns without a valid saved result, `Maestro` reports the
|
|
|
38
38
|
## External configuration
|
|
39
39
|
|
|
40
40
|
The `pi-subagents` extension must be installed and enabled in Pi when `builder` is enabled. Workflows with only an approved `spec` need no agent availability or ping.
|
|
41
|
-
`Builder`, `verifier`, and `simplifier` model choices
|
|
42
|
-
configuration remains the owner's responsibility.
|
|
41
|
+
`Builder`, `verifier`, and `simplifier` model choices and thinking levels come from [Maestro configuration](configuration.md). `Maestro` does not supply a per-agent
|
|
42
|
+
deadline. Other `pi-subagents` configuration remains the owner's responsibility.
|
|
43
43
|
|
|
44
44
|
Only `maestro_run_simplifier` checks Git readiness, with `git rev-parse --is-inside-work-tree`. If readiness cannot be confirmed, it warns and completes without
|
|
45
45
|
delegation. The `simplifier` uses Git to select all uncommitted code changes. `Maestro` does not change the Git index or create commits or checkpoints.
|
package/docs/workflow.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
The approved `spec` is one document set: `proposal.md` explains motivation and scope, `spec.md` defines behavior and `acceptance criteria`, and optional `design.md`
|
|
6
6
|
records significant technical decisions. The set forms one contract for the change.
|
|
7
7
|
|
|
8
|
-
An `acceptance criterion` contains
|
|
8
|
+
An `acceptance criterion` contains one `GIVEN`, one `WHEN`, one `THEN`, and an example. `GIVEN` and `WHEN` describe the scenario, and `THEN` states the measurable
|
|
9
|
+
outcome.
|
|
9
10
|
|
|
10
11
|
A `handoff` is a saved agent report. `Builder` and `verifier` reports contain `probe` outcomes. The simplifier's report records refactored files, a summary, and notes.
|
|
11
12
|
|
|
@@ -14,13 +15,13 @@ issues.
|
|
|
14
15
|
|
|
15
16
|
## Roles
|
|
16
17
|
|
|
17
|
-
| Role | Responsibility
|
|
18
|
-
| ------------ |
|
|
19
|
-
| `Owner` | Decides requirements, scope, technical decisions, `spec` approval, `escalation` answers, and `finding` decisions. Performs the final review.
|
|
20
|
-
| `Maestro` | Prepares the `spec` with the `owner`, records workflow progress, runs agents, presents issues, and saves `owner` decisions.
|
|
21
|
-
| `Builder` | Implements the approved `spec` and runs every `
|
|
22
|
-
| `Verifier` | Independently runs every `
|
|
23
|
-
| `Simplifier` | Refactors accepted code for clarity without changing behavior or introducing check failures.
|
|
18
|
+
| Role | Responsibility |
|
|
19
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `Owner` | Decides requirements, scope, technical decisions, `spec` approval, `escalation` answers, and `finding` decisions. Performs the final review. |
|
|
21
|
+
| `Maestro` | Prepares the `spec` with the `owner`, records workflow progress, runs agents, presents issues, and saves `owner` decisions. |
|
|
22
|
+
| `Builder` | Implements the approved `spec` and runs every `GIVEN`, `WHEN`, and `THEN` scenario. Reports completion or an `escalation`, including technical problems that prevent completion. |
|
|
23
|
+
| `Verifier` | Independently runs every `GIVEN`, `WHEN`, and `THEN` scenario against the live project files. Reports `findings` without repairing product code. |
|
|
24
|
+
| `Simplifier` | Refactors accepted code for clarity without changing behavior or introducing check failures. |
|
|
24
25
|
|
|
25
26
|
`Builder`, `verifier`, and `simplifier` `runs` are sequential foreground operations. Each starts with a fresh conversation and reads the `spec` and saved `artifacts`.
|
|
26
27
|
|
|
@@ -107,18 +108,18 @@ The default path is `.specs/<spec-id>/workflow.json`. The `spec` directory is [c
|
|
|
107
108
|
|
|
108
109
|
Each new `spec` uses these documents in the same directory:
|
|
109
110
|
|
|
110
|
-
| Document | Content
|
|
111
|
-
| ------------- |
|
|
112
|
-
| `proposal.md` | Problem, goal, scope, and exclusions.
|
|
113
|
-
| `spec.md` | Required behavior, constraints, deltas,
|
|
114
|
-
| `design.md` | Approved technical decisions, brief reasons, relevant contracts, risks, and prototype references.
|
|
111
|
+
| Document | Content | Presence |
|
|
112
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
|
|
113
|
+
| `proposal.md` | Problem, goal, scope, and exclusions. | Always created. |
|
|
114
|
+
| `spec.md` | Required behavior, constraints, deltas, `acceptance criteria` next to their requirements, and `assumptions`. | Always created. |
|
|
115
|
+
| `design.md` | Approved technical decisions, brief reasons, relevant contracts, risks, and prototype references. | Only when significant technical decisions need to be recorded. |
|
|
115
116
|
|
|
116
117
|
## Spec approval
|
|
117
118
|
|
|
118
119
|
For the initial `spec`:
|
|
119
120
|
|
|
120
121
|
1. `Maestro` creates `proposal.md`, `spec.md`, and `workflow.json` in `drafting-spec`, then prepares the documents with the `owner`. It adds `design.md` only when needed.
|
|
121
|
-
2. The `owner` reviews the complete document set: motivation, scope, requirements, `acceptance criteria`, and any approved technical decisions.
|
|
122
|
+
2. The `owner` reviews the complete document set: motivation, scope, requirements, `acceptance criteria`, `assumptions`, and any approved technical decisions.
|
|
122
123
|
3. `Maestro` asks the `owner` to inspect `proposal.md`, `spec.md`, and `design.md` when present and reply `GREEN FLAG` once to approve the entire set and proceed through
|
|
123
124
|
the selected phases.
|
|
124
125
|
4. After that reply, `Maestro` saves `ready-for-builder` and starts the enabled `builder` in the foreground, or saves `candidate-ready` with all agents skipped.
|
|
@@ -152,24 +153,23 @@ continuous across the whole document, including standalone regression criteria,
|
|
|
152
153
|
For example, a change that introduces persistent item titles can describe its requirement and linked `acceptance criterion` in `spec.md` as follows:
|
|
153
154
|
|
|
154
155
|
```markdown
|
|
155
|
-
### REQ1:
|
|
156
|
+
### REQ1: The system MUST persist saved titles
|
|
156
157
|
|
|
157
158
|
Delta: added
|
|
158
159
|
|
|
159
|
-
Saving and reopening an item must preserve its title.
|
|
160
|
-
|
|
161
160
|
Verified by: [AC1](#ac1-saved-title-is-persisted)
|
|
162
161
|
|
|
163
162
|
#### AC1: Saved title is persisted
|
|
164
163
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
- GIVEN a saved item.
|
|
165
|
+
- WHEN its title is changed and saved, then the item is reopened.
|
|
166
|
+
- THEN the saved title is present.
|
|
167
|
+
- Example: Change the title from "Draft" to "Ready", save, and reopen the item. The title is "Ready".
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
A requirement can link to several `acceptance criteria`, and one criterion can verify several requirements (1:N). When an existing test covers the exact starting
|
|
171
|
-
conditions, action, and
|
|
172
|
-
|
|
171
|
+
conditions, action, and outcome, `Verified by` links to the test file relative to `spec.md` and includes the exact scenario name. No duplicate criterion is needed. A
|
|
172
|
+
requirement can reference both criteria and existing tests.
|
|
173
173
|
|
|
174
174
|
Before approval, `Maestro` follows the links and checks that their combined scenarios verify the whole requirement. Missing, partial, or uncertain coverage must be
|
|
175
175
|
resolved with an appropriate criterion or a confirmed existing test. References are updated when criterion headings or test locations change, including during `spec`
|
|
@@ -190,19 +190,27 @@ For example, if saving item descriptions already works, adding title persistence
|
|
|
190
190
|
|
|
191
191
|
### AC2: Existing descriptions remain saved
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
- GIVEN an item with a saved description.
|
|
194
|
+
- WHEN its title is changed and saved, then the item is reopened.
|
|
195
|
+
- THEN the description is unchanged.
|
|
196
|
+
- Example: An item's saved description is "Buy milk". Change its title from "Draft" to "Shopping", save, and reopen it. Its description is still "Buy milk".
|
|
196
197
|
```
|
|
197
198
|
|
|
198
199
|
Both nested and standalone criteria share one continuous AC sequence and are checked by the `builder` and `verifier`. Standalone criteria have the same fields as
|
|
199
200
|
requirement-associated criteria:
|
|
200
201
|
|
|
201
|
-
| Part
|
|
202
|
-
|
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
|
|
|
202
|
+
| Part | Content |
|
|
203
|
+
| --------- | ------------------------------------------------------------------------ |
|
|
204
|
+
| `GIVEN` | Starting conditions. |
|
|
205
|
+
| `WHEN` | The action or observation that checks the behavior. |
|
|
206
|
+
| `THEN` | The measurable outcome the `WHEN` must produce. |
|
|
207
|
+
| `Example` | Concrete starting conditions, an input or action, and the exact outcome. |
|
|
208
|
+
|
|
209
|
+
## Assumptions
|
|
210
|
+
|
|
211
|
+
An `assumption` records a detail the request did not specify or a scope boundary taken as given. `Maestro` writes short `assumptions` in a `## Assumptions` section of
|
|
212
|
+
`spec.md` as a bullet list, without paragraphs. The section is omitted when there are none. An `assumption` that proves false during implementation is an `escalation`
|
|
213
|
+
trigger.
|
|
206
214
|
|
|
207
215
|
## Escalations
|
|
208
216
|
|
|
@@ -262,8 +270,8 @@ The revised `spec` controls subsequent work. Earlier `escalations`, `findings`,
|
|
|
262
270
|
|
|
263
271
|
## Verification boundary
|
|
264
272
|
|
|
265
|
-
The `verifier` can make temporary changes
|
|
266
|
-
it created.
|
|
273
|
+
The `verifier` can make temporary changes to run its checks. Before submitting its `handoff`, it restores the exact original contents of affected files and removes only
|
|
274
|
+
files it created.
|
|
267
275
|
|
|
268
276
|
Cleanup relies on the `verifier`. If cleanup cannot finish safely, the `verifier` stops and reports the remaining changes.
|
|
269
277
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emiliosp/pi-maestro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "A spec-driven multiagent development workflow for Pi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orchestration",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"pi-subagents": "0.78.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@earendil-works/pi-
|
|
70
|
-
"@earendil-works/pi-tui": "1.0
|
|
69
|
+
"@earendil-works/pi-ai": "1.5.0",
|
|
70
|
+
"@earendil-works/pi-tui": "1.5.0",
|
|
71
71
|
"@types/node": "26.6.4",
|
|
72
72
|
"@vitest/coverage-v8": "5.0.3",
|
|
73
73
|
"oxfmt": "0.73.0",
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Used: When Maestro handles builder handoff artifacts.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { StringEnum } from '@earendil-works/pi-ai';
|
|
7
6
|
import { type Static, Type } from 'typebox';
|
|
8
7
|
|
|
9
8
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
@@ -26,7 +25,7 @@ export const PROBE_STATUSES = {
|
|
|
26
25
|
|
|
27
26
|
export type ProbeStatus = (typeof PROBE_STATUSES)[keyof typeof PROBE_STATUSES];
|
|
28
27
|
|
|
29
|
-
const ProbeStatusSchema =
|
|
28
|
+
const ProbeStatusSchema = Type.Enum(PROBE_STATUSES, { type: 'string' });
|
|
30
29
|
|
|
31
30
|
export const BuilderAcceptanceCriterionSchema = Type.Object(
|
|
32
31
|
{
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
import { Value } from 'typebox/value';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
MAX_TIMEOUT_MINUTES,
|
|
10
9
|
MaestroConfigInputSchema,
|
|
11
|
-
MIN_TIMEOUT_MINUTES,
|
|
12
10
|
type PartialMaestroConfig,
|
|
13
11
|
SUPPORTED_CONFIG_VERSION,
|
|
14
12
|
THINKING_LEVELS,
|
|
@@ -34,10 +32,6 @@ const formatError = (error: SchemaValidationError): string => {
|
|
|
34
32
|
return `Invalid thinking level at "${path}". Allowed: ${Object.values(THINKING_LEVELS).join(', ')}.`;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
if (path.endsWith('timeoutMinutes')) {
|
|
38
|
-
return `Invalid timeout at "${path}". Must be an integer between ${MIN_TIMEOUT_MINUTES} and ${MAX_TIMEOUT_MINUTES}.`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
35
|
if (path.endsWith('model')) {
|
|
42
36
|
return `Invalid model at "${path}": expected a string.`;
|
|
43
37
|
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -19,8 +19,6 @@ export const DEFAULT_VERIFIER_THINKING = THINKING_LEVELS.HIGH;
|
|
|
19
19
|
|
|
20
20
|
export const DEFAULT_SIMPLIFIER_MODEL = 'openai-codex/gpt-6.1-sol';
|
|
21
21
|
|
|
22
|
-
export const DEFAULT_TIMEOUT_MINUTES = 60;
|
|
23
|
-
|
|
24
22
|
export const CONFIG_DIRECTORY_NAME = '.pi';
|
|
25
23
|
|
|
26
24
|
export const CONFIG_FILE_NAME = 'maestro.json';
|
|
@@ -34,18 +32,15 @@ export const DEFAULT_CONFIG: MaestroConfig = {
|
|
|
34
32
|
required: true,
|
|
35
33
|
model: DEFAULT_BUILDER_MODEL,
|
|
36
34
|
thinking: DEFAULT_BUILDER_THINKING,
|
|
37
|
-
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
38
35
|
},
|
|
39
36
|
simplifier: {
|
|
40
37
|
required: true,
|
|
41
38
|
model: DEFAULT_SIMPLIFIER_MODEL,
|
|
42
39
|
thinking: THINKING_LEVELS.HIGH,
|
|
43
|
-
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
44
40
|
},
|
|
45
41
|
verifier: {
|
|
46
42
|
required: true,
|
|
47
43
|
model: DEFAULT_VERIFIER_MODEL,
|
|
48
44
|
thinking: DEFAULT_VERIFIER_THINKING,
|
|
49
|
-
timeoutMinutes: DEFAULT_TIMEOUT_MINUTES,
|
|
50
45
|
},
|
|
51
46
|
};
|
package/src/config/schema.ts
CHANGED
|
@@ -25,10 +25,6 @@ export type ThinkingLevel = (typeof THINKING_LEVELS)[keyof typeof THINKING_LEVEL
|
|
|
25
25
|
|
|
26
26
|
export const SUPPORTED_CONFIG_VERSION = '1.0.0';
|
|
27
27
|
|
|
28
|
-
export const MIN_TIMEOUT_MINUTES = 1;
|
|
29
|
-
|
|
30
|
-
export const MAX_TIMEOUT_MINUTES = 1440;
|
|
31
|
-
|
|
32
28
|
export const ThinkingSchema = Type.Union([
|
|
33
29
|
Type.Literal(THINKING_LEVELS.OFF),
|
|
34
30
|
Type.Literal(THINKING_LEVELS.MINIMAL),
|
|
@@ -44,12 +40,6 @@ export const PartialAgentConfigSchema = Type.Object(
|
|
|
44
40
|
required: Type.Optional(Type.Boolean()),
|
|
45
41
|
model: Type.Optional(Type.String()),
|
|
46
42
|
thinking: Type.Optional(ThinkingSchema),
|
|
47
|
-
timeoutMinutes: Type.Optional(
|
|
48
|
-
Type.Integer({
|
|
49
|
-
minimum: MIN_TIMEOUT_MINUTES,
|
|
50
|
-
maximum: MAX_TIMEOUT_MINUTES,
|
|
51
|
-
}),
|
|
52
|
-
),
|
|
53
43
|
},
|
|
54
44
|
{ additionalProperties: false },
|
|
55
45
|
);
|
|
@@ -70,10 +60,6 @@ export const ResolvedAgentConfigSchema = Type.Object(
|
|
|
70
60
|
required: Type.Boolean(),
|
|
71
61
|
model: Type.String(),
|
|
72
62
|
thinking: ThinkingSchema,
|
|
73
|
-
timeoutMinutes: Type.Integer({
|
|
74
|
-
minimum: MIN_TIMEOUT_MINUTES,
|
|
75
|
-
maximum: MAX_TIMEOUT_MINUTES,
|
|
76
|
-
}),
|
|
77
63
|
},
|
|
78
64
|
{ additionalProperties: false },
|
|
79
65
|
);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Own the native session, cancellation, and cleanup for one child execution.
|
|
3
|
+
* Used: By the task executor while opening and closing a fresh SDK conversation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { realpath } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
type AgentSession,
|
|
12
|
+
createAgentSession,
|
|
13
|
+
type CreateAgentSessionOptions,
|
|
14
|
+
SessionManager,
|
|
15
|
+
type ToolDefinition,
|
|
16
|
+
} from '@earendil-works/pi-coding-agent';
|
|
17
|
+
|
|
18
|
+
import { createChildServices } from '#src/delegation/createChildServices.ts';
|
|
19
|
+
|
|
20
|
+
export type SubSessionInput = {
|
|
21
|
+
roleGuidance: string;
|
|
22
|
+
cwd: string;
|
|
23
|
+
model: string;
|
|
24
|
+
thinkingLevel: NonNullable<CreateAgentSessionOptions['thinkingLevel']>;
|
|
25
|
+
additionalTools: ToolDefinition[];
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class SubSession {
|
|
30
|
+
private session?: AgentSession;
|
|
31
|
+
private sessionManager?: SessionManager;
|
|
32
|
+
private abortPromise?: Promise<void>;
|
|
33
|
+
|
|
34
|
+
private readonly input: SubSessionInput;
|
|
35
|
+
|
|
36
|
+
constructor(input: SubSessionInput) {
|
|
37
|
+
this.input = input;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public getSavedFile = (): string | undefined => {
|
|
41
|
+
const path = this.sessionManager?.getSessionFile();
|
|
42
|
+
|
|
43
|
+
return path && existsSync(path) ? path : undefined;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
private abort = (): void => {
|
|
47
|
+
this.abortPromise = this.session?.abort();
|
|
48
|
+
// The cleanup path awaits this promise. Handle rejection immediately as well.
|
|
49
|
+
void this.abortPromise?.catch(() => {});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
public open = async (): Promise<AgentSession> => {
|
|
53
|
+
const cwd = await realpath(this.input.cwd);
|
|
54
|
+
const services = await createChildServices({ cwd, roleGuidance: this.input.roleGuidance });
|
|
55
|
+
|
|
56
|
+
const model = (await services.modelRuntime.getAvailable()).find(
|
|
57
|
+
(entry) => `${entry.provider}/${entry.id}` === this.input.model,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!model) throw new Error(`Model is not available: "${this.input.model}".`);
|
|
61
|
+
|
|
62
|
+
this.sessionManager = SessionManager.create(
|
|
63
|
+
cwd,
|
|
64
|
+
join(SessionManager.create(cwd).getSessionDir(), 'subagents'),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const { session } = await createAgentSession({
|
|
68
|
+
...services,
|
|
69
|
+
sessionManager: this.sessionManager,
|
|
70
|
+
model,
|
|
71
|
+
thinkingLevel: this.input.thinkingLevel,
|
|
72
|
+
customTools: this.input.additionalTools,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.session = session;
|
|
76
|
+
|
|
77
|
+
this.input.signal?.addEventListener('abort', this.abort, { once: true });
|
|
78
|
+
|
|
79
|
+
await this.session.bindExtensions({ mode: 'print' });
|
|
80
|
+
|
|
81
|
+
return this.session;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
public close = async (): Promise<void> => {
|
|
85
|
+
this.input.signal?.removeEventListener('abort', this.abort);
|
|
86
|
+
|
|
87
|
+
if (!this.session) return;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await this.session.abort();
|
|
91
|
+
await this.abortPromise;
|
|
92
|
+
} finally {
|
|
93
|
+
try {
|
|
94
|
+
// AgentSession.dispose() does not emit shutdown. This is the native runtime's
|
|
95
|
+
// shutdown sequence, including extensions such as MCP that own connections.
|
|
96
|
+
await this.session.extensionRunner.emit({ type: 'session_shutdown', reason: 'quit' });
|
|
97
|
+
} finally {
|
|
98
|
+
this.session.dispose();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Preserve task execution failures and their saved native session paths.
|
|
3
|
+
* Used: When standalone SDK execution fails or is interrupted.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type TaskExecutionErrorInput = {
|
|
7
|
+
cause: unknown;
|
|
8
|
+
sessionFile?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class TaskExecutionError extends Error {
|
|
12
|
+
readonly sessionFile?: string;
|
|
13
|
+
|
|
14
|
+
constructor({ cause, sessionFile }: TaskExecutionErrorInput) {
|
|
15
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
16
|
+
this.name =
|
|
17
|
+
cause instanceof Error && cause.name === 'AbortError' ? 'AbortError' : 'TaskExecutionError';
|
|
18
|
+
this.sessionFile = sessionFile;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Discover native Pi resources without loading the main Maestro extension.
|
|
3
|
+
* Used: During preparation of each standalone child execution.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { realpathSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { basename, dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createAgentSessionServices,
|
|
12
|
+
createCodemodeExtension,
|
|
13
|
+
createMcpExtension,
|
|
14
|
+
createToolSearchExtension,
|
|
15
|
+
DefaultPackageManager,
|
|
16
|
+
getAgentDir,
|
|
17
|
+
SettingsManager,
|
|
18
|
+
} from '@earendil-works/pi-coding-agent';
|
|
19
|
+
|
|
20
|
+
const MAIN_EXTENSION = fileURLToPath(new URL('../../extensions/maestro.ts', import.meta.url));
|
|
21
|
+
|
|
22
|
+
type MaestroPackage = { name: '@emiliosp/pi-maestro' };
|
|
23
|
+
|
|
24
|
+
const isMaestroPackage = (value: unknown): value is MaestroPackage =>
|
|
25
|
+
typeof value === 'object' &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
'name' in value &&
|
|
28
|
+
value.name === '@emiliosp/pi-maestro';
|
|
29
|
+
|
|
30
|
+
const isMainExtension = (path: string): boolean => {
|
|
31
|
+
if (path.startsWith('builtin:')) return false;
|
|
32
|
+
|
|
33
|
+
const canonicalPath = realpathSync(path);
|
|
34
|
+
|
|
35
|
+
if (canonicalPath === realpathSync(MAIN_EXTENSION)) return true;
|
|
36
|
+
|
|
37
|
+
// Discovery can find an installed copy rather than the currently executing checkout.
|
|
38
|
+
if (
|
|
39
|
+
basename(canonicalPath) !== 'maestro.ts' ||
|
|
40
|
+
basename(dirname(canonicalPath)) !== 'extensions'
|
|
41
|
+
) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return isMaestroPackage(
|
|
47
|
+
JSON.parse(readFileSync(join(dirname(canonicalPath), '../package.json'), 'utf8')),
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type CreateChildServicesInput = {
|
|
55
|
+
cwd: string;
|
|
56
|
+
roleGuidance: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const createChildServices = async ({ cwd, roleGuidance }: CreateChildServicesInput) => {
|
|
60
|
+
const agentDir = getAgentDir();
|
|
61
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
62
|
+
|
|
63
|
+
const extensionFactories = [
|
|
64
|
+
{ name: 'codemode', factory: createCodemodeExtension(), builtin: true, replaceable: true },
|
|
65
|
+
{ name: 'tool-search', factory: createToolSearchExtension(), builtin: true, replaceable: true },
|
|
66
|
+
{ name: 'mcp', factory: createMcpExtension(), builtin: true, replaceable: true },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const packages = new DefaultPackageManager({
|
|
70
|
+
cwd,
|
|
71
|
+
agentDir,
|
|
72
|
+
settingsManager,
|
|
73
|
+
builtinExtensions: extensionFactories.map(({ name }) => name),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const resources = await packages.resolve(async () => 'error');
|
|
77
|
+
|
|
78
|
+
if (resources.installFailures?.length) {
|
|
79
|
+
throw new Error(resources.installFailures.map(({ message }) => message).join('\n'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// extensionsOverride filters extensions after Pi has loaded and initialized them.
|
|
83
|
+
// It would exclude Maestro's event handlers, but its initialization code would still run.
|
|
84
|
+
// Use additionalExtensionPaths instead to exclude Maestro before loading it,
|
|
85
|
+
// so none of its code executes in the child session.
|
|
86
|
+
const additionalExtensionPaths = resources.extensions
|
|
87
|
+
.filter(({ enabled, path }) => enabled && !isMainExtension(path))
|
|
88
|
+
.map(({ path }) => path);
|
|
89
|
+
|
|
90
|
+
const services = await createAgentSessionServices({
|
|
91
|
+
cwd,
|
|
92
|
+
agentDir,
|
|
93
|
+
settingsManager,
|
|
94
|
+
resourceLoaderOptions: {
|
|
95
|
+
noExtensions: true,
|
|
96
|
+
additionalExtensionPaths,
|
|
97
|
+
extensionFactories,
|
|
98
|
+
appendSystemPromptOverride: (base) => [...base, roleGuidance],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const errors = [
|
|
103
|
+
...services.resourceLoader.getExtensions().errors.map(({ error }) => error),
|
|
104
|
+
...services.diagnostics.filter(({ type }) => type === 'error').map(({ message }) => message),
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
if (errors.length) throw new Error(errors.join('\n'));
|
|
108
|
+
|
|
109
|
+
return services;
|
|
110
|
+
};
|