@emiliosp/pi-maestro 0.14.0 → 0.16.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 +155 -0
- package/CHANGELOG.md +19 -0
- package/README.md +33 -19
- package/ROADMAP.md +176 -40
- package/agents/simplifier.md +2 -1
- package/agents/verifier.md +2 -1
- package/docs/configuration.md +49 -26
- package/docs/glossary.md +1 -1
- package/docs/subagent-integration.md +8 -6
- package/docs/workflow.md +32 -23
- package/extensions/maestro.ts +11 -13
- 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 +3 -5
- package/src/config/schema.ts +2 -14
- package/src/maestro/checks/assertEnvironment.ts +24 -9
- package/src/maestro/instructions/getMaestroInstructions.ts +19 -11
- package/src/tools/child/record-builder-handoff.ts +2 -6
- package/src/tools/child/record-simplifier-handoff.ts +2 -1
- package/src/tools/child/record-verifier-handoff.ts +2 -6
- package/src/tools/child/utils/resolveWorkflowContext.ts +2 -2
- package/src/tools/main/mark-spec-ready.ts +8 -3
- package/src/tools/main/resolve-escalations.ts +2 -1
- package/src/tools/main/resolve-findings.ts +9 -10
- package/src/tools/main/run-builder.ts +47 -15
- package/src/tools/main/run-simplifier.ts +3 -3
- package/src/tools/main/run-verifier.ts +19 -8
- package/src/tools/utils/pi-subagent-delegation.ts +3 -0
- package/src/workflow/builder/completeBuilderPass.ts +4 -0
- package/src/workflow/builder/prepareBuilderRun.ts +7 -1
- package/src/workflow/builder/resolveEscalations.ts +4 -0
- package/src/workflow/simplifier/completeSimplifierPass.ts +4 -0
- package/src/workflow/spec/markSpecReady.ts +4 -0
- package/src/workflow/transitions.ts +28 -7
- package/src/workflow/verifier/completeVerifierPass.ts +4 -0
- package/src/workflow/verifier/prepareVerifierRun.ts +7 -1
- package/src/workflow/verifier/resolveFindings.ts +4 -0
|
@@ -44,12 +44,13 @@ export const registerRecordSimplifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
44
44
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
45
45
|
assertSimplifierSubmission(params);
|
|
46
46
|
|
|
47
|
-
const { paths } = await resolveWorkflowContext({
|
|
47
|
+
const { paths, config } = await resolveWorkflowContext({
|
|
48
48
|
cwd: context.cwd,
|
|
49
49
|
specId: params.specId,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
const completed = await completeSimplifierPass({
|
|
53
|
+
config,
|
|
53
54
|
paths,
|
|
54
55
|
specId: params.specId,
|
|
55
56
|
handoff: { ...params, version: SIMPLIFIER_HANDOFF_VERSION },
|
|
@@ -53,7 +53,7 @@ export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
53
53
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
54
54
|
const { specId, ...submission } = params;
|
|
55
55
|
|
|
56
|
-
const { paths } = await resolveWorkflowContext({
|
|
56
|
+
const { paths, config } = await resolveWorkflowContext({
|
|
57
57
|
cwd: context.cwd,
|
|
58
58
|
specId,
|
|
59
59
|
});
|
|
@@ -69,11 +69,7 @@ export const registerRecordVerifierHandoffTool = (pi: ExtensionAPI): void => {
|
|
|
69
69
|
notes: submission.notes,
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const completed = await completeVerifierPass({
|
|
73
|
-
paths,
|
|
74
|
-
specId,
|
|
75
|
-
handoff,
|
|
76
|
-
});
|
|
72
|
+
const completed = await completeVerifierPass({ config, paths, specId, handoff });
|
|
77
73
|
|
|
78
74
|
return {
|
|
79
75
|
content: [
|
|
@@ -16,7 +16,7 @@ export const resolveWorkflowContext = async ({ cwd, specId }: ResolveWorkflowCon
|
|
|
16
16
|
throw new Error(`Invalid spec ID: "${specId}".`);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { paths, projectRoot } = await resolveToolRunContext(cwd);
|
|
19
|
+
const { paths, projectRoot, config } = await resolveToolRunContext(cwd);
|
|
20
20
|
|
|
21
|
-
return { paths, specId, projectRoot };
|
|
21
|
+
return { paths, specId, projectRoot, config };
|
|
22
22
|
};
|
|
@@ -10,12 +10,13 @@ import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
|
10
10
|
import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
|
|
11
11
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
12
12
|
import { markSpecReady } from '#workflow/spec/markSpecReady.ts';
|
|
13
|
+
import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
13
14
|
|
|
14
15
|
export const MARK_SPEC_READY_TOOL = {
|
|
15
16
|
NAME: 'maestro_mark_spec_ready',
|
|
16
17
|
LABEL: 'Mark Spec Ready',
|
|
17
18
|
DESCRIPTION:
|
|
18
|
-
'Approve proposal.md and spec.md together with design.md if present, only from drafting-spec after the owner replies GREEN FLAG. Freeze all present documents and move to
|
|
19
|
+
'Approve proposal.md and spec.md together with design.md if present, only from drafting-spec after the owner replies GREEN FLAG. Freeze all present documents and move to the configured next phase. Only the owner reply GREEN FLAG authorizes approval. Run the builder only if the returned phase is ready-for-builder; otherwise summarize the approved spec and skipped agents.',
|
|
19
20
|
} as const;
|
|
20
21
|
|
|
21
22
|
const MarkSpecReadyToolParameters = Type.Object(
|
|
@@ -32,9 +33,10 @@ export const registerMarkSpecReadyTool = (pi: ExtensionAPI): void => {
|
|
|
32
33
|
description: MARK_SPEC_READY_TOOL.DESCRIPTION,
|
|
33
34
|
parameters: MarkSpecReadyToolParameters,
|
|
34
35
|
async execute(_toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
35
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
36
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
36
37
|
|
|
37
38
|
const state = await markSpecReady({
|
|
39
|
+
config,
|
|
38
40
|
paths,
|
|
39
41
|
specId,
|
|
40
42
|
activeWorkflowSpecId: maestroSessionState.getActiveSpecId(),
|
|
@@ -44,7 +46,10 @@ export const registerMarkSpecReadyTool = (pi: ExtensionAPI): void => {
|
|
|
44
46
|
content: [
|
|
45
47
|
{
|
|
46
48
|
type: 'text',
|
|
47
|
-
text:
|
|
49
|
+
text:
|
|
50
|
+
state.phase === WORKFLOW_PHASES.CANDIDATE_READY
|
|
51
|
+
? `Spec ${state.specId} is approved. The workflow is candidate-ready. Builder, verifier, and simplifier skipped by configuration. No implementation or verification was performed.`
|
|
52
|
+
: `Spec ${state.specId} is approved and ready-for-builder. Call maestro_run_builder now. No separate owner start request is needed.`,
|
|
48
53
|
},
|
|
49
54
|
],
|
|
50
55
|
details: state,
|
|
@@ -47,9 +47,10 @@ export const registerResolveEscalationsTool = (pi: ExtensionAPI): void => {
|
|
|
47
47
|
parameters: ResolveEscalationsToolParameters,
|
|
48
48
|
async execute(_toolCallId, params, _signal, _onUpdate, context) {
|
|
49
49
|
const { specId, specChangeRequired, decisions } = params;
|
|
50
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
50
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
51
51
|
|
|
52
52
|
const resolved = await resolveEscalations({
|
|
53
|
+
config,
|
|
53
54
|
paths,
|
|
54
55
|
specId,
|
|
55
56
|
specChangeRequired,
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Used: When the owner invokes the resolve-findings tool.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { StringEnum } from '@earendil-works/pi-ai';
|
|
7
6
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
8
7
|
import { Type } from 'typebox';
|
|
9
8
|
|
|
@@ -27,7 +26,7 @@ const FindingDecisionFields = {
|
|
|
27
26
|
const RejectFindingDecisionSchema = Type.Object(
|
|
28
27
|
{
|
|
29
28
|
...FindingDecisionFields,
|
|
30
|
-
decision:
|
|
29
|
+
decision: Type.Enum([FINDING_DECISIONS.REJECT] as const, { type: 'string' }),
|
|
31
30
|
reason: Type.String({ minLength: 1 }),
|
|
32
31
|
},
|
|
33
32
|
{
|
|
@@ -38,7 +37,7 @@ const RejectFindingDecisionSchema = Type.Object(
|
|
|
38
37
|
const FixCodeFindingDecisionSchema = Type.Object(
|
|
39
38
|
{
|
|
40
39
|
...FindingDecisionFields,
|
|
41
|
-
decision:
|
|
40
|
+
decision: Type.Enum([FINDING_DECISIONS.FIX_CODE] as const, { type: 'string' }),
|
|
42
41
|
},
|
|
43
42
|
{
|
|
44
43
|
additionalProperties: false,
|
|
@@ -48,7 +47,7 @@ const FixCodeFindingDecisionSchema = Type.Object(
|
|
|
48
47
|
const ReviseSpecFindingDecisionSchema = Type.Object(
|
|
49
48
|
{
|
|
50
49
|
...FindingDecisionFields,
|
|
51
|
-
decision:
|
|
50
|
+
decision: Type.Enum([FINDING_DECISIONS.REVISE_SPEC] as const, { type: 'string' }),
|
|
52
51
|
reason: Type.String({ minLength: 1, pattern: '\\S' }),
|
|
53
52
|
specChange: Type.String({ minLength: 1, pattern: '\\S' }),
|
|
54
53
|
},
|
|
@@ -96,6 +95,10 @@ const formatResolution = (details: FindingResolutionDetails): string => {
|
|
|
96
95
|
return `${summary} The workflow is ready-for-builder. Run the builder separately.`;
|
|
97
96
|
}
|
|
98
97
|
|
|
98
|
+
if (details.phase === WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
99
|
+
return `${summary} The workflow is candidate-ready. Simplifier skipped by configuration.`;
|
|
100
|
+
}
|
|
101
|
+
|
|
99
102
|
return `${summary} The workflow is simplifier-running. Call maestro_run_simplifier next with this specId, without another approval or verifier pass.`;
|
|
100
103
|
};
|
|
101
104
|
|
|
@@ -106,13 +109,9 @@ export const registerResolveFindingsTool = (pi: ExtensionAPI): void => {
|
|
|
106
109
|
description: RESOLVE_FINDINGS_TOOL.DESCRIPTION,
|
|
107
110
|
parameters: ResolveFindingsToolParameters,
|
|
108
111
|
async execute(_toolCallId, { specId, decisions }, _signal, _onUpdate, context) {
|
|
109
|
-
const { paths } = await resolveToolRunContext(context.cwd);
|
|
112
|
+
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
110
113
|
|
|
111
|
-
const resolved = await resolveFindings({
|
|
112
|
-
paths,
|
|
113
|
-
specId,
|
|
114
|
-
decisions,
|
|
115
|
-
});
|
|
114
|
+
const resolved = await resolveFindings({ config, paths, specId, decisions });
|
|
116
115
|
|
|
117
116
|
const details: FindingResolutionDetails = {
|
|
118
117
|
specId: resolved.state.specId,
|
|
@@ -14,19 +14,21 @@ import {
|
|
|
14
14
|
BUILDER_HANDOFF_STATUSES,
|
|
15
15
|
type BuilderHandoff,
|
|
16
16
|
} from '#artifacts/builder-handoff/schema.ts';
|
|
17
|
-
import { AGENTS } from '#config/schema.ts';
|
|
17
|
+
import { AGENTS, type MaestroConfig } from '#config/schema.ts';
|
|
18
18
|
import { SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
19
19
|
import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
20
20
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
21
21
|
import {
|
|
22
22
|
assertDelegationResponse,
|
|
23
|
+
SUBAGENT_TIMEOUT_MS,
|
|
23
24
|
waitForDelegationResponse,
|
|
24
25
|
} from '#tools/utils/pi-subagent-delegation.ts';
|
|
25
26
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
26
27
|
import { prepareBuilderRun } from '#workflow/builder/prepareBuilderRun.ts';
|
|
27
28
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
28
29
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
29
|
-
import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
30
|
+
import { WORKFLOW_PHASES, WORKFLOW_EVENTS, type WorkflowState } from '#workflow/state/schema.ts';
|
|
31
|
+
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
30
32
|
|
|
31
33
|
export const RUN_BUILDER_TOOL = {
|
|
32
34
|
NAME: 'maestro_run_builder',
|
|
@@ -42,13 +44,14 @@ const RunBuilderToolParameters = Type.Object(
|
|
|
42
44
|
{ additionalProperties: false },
|
|
43
45
|
);
|
|
44
46
|
|
|
45
|
-
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
46
|
-
|
|
47
47
|
type BuilderRunResult =
|
|
48
48
|
| {
|
|
49
49
|
outcome: typeof BUILDER_HANDOFF_STATUSES.DONE;
|
|
50
50
|
specId: string;
|
|
51
|
-
phase:
|
|
51
|
+
phase:
|
|
52
|
+
| typeof WORKFLOW_PHASES.READY_FOR_VERIFIER
|
|
53
|
+
| typeof WORKFLOW_PHASES.SIMPLIFIER_RUNNING
|
|
54
|
+
| typeof WORKFLOW_PHASES.CANDIDATE_READY;
|
|
52
55
|
handoff: BuilderHandoff;
|
|
53
56
|
}
|
|
54
57
|
| {
|
|
@@ -59,11 +62,15 @@ type BuilderRunResult =
|
|
|
59
62
|
};
|
|
60
63
|
|
|
61
64
|
type ReadBuilderResultInput = {
|
|
65
|
+
config: MaestroConfig;
|
|
66
|
+
builderRunningState: WorkflowState;
|
|
62
67
|
paths: MaestroPaths;
|
|
63
68
|
specId: string;
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
const readBuilderResult = async ({
|
|
72
|
+
config,
|
|
73
|
+
builderRunningState,
|
|
67
74
|
paths,
|
|
68
75
|
specId,
|
|
69
76
|
}: ReadBuilderResultInput): Promise<BuilderRunResult> => {
|
|
@@ -74,20 +81,32 @@ const readBuilderResult = async ({
|
|
|
74
81
|
throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
|
|
75
82
|
}
|
|
76
83
|
|
|
84
|
+
const donePhase = transitionWorkflow({
|
|
85
|
+
state: builderRunningState,
|
|
86
|
+
event: WORKFLOW_EVENTS.BUILDER_DONE,
|
|
87
|
+
config,
|
|
88
|
+
}).phase;
|
|
89
|
+
|
|
77
90
|
if (
|
|
78
91
|
state.phase !== WORKFLOW_PHASES.READY_FOR_VERIFIER &&
|
|
92
|
+
state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING &&
|
|
93
|
+
state.phase !== WORKFLOW_PHASES.CANDIDATE_READY &&
|
|
79
94
|
state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS
|
|
80
95
|
) {
|
|
81
96
|
throw new Error(`Builder returned with unexpected workflow phase "${state.phase}".`);
|
|
82
97
|
}
|
|
83
98
|
|
|
99
|
+
if (state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS && state.phase !== donePhase) {
|
|
100
|
+
throw new Error(`Builder returned with unexpected workflow phase "${state.phase}".`);
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
const handoff = await readBuilderHandoff({
|
|
85
104
|
path: await paths.getActiveBuilderHandoffPath(specId),
|
|
86
105
|
specId,
|
|
87
106
|
});
|
|
88
107
|
|
|
89
108
|
const expectedStatus =
|
|
90
|
-
state.phase
|
|
109
|
+
state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS
|
|
91
110
|
? BUILDER_HANDOFF_STATUSES.DONE
|
|
92
111
|
: BUILDER_HANDOFF_STATUSES.ESCALATION;
|
|
93
112
|
|
|
@@ -95,7 +114,7 @@ const readBuilderResult = async ({
|
|
|
95
114
|
throw new Error(`Builder handoff status does not match workflow phase "${state.phase}".`);
|
|
96
115
|
}
|
|
97
116
|
|
|
98
|
-
if (state.phase
|
|
117
|
+
if (state.phase !== WORKFLOW_PHASES.ESCALATION_DECISIONS) {
|
|
99
118
|
return {
|
|
100
119
|
outcome: BUILDER_HANDOFF_STATUSES.DONE,
|
|
101
120
|
specId,
|
|
@@ -122,11 +141,19 @@ const readBuilderResult = async ({
|
|
|
122
141
|
};
|
|
123
142
|
|
|
124
143
|
const formatBuilderResult = (result: BuilderRunResult): string => {
|
|
125
|
-
if (result.outcome
|
|
126
|
-
return `Builder
|
|
144
|
+
if (result.outcome !== BUILDER_HANDOFF_STATUSES.DONE) {
|
|
145
|
+
return `Builder submitted escalation questions for spec ${result.specId}. The workflow is waiting for an owner decision.`;
|
|
127
146
|
}
|
|
128
147
|
|
|
129
|
-
|
|
148
|
+
if (result.phase === WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
149
|
+
return `Builder completed spec ${result.specId}. The workflow is candidate-ready. Verifier and simplifier skipped by configuration. No independent verification was performed.`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (result.phase === WORKFLOW_PHASES.SIMPLIFIER_RUNNING) {
|
|
153
|
+
return `Builder completed spec ${result.specId}. The workflow is simplifier-running. Verifier skipped by configuration. Call maestro_run_simplifier next.`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return `Builder completed spec ${result.specId}. The workflow is ${result.phase}.`;
|
|
130
157
|
};
|
|
131
158
|
|
|
132
159
|
export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
@@ -138,7 +165,12 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
138
165
|
async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
139
166
|
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
140
167
|
|
|
141
|
-
const
|
|
168
|
+
const { projectRoot, state: builderRunningState } = await prepareBuilderRun({
|
|
169
|
+
config,
|
|
170
|
+
paths,
|
|
171
|
+
specId,
|
|
172
|
+
});
|
|
173
|
+
|
|
142
174
|
await refreshMaestroStatus(context);
|
|
143
175
|
|
|
144
176
|
const request: SubagentDelegationRequest = {
|
|
@@ -148,12 +180,12 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
148
180
|
// pi-subagents loads this name from agents/builder.md through package.json.
|
|
149
181
|
// See docs/subagent-integration.md.
|
|
150
182
|
agent: AGENTS.BUILDER,
|
|
151
|
-
task: `Implement specId "${specId}" in the project directory "${
|
|
183
|
+
task: `Implement specId "${specId}" in the project directory "${projectRoot}".`,
|
|
152
184
|
context: 'fresh',
|
|
153
|
-
cwd:
|
|
185
|
+
cwd: projectRoot,
|
|
154
186
|
model: config.builder.model,
|
|
155
187
|
thinking: config.builder.thinking,
|
|
156
|
-
timeoutMs:
|
|
188
|
+
timeoutMs: SUBAGENT_TIMEOUT_MS,
|
|
157
189
|
result: { kind: 'text' },
|
|
158
190
|
};
|
|
159
191
|
|
|
@@ -163,7 +195,7 @@ export const registerRunBuilderTool = (pi: ExtensionAPI): void => {
|
|
|
163
195
|
});
|
|
164
196
|
|
|
165
197
|
assertDelegationResponse(response);
|
|
166
|
-
const result = await readBuilderResult({ paths, specId });
|
|
198
|
+
const result = await readBuilderResult({ paths, specId, config, builderRunningState });
|
|
167
199
|
|
|
168
200
|
return {
|
|
169
201
|
content: [{ type: 'text', text: formatBuilderResult(result) }],
|
|
@@ -16,6 +16,7 @@ import { refreshMaestroStatus } from '#maestro/status/refreshMaestroStatus.ts';
|
|
|
16
16
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
17
17
|
import {
|
|
18
18
|
assertDelegationResponse,
|
|
19
|
+
SUBAGENT_TIMEOUT_MS,
|
|
19
20
|
waitForDelegationResponse,
|
|
20
21
|
} from '#tools/utils/pi-subagent-delegation.ts';
|
|
21
22
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
@@ -37,8 +38,6 @@ const RunSimplifierToolParameters = Type.Object(
|
|
|
37
38
|
{ additionalProperties: false },
|
|
38
39
|
);
|
|
39
40
|
|
|
40
|
-
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
41
|
-
|
|
42
41
|
const GIT_SKIP_WARNING = 'Simplifier skipped: Git repository unavailable.';
|
|
43
42
|
|
|
44
43
|
type GitReadinessInput = {
|
|
@@ -118,6 +117,7 @@ export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
|
|
|
118
117
|
|
|
119
118
|
if (!(await isGitReady({ pi, projectRoot }))) {
|
|
120
119
|
const nextState = transitionWorkflow({
|
|
120
|
+
config,
|
|
121
121
|
state,
|
|
122
122
|
event: WORKFLOW_EVENTS.SIMPLIFIER_SKIPPED,
|
|
123
123
|
});
|
|
@@ -146,7 +146,7 @@ export const registerRunSimplifierTool = (pi: ExtensionAPI): void => {
|
|
|
146
146
|
cwd: projectRoot,
|
|
147
147
|
model: config.simplifier.model,
|
|
148
148
|
thinking: config.simplifier.thinking,
|
|
149
|
-
timeoutMs:
|
|
149
|
+
timeoutMs: SUBAGENT_TIMEOUT_MS,
|
|
150
150
|
result: { kind: 'text' },
|
|
151
151
|
};
|
|
152
152
|
|
|
@@ -11,18 +11,20 @@ import { Type } from 'typebox';
|
|
|
11
11
|
|
|
12
12
|
import { readVerifierHandoff } from '#artifacts/verifier-handoff/readVerifierHandoff.ts';
|
|
13
13
|
import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
|
|
14
|
-
import { AGENTS } from '#config/schema.ts';
|
|
14
|
+
import { AGENTS, 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
18
|
import {
|
|
19
19
|
assertDelegationResponse,
|
|
20
|
+
SUBAGENT_TIMEOUT_MS,
|
|
20
21
|
waitForDelegationResponse,
|
|
21
22
|
} from '#tools/utils/pi-subagent-delegation.ts';
|
|
22
23
|
import { resolveToolRunContext } from '#tools/utils/resolveToolRunContext.ts';
|
|
23
24
|
import { WORKFLOW_ROLES } from '#workflow/roles.ts';
|
|
24
25
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
25
|
-
import { WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
26
|
+
import { WORKFLOW_PHASES, WORKFLOW_EVENTS, type WorkflowState } from '#workflow/state/schema.ts';
|
|
27
|
+
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
26
28
|
import { prepareVerifierRun } from '#workflow/verifier/prepareVerifierRun.ts';
|
|
27
29
|
|
|
28
30
|
export const RUN_VERIFIER_TOOL = {
|
|
@@ -39,9 +41,8 @@ const RunVerifierToolParameters = Type.Object(
|
|
|
39
41
|
{ additionalProperties: false },
|
|
40
42
|
);
|
|
41
43
|
|
|
42
|
-
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
43
|
-
|
|
44
44
|
type VerifierTerminalPhase =
|
|
45
|
+
| typeof WORKFLOW_PHASES.CANDIDATE_READY
|
|
45
46
|
| typeof WORKFLOW_PHASES.SIMPLIFIER_RUNNING
|
|
46
47
|
| typeof WORKFLOW_PHASES.FINDING_DECISIONS;
|
|
47
48
|
|
|
@@ -53,11 +54,15 @@ type VerifierRunResult = {
|
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
type ReadVerifierResultInput = {
|
|
57
|
+
config: MaestroConfig;
|
|
58
|
+
runningState: WorkflowState;
|
|
56
59
|
paths: MaestroPaths;
|
|
57
60
|
specId: string;
|
|
58
61
|
};
|
|
59
62
|
|
|
60
63
|
const readVerifierResult = async ({
|
|
64
|
+
config,
|
|
65
|
+
runningState,
|
|
61
66
|
paths,
|
|
62
67
|
specId,
|
|
63
68
|
}: ReadVerifierResultInput): Promise<VerifierRunResult> => {
|
|
@@ -74,6 +79,7 @@ const readVerifierResult = async ({
|
|
|
74
79
|
|
|
75
80
|
if (
|
|
76
81
|
state.phase !== WORKFLOW_PHASES.SIMPLIFIER_RUNNING &&
|
|
82
|
+
state.phase !== WORKFLOW_PHASES.CANDIDATE_READY &&
|
|
77
83
|
state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS
|
|
78
84
|
) {
|
|
79
85
|
throw new Error(`Verifier returned with unexpected workflow phase "${state.phase}".`);
|
|
@@ -87,7 +93,8 @@ const readVerifierResult = async ({
|
|
|
87
93
|
const expectedPhase =
|
|
88
94
|
handoff.findings.length > 0
|
|
89
95
|
? WORKFLOW_PHASES.FINDING_DECISIONS
|
|
90
|
-
:
|
|
96
|
+
: transitionWorkflow({ state: runningState, event: WORKFLOW_EVENTS.VERIFIER_DONE, config })
|
|
97
|
+
.phase;
|
|
91
98
|
|
|
92
99
|
if (state.phase !== expectedPhase) {
|
|
93
100
|
throw new Error(`Verifier handoff findings do not match workflow phase "${state.phase}".`);
|
|
@@ -107,6 +114,10 @@ const readVerifierResult = async ({
|
|
|
107
114
|
};
|
|
108
115
|
|
|
109
116
|
const formatVerifierResult = (result: VerifierRunResult): string => {
|
|
117
|
+
if (result.outcome === WORKFLOW_PHASES.CANDIDATE_READY) {
|
|
118
|
+
return `Verifier completed spec ${result.specId}. The workflow is candidate-ready. Simplifier skipped by configuration.`;
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
if (result.outcome === WORKFLOW_PHASES.SIMPLIFIER_RUNNING) {
|
|
111
122
|
return `Verifier completed spec ${result.specId}. The workflow is simplifier-running. Call maestro_run_simplifier next with this specId, without another approval or verifier pass.`;
|
|
112
123
|
}
|
|
@@ -123,7 +134,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
123
134
|
async execute(toolCallId, { specId }, _signal, _onUpdate, context) {
|
|
124
135
|
const { paths, config } = await resolveToolRunContext(context.cwd);
|
|
125
136
|
|
|
126
|
-
const run = await prepareVerifierRun({ paths, specId });
|
|
137
|
+
const run = await prepareVerifierRun({ config, paths, specId });
|
|
127
138
|
await refreshMaestroStatus(context);
|
|
128
139
|
|
|
129
140
|
const request: SubagentDelegationRequest = {
|
|
@@ -137,7 +148,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
137
148
|
cwd: run.projectRoot,
|
|
138
149
|
model: config.verifier.model,
|
|
139
150
|
thinking: config.verifier.thinking,
|
|
140
|
-
timeoutMs:
|
|
151
|
+
timeoutMs: SUBAGENT_TIMEOUT_MS,
|
|
141
152
|
result: { kind: 'text' },
|
|
142
153
|
};
|
|
143
154
|
|
|
@@ -147,7 +158,7 @@ export const registerRunVerifierTool = (pi: ExtensionAPI): void => {
|
|
|
147
158
|
});
|
|
148
159
|
|
|
149
160
|
assertDelegationResponse(response);
|
|
150
|
-
const result = await readVerifierResult({ paths, specId });
|
|
161
|
+
const result = await readVerifierResult({ paths, specId, config, runningState: run.state });
|
|
151
162
|
|
|
152
163
|
return {
|
|
153
164
|
content: [{ type: 'text', text: formatVerifierResult(result) }],
|
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// pi-subagents exports SubagentDelegationStatus as a type, not runtime values.
|
|
15
15
|
// TypeScript erases the type, so the adapter needs these constants for comparisons.
|
|
16
|
+
// Fixed deadline passed to pi-subagents in place of the removed per-agent timeout configuration.
|
|
17
|
+
export const SUBAGENT_TIMEOUT_MS = 9_999 * 60_000;
|
|
18
|
+
|
|
16
19
|
export const DELEGATION_STATUSES = {
|
|
17
20
|
COMPLETED: 'completed',
|
|
18
21
|
FAILED: 'failed',
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
BuilderHandoffSubmissionSchema,
|
|
16
16
|
} from '#artifacts/builder-handoff/schema.ts';
|
|
17
17
|
import { writeBuilderHandoff } from '#artifacts/builder-handoff/writeBuilderHandoff.ts';
|
|
18
|
+
import type { MaestroConfig } from '#config/schema.ts';
|
|
18
19
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
19
20
|
import { getWorkflowEventByBuilderHandoff } from '#workflow/builder/utils/getWorkflowEventByBuilderHandoff.ts';
|
|
20
21
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -39,12 +40,14 @@ function assertBuilderHandoffSubmission(
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
type CompleteBuilderPassInput = {
|
|
43
|
+
config: MaestroConfig;
|
|
42
44
|
paths: MaestroPaths;
|
|
43
45
|
specId: string;
|
|
44
46
|
handoff: BuilderHandoffSubmissionInput;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
export const completeBuilderPass = async ({
|
|
50
|
+
config,
|
|
48
51
|
paths,
|
|
49
52
|
specId,
|
|
50
53
|
handoff: draftHandoff,
|
|
@@ -84,6 +87,7 @@ export const completeBuilderPass = async ({
|
|
|
84
87
|
});
|
|
85
88
|
|
|
86
89
|
const nextState = transitionWorkflow({
|
|
90
|
+
config,
|
|
87
91
|
state: currentState,
|
|
88
92
|
event: getWorkflowEventByBuilderHandoff(handoff),
|
|
89
93
|
});
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
* Used: Before Maestro runs a builder pass.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { MaestroConfig } from '#config/schema.ts';
|
|
6
7
|
import maestroSessionState from '#maestro/session/MaestroSessionState.ts';
|
|
7
8
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
8
9
|
import { pathExists } from '#utils/path-exists.ts';
|
|
9
10
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
10
|
-
import { WORKFLOW_EVENTS, WORKFLOW_PHASES } from '#workflow/state/schema.ts';
|
|
11
|
+
import { WORKFLOW_EVENTS, WORKFLOW_PHASES, type WorkflowState } from '#workflow/state/schema.ts';
|
|
11
12
|
import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
12
13
|
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
13
14
|
|
|
14
15
|
export type BuilderRun = {
|
|
16
|
+
state: WorkflowState;
|
|
15
17
|
specId: string;
|
|
16
18
|
projectRoot: string;
|
|
17
19
|
};
|
|
@@ -38,11 +40,13 @@ async function assertBuilderRunBase({ paths, specId }: AssertBuilderRunBaseInput
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
type PrepareBuilderRunInput = {
|
|
43
|
+
config: MaestroConfig;
|
|
41
44
|
paths: MaestroPaths;
|
|
42
45
|
specId: string;
|
|
43
46
|
};
|
|
44
47
|
|
|
45
48
|
export const prepareBuilderRun = async ({
|
|
49
|
+
config,
|
|
46
50
|
paths,
|
|
47
51
|
specId,
|
|
48
52
|
}: PrepareBuilderRunInput): Promise<BuilderRun> => {
|
|
@@ -60,6 +64,7 @@ export const prepareBuilderRun = async ({
|
|
|
60
64
|
const currentState = await readWorkflowState(workflowPath);
|
|
61
65
|
|
|
62
66
|
const nextState = transitionWorkflow({
|
|
67
|
+
config,
|
|
63
68
|
state: currentState,
|
|
64
69
|
event: WORKFLOW_EVENTS.RUN_BUILDER,
|
|
65
70
|
});
|
|
@@ -70,6 +75,7 @@ export const prepareBuilderRun = async ({
|
|
|
70
75
|
});
|
|
71
76
|
|
|
72
77
|
return {
|
|
78
|
+
state: nextState,
|
|
73
79
|
specId,
|
|
74
80
|
projectRoot: paths.getProjectRoot(),
|
|
75
81
|
};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type EscalationResolution,
|
|
11
11
|
} from '#artifacts/builder-handoff/schema.ts';
|
|
12
12
|
import { writeBuilderHandoff } from '#artifacts/builder-handoff/writeBuilderHandoff.ts';
|
|
13
|
+
import type { MaestroConfig } from '#config/schema.ts';
|
|
13
14
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
14
15
|
import { getWorkflowEventBySpecChangeRequired } from '#workflow/builder/utils/getWorkflowEventBySpecChangeRequired.ts';
|
|
15
16
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -57,6 +58,7 @@ const applyEscalationDecisions = ({ escalations, decisions }: ApplyEscalationDec
|
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
type ResolveEscalationsInput = {
|
|
61
|
+
config: MaestroConfig;
|
|
60
62
|
paths: MaestroPaths;
|
|
61
63
|
specId: string;
|
|
62
64
|
specChangeRequired: boolean;
|
|
@@ -64,6 +66,7 @@ type ResolveEscalationsInput = {
|
|
|
64
66
|
};
|
|
65
67
|
|
|
66
68
|
export const resolveEscalations = async ({
|
|
69
|
+
config,
|
|
67
70
|
paths,
|
|
68
71
|
specId,
|
|
69
72
|
specChangeRequired,
|
|
@@ -95,6 +98,7 @@ export const resolveEscalations = async ({
|
|
|
95
98
|
const nextHandoff = { ...handoff, escalations };
|
|
96
99
|
|
|
97
100
|
const nextState = transitionWorkflow({
|
|
101
|
+
config,
|
|
98
102
|
state,
|
|
99
103
|
event: getWorkflowEventBySpecChangeRequired(specChangeRequired),
|
|
100
104
|
});
|
|
@@ -7,6 +7,7 @@ import { mkdir } from 'node:fs/promises';
|
|
|
7
7
|
import { dirname } from 'node:path';
|
|
8
8
|
|
|
9
9
|
import { assertSimplifierHandoff } from '#artifacts/simplifier-handoff/assertSimplifierHandoff.ts';
|
|
10
|
+
import type { MaestroConfig } from '#config/schema.ts';
|
|
10
11
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
11
12
|
import { writeJson } from '#utils/write-json.ts';
|
|
12
13
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -15,12 +16,14 @@ import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
|
15
16
|
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
16
17
|
|
|
17
18
|
type CompleteSimplifierPassInput = {
|
|
19
|
+
config: MaestroConfig;
|
|
18
20
|
paths: MaestroPaths;
|
|
19
21
|
specId: string;
|
|
20
22
|
handoff: unknown;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const completeSimplifierPass = async ({
|
|
26
|
+
config,
|
|
24
27
|
paths,
|
|
25
28
|
specId,
|
|
26
29
|
handoff,
|
|
@@ -46,6 +49,7 @@ export const completeSimplifierPass = async ({
|
|
|
46
49
|
await writeJson({ path: handoffPath, data: input.handoff });
|
|
47
50
|
|
|
48
51
|
const nextState = transitionWorkflow({
|
|
52
|
+
config,
|
|
49
53
|
state,
|
|
50
54
|
event: WORKFLOW_EVENTS.SIMPLIFIER_DONE,
|
|
51
55
|
});
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { readFile } from 'node:fs/promises';
|
|
7
7
|
|
|
8
|
+
import type { MaestroConfig } from '#config/schema.ts';
|
|
8
9
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
9
10
|
import { pathExists } from '#utils/path-exists.ts';
|
|
10
11
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -22,12 +23,14 @@ function assertAcceptanceCriterionOrdering(markdown: string): void {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
type MarkSpecReadyInput = {
|
|
26
|
+
config: MaestroConfig;
|
|
25
27
|
paths: MaestroPaths;
|
|
26
28
|
specId: string;
|
|
27
29
|
activeWorkflowSpecId: string | null;
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
export const markSpecReady = async ({
|
|
33
|
+
config,
|
|
31
34
|
paths,
|
|
32
35
|
specId,
|
|
33
36
|
activeWorkflowSpecId,
|
|
@@ -62,6 +65,7 @@ export const markSpecReady = async ({
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const state = transitionWorkflow({
|
|
68
|
+
config,
|
|
65
69
|
state: current,
|
|
66
70
|
event: WORKFLOW_EVENTS.MARK_SPEC_READY,
|
|
67
71
|
});
|