pi-crew 0.1.24 → 0.1.26
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/docs/refactor-tasks-phase3.md +394 -0
- package/docs/refactor-tasks-phase4.md +564 -0
- package/docs/refactor-tasks-phase5.md +402 -0
- package/docs/refactor-tasks.md +1484 -0
- package/package.json +98 -95
- package/src/agents/agent-config.ts +30 -30
- package/src/config/config.ts +153 -89
- package/src/config/defaults.ts +60 -0
- package/src/extension/autonomous-policy.ts +1 -1
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +15 -2
- package/src/extension/register.ts +124 -170
- package/src/extension/registration/command-utils.ts +54 -0
- package/src/extension/registration/subagent-helpers.ts +70 -0
- package/src/extension/registration/viewers.ts +32 -0
- package/src/extension/result-watcher.ts +98 -89
- package/src/extension/team-tool/api.ts +276 -0
- package/src/extension/team-tool/config-patch.ts +36 -0
- package/src/extension/team-tool/context.ts +48 -0
- package/src/extension/team-tool/doctor.ts +178 -0
- package/src/extension/team-tool/run.ts +133 -0
- package/src/extension/team-tool-types.ts +6 -0
- package/src/extension/team-tool.ts +31 -623
- package/src/extension/tool-result.ts +16 -16
- package/src/runtime/async-runner.ts +42 -60
- package/src/runtime/child-pi.ts +434 -332
- package/src/runtime/concurrency.ts +50 -42
- package/src/runtime/crew-agent-records.ts +166 -156
- package/src/runtime/manifest-cache.ts +214 -0
- package/src/runtime/parallel-utils.ts +99 -0
- package/src/runtime/post-exit-stdio-guard.ts +86 -0
- package/src/runtime/runtime-resolver.ts +77 -74
- package/src/runtime/subagent-manager.ts +291 -236
- package/src/runtime/task-graph-scheduler.ts +122 -107
- package/src/runtime/team-runner.ts +46 -51
- package/src/schema/config-schema.ts +92 -0
- package/src/state/artifact-store.ts +108 -36
- package/src/state/atomic-write.ts +114 -49
- package/src/state/event-log.ts +189 -138
- package/src/state/jsonl-writer.ts +77 -0
- package/src/state/locks.ts +149 -40
- package/src/state/mailbox.ts +200 -188
- package/src/state/state-store.ts +104 -15
- package/src/teams/discover-teams.ts +94 -84
- package/src/teams/team-config.ts +26 -22
- package/src/ui/crew-footer.ts +101 -0
- package/src/ui/crew-select-list.ts +111 -0
- package/src/ui/crew-widget.ts +285 -219
- package/src/ui/dynamic-border.ts +25 -0
- package/src/ui/layout-primitives.ts +106 -0
- package/src/ui/live-run-sidebar.ts +163 -95
- package/src/ui/loaders.ts +158 -0
- package/src/ui/mascot.ts +441 -0
- package/src/ui/powerbar-publisher.ts +94 -71
- package/src/ui/render-diff.ts +119 -0
- package/src/ui/run-dashboard.ts +155 -120
- package/src/ui/status-colors.ts +54 -0
- package/src/ui/syntax-highlight.ts +116 -0
- package/src/ui/theme-adapter.ts +190 -0
- package/src/ui/transcript-viewer.ts +194 -111
- package/src/utils/completion-dedupe.ts +63 -0
- package/src/utils/file-coalescer.ts +84 -33
- package/src/utils/fs-watch.ts +31 -0
- package/src/utils/git.ts +262 -0
- package/src/utils/internal-error.ts +6 -0
- package/src/utils/paths.ts +33 -15
- package/src/utils/sleep.ts +32 -0
- package/src/utils/timings.ts +31 -0
- package/src/utils/visual.ts +159 -0
- package/src/workflows/discover-workflows.ts +109 -101
- package/src/workflows/workflow-config.ts +25 -24
- package/src/workflows/workflow-serializer.ts +32 -31
- package/tsconfig.json +19 -19
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { TeamToolDetails } from "../team-tool-types.ts";
|
|
3
|
+
import { toolResult, type PiTeamsToolResult } from "../tool-result.ts";
|
|
4
|
+
|
|
5
|
+
export type TeamContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "model">> & {
|
|
6
|
+
modelRegistry?: unknown;
|
|
7
|
+
sessionManager?: { getBranch?: () => unknown[] };
|
|
8
|
+
events?: { emit?: (event: string, data: unknown) => void };
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
startForegroundRun?: (runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
|
|
11
|
+
onRunStarted?: (runId: string) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function result(text: string, details: TeamToolDetails, isError = false): PiTeamsToolResult {
|
|
15
|
+
return toolResult(text, details, isError);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatScoped(name: string, source: string, description: string): string {
|
|
19
|
+
return `- ${name} (${source}): ${description}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractTextContent(content: unknown): string {
|
|
23
|
+
if (typeof content === "string") return content;
|
|
24
|
+
if (!Array.isArray(content)) return "";
|
|
25
|
+
return content.map((part) => part && typeof part === "object" && !Array.isArray(part) && typeof (part as { text?: unknown }).text === "string" ? (part as { text: string }).text : "").filter(Boolean).join("\n");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildParentContext(ctx: TeamContext): string | undefined {
|
|
29
|
+
const branch = ctx.sessionManager?.getBranch?.();
|
|
30
|
+
if (!Array.isArray(branch) || branch.length === 0) return undefined;
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
for (const entry of branch.slice(-20)) {
|
|
33
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
34
|
+
const record = entry as { type?: unknown; message?: unknown; summary?: unknown };
|
|
35
|
+
if (record.type === "compaction" && typeof record.summary === "string") parts.push(`[Summary]: ${record.summary}`);
|
|
36
|
+
const message = record.message && typeof record.message === "object" && !Array.isArray(record.message) ? record.message as { role?: unknown; content?: unknown } : undefined;
|
|
37
|
+
if (!message || (message.role !== "user" && message.role !== "assistant")) continue;
|
|
38
|
+
const text = extractTextContent(message.content).trim();
|
|
39
|
+
if (text) parts.push(`[${message.role === "user" ? "User" : "Assistant"}]: ${text}`);
|
|
40
|
+
}
|
|
41
|
+
if (!parts.length) return undefined;
|
|
42
|
+
return [`# Parent Conversation Context`, "The following context was inherited from the parent Pi session. Treat it as reference-only.", "", parts.join("\n\n")].join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function configRecord(config: unknown): Record<string, unknown> {
|
|
46
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) return {};
|
|
47
|
+
return config as Record<string, unknown>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { allAgents, discoverAgents } from "../../agents/discover-agents.ts";
|
|
5
|
+
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
6
|
+
import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
7
|
+
import { loadConfig } from "../../config/config.ts";
|
|
8
|
+
import { projectPiRoot, userPiRoot } from "../../utils/paths.ts";
|
|
9
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
10
|
+
import { getPiSpawnCommand } from "../../runtime/pi-spawn.ts";
|
|
11
|
+
import { validateResources } from "../validate-resources.ts";
|
|
12
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
13
|
+
import { configRecord, result, type TeamContext } from "./context.ts";
|
|
14
|
+
|
|
15
|
+
interface DoctorCheck {
|
|
16
|
+
label: string;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
detail: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function firstOutputLine(stdout: string | null | undefined, stderr: string | null | undefined): string {
|
|
22
|
+
const output = `${stdout ?? ""}\n${stderr ?? ""}`.trim();
|
|
23
|
+
return output.split(/\r?\n/).find((line) => line.trim().length > 0)?.trim() ?? "available";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function commandExists(command: string, args: string[]): { ok: boolean; detail: string } {
|
|
27
|
+
try {
|
|
28
|
+
const output = spawnSync(command, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
29
|
+
if (output.error) {
|
|
30
|
+
return { ok: false, detail: output.error.message };
|
|
31
|
+
}
|
|
32
|
+
if (output.status !== 0) {
|
|
33
|
+
return { ok: false, detail: firstOutputLine(output.stdout, output.stderr) || `status ${output.status}` };
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, detail: firstOutputLine(output.stdout, output.stderr) };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function piCommandExists(): { ok: boolean; detail: string } {
|
|
42
|
+
const spec = getPiSpawnCommand(["--version"]);
|
|
43
|
+
const output = commandExists(spec.command, spec.args);
|
|
44
|
+
if (!output.ok) return output;
|
|
45
|
+
const executable = spec.command === "pi" ? "pi" : `${spec.command} ${spec.args[0] ?? ""}`.trim();
|
|
46
|
+
return { ok: true, detail: `${output.detail} (${executable})` };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function checkWritableDir(dir: string): { ok: boolean; detail: string } {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(dir)) return { ok: false, detail: `${dir}: missing` };
|
|
52
|
+
if (!fs.statSync(dir).isDirectory()) return { ok: false, detail: `${dir}: not a directory` };
|
|
53
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
54
|
+
return { ok: true, detail: dir };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
return { ok: false, detail: `${dir}: ${message}` };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function makeLine(check: DoctorCheck): string {
|
|
62
|
+
return `- ${check.ok ? "OK" : "FAIL"} ${check.label}: ${check.detail}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function section(title: string, checks: () => DoctorCheck[]): string[] {
|
|
66
|
+
try {
|
|
67
|
+
return [title, ...checks().map(makeLine)];
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
70
|
+
return [title, `- FAIL ${title}: ${detail}`];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TeamDoctorReportInput {
|
|
75
|
+
cwd: string;
|
|
76
|
+
configPath: string;
|
|
77
|
+
configErrors: string[];
|
|
78
|
+
configWarnings: string[];
|
|
79
|
+
model?: { provider: string; id: string };
|
|
80
|
+
validationErrors: number;
|
|
81
|
+
validationWarnings: number;
|
|
82
|
+
smokeChildPi?: { ok: boolean; detail: string };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TeamDoctorReport {
|
|
86
|
+
text: string;
|
|
87
|
+
hasErrors: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function buildTeamDoctorReport(input: TeamDoctorReportInput): TeamDoctorReport {
|
|
91
|
+
const sections = [
|
|
92
|
+
section("Runtime", () => {
|
|
93
|
+
const git = commandExists("git", ["--version"]);
|
|
94
|
+
const pi = piCommandExists();
|
|
95
|
+
return [
|
|
96
|
+
{ label: "cwd", ok: true, detail: input.cwd },
|
|
97
|
+
{ label: "platform", ok: true, detail: `${process.platform}/${process.arch} node=${process.version}` },
|
|
98
|
+
{ label: "pi command", ok: pi.ok, detail: pi.detail },
|
|
99
|
+
{ label: "git command", ok: git.ok, detail: git.detail },
|
|
100
|
+
{ label: "config", ok: input.configErrors.length === 0, detail: `${input.configPath} (${input.configErrors.length} errors)` },
|
|
101
|
+
{ label: "model", ok: true, detail: input.model ? `${input.model.provider}/${input.model.id}` : "not available in this context" },
|
|
102
|
+
{ label: "config warnings", ok: true, detail: `${input.configWarnings.length} warnings` },
|
|
103
|
+
];
|
|
104
|
+
}),
|
|
105
|
+
section("Filesystem", () => {
|
|
106
|
+
const userWritable = checkWritableDir(path.join(userPiRoot(), "extensions", "pi-crew"));
|
|
107
|
+
const projectWritable = checkWritableDir(path.join(projectPiRoot(input.cwd), "teams"));
|
|
108
|
+
return [
|
|
109
|
+
{ label: "user state", ok: userWritable.ok, detail: userWritable.detail },
|
|
110
|
+
{ label: "project state", ok: projectWritable.ok, detail: projectWritable.detail },
|
|
111
|
+
{ label: "project state root", ok: true, detail: path.join(projectPiRoot(input.cwd), "teams") },
|
|
112
|
+
{ label: "artifacts root", ok: true, detail: path.join(projectPiRoot(input.cwd), "artifacts") },
|
|
113
|
+
];
|
|
114
|
+
}),
|
|
115
|
+
section("Discovery", () => {
|
|
116
|
+
const discoveredAgents = allAgents(discoverAgents(input.cwd));
|
|
117
|
+
const discoveredTeams = allTeams(discoverTeams(input.cwd));
|
|
118
|
+
const discoveredWorkflows = allWorkflows(discoverWorkflows(input.cwd));
|
|
119
|
+
const agentModelHints = discoveredAgents.filter((agent) => agent.model || agent.fallbackModels?.length).length;
|
|
120
|
+
return [
|
|
121
|
+
{ label: "agents", ok: true, detail: `${discoveredAgents.length} discovered` },
|
|
122
|
+
{ label: "teams", ok: true, detail: `${discoveredTeams.length} discovered` },
|
|
123
|
+
{ label: "workflows", ok: true, detail: `${discoveredWorkflows.length} discovered` },
|
|
124
|
+
{ label: "resource model hints", ok: true, detail: `${agentModelHints} agents declare model/fallback preferences` },
|
|
125
|
+
];
|
|
126
|
+
}),
|
|
127
|
+
section("Resource validation", () => [{
|
|
128
|
+
label: "resource validation",
|
|
129
|
+
ok: input.validationErrors === 0,
|
|
130
|
+
detail: `${input.validationErrors} errors, ${input.validationWarnings} warnings`,
|
|
131
|
+
}]),
|
|
132
|
+
];
|
|
133
|
+
if (input.smokeChildPi) {
|
|
134
|
+
sections.push([`Child check`, `- ${input.smokeChildPi.ok ? "OK" : "FAIL"} child Pi smoke: ${input.smokeChildPi.detail}`]);
|
|
135
|
+
}
|
|
136
|
+
const lines = ["pi-crew doctor report"];
|
|
137
|
+
for (const block of sections) {
|
|
138
|
+
if (block.length > 0) {
|
|
139
|
+
lines.push(...block);
|
|
140
|
+
lines.push("");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (lines.at(-1) === "") lines.pop();
|
|
144
|
+
const text = lines.join("\n");
|
|
145
|
+
return { text, hasErrors: sections.some((sectionLines) => sectionLines.some((line) => line.includes("FAIL"))) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function handleDoctor(ctx: TeamContext, params: TeamToolParamsValue = {}): PiTeamsToolResult {
|
|
149
|
+
const loadedConfig = loadConfig(ctx.cwd);
|
|
150
|
+
let smokeChildPi: { ok: boolean; detail: string } | undefined;
|
|
151
|
+
if (configRecord(params.config).smokeChildPi === true) {
|
|
152
|
+
try {
|
|
153
|
+
const spec = getPiSpawnCommand(["--mode", "json", "-p", "Reply with exactly PI-TEAMS-SMOKE-OK"]);
|
|
154
|
+
const output = execFileSync(spec.command, spec.args, {
|
|
155
|
+
cwd: ctx.cwd,
|
|
156
|
+
encoding: "utf-8",
|
|
157
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
158
|
+
timeout: 15_000,
|
|
159
|
+
}).trim();
|
|
160
|
+
smokeChildPi = { ok: output.includes("PI-TEAMS-SMOKE-OK"), detail: output.split("\n").slice(-1)[0] ?? "completed" };
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
smokeChildPi = { ok: false, detail: message };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const validation = validateResources(ctx.cwd);
|
|
167
|
+
const { text, hasErrors } = buildTeamDoctorReport({
|
|
168
|
+
cwd: ctx.cwd,
|
|
169
|
+
configPath: loadedConfig.path,
|
|
170
|
+
configErrors: loadedConfig.error ? [loadedConfig.error] : [],
|
|
171
|
+
configWarnings: loadedConfig.warnings ?? [],
|
|
172
|
+
model: ctx.model,
|
|
173
|
+
validationErrors: validation.issues.filter((issue) => issue.level === "error").length,
|
|
174
|
+
validationWarnings: validation.issues.filter((issue) => issue.level === "warning").length,
|
|
175
|
+
smokeChildPi,
|
|
176
|
+
});
|
|
177
|
+
return result(text, { action: "doctor", status: hasErrors ? "error" : "ok" }, hasErrors);
|
|
178
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
import { loadConfig } from "../../config/config.ts";
|
|
5
|
+
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
6
|
+
import { writeArtifact } from "../../state/artifact-store.ts";
|
|
7
|
+
import { createRunManifest } from "../../state/state-store.ts";
|
|
8
|
+
import { atomicWriteJson } from "../../state/atomic-write.ts";
|
|
9
|
+
import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
|
|
10
|
+
import { executeTeamRun } from "../../runtime/team-runner.ts";
|
|
11
|
+
import { spawnBackgroundTeamRun } from "../../runtime/async-runner.ts";
|
|
12
|
+
import { appendEvent } from "../../state/event-log.ts";
|
|
13
|
+
import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
|
|
14
|
+
import { expandParallelResearchWorkflow } from "../../runtime/parallel-research.ts";
|
|
15
|
+
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
16
|
+
import { buildParentContext, result, type TeamContext } from "./context.ts";
|
|
17
|
+
import { effectiveRunConfig } from "./config-patch.ts";
|
|
18
|
+
|
|
19
|
+
export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
20
|
+
const goal = params.goal ?? params.task;
|
|
21
|
+
if (!goal) return result("Run requires goal or task.", { action: "run", status: "error" }, true);
|
|
22
|
+
|
|
23
|
+
const teams = allTeams(discoverTeams(ctx.cwd));
|
|
24
|
+
const workflows = allWorkflows(discoverWorkflows(ctx.cwd));
|
|
25
|
+
const agents = allAgents(discoverAgents(ctx.cwd));
|
|
26
|
+
const directAgent = params.agent ? agents.find((item) => item.name === params.agent) : undefined;
|
|
27
|
+
if (params.agent && !directAgent) return result(`Agent '${params.agent}' not found.`, { action: "run", status: "error" }, true);
|
|
28
|
+
const teamName = params.team ?? "default";
|
|
29
|
+
const team = directAgent ? {
|
|
30
|
+
name: `direct-${directAgent.name}`,
|
|
31
|
+
description: `Direct subagent run for ${directAgent.name}`,
|
|
32
|
+
source: "builtin" as const,
|
|
33
|
+
filePath: "<generated>",
|
|
34
|
+
roles: [{ name: params.role ?? "agent", agent: directAgent.name, description: directAgent.description }],
|
|
35
|
+
defaultWorkflow: "direct-agent",
|
|
36
|
+
workspaceMode: params.workspaceMode,
|
|
37
|
+
} : teams.find((item) => item.name === teamName);
|
|
38
|
+
if (!team) return result(`Team '${teamName}' not found.`, { action: "run", status: "error" }, true);
|
|
39
|
+
const workflowName = directAgent ? "direct-agent" : params.workflow ?? team.defaultWorkflow ?? "default";
|
|
40
|
+
const baseWorkflow = directAgent ? {
|
|
41
|
+
name: "direct-agent",
|
|
42
|
+
description: `Direct task for ${directAgent.name}`,
|
|
43
|
+
source: "builtin" as const,
|
|
44
|
+
filePath: "<generated>",
|
|
45
|
+
steps: [{ id: "01_agent", role: params.role ?? "agent", task: "{goal}", model: params.model }],
|
|
46
|
+
} : workflows.find((item) => item.name === workflowName);
|
|
47
|
+
if (!baseWorkflow) return result(`Workflow '${workflowName}' not found.`, { action: "run", status: "error" }, true);
|
|
48
|
+
const workflow = directAgent ? baseWorkflow : expandParallelResearchWorkflow(baseWorkflow, ctx.cwd);
|
|
49
|
+
|
|
50
|
+
const validationErrors = validateWorkflowForTeam(workflow, team);
|
|
51
|
+
if (validationErrors.length > 0) {
|
|
52
|
+
return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...validationErrors.map((error) => `- ${error}`)].join("\n"), { action: "run", status: "error" }, true);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { manifest, tasks, paths } = createRunManifest({
|
|
56
|
+
cwd: ctx.cwd,
|
|
57
|
+
team,
|
|
58
|
+
workflow,
|
|
59
|
+
goal,
|
|
60
|
+
workspaceMode: params.workspaceMode,
|
|
61
|
+
});
|
|
62
|
+
const goalArtifact = writeArtifact(paths.artifactsRoot, {
|
|
63
|
+
kind: "prompt",
|
|
64
|
+
relativePath: "goal.md",
|
|
65
|
+
content: `${goal}\n`,
|
|
66
|
+
producer: "team-tool",
|
|
67
|
+
});
|
|
68
|
+
const updatedManifest = { ...manifest, artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
|
|
69
|
+
atomicWriteJson(paths.manifestPath, updatedManifest);
|
|
70
|
+
|
|
71
|
+
const loadedConfig = loadConfig(ctx.cwd);
|
|
72
|
+
const runAsync = params.async ?? loadedConfig.config.asyncByDefault ?? false;
|
|
73
|
+
if (runAsync) {
|
|
74
|
+
const spawned = spawnBackgroundTeamRun(updatedManifest);
|
|
75
|
+
const asyncManifest = { ...updatedManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
|
|
76
|
+
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
77
|
+
appendEvent(updatedManifest.eventsPath, { type: "async.spawned", runId: updatedManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
|
|
78
|
+
const text = [
|
|
79
|
+
`Started async pi-crew run ${updatedManifest.runId}.`,
|
|
80
|
+
`Team: ${team.name}`,
|
|
81
|
+
`Workflow: ${workflow.name}`,
|
|
82
|
+
`Status: ${updatedManifest.status}`,
|
|
83
|
+
`Tasks: ${tasks.length}`,
|
|
84
|
+
`State: ${updatedManifest.stateRoot}`,
|
|
85
|
+
`Artifacts: ${updatedManifest.artifactsRoot}`,
|
|
86
|
+
`Background log: ${spawned.logPath}`,
|
|
87
|
+
"",
|
|
88
|
+
`Check status with: team status runId=${updatedManifest.runId}`,
|
|
89
|
+
].join("\n");
|
|
90
|
+
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const runtime = await resolveCrewRuntime(effectiveRunConfig(loadedConfig.config, params.config));
|
|
94
|
+
const executeWorkers = runtime.kind !== "scaffold";
|
|
95
|
+
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
96
|
+
if (executeWorkers && ctx.startForegroundRun) {
|
|
97
|
+
ctx.onRunStarted?.(updatedManifest.runId);
|
|
98
|
+
ctx.startForegroundRun(async (signal) => {
|
|
99
|
+
await executeTeamRun({ manifest: updatedManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, signal });
|
|
100
|
+
}, updatedManifest.runId);
|
|
101
|
+
const text = [
|
|
102
|
+
`Started foreground pi-crew run ${updatedManifest.runId}.`,
|
|
103
|
+
`Team: ${team.name}`,
|
|
104
|
+
`Workflow: ${workflow.name}`,
|
|
105
|
+
"Status: running",
|
|
106
|
+
`Tasks: ${tasks.length}`,
|
|
107
|
+
`Runtime: ${runtime.kind}`,
|
|
108
|
+
`State: ${updatedManifest.stateRoot}`,
|
|
109
|
+
`Artifacts: ${updatedManifest.artifactsRoot}`,
|
|
110
|
+
"",
|
|
111
|
+
"The run continues in this Pi session without blocking the chat. It will be interrupted on session shutdown. Use /team-dashboard or /team-status to watch it.",
|
|
112
|
+
].join("\n");
|
|
113
|
+
return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
|
|
114
|
+
}
|
|
115
|
+
const executed = await executeTeamRun({ manifest: updatedManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, signal: ctx.signal });
|
|
116
|
+
const text = [
|
|
117
|
+
`Created pi-crew run ${executed.manifest.runId}.`,
|
|
118
|
+
`Team: ${team.name}`,
|
|
119
|
+
`Workflow: ${workflow.name}`,
|
|
120
|
+
`Status: ${executed.manifest.status}`,
|
|
121
|
+
`Tasks: ${executed.tasks.length}`,
|
|
122
|
+
`State: ${executed.manifest.stateRoot}`,
|
|
123
|
+
`Artifacts: ${executed.manifest.artifactsRoot}`,
|
|
124
|
+
"",
|
|
125
|
+
`Runtime: ${runtime.kind}${runtime.fallback ? ` (fallback from ${runtime.requestedMode})` : ""}${runtime.reason ? ` - ${runtime.reason}` : ""}`,
|
|
126
|
+
runtime.kind === "child-process"
|
|
127
|
+
? "Child Pi worker execution is enabled by default; each task is launched as a separate Pi process. Set runtime.mode=scaffold or executeWorkers=false only for dry runs."
|
|
128
|
+
: runtime.kind === "live-session"
|
|
129
|
+
? "Experimental live-session worker execution was enabled."
|
|
130
|
+
: "Safe scaffold mode: child Pi workers were not launched because runtime.mode=scaffold or executeWorkers=false was configured.",
|
|
131
|
+
].join("\n");
|
|
132
|
+
return result(text, { action: "run", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
|
|
133
|
+
}
|