@emiliosp/pi-maestro 0.18.0 → 0.18.2
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 +1 -1
- package/CHANGELOG.md +10 -0
- package/ROADMAP.md +10 -5
- package/package.json +1 -1
- package/src/delegation/createAgentProgress.ts +60 -0
- package/src/{tools/utils → delegation}/executeRoleTask.ts +46 -8
- package/src/delegation/executeTask.ts +95 -20
- package/src/delegation/{createChildServices.ts → prepareSubagentEnvironment.ts} +7 -2
- package/src/ids/createSpecId.ts +3 -5
- package/src/specs/create.ts +1 -1
- package/src/tools/main/run-builder.ts +3 -2
- package/src/tools/main/run-simplifier.ts +3 -2
- package/src/tools/main/run-verifier.ts +3 -2
- package/src/delegation/SubSessionManager.ts +0 -102
package/ADR.md
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
This file summarizes meaningful changes to the codebase, including behavior, data formats, architecture, and development tools. Git tags identify version boundaries;
|
|
4
4
|
GitHub Releases are not required.
|
|
5
5
|
|
|
6
|
+
## 0.18.2
|
|
7
|
+
|
|
8
|
+
1. The `run` progress for `builder`, `verifier`, and `simplifier` now shows only elapsed time and the latest assistant text. The role name and the active tool no longer
|
|
9
|
+
appear.
|
|
10
|
+
|
|
11
|
+
## 0.18.1
|
|
12
|
+
|
|
13
|
+
1. Running `builder`, `verifier`, and `simplifier` tools now show elapsed time, active tools, and streaming normal assistant text. Elapsed time keeps updating during
|
|
14
|
+
silent work. Pi controls preview and expansion, while intermediate activity stays out of the main model's context.
|
|
15
|
+
|
|
6
16
|
## 0.18.0
|
|
7
17
|
|
|
8
18
|
1. `Builder`, `verifier`, and `simplifier` now run in fresh native Pi SDK sessions. Activation no longer requires a separate agent extension. The `owner` can interrupt
|
package/ROADMAP.md
CHANGED
|
@@ -38,7 +38,7 @@ Completed entries preserve recorded outcomes and evidence, which may describe be
|
|
|
38
38
|
| MAESTRO-FREE-3 | [Run the builder through the Pi SDK](#maestro-free-3-run-the-builder-through-the-pi-sdk) | Completed |
|
|
39
39
|
| MAESTRO-FREE-4 | [Run the verifier through the Pi SDK](#maestro-free-4-run-the-verifier-through-the-pi-sdk) | Completed |
|
|
40
40
|
| MAESTRO-FREE-5 | [Run the simplifier through the Pi SDK](#maestro-free-5-run-the-simplifier-through-the-pi-sdk) | Completed |
|
|
41
|
-
| MAESTRO-FREE-6 | [Show SDK agent activity through Pi](#maestro-free-6-show-sdk-agent-activity-through-pi) |
|
|
41
|
+
| MAESTRO-FREE-6 | [Show SDK agent activity through Pi](#maestro-free-6-show-sdk-agent-activity-through-pi) | Completed |
|
|
42
42
|
| MAESTRO-FREE-7 | [Remove the pi-subagents dependency](#maestro-free-7-remove-the-pi-subagents-dependency) | Completed |
|
|
43
43
|
|
|
44
44
|
## MAESTRO-6: Display specs in a browser
|
|
@@ -176,6 +176,10 @@ Connect the `simplifier` to the SDK executor delivered by [MAESTRO-FREE-PRE-3](#
|
|
|
176
176
|
|
|
177
177
|
## MAESTRO-FREE-6: Show SDK agent activity through Pi
|
|
178
178
|
|
|
179
|
+
Implemented under the [approved specification](.specs/20261106-143021-maestro-free-6-show-sdk-agent-activity-through-pi/spec.md), with `builder` checks passing. `Owner`
|
|
180
|
+
acceptance is pending. Review follow-up moved the shared role executor to `src/delegation/executeRoleTask.ts` and separated event tracking in `trackAgentActivity.ts` from
|
|
181
|
+
UI updates in `createAgentProgress.ts`. The full check passes all 293 tests.
|
|
182
|
+
|
|
179
183
|
### Objective
|
|
180
184
|
|
|
181
185
|
Show activity for SDK-based agent `runs` through Pi's standard `onUpdate` presentation, as decided in
|
|
@@ -192,7 +196,7 @@ Show activity for SDK-based agent `runs` through Pi's standard `onUpdate` presen
|
|
|
192
196
|
## MAESTRO-FREE-7: Remove the pi-subagents dependency
|
|
193
197
|
|
|
194
198
|
Implementation follows the [approved migration specification](.specs/20261106-092601-feat-migrate-builder-verifier-and-simplifier-to-the-pi-sdk/spec.md). Code cleanup is
|
|
195
|
-
included in this migration. Package declarations, lockfile entries, bundling, and `pi.subagents` metadata
|
|
199
|
+
included in this migration. Package declarations, lockfile entries, bundling, and `pi.subagents` metadata are removed.
|
|
196
200
|
|
|
197
201
|
### Objective
|
|
198
202
|
|
|
@@ -221,8 +225,7 @@ Linear marks this item Done, but its description still says final `owner` accept
|
|
|
221
225
|
[specification](.specs/20261030-145712-automatically-exit-maestro-mode-at-workflow-completion/spec.md), and
|
|
222
226
|
[original issue](https://linear.app/emilio-spatola/issue/MAESTRO-26/feat-exit-maestro-mode-automatically-at-workflow-completion).
|
|
223
227
|
|
|
224
|
-
Completed entries include historical records imported from Linear
|
|
225
|
-
final `owner` acceptance.
|
|
228
|
+
Completed entries include historical records imported from Linear.
|
|
226
229
|
|
|
227
230
|
## MAESTRO-FREE-1: Require Pi 1.5.0
|
|
228
231
|
|
|
@@ -261,7 +264,7 @@ The `pi-subagents` integration then in use still received a fixed deadline of 9,
|
|
|
261
264
|
### Implementation and evidence
|
|
262
265
|
|
|
263
266
|
The standalone executor is implemented under `src/delegation/` against the [approved specification](.specs/20261105-212231-implement-internal-pi-sdk-delegation/spec.md).
|
|
264
|
-
|
|
267
|
+
Review follow-up consolidated execution orchestration and session lifecycle in `executeTask`; pure reply interpretation remains in `readTaskResult`. Manual verification
|
|
265
268
|
on Pi 1.5.0 exercised fresh context, native and additional tools, MCP through tool search and codemode, sequential waiting, failure, cancellation, cleanup, and native
|
|
266
269
|
session persistence. See [manual evidence](.specs/20261105-212231-implement-internal-pi-sdk-delegation/handoffs/verifier/V1.json). The three workflow roles still use
|
|
267
270
|
`pi-subagents`. The `owner` confirmed completion. The caller's message is named `prompt`, execution is inlined in `executeTask`, and cancellation comments explain Node.js
|
|
@@ -427,6 +430,8 @@ and [merged PR #34](https://github.com/emilioSp/pi-maestro/pull/34).
|
|
|
427
430
|
|
|
428
431
|
## MAESTRO-13: Check subagent extensions at activation
|
|
429
432
|
|
|
433
|
+
Historical outcome: activation no longer requires the parent `pi-subagents` availability check after the SDK migration. See [changelog](CHANGELOG.md).
|
|
434
|
+
|
|
430
435
|
Require a successful parent `pi-subagents` availability reply before activation.
|
|
431
436
|
|
|
432
437
|
Implemented in `0.9.3`. Activation requires a matching successful version 1 `ping` reply from the parent `pi-subagents` extension within 2000 ms. A failed or invalid
|
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective: Publish elapsed run time and the latest normal assistant text through Pi's progress UI.
|
|
3
|
+
* Used: During a delegated run, on assistant text events and once per second during silence.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AgentToolUpdateCallback } from '@earendil-works/pi-coding-agent';
|
|
7
|
+
|
|
8
|
+
type FormatProgressInput = {
|
|
9
|
+
elapsedSeconds: number;
|
|
10
|
+
assistantText: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const formatProgress = ({ elapsedSeconds, assistantText }: FormatProgressInput): string => {
|
|
14
|
+
const elapsed = `${Math.floor(elapsedSeconds / 60)}m ${elapsedSeconds % 60}s`;
|
|
15
|
+
const recentText = assistantText ? `\n\n${assistantText}` : '';
|
|
16
|
+
|
|
17
|
+
return `Elapsed: ${elapsed}${recentText}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type CreateAgentProgressInput = {
|
|
21
|
+
onUpdate?: AgentToolUpdateCallback;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const createAgentProgress = ({ onUpdate }: CreateAgentProgressInput) => {
|
|
25
|
+
const startedAt = performance.now();
|
|
26
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
27
|
+
let stopped = false;
|
|
28
|
+
let latestText = '';
|
|
29
|
+
|
|
30
|
+
const show = (assistantText: string = latestText): void => {
|
|
31
|
+
if (stopped) return;
|
|
32
|
+
|
|
33
|
+
latestText = assistantText;
|
|
34
|
+
|
|
35
|
+
onUpdate?.({
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: formatProgress({
|
|
40
|
+
elapsedSeconds: Math.floor((performance.now() - startedAt) / 1000),
|
|
41
|
+
assistantText,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
details: undefined,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const startShow = (): void => {
|
|
50
|
+
timer = setInterval(show, 1000);
|
|
51
|
+
show();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const stopShow = (): void => {
|
|
55
|
+
stopped = true;
|
|
56
|
+
clearInterval(timer);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return { startShow, show, stopShow };
|
|
60
|
+
};
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Objective:
|
|
2
|
+
* Objective: Run a bundled SDK role with its handoff tool and UI-only live activity.
|
|
3
3
|
* Used: By the three foreground run tools after preparing their workflow phase.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFile } from 'node:fs/promises';
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AgentSessionEvent,
|
|
10
|
+
AgentToolUpdateCallback,
|
|
11
|
+
ExtensionContext,
|
|
12
|
+
} from '@earendil-works/pi-coding-agent';
|
|
9
13
|
|
|
14
|
+
import { createAgentProgress } from '#src/delegation/createAgentProgress.ts';
|
|
10
15
|
import { executeTask, type ExecuteTaskInput } from '#src/delegation/executeTask.ts';
|
|
11
16
|
import { TaskExecutionError } from '#src/delegation/TaskExecutionError.ts';
|
|
12
17
|
import { recordBuilderHandoffTool } from '#tools/child/record-builder-handoff.ts';
|
|
@@ -20,29 +25,59 @@ const HANDOFF_TOOL_BY_ROLE = {
|
|
|
20
25
|
[WORKFLOW_ROLES.SIMPLIFIER]: recordSimplifierHandoffTool,
|
|
21
26
|
} as const;
|
|
22
27
|
|
|
28
|
+
const readAssistantText = (event: AgentSessionEvent): string => {
|
|
29
|
+
if (event.type !== 'message_update' && event.type !== 'message_end') return '';
|
|
30
|
+
|
|
31
|
+
if (event.message.role !== 'assistant') return '';
|
|
32
|
+
|
|
33
|
+
return event.message.content
|
|
34
|
+
.filter((block) => block.type === 'text')
|
|
35
|
+
.map((block) => block.text)
|
|
36
|
+
.join('\n');
|
|
37
|
+
};
|
|
38
|
+
|
|
23
39
|
type ExecuteRoleTaskInput = Pick<
|
|
24
40
|
ExecuteTaskInput,
|
|
25
41
|
'prompt' | 'cwd' | 'model' | 'thinkingLevel' | 'signal'
|
|
26
42
|
> & {
|
|
27
43
|
role: WorkflowRole;
|
|
28
44
|
ui: ExtensionContext['ui'];
|
|
45
|
+
onUpdate?: AgentToolUpdateCallback;
|
|
29
46
|
};
|
|
30
47
|
|
|
31
48
|
export const executeRoleTask = async ({
|
|
32
49
|
role,
|
|
33
50
|
ui,
|
|
34
|
-
|
|
51
|
+
onUpdate,
|
|
52
|
+
prompt,
|
|
53
|
+
cwd,
|
|
54
|
+
model,
|
|
55
|
+
thinkingLevel,
|
|
56
|
+
signal,
|
|
35
57
|
}: ExecuteRoleTaskInput): Promise<void> => {
|
|
36
|
-
const roleGuidance = await readFile(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
58
|
+
const roleGuidance = await readFile(new URL(`../../agents/${role}.md`, import.meta.url), 'utf8');
|
|
59
|
+
|
|
60
|
+
const agentProgress = createAgentProgress({ onUpdate });
|
|
40
61
|
|
|
41
62
|
try {
|
|
63
|
+
agentProgress.startShow();
|
|
64
|
+
signal?.addEventListener('abort', agentProgress.stopShow, { once: true });
|
|
65
|
+
|
|
66
|
+
if (signal?.aborted) agentProgress.stopShow();
|
|
67
|
+
|
|
42
68
|
const result = await executeTask({
|
|
43
|
-
|
|
69
|
+
prompt,
|
|
70
|
+
cwd,
|
|
71
|
+
model,
|
|
72
|
+
thinkingLevel,
|
|
73
|
+
signal,
|
|
44
74
|
roleGuidance,
|
|
45
75
|
additionalTools: [HANDOFF_TOOL_BY_ROLE[role]],
|
|
76
|
+
onEvent: (event) => {
|
|
77
|
+
const assistantText = readAssistantText(event);
|
|
78
|
+
|
|
79
|
+
if (assistantText) agentProgress.show(assistantText);
|
|
80
|
+
},
|
|
46
81
|
});
|
|
47
82
|
|
|
48
83
|
// Publish before protocol validation so a missing handoff still exposes the transcript.
|
|
@@ -53,5 +88,8 @@ export const executeRoleTask = async ({
|
|
|
53
88
|
}
|
|
54
89
|
|
|
55
90
|
throw error;
|
|
91
|
+
} finally {
|
|
92
|
+
agentProgress.stopShow();
|
|
93
|
+
signal?.removeEventListener('abort', agentProgress.stopShow);
|
|
56
94
|
}
|
|
57
95
|
};
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Objective: Execute one independent task
|
|
2
|
+
* Objective: Execute one independent task and own its complete native Pi session lifecycle.
|
|
3
3
|
* Used: By internal callers that await child executions sequentially, outside workflow routing.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { realpath } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
7
10
|
import {
|
|
8
|
-
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
+
type AgentSession,
|
|
12
|
+
type AgentSessionEvent,
|
|
13
|
+
createAgentSession,
|
|
14
|
+
type CreateAgentSessionOptions,
|
|
15
|
+
SessionManager,
|
|
16
|
+
type ToolDefinition,
|
|
17
|
+
} from '@earendil-works/pi-coding-agent';
|
|
18
|
+
|
|
19
|
+
import { prepareSubagentEnvironment } from '#src/delegation/prepareSubagentEnvironment.ts';
|
|
20
|
+
import { readTaskResult } from '#src/delegation/readTaskResult.ts';
|
|
11
21
|
import { TaskExecutionError } from '#src/delegation/TaskExecutionError.ts';
|
|
12
22
|
|
|
13
|
-
export type ExecuteTaskInput =
|
|
23
|
+
export type ExecuteTaskInput = {
|
|
14
24
|
prompt: string;
|
|
25
|
+
roleGuidance: string;
|
|
26
|
+
cwd: string;
|
|
27
|
+
model: string;
|
|
28
|
+
thinkingLevel: NonNullable<CreateAgentSessionOptions['thinkingLevel']>;
|
|
29
|
+
additionalTools: ToolDefinition[];
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
onEvent?: (event: AgentSessionEvent) => void;
|
|
15
32
|
};
|
|
16
33
|
|
|
17
34
|
export type TaskExecutionResult = {
|
|
@@ -19,38 +36,96 @@ export type TaskExecutionResult = {
|
|
|
19
36
|
sessionFile?: string;
|
|
20
37
|
};
|
|
21
38
|
|
|
39
|
+
const getSavedFile = (sessionManager?: SessionManager): string | undefined => {
|
|
40
|
+
const path = sessionManager?.getSessionFile();
|
|
41
|
+
|
|
42
|
+
return path && existsSync(path) ? path : undefined;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type CloseSessionInput = {
|
|
46
|
+
session?: AgentSession;
|
|
47
|
+
abortPromise?: Promise<void>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const closeSession = async ({ session, abortPromise }: CloseSessionInput): Promise<void> => {
|
|
51
|
+
if (!session) return;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await session.abort();
|
|
55
|
+
await abortPromise;
|
|
56
|
+
} finally {
|
|
57
|
+
try {
|
|
58
|
+
// AgentSession.dispose() does not emit shutdown. Extensions such as MCP need it
|
|
59
|
+
// to release their connections before the session is disposed.
|
|
60
|
+
await session.extensionRunner.emit({ type: 'session_shutdown', reason: 'quit' });
|
|
61
|
+
} finally {
|
|
62
|
+
session.dispose();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
22
67
|
export const executeTask = async ({
|
|
23
68
|
prompt,
|
|
24
69
|
roleGuidance,
|
|
25
|
-
cwd,
|
|
26
|
-
model,
|
|
70
|
+
cwd: workingDirectory,
|
|
71
|
+
model: modelId,
|
|
27
72
|
thinkingLevel,
|
|
28
73
|
additionalTools,
|
|
29
74
|
signal,
|
|
75
|
+
onEvent,
|
|
30
76
|
}: ExecuteTaskInput): Promise<TaskExecutionResult> => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
77
|
+
let session: AgentSession | undefined;
|
|
78
|
+
let sessionManager: SessionManager | undefined;
|
|
79
|
+
let abortPromise: Promise<void> | undefined;
|
|
80
|
+
let unsubscribe: (() => void) | undefined;
|
|
81
|
+
|
|
82
|
+
const abort = (): void => {
|
|
83
|
+
abortPromise = session?.abort();
|
|
84
|
+
// Cleanup awaits this promise. Handle rejection immediately as well.
|
|
85
|
+
void abortPromise?.catch(() => {});
|
|
86
|
+
};
|
|
39
87
|
|
|
40
88
|
try {
|
|
41
89
|
try {
|
|
42
|
-
const
|
|
90
|
+
const cwd = await realpath(workingDirectory);
|
|
91
|
+
const services = await prepareSubagentEnvironment({ cwd, roleGuidance });
|
|
92
|
+
|
|
93
|
+
const model = (await services.modelRuntime.getAvailable()).find(
|
|
94
|
+
(entry) => `${entry.provider}/${entry.id}` === modelId,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (!model) throw new Error(`Model is not available: "${modelId}".`);
|
|
98
|
+
|
|
99
|
+
sessionManager = SessionManager.create(
|
|
100
|
+
cwd,
|
|
101
|
+
join(SessionManager.create(cwd).getSessionDir(), 'subagents'),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
({ session } = await createAgentSession({
|
|
105
|
+
...services,
|
|
106
|
+
sessionManager,
|
|
107
|
+
model,
|
|
108
|
+
thinkingLevel,
|
|
109
|
+
customTools: additionalTools,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
if (onEvent) unsubscribe = session.subscribe(onEvent);
|
|
113
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
114
|
+
|
|
115
|
+
await session.bindExtensions({ mode: 'print' });
|
|
43
116
|
await session.prompt(prompt, { expandPromptTemplates: false });
|
|
44
117
|
|
|
45
118
|
const text = readTaskResult(
|
|
46
119
|
session.messages.findLast((message) => message.role === 'assistant'),
|
|
47
120
|
);
|
|
48
121
|
|
|
49
|
-
return { text, sessionFile:
|
|
122
|
+
return { text, sessionFile: getSavedFile(sessionManager) };
|
|
50
123
|
} finally {
|
|
51
|
-
|
|
124
|
+
unsubscribe?.();
|
|
125
|
+
signal?.removeEventListener('abort', abort);
|
|
126
|
+
await closeSession({ session, abortPromise });
|
|
52
127
|
}
|
|
53
128
|
} catch (cause) {
|
|
54
|
-
throw new TaskExecutionError({ cause, sessionFile:
|
|
129
|
+
throw new TaskExecutionError({ cause, sessionFile: getSavedFile(sessionManager) });
|
|
55
130
|
}
|
|
56
131
|
};
|
|
@@ -51,12 +51,15 @@ const isMainExtension = (path: string): boolean => {
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
type
|
|
54
|
+
type PrepareSubagentEnvironmentInput = {
|
|
55
55
|
cwd: string;
|
|
56
56
|
roleGuidance: string;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
export const
|
|
59
|
+
export const prepareSubagentEnvironment = async ({
|
|
60
|
+
cwd,
|
|
61
|
+
roleGuidance,
|
|
62
|
+
}: PrepareSubagentEnvironmentInput) => {
|
|
60
63
|
const agentDir = getAgentDir();
|
|
61
64
|
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
62
65
|
|
|
@@ -92,6 +95,8 @@ export const createChildServices = async ({ cwd, roleGuidance }: CreateChildServ
|
|
|
92
95
|
agentDir,
|
|
93
96
|
settingsManager,
|
|
94
97
|
resourceLoaderOptions: {
|
|
98
|
+
// Disable automatic extension discovery; load only the filtered paths below
|
|
99
|
+
// and the built-in factories, so discovery cannot add Maestro back.
|
|
95
100
|
noExtensions: true,
|
|
96
101
|
additionalExtensionPaths,
|
|
97
102
|
extensionFactories,
|
package/src/ids/createSpecId.ts
CHANGED
|
@@ -35,10 +35,8 @@ const normalizeSlug = (value: string): string => {
|
|
|
35
35
|
|
|
36
36
|
type CreateSpecIdInput = {
|
|
37
37
|
title: string;
|
|
38
|
-
instant
|
|
38
|
+
instant: Temporal.Instant;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
export const createSpecId = ({
|
|
42
|
-
title
|
|
43
|
-
instant = Temporal.Now.instant(),
|
|
44
|
-
}: CreateSpecIdInput): string => `${formatUtcTimestamp(instant)}-${normalizeSlug(title)}`;
|
|
41
|
+
export const createSpecId = ({ title, instant }: CreateSpecIdInput): string =>
|
|
42
|
+
`${formatUtcTimestamp(instant)}-${normalizeSlug(title)}`;
|
package/src/specs/create.ts
CHANGED
|
@@ -44,7 +44,7 @@ export const createSpec = async ({
|
|
|
44
44
|
throw new Error(`Workflow ${activeWorkflowSpecId} is already active.`);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const specId = createSpecId({ title, instant });
|
|
47
|
+
const specId = createSpecId({ title, instant: instant ?? Temporal.Now.instant() });
|
|
48
48
|
const specPath = paths.getSpecPath(specId);
|
|
49
49
|
const proposalFilePath = paths.getProposalFilePath(specId);
|
|
50
50
|
const specFilePath = paths.getSpecFilePath(specId);
|
|
@@ -15,7 +15,7 @@ import type { MaestroConfig } from '#config/schema.ts';
|
|
|
15
15
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
16
16
|
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
17
17
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
18
|
-
import { executeRoleTask } from '#
|
|
18
|
+
import { executeRoleTask } from '#src/delegation/executeRoleTask.ts';
|
|
19
19
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
20
20
|
import { prepareBuilderRun } from '#workflow/builder/prepareBuilderRun.ts';
|
|
21
21
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
@@ -155,7 +155,7 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
155
155
|
label: RUN_BUILDER_TOOL.LABEL,
|
|
156
156
|
description: RUN_BUILDER_TOOL.DESCRIPTION,
|
|
157
157
|
parameters: RunBuilderToolParameters,
|
|
158
|
-
async execute(_toolCallId, { specId }, signal,
|
|
158
|
+
async execute(_toolCallId, { specId }, signal, onUpdate, context) {
|
|
159
159
|
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
160
160
|
|
|
161
161
|
const { projectRoot, state: builderRunningState } = await prepareBuilderRun({
|
|
@@ -174,6 +174,7 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
174
174
|
thinkingLevel: config.builder.thinking,
|
|
175
175
|
signal,
|
|
176
176
|
ui: context.ui,
|
|
177
|
+
onUpdate,
|
|
177
178
|
});
|
|
178
179
|
|
|
179
180
|
const result = await readBuilderResult({ paths, specId, config, builderRunningState });
|
|
@@ -10,7 +10,7 @@ import { readSimplifierHandoff } from '#artifacts/simplifier-handoff/readSimplif
|
|
|
10
10
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
11
11
|
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
12
12
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
13
|
-
import { executeRoleTask } from '#
|
|
13
|
+
import { executeRoleTask } from '#src/delegation/executeRoleTask.ts';
|
|
14
14
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
15
15
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
16
16
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -87,7 +87,7 @@ export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
|
|
|
87
87
|
label: RUN_SIMPLIFIER_TOOL.LABEL,
|
|
88
88
|
description: RUN_SIMPLIFIER_TOOL.DESCRIPTION,
|
|
89
89
|
parameters: RunSimplifierToolParameters,
|
|
90
|
-
async execute(_toolCallId, { specId }, signal,
|
|
90
|
+
async execute(_toolCallId, { specId }, signal, onUpdate, context) {
|
|
91
91
|
const { paths, config, projectRoot } = await resolveToolRunContext(context.cwd);
|
|
92
92
|
|
|
93
93
|
const workflowPath = paths.getWorkflowPath(specId);
|
|
@@ -136,6 +136,7 @@ export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
|
|
|
136
136
|
thinkingLevel: config.simplifier.thinking,
|
|
137
137
|
signal,
|
|
138
138
|
ui: context.ui,
|
|
139
|
+
onUpdate,
|
|
139
140
|
});
|
|
140
141
|
|
|
141
142
|
const result = await readSimplifierResult({ paths, specId });
|
|
@@ -12,7 +12,7 @@ import type { MaestroConfig } from '#config/schema.ts';
|
|
|
12
12
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
13
13
|
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
14
14
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
15
|
-
import { executeRoleTask } from '#
|
|
15
|
+
import { executeRoleTask } from '#src/delegation/executeRoleTask.ts';
|
|
16
16
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
17
17
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
18
18
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -124,7 +124,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
124
124
|
label: RUN_VERIFIER_TOOL.LABEL,
|
|
125
125
|
description: RUN_VERIFIER_TOOL.DESCRIPTION,
|
|
126
126
|
parameters: RunVerifierToolParameters,
|
|
127
|
-
async execute(_toolCallId, { specId }, signal,
|
|
127
|
+
async execute(_toolCallId, { specId }, signal, onUpdate, context) {
|
|
128
128
|
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
129
129
|
|
|
130
130
|
const run = await prepareVerifierRun({ config, paths, specId });
|
|
@@ -138,6 +138,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
138
138
|
thinkingLevel: config.verifier.thinking,
|
|
139
139
|
signal,
|
|
140
140
|
ui: context.ui,
|
|
141
|
+
onUpdate,
|
|
141
142
|
});
|
|
142
143
|
|
|
143
144
|
const result = await readVerifierResult({ paths, specId, config, runningState: run.state });
|
|
@@ -1,102 +0,0 @@
|
|
|
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 SubSessionManagerInput = {
|
|
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 SubSessionManager {
|
|
30
|
-
private session?: AgentSession;
|
|
31
|
-
private sessionManager?: SessionManager;
|
|
32
|
-
private abortPromise?: Promise<void>;
|
|
33
|
-
|
|
34
|
-
private readonly input: SubSessionManagerInput;
|
|
35
|
-
|
|
36
|
-
constructor(input: SubSessionManagerInput) {
|
|
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, // here child tool registration happens through SDK
|
|
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
|
-
}
|