@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.
@@ -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 | Required | Default | Rules |
41
- | --------------------------- | ------------------------: | --------------------------- | --------------------------------------------------------------- |
42
- | `version` | Yes, when the file exists | `1.0.0` | Configuration format version. Only `1.0.0` is supported. |
43
- | `specDirectory` | No | `.specs` | Relative path below the project root. |
44
- | `builder` | No | `Builder` defaults | Overrides supported `builder` fields. |
45
- | `builder.required` | No | `true` | Boolean selecting the `builder` phase. |
46
- | `builder.model` | No | `openai-codex/gpt-5.6-luna` | Model string passed unchanged to Pi. |
47
- | `builder.thinking` | No | `max` | One supported thinking level. |
48
- | `builder.timeoutMinutes` | No | `60` | Integer from `1` to `1440`. Applies to each `builder` `run`. |
49
- | `verifier` | No | `Verifier` defaults | Overrides supported `verifier` fields. |
50
- | `verifier.required` | No | `true` | Boolean selecting the `verifier` phase. |
51
- | `verifier.model` | No | `openai-codex/gpt-6.1-sol` | Model string passed unchanged to Pi. |
52
- | `verifier.thinking` | No | `high` | One supported thinking level. |
53
- | `verifier.timeoutMinutes` | No | `60` | Integer from `1` to `1440`. Applies to each `verifier` `run`. |
54
- | `simplifier` | No | `Simplifier` defaults | Overrides supported `simplifier` fields. |
55
- | `simplifier.required` | No | `true` | Boolean selecting the `simplifier` phase. |
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
- "timeoutMinutes": 90
77
+ "thinking": "high"
84
78
  }
85
79
  }
86
80
  ```
87
81
 
88
- This example keeps every default except the `builder` timeout.
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 its `probes`. |
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
- | `Probe` | A scenario used to test an `acceptance criterion`. Reports record the actual command or procedure and its outcome. |
15
- | `Expected result` | The observable result that a `probe` must produce. |
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, and timeout. The agents read all approved documents (`proposal.md`, `spec.md`, and `design.md`
20
- when present) and saved `artifacts` to understand the work. Earlier `artifacts` provide context, not proof.
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, thinking levels, and timeouts come from [Maestro configuration](configuration.md). Other `pi-subagents`
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 a `probe`, an `expected result`, and an example. A `probe` is a scenario used to test behavior.
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 `probe`. Reports completion or an `escalation`, including technical problems that prevent completion. |
22
- | `Verifier` | Independently runs every `probe` against the live project files. Reports `findings` without repairing product code. |
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 | Presence |
111
- | ------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
112
- | `proposal.md` | Problem, goal, scope, and exclusions. | Always created. |
113
- | `spec.md` | Required behavior, constraints, deltas, and `acceptance criteria` next to their requirements. | Always created. |
114
- | `design.md` | Approved technical decisions, brief reasons, relevant contracts, risks, and prototype references. | Only when significant technical decisions need to be recorded. |
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: Persist saved titles
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
- 1. Probe: Save a change to an item's title, then reopen the item.
166
- 2. Expected result: The saved title is still present.
167
- 3. Example: Change the title from "Draft" to "Ready", save, and reopen the item. The title is "Ready".
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 `expected result`, `Verified by` links to the test file relative to `spec.md` and includes the exact scenario name. No duplicate criterion is
172
- needed. A requirement can reference both criteria and existing tests.
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
- 1. Probe: Change and save the title of an item with a saved description, then reopen it.
194
- 2. Expected result: The description is unchanged.
195
- 3. 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".
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 | Content |
202
- | ----------------- | ---------------------------------------------------------------------------------- |
203
- | `Probe` | Starting conditions and the action or observation that checks the behavior. |
204
- | `Expected result` | The measurable result the `probe` observes. |
205
- | Example | Concrete starting conditions, an input or action, and the exact `expected result`. |
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 for `probes`. Before submitting its `handoff`, it restores the exact original contents of affected files and removes only files
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.15.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-agent-core": "1.0.3",
70
- "@earendil-works/pi-tui": "1.0.3",
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 = StringEnum(Object.values(PROBE_STATUSES));
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
  }
@@ -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
  };
@@ -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
+ };