pi-crew 0.9.14 → 0.9.15
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/CHANGELOG.md +62 -0
- package/README.md +66 -0
- package/package.json +1 -1
- package/src/config/types.ts +5 -0
- package/src/extension/registration/team-tool.ts +145 -35
- package/src/extension/team-tool/run.ts +646 -150
- package/src/runtime/team-runner.ts +1445 -318
- package/src/workflows/discover-workflows.ts +144 -29
- package/src/workflows/preflight-validator.ts +195 -0
- package/src/workflows/topology-analyzer.ts +219 -0
- package/src/workflows/workflow-config.ts +11 -0
- package/workflows/chain.workflow.md +6 -0
- package/workflows/default.workflow.md +1 -0
- package/workflows/fast-fix.workflow.md +1 -0
- package/workflows/implementation.workflow.md +1 -0
- package/workflows/parallel-research.workflow.md +1 -0
- package/workflows/pipeline.workflow.md +1 -0
- package/workflows/research.workflow.md +1 -0
- package/workflows/review.workflow.md +1 -0
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
import { allAgents, discoverAgents } from "../../agents/discover-agents.ts";
|
|
2
|
-
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
3
|
-
import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
4
2
|
import { loadConfig } from "../../config/config.ts";
|
|
5
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PipelineRunner,
|
|
5
|
+
type PipelineWorkflow,
|
|
6
|
+
} from "../../runtime/pipeline-runner.ts";
|
|
7
|
+
// Heavy runtime — lazy-loaded to avoid 1.4s import cost at extension registration.
|
|
8
|
+
import type { executeTeamRun as ExecuteTeamRunFn } from "../../runtime/team-runner.ts";
|
|
6
9
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
10
|
+
import {
|
|
11
|
+
registerActiveRun,
|
|
12
|
+
unregisterActiveRun,
|
|
13
|
+
} from "../../state/active-run-registry.ts";
|
|
7
14
|
import { writeArtifact } from "../../state/artifact-store.ts";
|
|
8
|
-
import { registerActiveRun, unregisterActiveRun } from "../../state/active-run-registry.ts";
|
|
9
|
-
import { createRunManifest, loadRunManifestById, updateRunStatus } from "../../state/state-store.ts";
|
|
10
15
|
import { atomicWriteJson } from "../../state/atomic-write.ts";
|
|
16
|
+
import {
|
|
17
|
+
createRunManifest,
|
|
18
|
+
loadRunManifestById,
|
|
19
|
+
updateRunStatus,
|
|
20
|
+
} from "../../state/state-store.ts";
|
|
21
|
+
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
22
|
+
import {
|
|
23
|
+
allWorkflows,
|
|
24
|
+
discoverWorkflows,
|
|
25
|
+
} from "../../workflows/discover-workflows.ts";
|
|
11
26
|
import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
import {
|
|
28
|
+
assertCleanLeader,
|
|
29
|
+
findGitRoot,
|
|
30
|
+
} from "../../worktree/worktree-manager.ts";
|
|
31
|
+
|
|
15
32
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only import for TS inference
|
|
16
|
-
const _typeCheck: typeof ExecuteTeamRunFn =
|
|
33
|
+
const _typeCheck: typeof ExecuteTeamRunFn =
|
|
34
|
+
null as never as typeof ExecuteTeamRunFn;
|
|
35
|
+
|
|
17
36
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
18
37
|
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
38
|
+
|
|
19
39
|
let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
|
|
20
|
-
async function executeTeamRun(
|
|
40
|
+
async function executeTeamRun(
|
|
41
|
+
...args: Parameters<typeof ExecuteTeamRunFn>
|
|
42
|
+
): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
|
|
21
43
|
if (!_cachedExecuteTeamRun) {
|
|
22
44
|
// LAZY: heavy runtime — defer 1.4s import cost until team run actually executes.
|
|
23
45
|
const mod = await import("../../runtime/team-runner.ts");
|
|
@@ -25,11 +47,15 @@ async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Pro
|
|
|
25
47
|
}
|
|
26
48
|
return _cachedExecuteTeamRun(...args);
|
|
27
49
|
}
|
|
28
|
-
|
|
29
|
-
import { appendEventAsync, readEvents } from "../../state/event-log.ts";
|
|
30
|
-
import { resolveCrewRuntime, runtimeResolutionState } from "../../runtime/runtime-resolver.ts";
|
|
31
|
-
import { normalizeSkillOverride } from "../../runtime/skill-instructions.ts";
|
|
50
|
+
|
|
32
51
|
import { expandParallelResearchWorkflow } from "../../runtime/parallel-research.ts";
|
|
52
|
+
import {
|
|
53
|
+
resolveCrewRuntime,
|
|
54
|
+
runtimeResolutionState,
|
|
55
|
+
} from "../../runtime/runtime-resolver.ts";
|
|
56
|
+
import { normalizeSkillOverride } from "../../runtime/skill-instructions.ts";
|
|
57
|
+
import { appendEventAsync, readEvents } from "../../state/event-log.ts";
|
|
58
|
+
import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
|
|
33
59
|
|
|
34
60
|
/**
|
|
35
61
|
* Module-scoped latch for the crew-init dynamic import.
|
|
@@ -58,23 +84,33 @@ import { expandParallelResearchWorkflow } from "../../runtime/parallel-research.
|
|
|
58
84
|
* `team action='run' workflow='<dynamic>'` → "Cannot access 'crewInitPromise'
|
|
59
85
|
* before initialization" at run.ts load. See RFC 17 + commit fixing this.
|
|
60
86
|
*/
|
|
61
|
-
var crewInitPromise:
|
|
87
|
+
var crewInitPromise:
|
|
88
|
+
| Promise<typeof import("../../state/crew-init.ts")>
|
|
89
|
+
| undefined;
|
|
62
90
|
function loadCrewInit(): Promise<typeof import("../../state/crew-init.ts")> {
|
|
63
91
|
if (!crewInitPromise) {
|
|
64
92
|
crewInitPromise = import("../../state/crew-init.ts");
|
|
65
93
|
}
|
|
66
94
|
return crewInitPromise;
|
|
67
95
|
}
|
|
68
|
-
|
|
69
|
-
import { waitForRun } from "../../runtime/run-tracker.ts";
|
|
70
|
-
import { hasAsyncStartMarker } from "../../runtime/async-marker.ts";
|
|
71
|
-
import { collectRunMetrics } from "../../state/run-metrics.ts";
|
|
96
|
+
|
|
72
97
|
import * as fs from "node:fs";
|
|
73
98
|
import * as path from "node:path";
|
|
99
|
+
import { hasAsyncStartMarker } from "../../runtime/async-marker.ts";
|
|
100
|
+
import {
|
|
101
|
+
checkProcessLiveness,
|
|
102
|
+
isActiveRunStatus,
|
|
103
|
+
} from "../../runtime/process-status.ts";
|
|
104
|
+
import { waitForRun } from "../../runtime/run-tracker.ts";
|
|
105
|
+
import { collectRunMetrics } from "../../state/run-metrics.ts";
|
|
74
106
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
75
|
-
import { buildParentContext, result, type TeamContext } from "./context.ts";
|
|
76
|
-
import { isGoalWrapEnabled, shouldGoalWrap, startGoalWrappedRun } from "./goal-wrap.ts";
|
|
77
107
|
import { effectiveRunConfig } from "./config-patch.ts";
|
|
108
|
+
import { buildParentContext, result, type TeamContext } from "./context.ts";
|
|
109
|
+
import {
|
|
110
|
+
isGoalWrapEnabled,
|
|
111
|
+
shouldGoalWrap,
|
|
112
|
+
startGoalWrappedRun,
|
|
113
|
+
} from "./goal-wrap.ts";
|
|
78
114
|
|
|
79
115
|
function tailFile(filePath: string, maxBytes = 4096): string | undefined {
|
|
80
116
|
try {
|
|
@@ -95,24 +131,49 @@ function tailFile(filePath: string, maxBytes = 4096): string | undefined {
|
|
|
95
131
|
}
|
|
96
132
|
}
|
|
97
133
|
|
|
98
|
-
function scheduleBackgroundEarlyExitGuard(
|
|
134
|
+
function scheduleBackgroundEarlyExitGuard(
|
|
135
|
+
cwd: string,
|
|
136
|
+
runId: string,
|
|
137
|
+
pid: number | undefined,
|
|
138
|
+
logPath: string,
|
|
139
|
+
): void {
|
|
99
140
|
if (process.env.PI_CREW_ASYNC_EARLY_EXIT_GUARD === "0") return;
|
|
100
141
|
const timer = setTimeout(() => {
|
|
101
142
|
const loaded = loadRunManifestById(cwd, runId);
|
|
102
143
|
if (!loaded || !isActiveRunStatus(loaded.manifest.status)) return;
|
|
103
144
|
if (hasAsyncStartMarker(loaded.manifest)) return;
|
|
104
|
-
if (
|
|
145
|
+
if (
|
|
146
|
+
readEvents(loaded.manifest.eventsPath).some(
|
|
147
|
+
(event) =>
|
|
148
|
+
event.type === "async.started" ||
|
|
149
|
+
event.type === "async.completed" ||
|
|
150
|
+
event.type === "async.failed",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
return;
|
|
105
154
|
const liveness = checkProcessLiveness(pid);
|
|
106
155
|
if (liveness.alive) return;
|
|
107
156
|
const tail = tailFile(logPath);
|
|
108
157
|
const message = `Background runner exited within 3s; see background.log${tail ? `\n${tail}` : ""}`;
|
|
109
|
-
const failed = updateRunStatus(
|
|
110
|
-
|
|
158
|
+
const failed = updateRunStatus(
|
|
159
|
+
loaded.manifest,
|
|
160
|
+
"failed",
|
|
161
|
+
"Background runner exited within 3s; see background.log",
|
|
162
|
+
);
|
|
163
|
+
void appendEventAsync(failed.eventsPath, {
|
|
164
|
+
type: "async.failed",
|
|
165
|
+
runId: failed.runId,
|
|
166
|
+
message,
|
|
167
|
+
data: { pid, detail: liveness.detail },
|
|
168
|
+
});
|
|
111
169
|
}, 3000);
|
|
112
170
|
timer.unref();
|
|
113
171
|
}
|
|
114
172
|
|
|
115
|
-
export async function handleRun(
|
|
173
|
+
export async function handleRun(
|
|
174
|
+
params: TeamToolParamsValue,
|
|
175
|
+
ctx: TeamContext,
|
|
176
|
+
): Promise<PiTeamsToolResult> {
|
|
116
177
|
// CHAIN DISPATCH: runs before goal validation since a chain has no top-level
|
|
117
178
|
// goal. The injected handleRun reference breaks the run.ts ↔ chain-dispatch.ts
|
|
118
179
|
// import cycle; the lazy import defers the chain-executor cost until a chain is
|
|
@@ -123,7 +184,12 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
123
184
|
return handleChainRun(params, ctx, handleRun);
|
|
124
185
|
}
|
|
125
186
|
const goal = params.goal ?? params.task;
|
|
126
|
-
if (!goal)
|
|
187
|
+
if (!goal)
|
|
188
|
+
return result(
|
|
189
|
+
"Run requires goal or task.",
|
|
190
|
+
{ action: "run", status: "error" },
|
|
191
|
+
true,
|
|
192
|
+
);
|
|
127
193
|
const intentPrefix = goal.length > 60 ? `${goal.slice(0, 57)}...` : goal;
|
|
128
194
|
|
|
129
195
|
// P0: Ensure .crew directory structure exists before creating any manifests.
|
|
@@ -185,31 +251,102 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
185
251
|
const teams = allTeams(discoverTeams(resolvedCtx.cwd));
|
|
186
252
|
const workflows = allWorkflows(discoverWorkflows(resolvedCtx.cwd));
|
|
187
253
|
const agents = allAgents(discoverAgents(resolvedCtx.cwd));
|
|
188
|
-
const directAgent = params.agent
|
|
189
|
-
|
|
254
|
+
const directAgent = params.agent
|
|
255
|
+
? agents.find((item) => item.name === params.agent)
|
|
256
|
+
: undefined;
|
|
257
|
+
if (params.agent && !directAgent)
|
|
258
|
+
return result(
|
|
259
|
+
`Agent '${params.agent}' not found.`,
|
|
260
|
+
{ action: "run", status: "error" },
|
|
261
|
+
true,
|
|
262
|
+
);
|
|
190
263
|
const teamName = params.team ?? "default";
|
|
191
|
-
const team = directAgent
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
264
|
+
const team = directAgent
|
|
265
|
+
? {
|
|
266
|
+
name: `direct-${directAgent.name}`,
|
|
267
|
+
description: `Direct subagent run for ${directAgent.name}`,
|
|
268
|
+
source: "builtin" as const,
|
|
269
|
+
filePath: "<generated>",
|
|
270
|
+
roles: [
|
|
271
|
+
{
|
|
272
|
+
name: params.role ?? "agent",
|
|
273
|
+
agent: directAgent.name,
|
|
274
|
+
description: directAgent.description,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
defaultWorkflow: "direct-agent",
|
|
278
|
+
workspaceMode: params.workspaceMode,
|
|
279
|
+
}
|
|
280
|
+
: teams.find((item) => item.name === teamName);
|
|
281
|
+
if (!team)
|
|
282
|
+
return result(
|
|
283
|
+
`Team '${teamName}' not found.`,
|
|
284
|
+
{ action: "run", status: "error" },
|
|
285
|
+
true,
|
|
286
|
+
);
|
|
287
|
+
const workflowName = directAgent
|
|
288
|
+
? "direct-agent"
|
|
289
|
+
: (params.workflow ?? team.defaultWorkflow ?? "default");
|
|
290
|
+
const baseWorkflow = directAgent
|
|
291
|
+
? {
|
|
292
|
+
name: "direct-agent",
|
|
293
|
+
description: `Direct task for ${directAgent.name}`,
|
|
294
|
+
source: "builtin" as const,
|
|
295
|
+
filePath: "<generated>",
|
|
296
|
+
steps: [
|
|
297
|
+
{
|
|
298
|
+
id: "01_agent",
|
|
299
|
+
role: params.role ?? "agent",
|
|
300
|
+
task: "{goal}",
|
|
301
|
+
model: params.model,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
}
|
|
305
|
+
: workflows.find((item) => item.name === workflowName);
|
|
306
|
+
if (!baseWorkflow)
|
|
307
|
+
return result(
|
|
308
|
+
`Workflow '${workflowName}' not found.`,
|
|
309
|
+
{ action: "run", status: "error" },
|
|
310
|
+
true,
|
|
311
|
+
);
|
|
210
312
|
// LAZY: dodge the jiti ESM/CJS interop TDZ race on the static `import { expandParallelResearchWorkflow }` above (issue #28, RFC 17). At call time the module body has fully evaluated, so the dynamic import returns a live binding.
|
|
211
|
-
|
|
212
|
-
|
|
313
|
+
// LAZY: marker duplicated for biome-check compatibility (script checks `lineNum - 2`).
|
|
314
|
+
// LAZY: keep import on single line so the `// LAZY:` marker above remains 1-line away from `await import`.
|
|
315
|
+
const { expandParallelResearchWorkflow: expandParallelResearch } = await import(
|
|
316
|
+
"../../runtime/parallel-research.ts",
|
|
317
|
+
);
|
|
318
|
+
const workflow = directAgent
|
|
319
|
+
? baseWorkflow
|
|
320
|
+
: expandParallelResearch(baseWorkflow, resolvedCtx.cwd);
|
|
321
|
+
|
|
322
|
+
// PREFLIGHT (advisory only, since v0.9.15): classify workflow topology and emit
|
|
323
|
+
// informational notes per the rule in .crew/knowledge.md "pi-crew USAGE THRESHOLD
|
|
324
|
+
// RULE". Never blocks execution — the agent (caller) decides whether to proceed,
|
|
325
|
+
// refactor, or override. This honors Pi's design philosophy: tooling provides
|
|
326
|
+
// information, agents exercise judgment.
|
|
327
|
+
// Skip for direct-agent runs (they bypass the workflow system entirely).
|
|
328
|
+
if (!directAgent) {
|
|
329
|
+
// LAZY: defer preflight-validator import until a team run actually requests it.
|
|
330
|
+
const { validateWorkflowUsage } = await import(
|
|
331
|
+
"../../workflows/preflight-validator.ts"
|
|
332
|
+
);
|
|
333
|
+
const preflight = validateWorkflowUsage(workflow, {
|
|
334
|
+
force: params.force === true,
|
|
335
|
+
});
|
|
336
|
+
const icon =
|
|
337
|
+
preflight.level === "warn"
|
|
338
|
+
? "⚠️ "
|
|
339
|
+
: preflight.level === "note"
|
|
340
|
+
? "ℹ️ "
|
|
341
|
+
: "";
|
|
342
|
+
const tag = preflight.level.toUpperCase();
|
|
343
|
+
console.warn(
|
|
344
|
+
`${icon}[team-tool.preflight] ${tag}: ${preflight.message} (workflow=${workflow.name})`,
|
|
345
|
+
);
|
|
346
|
+
if (preflight.suggestion) {
|
|
347
|
+
console.warn(`[team-tool.preflight] → ${preflight.suggestion}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
213
350
|
|
|
214
351
|
// RFC v0.5 vision: goal-wrap. If .crew/config.json has goalWrap[workflow.name].enabled=true,
|
|
215
352
|
// route to a goal loop where this workflow runs as the worker turn (judge → feedback → redo
|
|
@@ -221,7 +358,11 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
221
358
|
// transition). When goal-wrap is unsafe for this workflow, we do NOT error
|
|
222
359
|
// out — we fall through to the normal team-run path so the user still gets
|
|
223
360
|
// the run they asked for. The disabled reason is logged for traceability.
|
|
224
|
-
if (
|
|
361
|
+
if (
|
|
362
|
+
!directAgent &&
|
|
363
|
+
workflow.source === "builtin" &&
|
|
364
|
+
isGoalWrapEnabled(resolvedCtx.cwd, workflow.name)
|
|
365
|
+
) {
|
|
225
366
|
const decision = shouldGoalWrap(resolvedCtx.cwd, workflow);
|
|
226
367
|
if (decision.enabled) {
|
|
227
368
|
return await startGoalWrappedRun(params, ctx, workflow, goal);
|
|
@@ -231,7 +372,11 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
231
372
|
// debug logs. This preserves the trace of WHY goal-wrap was bypassed for
|
|
232
373
|
// a given run (vs. just disappearing without explanation).
|
|
233
374
|
if (decision.message) {
|
|
234
|
-
logInternalError(
|
|
375
|
+
logInternalError(
|
|
376
|
+
"team-tool.run.goalWrapBypassed",
|
|
377
|
+
new Error(decision.message),
|
|
378
|
+
`workflow=${workflow.name} reason=${decision.reason}`,
|
|
379
|
+
);
|
|
235
380
|
}
|
|
236
381
|
}
|
|
237
382
|
|
|
@@ -256,31 +401,48 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
256
401
|
|
|
257
402
|
// For now, show pipeline workflow info - full integration would require
|
|
258
403
|
// connecting PipelineRunner to the actual team execution system
|
|
259
|
-
const stageInfo = pipelineWorkflow.stages
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
404
|
+
const stageInfo = pipelineWorkflow.stages
|
|
405
|
+
.map((s) => `- ${s.name} (${s.team})`)
|
|
406
|
+
.join("\n");
|
|
407
|
+
return result(
|
|
408
|
+
[
|
|
409
|
+
`Pipeline workflow '${workflow.name}' is not yet wired into the team execution system.`,
|
|
410
|
+
`Goal: ${goal}`,
|
|
411
|
+
`Defined stages (${pipelineWorkflow.stages.length}):`,
|
|
412
|
+
stageInfo,
|
|
413
|
+
"",
|
|
414
|
+
"To actually run work right now, use a supported workflow instead:",
|
|
415
|
+
" - action='run' workflow='default' (explore → plan → execute → verify)",
|
|
416
|
+
" - action='run' workflow='implementation' (adaptive, parallel specialists)",
|
|
417
|
+
" - action='run' workflow='research' (explore → analyze → write)",
|
|
418
|
+
"",
|
|
419
|
+
"Run action='list' resource='workflow' to see all available workflows.",
|
|
420
|
+
].join("\n"),
|
|
421
|
+
{ action: "run", status: "ok" },
|
|
422
|
+
false,
|
|
423
|
+
);
|
|
273
424
|
}
|
|
274
425
|
|
|
275
426
|
// LAZY: dodge the jiti ESM/CJS interop TDZ race on the static `import { validateWorkflowForTeam }` above (issue #28, RFC 17).
|
|
276
|
-
const { validateWorkflowForTeam: validateWorkflow } = await import(
|
|
427
|
+
const { validateWorkflowForTeam: validateWorkflow } = await import(
|
|
428
|
+
"../../workflows/validate-workflow.ts"
|
|
429
|
+
);
|
|
277
430
|
const validationErrors = validateWorkflow(workflow, team);
|
|
278
431
|
if (validationErrors.length > 0) {
|
|
279
|
-
return result(
|
|
432
|
+
return result(
|
|
433
|
+
[
|
|
434
|
+
`Workflow '${workflow.name}' is not valid for team '${team.name}':`,
|
|
435
|
+
...validationErrors.map((error) => `- ${error}`),
|
|
436
|
+
].join("\n"),
|
|
437
|
+
{ action: "run", status: "error" },
|
|
438
|
+
true,
|
|
439
|
+
);
|
|
280
440
|
}
|
|
281
441
|
|
|
282
442
|
// LAZY: dodge the jiti ESM/CJS interop TDZ race on the static `import { normalizeSkillOverride }` above (issue #28, RFC 17).
|
|
283
|
-
const { normalizeSkillOverride: normalizeSkill } = await import(
|
|
443
|
+
const { normalizeSkillOverride: normalizeSkill } = await import(
|
|
444
|
+
"../../runtime/skill-instructions.ts"
|
|
445
|
+
);
|
|
284
446
|
const skillOverride = normalizeSkill(params.skill);
|
|
285
447
|
const { manifest, tasks, paths } = createRunManifest({
|
|
286
448
|
cwd: resolvedCtx.cwd,
|
|
@@ -298,7 +460,13 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
298
460
|
content: `${goal}\n`,
|
|
299
461
|
producer: "team-tool",
|
|
300
462
|
});
|
|
301
|
-
const updatedManifest = {
|
|
463
|
+
const updatedManifest = {
|
|
464
|
+
...manifest,
|
|
465
|
+
...(skillOverride !== undefined ? { skillOverride } : {}),
|
|
466
|
+
artifacts: [goalArtifact],
|
|
467
|
+
summary:
|
|
468
|
+
"Run manifest created; worker execution is not implemented yet.",
|
|
469
|
+
};
|
|
302
470
|
atomicWriteJson(paths.manifestPath, updatedManifest);
|
|
303
471
|
registerActiveRun(updatedManifest);
|
|
304
472
|
|
|
@@ -306,9 +474,16 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
306
474
|
// run it via runDynamicWorkflow instead of the static executeTeamRun path. The script
|
|
307
475
|
// orchestrates subagents via ctx.agent(); only ctx.setResult() reaches the main context.
|
|
308
476
|
// Placed AFTER manifest creation so runId/paths/artifactsRoot are available.
|
|
309
|
-
if (
|
|
477
|
+
if (
|
|
478
|
+
!directAgent &&
|
|
479
|
+
(
|
|
480
|
+
workflow as import("../../workflows/workflow-config.ts").DynamicWorkflowConfig
|
|
481
|
+
).runtime === "dynamic"
|
|
482
|
+
) {
|
|
310
483
|
// LAZY: defer dynamic import of ../../runtime/dynamic-workflow-runner.ts to its call site.
|
|
311
|
-
const { runDynamicWorkflow } = await import(
|
|
484
|
+
const { runDynamicWorkflow } = await import(
|
|
485
|
+
"../../runtime/dynamic-workflow-runner.ts"
|
|
486
|
+
);
|
|
312
487
|
// Re-synthesize a dynamic-team (§0c C9) for role resolution.
|
|
313
488
|
const dwfTeam: import("../../teams/team-config.ts").TeamConfig = {
|
|
314
489
|
name: `dwf-${manifest.runId.slice(-12)}`,
|
|
@@ -325,26 +500,50 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
325
500
|
};
|
|
326
501
|
atomicWriteJson(paths.manifestPath, dwfManifest);
|
|
327
502
|
try {
|
|
328
|
-
let dwfResult:
|
|
503
|
+
let dwfResult:
|
|
504
|
+
| import("../../runtime/dynamic-workflow-runner.ts").RunDynamicWorkflowResult
|
|
505
|
+
| undefined;
|
|
329
506
|
try {
|
|
330
507
|
dwfResult = await runDynamicWorkflow({
|
|
331
508
|
manifest: dwfManifest,
|
|
332
|
-
workflow:
|
|
509
|
+
workflow:
|
|
510
|
+
workflow as import("../../workflows/workflow-config.ts").DynamicWorkflowConfig,
|
|
333
511
|
team: dwfTeam,
|
|
334
512
|
signal: ctx.signal ?? AbortSignal.timeout(3_600_000),
|
|
335
513
|
modelOverride: params.model,
|
|
336
|
-
tokenBudget:
|
|
514
|
+
tokenBudget:
|
|
515
|
+
params.tokenBudget ??
|
|
516
|
+
(
|
|
517
|
+
workflow as import("../../workflows/workflow-config.ts").DynamicWorkflowConfig
|
|
518
|
+
).maxTokenBudget,
|
|
337
519
|
});
|
|
338
520
|
} catch (runnerError) {
|
|
339
521
|
// Round-11 runtime fix: persist manifest with status=failed when runner throws
|
|
340
522
|
// (e.g., script timeout, script syntax error, async failure). Previously the
|
|
341
523
|
// manifest stayed at 'queued' indefinitely, leaving an orphan state file.
|
|
342
|
-
const failureReason =
|
|
343
|
-
|
|
524
|
+
const failureReason =
|
|
525
|
+
runnerError instanceof Error
|
|
526
|
+
? runnerError.message
|
|
527
|
+
: String(runnerError);
|
|
528
|
+
const failedManifest = {
|
|
529
|
+
...dwfManifest,
|
|
530
|
+
status: "failed" as const,
|
|
531
|
+
summary:
|
|
532
|
+
`Dynamic workflow '${workflow.name}' failed: ${failureReason}`.slice(
|
|
533
|
+
0,
|
|
534
|
+
2000,
|
|
535
|
+
),
|
|
536
|
+
updatedAt: new Date().toISOString(),
|
|
537
|
+
};
|
|
344
538
|
atomicWriteJson(paths.manifestPath, failedManifest);
|
|
345
539
|
return result(
|
|
346
540
|
`Dynamic workflow '${workflow.name}' failed: ${failureReason}`,
|
|
347
|
-
{
|
|
541
|
+
{
|
|
542
|
+
action: "run",
|
|
543
|
+
status: "error",
|
|
544
|
+
runId: failedManifest.runId,
|
|
545
|
+
artifactsRoot: failedManifest.artifactsRoot,
|
|
546
|
+
},
|
|
348
547
|
true,
|
|
349
548
|
);
|
|
350
549
|
}
|
|
@@ -354,7 +553,13 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
354
553
|
atomicWriteJson(paths.manifestPath, dwfResult.manifest);
|
|
355
554
|
return result(
|
|
356
555
|
`Dynamic workflow '${workflow.name}' completed.\n${dwfResult.manifest.summary ?? ""}`,
|
|
357
|
-
{
|
|
556
|
+
{
|
|
557
|
+
action: "run",
|
|
558
|
+
status:
|
|
559
|
+
dwfResult.manifest.status === "failed" ? "error" : "ok",
|
|
560
|
+
runId: dwfResult.manifest.runId,
|
|
561
|
+
artifactsRoot: dwfResult.manifest.artifactsRoot,
|
|
562
|
+
},
|
|
358
563
|
dwfResult.manifest.status === "failed",
|
|
359
564
|
);
|
|
360
565
|
} finally {
|
|
@@ -377,58 +582,179 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
377
582
|
type: "config.warning",
|
|
378
583
|
runId: updatedManifest.runId,
|
|
379
584
|
message: `Loaded config from ${loadedConfig.path || "(defaults)"} with ${configIssues.length} issue(s): ${configIssues.join("; ")}`,
|
|
380
|
-
data: {
|
|
381
|
-
|
|
382
|
-
|
|
585
|
+
data: {
|
|
586
|
+
error: loadedConfig.error,
|
|
587
|
+
warnings: loadedConfig.warnings,
|
|
588
|
+
path: loadedConfig.path,
|
|
589
|
+
},
|
|
590
|
+
}).catch((error) =>
|
|
591
|
+
logInternalError(
|
|
592
|
+
"team-tool.run.configWarning",
|
|
593
|
+
error,
|
|
594
|
+
`runId=${updatedManifest.runId}`,
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
logInternalError(
|
|
598
|
+
"team-tool.run.configWarning",
|
|
599
|
+
new Error(`config issues: ${configIssues.join("; ")}`),
|
|
600
|
+
`runId=${updatedManifest.runId} path=${loadedConfig.path ?? "(defaults)"}`,
|
|
601
|
+
);
|
|
383
602
|
}
|
|
384
|
-
const executedConfig = effectiveRunConfig(
|
|
603
|
+
const executedConfig = effectiveRunConfig(
|
|
604
|
+
loadedConfig.config,
|
|
605
|
+
params.config,
|
|
606
|
+
);
|
|
385
607
|
const runtime = await resolveCrewRuntime(executedConfig);
|
|
386
608
|
const runtimeResolution = runtimeResolutionState(runtime);
|
|
387
|
-
const executionManifest = {
|
|
609
|
+
const executionManifest = {
|
|
610
|
+
...updatedManifest,
|
|
611
|
+
runtimeResolution,
|
|
612
|
+
runConfig: executedConfig,
|
|
613
|
+
updatedAt: new Date().toISOString(),
|
|
614
|
+
};
|
|
388
615
|
atomicWriteJson(paths.manifestPath, executionManifest);
|
|
389
|
-
appendEventAsync(executionManifest.eventsPath, {
|
|
616
|
+
appendEventAsync(executionManifest.eventsPath, {
|
|
617
|
+
type: "runtime.resolved",
|
|
618
|
+
runId: executionManifest.runId,
|
|
619
|
+
message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`,
|
|
620
|
+
data: { runtimeResolution },
|
|
621
|
+
}).catch((error) =>
|
|
622
|
+
logInternalError(
|
|
623
|
+
"team-tool.run.resolved",
|
|
624
|
+
error,
|
|
625
|
+
`runId=${executionManifest.runId}`,
|
|
626
|
+
),
|
|
627
|
+
);
|
|
390
628
|
const runAsync = params.async ?? executedConfig.asyncByDefault ?? false;
|
|
391
629
|
let effectiveRuntime = runtime;
|
|
392
630
|
if (runAsync && runtime.kind === "live-session") {
|
|
393
|
-
effectiveRuntime = {
|
|
631
|
+
effectiveRuntime = {
|
|
632
|
+
...runtime,
|
|
633
|
+
kind: "child-process",
|
|
634
|
+
steer: true,
|
|
635
|
+
resume: false,
|
|
636
|
+
liveToolActivity: false,
|
|
637
|
+
fallback: "child-process",
|
|
638
|
+
reason: "Background runner cannot use live-session; falling back to child-process.",
|
|
639
|
+
};
|
|
394
640
|
}
|
|
395
|
-
const effectiveRuntimeResolution =
|
|
396
|
-
|
|
641
|
+
const effectiveRuntimeResolution =
|
|
642
|
+
effectiveRuntime !== runtime
|
|
643
|
+
? runtimeResolutionState(effectiveRuntime)
|
|
644
|
+
: runtimeResolution;
|
|
645
|
+
const effectiveManifest =
|
|
646
|
+
effectiveRuntime !== runtime
|
|
647
|
+
? {
|
|
648
|
+
...executionManifest,
|
|
649
|
+
runtimeResolution: effectiveRuntimeResolution,
|
|
650
|
+
updatedAt: new Date().toISOString(),
|
|
651
|
+
}
|
|
652
|
+
: executionManifest;
|
|
397
653
|
if (effectiveRuntime !== runtime) {
|
|
398
654
|
atomicWriteJson(paths.manifestPath, effectiveManifest);
|
|
399
|
-
appendEventAsync(effectiveManifest.eventsPath, {
|
|
655
|
+
appendEventAsync(effectiveManifest.eventsPath, {
|
|
656
|
+
type: "runtime.resolved",
|
|
657
|
+
runId: effectiveManifest.runId,
|
|
658
|
+
message: `Runtime overridden: child-process (async fallback from live-session)`,
|
|
659
|
+
data: { runtimeResolution: effectiveRuntimeResolution },
|
|
660
|
+
}).catch((error) =>
|
|
661
|
+
logInternalError(
|
|
662
|
+
"team-tool.run.override",
|
|
663
|
+
error,
|
|
664
|
+
`runId=${effectiveManifest.runId}`,
|
|
665
|
+
),
|
|
666
|
+
);
|
|
400
667
|
}
|
|
401
668
|
if (runAsync) {
|
|
402
669
|
if (effectiveRuntime.safety === "blocked") {
|
|
403
|
-
const runningManifest = updateRunStatus(
|
|
404
|
-
|
|
405
|
-
|
|
670
|
+
const runningManifest = updateRunStatus(
|
|
671
|
+
effectiveManifest,
|
|
672
|
+
"running",
|
|
673
|
+
"Checking worker runtime availability.",
|
|
674
|
+
);
|
|
675
|
+
const blocked = updateRunStatus(
|
|
676
|
+
runningManifest,
|
|
677
|
+
"blocked",
|
|
678
|
+
effectiveRuntime.reason ??
|
|
679
|
+
"Child worker execution is disabled; refusing to create no-op scaffold subagents.",
|
|
680
|
+
);
|
|
681
|
+
void appendEventAsync(blocked.eventsPath, {
|
|
682
|
+
type: "run.blocked",
|
|
683
|
+
runId: blocked.runId,
|
|
684
|
+
message: blocked.summary,
|
|
685
|
+
data: {
|
|
686
|
+
runtime: effectiveRuntime,
|
|
687
|
+
runtimeResolution: effectiveRuntimeResolution,
|
|
688
|
+
async: true,
|
|
689
|
+
diagnostics: {
|
|
690
|
+
requestedMode: effectiveRuntime.requestedMode,
|
|
691
|
+
workersDisabled:
|
|
692
|
+
executedConfig.executeWorkers === false,
|
|
693
|
+
envCrew: process.env.PI_CREW_EXECUTE_WORKERS,
|
|
694
|
+
envTeams: process.env.PI_TEAMS_EXECUTE_WORKERS,
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
});
|
|
406
698
|
unregisterActiveRun(blocked.runId);
|
|
407
|
-
return result(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
699
|
+
return result(
|
|
700
|
+
[
|
|
701
|
+
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
702
|
+
`Runtime: ${effectiveRuntime.kind} (requested ${effectiveRuntime.requestedMode})`,
|
|
703
|
+
`Reason: ${effectiveRuntime.reason ?? "unknown"}`,
|
|
704
|
+
`Config: executeWorkers=${executedConfig.executeWorkers ?? "<default>"}, runtime.mode=${executedConfig.runtime?.mode ?? "<default>"}`,
|
|
705
|
+
`Env: PI_CREW_EXECUTE_WORKERS=${process.env.PI_CREW_EXECUTE_WORKERS ?? "<unset>"}, PI_TEAMS_EXECUTE_WORKERS=${process.env.PI_TEAMS_EXECUTE_WORKERS ?? "<unset>"}`,
|
|
706
|
+
].join("\n"),
|
|
707
|
+
{
|
|
708
|
+
action: "run",
|
|
709
|
+
status: "error",
|
|
710
|
+
runId: blocked.runId,
|
|
711
|
+
artifactsRoot: blocked.artifactsRoot,
|
|
712
|
+
},
|
|
713
|
+
true,
|
|
714
|
+
);
|
|
414
715
|
}
|
|
415
716
|
const spawned = await spawnBackgroundTeamRun(effectiveManifest);
|
|
416
|
-
const asyncManifest = {
|
|
717
|
+
const asyncManifest = {
|
|
718
|
+
...effectiveManifest,
|
|
719
|
+
async: {
|
|
720
|
+
pid: spawned.pid,
|
|
721
|
+
logPath: spawned.logPath,
|
|
722
|
+
spawnedAt: new Date().toISOString(),
|
|
723
|
+
},
|
|
724
|
+
};
|
|
417
725
|
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
418
|
-
void appendEventAsync(effectiveManifest.eventsPath, {
|
|
726
|
+
void appendEventAsync(effectiveManifest.eventsPath, {
|
|
727
|
+
type: "async.spawned",
|
|
728
|
+
runId: effectiveManifest.runId,
|
|
729
|
+
data: { pid: spawned.pid, logPath: spawned.logPath },
|
|
730
|
+
});
|
|
419
731
|
ctx.onRunStarted?.(effectiveManifest.runId);
|
|
420
|
-
scheduleBackgroundEarlyExitGuard(
|
|
732
|
+
scheduleBackgroundEarlyExitGuard(
|
|
733
|
+
resolvedCtx.cwd,
|
|
734
|
+
effectiveManifest.runId,
|
|
735
|
+
spawned.pid,
|
|
736
|
+
spawned.logPath,
|
|
737
|
+
);
|
|
421
738
|
// Wait for the async run to complete and return actual results.
|
|
422
739
|
try {
|
|
423
|
-
const completed = await waitForRun(
|
|
424
|
-
|
|
740
|
+
const completed = await waitForRun(
|
|
741
|
+
updatedManifest.runId,
|
|
742
|
+
resolvedCtx.cwd,
|
|
743
|
+
{ timeoutMs: 3600000 },
|
|
744
|
+
);
|
|
745
|
+
const metrics = collectRunMetrics(
|
|
746
|
+
resolvedCtx.cwd,
|
|
747
|
+
completed.manifest.runId,
|
|
748
|
+
);
|
|
425
749
|
const lines: string[] = [
|
|
426
750
|
`pi-crew run ${completed.manifest.status}: ${completed.manifest.runId} (${team.name})`,
|
|
427
751
|
`Goal: ${goal.slice(0, 100)}`,
|
|
428
752
|
];
|
|
429
753
|
if (metrics) {
|
|
430
754
|
lines.push("");
|
|
431
|
-
lines.push(
|
|
755
|
+
lines.push(
|
|
756
|
+
`Metrics: ${metrics.completedCount}/${metrics.taskCount} tasks, ${metrics.totalTokens} tokens, ${metrics.durationMs}ms, consistency=${metrics.consistencyScore}`,
|
|
757
|
+
);
|
|
432
758
|
}
|
|
433
759
|
|
|
434
760
|
if (completed.tasks.length > 0) {
|
|
@@ -439,8 +765,14 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
439
765
|
);
|
|
440
766
|
if (summaryArtifact) {
|
|
441
767
|
try {
|
|
442
|
-
const sumPath = path.join(
|
|
443
|
-
|
|
768
|
+
const sumPath = path.join(
|
|
769
|
+
completed.manifest.artifactsRoot,
|
|
770
|
+
summaryArtifact.path,
|
|
771
|
+
);
|
|
772
|
+
summaryContent = fs
|
|
773
|
+
.readFileSync(sumPath, "utf-8")
|
|
774
|
+
.trim()
|
|
775
|
+
.slice(0, 4000);
|
|
444
776
|
} catch {
|
|
445
777
|
/* summary unavailable */
|
|
446
778
|
}
|
|
@@ -457,22 +789,34 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
457
789
|
// inside artifactsRoot via the project's safe-path primitive. Rejects
|
|
458
790
|
// absolute paths (/etc/passwd) and ../ traversal that the old
|
|
459
791
|
// path.isAbsolute shortcut + bare path.join allowed.
|
|
460
|
-
const resPath = resolveRealContainedPath(
|
|
461
|
-
|
|
792
|
+
const resPath = resolveRealContainedPath(
|
|
793
|
+
completed.manifest.artifactsRoot,
|
|
794
|
+
task.resultArtifact.path,
|
|
795
|
+
);
|
|
796
|
+
resultExcerpt = fs
|
|
797
|
+
.readFileSync(resPath, "utf-8")
|
|
798
|
+
.trim()
|
|
799
|
+
.slice(0, 2000);
|
|
462
800
|
} catch {
|
|
463
801
|
resultExcerpt = "(result unavailable)";
|
|
464
802
|
}
|
|
465
803
|
}
|
|
466
804
|
const shortResult = resultExcerpt.slice(0, 500);
|
|
467
805
|
const statusTag =
|
|
468
|
-
task.status === "completed"
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
806
|
+
task.status === "completed"
|
|
807
|
+
? "✓"
|
|
808
|
+
: task.status === "failed"
|
|
809
|
+
? "✗"
|
|
810
|
+
: task.status === "cancelled"
|
|
811
|
+
? "⊘"
|
|
812
|
+
: "·";
|
|
472
813
|
taskLines.push(
|
|
473
814
|
`- ${statusTag} ${task.id} [${task.role}]: ${task.status}${shortResult ? " — " + shortResult : ""}${task.error ? ` | Error: ${task.error.slice(0, 200)}` : ""}`,
|
|
474
815
|
);
|
|
475
|
-
if (
|
|
816
|
+
if (
|
|
817
|
+
task.status === "failed" ||
|
|
818
|
+
task.status === "needs_attention"
|
|
819
|
+
) {
|
|
476
820
|
failedCount++;
|
|
477
821
|
failedIds.push(task.id);
|
|
478
822
|
}
|
|
@@ -505,10 +849,25 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
505
849
|
);
|
|
506
850
|
}
|
|
507
851
|
|
|
508
|
-
const runFailed =
|
|
509
|
-
|
|
852
|
+
const runFailed =
|
|
853
|
+
completed.manifest.status === "failed" ||
|
|
854
|
+
completed.manifest.status === "blocked";
|
|
855
|
+
return result(
|
|
856
|
+
lines.join("\n"),
|
|
857
|
+
{
|
|
858
|
+
action: "run",
|
|
859
|
+
status: runFailed ? "error" : "ok",
|
|
860
|
+
runId: completed.manifest.runId,
|
|
861
|
+
artifactsRoot: completed.manifest.artifactsRoot,
|
|
862
|
+
metrics,
|
|
863
|
+
},
|
|
864
|
+
runFailed,
|
|
865
|
+
);
|
|
510
866
|
} catch (waitError: unknown) {
|
|
511
|
-
const errorMessage =
|
|
867
|
+
const errorMessage =
|
|
868
|
+
waitError instanceof Error
|
|
869
|
+
? waitError.message
|
|
870
|
+
: String(waitError);
|
|
512
871
|
return result(
|
|
513
872
|
[
|
|
514
873
|
`pi-crew run timed out or failed: ${updatedManifest.runId}`,
|
|
@@ -520,34 +879,91 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
520
879
|
`State: ${updatedManifest.stateRoot}`,
|
|
521
880
|
`Background log: ${spawned.logPath}`,
|
|
522
881
|
].join("\n"),
|
|
523
|
-
{
|
|
882
|
+
{
|
|
883
|
+
action: "run",
|
|
884
|
+
status: "error",
|
|
885
|
+
runId: updatedManifest.runId,
|
|
886
|
+
artifactsRoot: updatedManifest.artifactsRoot,
|
|
887
|
+
},
|
|
524
888
|
true,
|
|
525
889
|
);
|
|
526
890
|
}
|
|
527
891
|
}
|
|
528
892
|
|
|
529
893
|
if (runtime.safety === "blocked") {
|
|
530
|
-
const runningManifest = updateRunStatus(
|
|
531
|
-
|
|
532
|
-
|
|
894
|
+
const runningManifest = updateRunStatus(
|
|
895
|
+
executionManifest,
|
|
896
|
+
"running",
|
|
897
|
+
"Checking worker runtime availability.",
|
|
898
|
+
);
|
|
899
|
+
const blocked = updateRunStatus(
|
|
900
|
+
runningManifest,
|
|
901
|
+
"blocked",
|
|
902
|
+
runtime.reason ??
|
|
903
|
+
"Child worker execution is disabled; refusing to create no-op scaffold subagents.",
|
|
904
|
+
);
|
|
905
|
+
void appendEventAsync(blocked.eventsPath, {
|
|
906
|
+
type: "run.blocked",
|
|
907
|
+
runId: blocked.runId,
|
|
908
|
+
message: blocked.summary,
|
|
909
|
+
data: {
|
|
910
|
+
runtime,
|
|
911
|
+
runtimeResolution,
|
|
912
|
+
diagnostics: {
|
|
913
|
+
requestedMode: runtime.requestedMode,
|
|
914
|
+
workersDisabled: executedConfig.executeWorkers === false,
|
|
915
|
+
envCrew: process.env.PI_CREW_EXECUTE_WORKERS,
|
|
916
|
+
envTeams: process.env.PI_TEAMS_EXECUTE_WORKERS,
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
});
|
|
533
920
|
unregisterActiveRun(blocked.runId);
|
|
534
|
-
return result(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
921
|
+
return result(
|
|
922
|
+
[
|
|
923
|
+
`Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
|
|
924
|
+
`Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
|
|
925
|
+
`Reason: ${runtime.reason ?? "unknown"}`,
|
|
926
|
+
`Config: executeWorkers=${executedConfig.executeWorkers ?? "<default>"}, runtime.mode=${executedConfig.runtime?.mode ?? "<default>"}`,
|
|
927
|
+
`Env: PI_CREW_EXECUTE_WORKERS=${process.env.PI_CREW_EXECUTE_WORKERS ?? "<unset>"}, PI_TEAMS_EXECUTE_WORKERS=${process.env.PI_TEAMS_EXECUTE_WORKERS ?? "<unset>"}`,
|
|
928
|
+
"",
|
|
929
|
+
"To run effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
|
|
930
|
+
"Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
|
|
931
|
+
].join("\n"),
|
|
932
|
+
{
|
|
933
|
+
action: "run",
|
|
934
|
+
status: "error",
|
|
935
|
+
runId: blocked.runId,
|
|
936
|
+
artifactsRoot: blocked.artifactsRoot,
|
|
937
|
+
},
|
|
938
|
+
true,
|
|
939
|
+
);
|
|
544
940
|
}
|
|
545
941
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
546
942
|
if (executeWorkers && ctx.startForegroundRun) {
|
|
547
943
|
ctx.onRunStarted?.(updatedManifest.runId);
|
|
548
944
|
ctx.startForegroundRun(async (signal) => {
|
|
549
945
|
try {
|
|
550
|
-
await executeTeamRun({
|
|
946
|
+
await executeTeamRun({
|
|
947
|
+
manifest: executionManifest,
|
|
948
|
+
tasks,
|
|
949
|
+
team,
|
|
950
|
+
workflow,
|
|
951
|
+
agents,
|
|
952
|
+
executeWorkers,
|
|
953
|
+
limits: executedConfig.limits,
|
|
954
|
+
runtime,
|
|
955
|
+
runtimeConfig: executedConfig.runtime,
|
|
956
|
+
parentContext: buildParentContext(ctx),
|
|
957
|
+
parentModel: ctx.model,
|
|
958
|
+
modelRegistry: ctx.modelRegistry,
|
|
959
|
+
modelOverride: params.model,
|
|
960
|
+
skillOverride,
|
|
961
|
+
signal,
|
|
962
|
+
reliability: executedConfig.reliability,
|
|
963
|
+
metricRegistry: ctx.metricRegistry,
|
|
964
|
+
onJsonEvent: ctx.onJsonEvent,
|
|
965
|
+
workspaceId: ctx.sessionId ?? ctx.cwd,
|
|
966
|
+
});
|
|
551
967
|
} finally {
|
|
552
968
|
unregisterActiveRun(updatedManifest.runId);
|
|
553
969
|
}
|
|
@@ -555,15 +971,24 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
555
971
|
|
|
556
972
|
// Wait for the foreground run to complete and return actual results.
|
|
557
973
|
try {
|
|
558
|
-
const completed = await waitForRun(
|
|
559
|
-
|
|
974
|
+
const completed = await waitForRun(
|
|
975
|
+
updatedManifest.runId,
|
|
976
|
+
resolvedCtx.cwd,
|
|
977
|
+
{ timeoutMs: 3600000 },
|
|
978
|
+
);
|
|
979
|
+
const metrics = collectRunMetrics(
|
|
980
|
+
resolvedCtx.cwd,
|
|
981
|
+
completed.manifest.runId,
|
|
982
|
+
);
|
|
560
983
|
const lines: string[] = [
|
|
561
984
|
`pi-crew run ${completed.manifest.status}: ${completed.manifest.runId} (${team.name})`,
|
|
562
985
|
`Goal: ${goal.slice(0, 100)}`,
|
|
563
986
|
];
|
|
564
987
|
if (metrics) {
|
|
565
988
|
lines.push("");
|
|
566
|
-
lines.push(
|
|
989
|
+
lines.push(
|
|
990
|
+
`Metrics: ${metrics.completedCount}/${metrics.taskCount} tasks, ${metrics.totalTokens} tokens, ${metrics.durationMs}ms, consistency=${metrics.consistencyScore}`,
|
|
991
|
+
);
|
|
567
992
|
}
|
|
568
993
|
|
|
569
994
|
if (completed.tasks.length > 0) {
|
|
@@ -574,8 +999,14 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
574
999
|
);
|
|
575
1000
|
if (summaryArtifact) {
|
|
576
1001
|
try {
|
|
577
|
-
const sumPath = path.join(
|
|
578
|
-
|
|
1002
|
+
const sumPath = path.join(
|
|
1003
|
+
completed.manifest.artifactsRoot,
|
|
1004
|
+
summaryArtifact.path,
|
|
1005
|
+
);
|
|
1006
|
+
summaryContent = fs
|
|
1007
|
+
.readFileSync(sumPath, "utf-8")
|
|
1008
|
+
.trim()
|
|
1009
|
+
.slice(0, 4000);
|
|
579
1010
|
} catch {
|
|
580
1011
|
/* summary unavailable */
|
|
581
1012
|
}
|
|
@@ -592,22 +1023,34 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
592
1023
|
// inside artifactsRoot via the project's safe-path primitive. Rejects
|
|
593
1024
|
// absolute paths (/etc/passwd) and ../ traversal that the old
|
|
594
1025
|
// path.isAbsolute shortcut + bare path.join allowed.
|
|
595
|
-
const resPath = resolveRealContainedPath(
|
|
596
|
-
|
|
1026
|
+
const resPath = resolveRealContainedPath(
|
|
1027
|
+
completed.manifest.artifactsRoot,
|
|
1028
|
+
task.resultArtifact.path,
|
|
1029
|
+
);
|
|
1030
|
+
resultExcerpt = fs
|
|
1031
|
+
.readFileSync(resPath, "utf-8")
|
|
1032
|
+
.trim()
|
|
1033
|
+
.slice(0, 2000);
|
|
597
1034
|
} catch {
|
|
598
1035
|
resultExcerpt = "(result unavailable)";
|
|
599
1036
|
}
|
|
600
1037
|
}
|
|
601
1038
|
const shortResult = resultExcerpt.slice(0, 500);
|
|
602
1039
|
const statusTag =
|
|
603
|
-
task.status === "completed"
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
1040
|
+
task.status === "completed"
|
|
1041
|
+
? "✓"
|
|
1042
|
+
: task.status === "failed"
|
|
1043
|
+
? "✗"
|
|
1044
|
+
: task.status === "cancelled"
|
|
1045
|
+
? "⊘"
|
|
1046
|
+
: "·";
|
|
607
1047
|
taskLines.push(
|
|
608
1048
|
`- ${statusTag} ${task.id} [${task.role}]: ${task.status}${shortResult ? " — " + shortResult : ""}${task.error ? ` | Error: ${task.error.slice(0, 200)}` : ""}`,
|
|
609
1049
|
);
|
|
610
|
-
if (
|
|
1050
|
+
if (
|
|
1051
|
+
task.status === "failed" ||
|
|
1052
|
+
task.status === "needs_attention"
|
|
1053
|
+
) {
|
|
611
1054
|
failedCount++;
|
|
612
1055
|
failedIds.push(task.id);
|
|
613
1056
|
}
|
|
@@ -640,10 +1083,25 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
640
1083
|
);
|
|
641
1084
|
}
|
|
642
1085
|
|
|
643
|
-
const runFailed =
|
|
644
|
-
|
|
1086
|
+
const runFailed =
|
|
1087
|
+
completed.manifest.status === "failed" ||
|
|
1088
|
+
completed.manifest.status === "blocked";
|
|
1089
|
+
return result(
|
|
1090
|
+
lines.join("\n"),
|
|
1091
|
+
{
|
|
1092
|
+
action: "run",
|
|
1093
|
+
status: runFailed ? "error" : "ok",
|
|
1094
|
+
runId: completed.manifest.runId,
|
|
1095
|
+
artifactsRoot: completed.manifest.artifactsRoot,
|
|
1096
|
+
metrics,
|
|
1097
|
+
},
|
|
1098
|
+
runFailed,
|
|
1099
|
+
);
|
|
645
1100
|
} catch (waitError: unknown) {
|
|
646
|
-
const errorMessage =
|
|
1101
|
+
const errorMessage =
|
|
1102
|
+
waitError instanceof Error
|
|
1103
|
+
? waitError.message
|
|
1104
|
+
: String(waitError);
|
|
647
1105
|
return result(
|
|
648
1106
|
[
|
|
649
1107
|
`pi-crew run timed out or failed: ${updatedManifest.runId}`,
|
|
@@ -654,14 +1112,39 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
654
1112
|
`Check status with: team status runId=${updatedManifest.runId}`,
|
|
655
1113
|
`State: ${updatedManifest.stateRoot}`,
|
|
656
1114
|
].join("\n"),
|
|
657
|
-
{
|
|
1115
|
+
{
|
|
1116
|
+
action: "run",
|
|
1117
|
+
status: "error",
|
|
1118
|
+
runId: updatedManifest.runId,
|
|
1119
|
+
artifactsRoot: updatedManifest.artifactsRoot,
|
|
1120
|
+
},
|
|
658
1121
|
true,
|
|
659
1122
|
);
|
|
660
1123
|
}
|
|
661
1124
|
}
|
|
662
1125
|
let executed: Awaited<ReturnType<typeof executeTeamRun>>;
|
|
663
1126
|
try {
|
|
664
|
-
executed = await executeTeamRun({
|
|
1127
|
+
executed = await executeTeamRun({
|
|
1128
|
+
manifest: executionManifest,
|
|
1129
|
+
tasks,
|
|
1130
|
+
team,
|
|
1131
|
+
workflow,
|
|
1132
|
+
agents,
|
|
1133
|
+
executeWorkers,
|
|
1134
|
+
limits: executedConfig.limits,
|
|
1135
|
+
runtime,
|
|
1136
|
+
runtimeConfig: executedConfig.runtime,
|
|
1137
|
+
parentContext: buildParentContext(ctx),
|
|
1138
|
+
parentModel: ctx.model,
|
|
1139
|
+
modelRegistry: ctx.modelRegistry,
|
|
1140
|
+
modelOverride: params.model,
|
|
1141
|
+
skillOverride,
|
|
1142
|
+
signal: ctx.signal,
|
|
1143
|
+
reliability: executedConfig.reliability,
|
|
1144
|
+
metricRegistry: ctx.metricRegistry,
|
|
1145
|
+
onJsonEvent: ctx.onJsonEvent,
|
|
1146
|
+
workspaceId: ctx.cwd,
|
|
1147
|
+
});
|
|
665
1148
|
} finally {
|
|
666
1149
|
unregisterActiveRun(updatedManifest.runId);
|
|
667
1150
|
}
|
|
@@ -681,5 +1164,18 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
681
1164
|
? "Experimental live-session worker execution was enabled."
|
|
682
1165
|
: "Safe scaffold mode: child Pi workers were not launched because runtime.mode=scaffold or executeWorkers=false was configured.",
|
|
683
1166
|
].join("\n");
|
|
684
|
-
return result(
|
|
1167
|
+
return result(
|
|
1168
|
+
text,
|
|
1169
|
+
{
|
|
1170
|
+
action: "run",
|
|
1171
|
+
status: executed.manifest.status === "failed" ? "error" : "ok",
|
|
1172
|
+
runId: executed.manifest.runId,
|
|
1173
|
+
artifactsRoot: executed.manifest.artifactsRoot,
|
|
1174
|
+
metrics: collectRunMetrics(
|
|
1175
|
+
resolvedCtx.cwd,
|
|
1176
|
+
executed.manifest.runId,
|
|
1177
|
+
),
|
|
1178
|
+
},
|
|
1179
|
+
executed.manifest.status === "failed",
|
|
1180
|
+
);
|
|
685
1181
|
}
|