@emiliosp/pi-maestro 0.9.2 → 0.9.3

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/ROADMAP.md CHANGED
@@ -76,6 +76,25 @@ All 5 `acceptance criteria` passed through independent inspection of the written
76
76
 
77
77
  ## Now
78
78
 
79
+ ### R-06: Check subagent extensions at activation
80
+
81
+ #### Objective
82
+
83
+ Make `pi-subagents` availability problems visible before a `builder` or `verifier` `run` starts.
84
+
85
+ #### Implementation status
86
+
87
+ Activation now requires a successful version 1 `ping` reply from the parent `pi-subagents` extension within 2000 ms. The check does not start an agent. All 7 `acceptance criteria` passed with no `verifier` `findings`; the workflow reached `candidate-ready`.
88
+
89
+ A later integration test requested by the `owner` directly checks the environment check's failed-reply error. This test addition is outside the completed independent verification.
90
+
91
+ [Specification](.specs/20261013-194407-check-pi-subagents-availability-with-ping-at-activation/spec.md).
92
+
93
+ #### Completed when
94
+
95
+ Unavailable `pi-subagents` prevents activation. The error identifies the problem.
96
+
97
+
79
98
  ## Next
80
99
 
81
100
  ## Later
@@ -104,16 +123,6 @@ When criteria are removed during `spec` preparation or revision, renumber the re
104
123
 
105
124
  The `spec` and its current references use consistent numbering.
106
125
 
107
- ### R-06: Check subagent extensions at activation
108
-
109
- #### Objective
110
-
111
- Make extension availability problems visible before a `builder` or `verifier` `run` starts. Activation already checks agent launch requirements, so first identify any missing extension checks.
112
-
113
- #### Completed when
114
-
115
- Unavailable required subagent extensions prevent activation. The error identifies the problem.
116
-
117
126
  ### R-07: Guide initial configuration
118
127
 
119
128
  #### Objective
@@ -83,7 +83,7 @@ export default (pi: ExtensionAPI): void => {
83
83
  }
84
84
 
85
85
  try {
86
- await assertEnvironment(context);
86
+ await assertEnvironment({ context, piEventsBus: pi.events });
87
87
  const config = await loadConfiguration(context.cwd);
88
88
 
89
89
  maestroSessionState.activate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emiliosp/pi-maestro",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "A spec-driven multiagent development workflow for Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -3,12 +3,26 @@
3
3
  * Used: When the owner activates Maestro mode.
4
4
  */
5
5
 
6
+ import { randomUUID } from 'node:crypto';
6
7
  import { realpath } from 'node:fs/promises';
7
- import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
8
+ import type {
9
+ ExtensionAPI,
10
+ ExtensionContext,
11
+ } from '@earendil-works/pi-coding-agent';
8
12
  import { resolveSubagentLaunchContract } from 'pi-subagents/preflight';
9
13
  import { loadConfiguration } from '#config/loadConfiguration.ts';
10
14
  import { AGENTS } from '#config/schema.ts';
11
15
 
16
+ export const SUBAGENT_PING = {
17
+ REQUEST_EVENT: 'subagents:rpc:v1:request',
18
+ REPLY_EVENT_PREFIX: 'subagents:rpc:v1:reply:',
19
+ VERSION: 1,
20
+ METHOD: 'ping',
21
+ TIMEOUT_MS: 2000,
22
+ INSTALLATION_ERROR:
23
+ 'Maestro flow requires pi-subagents installed in the Pi environment.',
24
+ } as const;
25
+
12
26
  const getErrorMessage = (error: unknown): string =>
13
27
  error instanceof Error ? error.message : String(error);
14
28
 
@@ -76,9 +90,81 @@ async function assertAgentsAvailable(projectRoot: string): Promise<void> {
76
90
  }
77
91
  }
78
92
 
79
- export async function assertEnvironment(
80
- context: ExtensionContext,
93
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
94
+ typeof value === 'object' && value !== null && !Array.isArray(value);
95
+
96
+ const isReplyWithRequestId = (
97
+ reply: unknown,
98
+ ): reply is Record<string, unknown> & { requestId: string } =>
99
+ isRecord(reply) && typeof reply.requestId === 'string';
100
+
101
+ type SuccessfulPingReply = {
102
+ version: typeof SUBAGENT_PING.VERSION;
103
+ requestId: string;
104
+ method: typeof SUBAGENT_PING.METHOD;
105
+ success: true;
106
+ data: { version: typeof SUBAGENT_PING.VERSION };
107
+ };
108
+
109
+ function assertSuccessfulPingReply(
110
+ reply: unknown,
111
+ ): asserts reply is SuccessfulPingReply {
112
+ if (
113
+ !isReplyWithRequestId(reply) ||
114
+ reply.version !== SUBAGENT_PING.VERSION ||
115
+ reply.method !== SUBAGENT_PING.METHOD ||
116
+ reply.success !== true ||
117
+ !isRecord(reply.data) ||
118
+ reply.data.version !== SUBAGENT_PING.VERSION
119
+ ) {
120
+ throw new Error('Unsuccessful or invalid pi-subagents ping reply.');
121
+ }
122
+ }
123
+
124
+ async function assertSubagentsRespond(
125
+ piEventsBus: ExtensionAPI['events'],
81
126
  ): Promise<void> {
127
+ const requestId = randomUUID();
128
+ const { promise, resolve, reject } = Promise.withResolvers<unknown>();
129
+
130
+ const unsubscribe = piEventsBus.on(
131
+ `${SUBAGENT_PING.REPLY_EVENT_PREFIX}${requestId}`,
132
+ (reply) => {
133
+ if (isReplyWithRequestId(reply) && reply.requestId !== requestId) {
134
+ return;
135
+ }
136
+
137
+ resolve(reply);
138
+ },
139
+ );
140
+
141
+ const timeout = setTimeout(
142
+ () => reject(new Error('Pi-subagents ping timed out after 2000 ms.')),
143
+ SUBAGENT_PING.TIMEOUT_MS,
144
+ );
145
+
146
+ try {
147
+ piEventsBus.emit(SUBAGENT_PING.REQUEST_EVENT, {
148
+ version: SUBAGENT_PING.VERSION,
149
+ requestId,
150
+ method: SUBAGENT_PING.METHOD,
151
+ });
152
+ assertSuccessfulPingReply(await promise);
153
+ } finally {
154
+ clearTimeout(timeout);
155
+ unsubscribe();
156
+ }
157
+ }
158
+
159
+ type AssertEnvironmentInput = {
160
+ context: ExtensionContext;
161
+ piEventsBus: ExtensionAPI['events'];
162
+ };
163
+
164
+ export async function assertEnvironment({
165
+ context,
166
+ piEventsBus,
167
+ }: AssertEnvironmentInput): Promise<void> {
82
168
  const projectRoot = await getProjectRoot(context);
83
169
 
84
170
  try {
@@ -92,4 +178,13 @@ export async function assertEnvironment(
92
178
  const config = await getActivationConfiguration(projectRoot);
93
179
 
94
180
  assertModelsAvailable({ context, config });
181
+
182
+ try {
183
+ await assertSubagentsRespond(piEventsBus);
184
+ } catch (error) {
185
+ throw new Error(
186
+ `${SUBAGENT_PING.INSTALLATION_ERROR} ${getErrorMessage(error)}`,
187
+ { cause: error },
188
+ );
189
+ }
95
190
  }