pi-crew 0.6.1 → 0.6.4
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 +194 -0
- package/README.md +81 -33
- package/docs/issue-29-analysis.md +189 -0
- package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
- package/docs/ui-optimization-plan.md +447 -0
- package/package.json +2 -2
- package/src/config/config.ts +106 -15
- package/src/errors.ts +107 -0
- package/src/extension/async-notifier.ts +6 -2
- package/src/extension/crew-cleanup.ts +8 -5
- package/src/extension/management.ts +464 -109
- package/src/extension/register.ts +213 -35
- package/src/extension/registration/brief-tool-overrides.ts +400 -0
- package/src/extension/registration/commands.ts +27 -2
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +9 -4
- package/src/extension/registration/team-tool.ts +17 -10
- package/src/extension/registration/viewers.ts +2 -2
- package/src/extension/team-tool/api.ts +3 -3
- package/src/extension/team-tool/cancel.ts +3 -3
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/handle-schedule.ts +40 -0
- package/src/extension/team-tool/inspect.ts +3 -3
- package/src/extension/team-tool/lifecycle-actions.ts +4 -4
- package/src/extension/team-tool/respond.ts +2 -2
- package/src/extension/team-tool/run.ts +64 -14
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +173 -46
- package/src/hooks/registry.ts +77 -13
- package/src/hooks/types.ts +9 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +14 -0
- package/src/plugins/plugins/vitest.ts +14 -0
- package/src/runtime/async-runner.ts +35 -7
- package/src/runtime/background-runner.ts +529 -144
- package/src/runtime/chain-parser.ts +5 -1
- package/src/runtime/chain-runner.ts +9 -3
- package/src/runtime/checkpoint.ts +262 -180
- package/src/runtime/child-pi.ts +164 -37
- package/src/runtime/crash-recovery.ts +25 -14
- package/src/runtime/crew-agent-records.ts +2 -0
- package/src/runtime/diagnostic-export.ts +1 -1
- package/src/runtime/dynamic-script-runner.ts +10 -7
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/heartbeat-watcher.ts +19 -2
- package/src/runtime/intercom-bridge.ts +7 -0
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/manifest-cache.ts +4 -0
- package/src/runtime/orphan-worker-registry.ts +444 -0
- package/src/runtime/parent-guard.ts +70 -16
- package/src/runtime/pi-args.ts +437 -14
- package/src/runtime/pi-spawn.ts +1 -0
- package/src/runtime/post-checks.ts +1 -1
- package/src/runtime/retry-runner.ts +9 -3
- package/src/runtime/run-tracker.ts +38 -10
- package/src/runtime/sandbox.ts +17 -2
- package/src/runtime/scheduler.ts +25 -2
- package/src/runtime/skill-effectiveness.ts +105 -62
- package/src/runtime/skill-instructions.ts +110 -32
- package/src/runtime/stale-reconciler.ts +310 -69
- package/src/runtime/subagent-manager.ts +251 -57
- package/src/runtime/task-health.ts +76 -0
- package/src/runtime/task-id.ts +20 -0
- package/src/runtime/task-runner/live-executor.ts +1 -1
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +110 -6
- package/src/runtime/task-runner.ts +92 -70
- package/src/runtime/team-runner.ts +186 -20
- package/src/schema/team-tool-schema.ts +27 -9
- package/src/skills/discover-skills.ts +9 -3
- package/src/state/active-run-registry.ts +170 -38
- package/src/state/artifact-store.ts +25 -13
- package/src/state/atomic-write-v2.ts +86 -0
- package/src/state/atomic-write.ts +346 -55
- package/src/state/blob-store.ts +178 -10
- package/src/state/crew-init.ts +161 -28
- package/src/state/decision-ledger.ts +172 -111
- package/src/state/event-log-rotation.ts +82 -52
- package/src/state/event-log.ts +254 -70
- package/src/state/health-store.ts +71 -0
- package/src/state/locks.ts +102 -20
- package/src/state/mailbox.ts +45 -7
- package/src/state/observation-store.ts +4 -1
- package/src/state/run-graph.ts +141 -130
- package/src/state/run-metrics.ts +24 -8
- package/src/state/session-state-map.ts +51 -0
- package/src/state/state-store.ts +330 -43
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/loaders.ts +4 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +2 -2
- package/src/ui/run-snapshot-cache.ts +1 -1
- package/src/ui/status-colors.ts +5 -1
- package/src/ui/theme-adapter.ts +80 -1
- package/src/ui/tool-progress-formatter.ts +9 -5
- package/src/ui/tool-render.ts +4 -0
- package/src/ui/tool-renderers/brief-mode.ts +207 -0
- package/src/ui/tool-renderers/index.ts +627 -0
- package/src/ui/widget/index.ts +224 -0
- package/src/ui/widget/widget-formatters.ts +148 -0
- package/src/ui/widget/widget-model.ts +90 -0
- package/src/ui/widget/widget-renderer.ts +130 -0
- package/src/ui/widget/widget-types.ts +37 -0
- package/src/utils/env-filter.ts +66 -0
- package/src/utils/file-coalescer.ts +9 -1
- package/src/utils/guards.ts +110 -0
- package/src/utils/paths.ts +80 -5
- package/src/utils/redaction.ts +6 -1
- package/src/utils/safe-paths.ts +281 -10
- package/src/worktree/cleanup.ts +112 -24
- package/src/worktree/worktree-manager.ts +128 -15
- package/test-bugs-all.mjs +10 -6
- package/test-integration-check.ts +4 -0
|
@@ -163,6 +163,43 @@ function persistScheduledJob(cwd: string, job: import("../../runtime/scheduler.t
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/** Update an existing scheduled job in persistent settings. */
|
|
167
|
+
export function persistScheduledJobUpdate(cwd: string, job: import("../../runtime/scheduler.ts").ScheduledJob): void {
|
|
168
|
+
try {
|
|
169
|
+
const settings = loadCrewSettings(cwd);
|
|
170
|
+
const existingJobs: import("../../runtime/scheduler.ts").ScheduledJob[] = Array.isArray(
|
|
171
|
+
(settings as Record<string, unknown>).scheduledJobs,
|
|
172
|
+
)
|
|
173
|
+
? ((settings as Record<string, unknown>).scheduledJobs as import("../../runtime/scheduler.ts").ScheduledJob[])
|
|
174
|
+
: [];
|
|
175
|
+
const updated = existingJobs.map((j) => j.id === job.id ? job : j);
|
|
176
|
+
saveCrewSettings(
|
|
177
|
+
{ ...settings, scheduledJobs: updated } as Parameters<typeof saveCrewSettings>[0],
|
|
178
|
+
cwd,
|
|
179
|
+
);
|
|
180
|
+
} catch {
|
|
181
|
+
/* best-effort persistence */
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Remove a scheduled job from persistent settings. */
|
|
186
|
+
function persistScheduledJobRemove(cwd: string, jobId: string): void {
|
|
187
|
+
try {
|
|
188
|
+
const settings = loadCrewSettings(cwd);
|
|
189
|
+
const existingJobs: import("../../runtime/scheduler.ts").ScheduledJob[] = Array.isArray(
|
|
190
|
+
(settings as Record<string, unknown>).scheduledJobs,
|
|
191
|
+
)
|
|
192
|
+
? ((settings as Record<string, unknown>).scheduledJobs as import("../../runtime/scheduler.ts").ScheduledJob[])
|
|
193
|
+
: [];
|
|
194
|
+
saveCrewSettings(
|
|
195
|
+
{ ...settings, scheduledJobs: existingJobs.filter((j) => j.id !== jobId) } as Parameters<typeof saveCrewSettings>[0],
|
|
196
|
+
cwd,
|
|
197
|
+
);
|
|
198
|
+
} catch {
|
|
199
|
+
/* best-effort persistence */
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
166
203
|
export function handleListScheduled(_params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
167
204
|
const scheduler = getCrewScheduler();
|
|
168
205
|
if (!scheduler) return result("Scheduler not running.", { action: "scheduled", status: "error" }, true);
|
|
@@ -177,6 +214,9 @@ export function handleListScheduled(_params: TeamToolParamsValue, ctx: TeamConte
|
|
|
177
214
|
` Next run: ${job.nextRun ?? "(unscheduled)"}`,
|
|
178
215
|
` Runs: ${job.runCount}, Last: ${job.lastRun ?? "(never)"} [${job.lastStatus ?? "?"}]`,
|
|
179
216
|
);
|
|
217
|
+
if (job.spawnedRunIds && job.spawnedRunIds.length > 0) {
|
|
218
|
+
lines.push(` Spawned runs: ${job.spawnedRunIds.join(", ")}`);
|
|
219
|
+
}
|
|
180
220
|
}
|
|
181
221
|
return result(lines.join("\n"), { action: "scheduled", status: "ok" });
|
|
182
222
|
}
|
|
@@ -10,7 +10,7 @@ export function handleEvents(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
10
10
|
if (!params.runId) return result("Events requires runId.", { action: "events", status: "error" }, true);
|
|
11
11
|
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
12
12
|
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "events", status: "error" }, true);
|
|
13
|
-
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
13
|
+
const loaded = loadRunManifestById(runCwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
14
14
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "events", status: "error" }, true);
|
|
15
15
|
const events = readEvents(loaded.manifest.eventsPath);
|
|
16
16
|
const lines = [`Events for ${loaded.manifest.runId}:`, ...(events.length ? events.map((event) => `${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}${event.data ? ` ${JSON.stringify(event.data)}` : ""}`) : ["(none)"])];
|
|
@@ -21,7 +21,7 @@ export function handleArtifacts(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
21
21
|
if (!params.runId) return result("Artifacts requires runId.", { action: "artifacts", status: "error" }, true);
|
|
22
22
|
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
23
23
|
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "artifacts", status: "error" }, true);
|
|
24
|
-
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
24
|
+
const loaded = loadRunManifestById(runCwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
25
25
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "artifacts", status: "error" }, true);
|
|
26
26
|
const lines = [`Artifacts for ${loaded.manifest.runId}:`, ...(loaded.manifest.artifacts.length ? loaded.manifest.artifacts.map((artifact) => `- ${artifact.kind}: ${artifact.path}${artifact.sizeBytes !== undefined ? ` (${artifact.sizeBytes} bytes)` : ""}${artifact.contentHash ? ` sha256=${artifact.contentHash.slice(0, 12)}` : ""}`) : ["- (none)"])];
|
|
27
27
|
return result(lines.join("\n"), { action: "artifacts", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
@@ -31,7 +31,7 @@ export function handleSummary(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
31
31
|
if (!params.runId) return result("Summary requires runId.", { action: "summary", status: "error" }, true);
|
|
32
32
|
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
33
33
|
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "summary", status: "error" }, true);
|
|
34
|
-
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
34
|
+
const loaded = loadRunManifestById(runCwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
35
35
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "summary", status: "error" }, true);
|
|
36
36
|
const usage = aggregateUsage(loaded.tasks);
|
|
37
37
|
const lines = [
|
|
@@ -17,7 +17,7 @@ import * as path from "node:path";
|
|
|
17
17
|
|
|
18
18
|
export function handleWorktrees(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
|
19
19
|
if (!params.runId) return result("Worktrees requires runId.", { action: "worktrees", status: "error" }, true);
|
|
20
|
-
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
20
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
21
21
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "worktrees", status: "error" }, true);
|
|
22
22
|
const withWorktrees = loaded.tasks.filter((task) => task.worktree);
|
|
23
23
|
const lines = [`Worktrees for ${loaded.manifest.runId}:`, ...(withWorktrees.length ? withWorktrees.map((task) => `- ${task.id}: ${task.worktree!.path} branch=${task.worktree!.branch} reused=${task.worktree!.reused ? "true" : "false"}`) : ["- (none)"])];
|
|
@@ -46,7 +46,7 @@ export function handleImport(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
46
46
|
|
|
47
47
|
export async function handleExport(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
48
48
|
if (!params.runId) return result("Export requires runId.", { action: "export", status: "error" }, true);
|
|
49
|
-
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
49
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
50
50
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "export", status: "error" }, true);
|
|
51
51
|
|
|
52
52
|
// SECURITY: Ownership check — only the owner session may export a run.
|
|
@@ -95,7 +95,7 @@ export async function handleForget(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
95
95
|
if (intentError) return intentError;
|
|
96
96
|
if (!params.runId) return result("Forget requires runId.", { action: "forget", status: "error" }, true);
|
|
97
97
|
if (!params.confirm) return result("forget requires confirm: true.", { action: "forget", status: "error" }, true);
|
|
98
|
-
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
98
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
99
99
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "forget", status: "error" }, true);
|
|
100
100
|
|
|
101
101
|
// Ownership check — prevent cross-session deletion unless force is set
|
|
@@ -125,7 +125,7 @@ export async function handleCleanup(params: TeamToolParamsValue, ctx: TeamContex
|
|
|
125
125
|
const intentError = enforceDestructiveIntent("cleanup", params, ctx.config);
|
|
126
126
|
if (intentError) return intentError;
|
|
127
127
|
if (!params.runId) return result("Cleanup requires runId.", { action: "cleanup", status: "error" }, true);
|
|
128
|
-
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
|
128
|
+
const loaded = loadRunManifestById(ctx.cwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
129
129
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cleanup", status: "error" }, true);
|
|
130
130
|
|
|
131
131
|
// Ownership check — prevent cross-session worktree cleanup unless force is set
|
|
@@ -20,11 +20,11 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
|
|
|
20
20
|
|
|
21
21
|
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
22
22
|
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
|
23
|
-
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
23
|
+
const loaded = loadRunManifestById(runCwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
24
24
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
|
25
25
|
|
|
26
26
|
return withRunLockSync(loaded.manifest, () => {
|
|
27
|
-
const fresh = loadRunManifestById(loaded.manifest.cwd, params.runId!);
|
|
27
|
+
const fresh = loadRunManifestById(loaded.manifest.cwd, params.runId!); // NOTE: inside withRunLockSync - consistent read
|
|
28
28
|
if (!fresh) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
|
29
29
|
const foreignRun = typeof fresh.manifest.ownerSessionId === "string" && fresh.manifest.ownerSessionId !== ctx.sessionId;
|
|
30
30
|
if (foreignRun && !params.force) return result(`Run ${fresh.manifest.runId} belongs to another session. Use force: true to override.`, { action: "respond", status: "error", runId: fresh.manifest.runId }, true);
|
|
@@ -2,6 +2,7 @@ import { allAgents, discoverAgents } from "../../agents/discover-agents.ts";
|
|
|
2
2
|
import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
|
|
3
3
|
import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
|
|
4
4
|
import { loadConfig } from "../../config/config.ts";
|
|
5
|
+
import { findGitRoot, assertCleanLeader } from "../../worktree/worktree-manager.ts";
|
|
5
6
|
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
|
6
7
|
import { writeArtifact } from "../../state/artifact-store.ts";
|
|
7
8
|
import { registerActiveRun, unregisterActiveRun } from "../../state/active-run-registry.ts";
|
|
@@ -85,9 +86,57 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
85
86
|
const { ensureCrewDirectory } = await import("../../state/crew-init.ts");
|
|
86
87
|
await ensureCrewDirectory(workingDir);
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// WORKTREE FIX: If worktree mode is needed but cwd is not a git repo,
|
|
90
|
+
// auto-correct to the nearest git repo root. This prevents "not a git repository"
|
|
91
|
+
// errors when ctx.cwd points to a parent directory that isn't a git repo.
|
|
92
|
+
let resolvedCtx = ctx;
|
|
93
|
+
if (workingDir) {
|
|
94
|
+
try {
|
|
95
|
+
const gitRoot = findGitRoot(workingDir);
|
|
96
|
+
if (gitRoot && gitRoot !== workingDir) {
|
|
97
|
+
resolvedCtx = { ...ctx, cwd: gitRoot };
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// cwd is not in a git repo — validate below if worktree mode is needed
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// WORKTREE PRECONDITION CHECK: validate git repo exists and is clean
|
|
105
|
+
// BEFORE creating the run manifest, so we return a friendly error
|
|
106
|
+
// instead of crashing mid-execution in prepareTaskWorkspace.
|
|
107
|
+
if (params.workspaceMode === "worktree") {
|
|
108
|
+
let gitRoot: string | undefined;
|
|
109
|
+
try {
|
|
110
|
+
gitRoot = findGitRoot(resolvedCtx.cwd);
|
|
111
|
+
} catch {
|
|
112
|
+
// not a git repo
|
|
113
|
+
}
|
|
114
|
+
if (!gitRoot) {
|
|
115
|
+
return result(
|
|
116
|
+
`Worktree mode requires a git repository. '${resolvedCtx.cwd}' is not inside a git repo.\nUse workspaceMode: 'single' or run from a git repository.`,
|
|
117
|
+
{ action: "run", status: "error" },
|
|
118
|
+
true,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
// Check if clean leader is required (can be disabled via config)
|
|
122
|
+
const preCheckConfig = loadConfig(resolvedCtx.cwd);
|
|
123
|
+
if (preCheckConfig.config.requireCleanWorktreeLeader !== false) {
|
|
124
|
+
try {
|
|
125
|
+
assertCleanLeader(gitRoot);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
128
|
+
return result(
|
|
129
|
+
`${msg}\nCommit or stash changes before using worktree mode, or use workspaceMode: 'single'.`,
|
|
130
|
+
{ action: "run", status: "error" },
|
|
131
|
+
true,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const teams = allTeams(discoverTeams(resolvedCtx.cwd));
|
|
138
|
+
const workflows = allWorkflows(discoverWorkflows(resolvedCtx.cwd));
|
|
139
|
+
const agents = allAgents(discoverAgents(resolvedCtx.cwd));
|
|
91
140
|
const directAgent = params.agent ? agents.find((item) => item.name === params.agent) : undefined;
|
|
92
141
|
if (params.agent && !directAgent) return result(`Agent '${params.agent}' not found.`, { action: "run", status: "error" }, true);
|
|
93
142
|
const teamName = params.team ?? "default";
|
|
@@ -110,7 +159,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
110
159
|
steps: [{ id: "01_agent", role: params.role ?? "agent", task: "{goal}", model: params.model }],
|
|
111
160
|
} : workflows.find((item) => item.name === workflowName);
|
|
112
161
|
if (!baseWorkflow) return result(`Workflow '${workflowName}' not found.`, { action: "run", status: "error" }, true);
|
|
113
|
-
const workflow = directAgent ? baseWorkflow : expandParallelResearchWorkflow(baseWorkflow,
|
|
162
|
+
const workflow = directAgent ? baseWorkflow : expandParallelResearchWorkflow(baseWorkflow, resolvedCtx.cwd);
|
|
114
163
|
|
|
115
164
|
// Check if this is a pipeline workflow - special handling for multi-stage execution
|
|
116
165
|
const isPipelineWorkflow = workflowName === "pipeline" && !directAgent;
|
|
@@ -152,7 +201,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
152
201
|
|
|
153
202
|
const skillOverride = normalizeSkillOverride(params.skill);
|
|
154
203
|
const { manifest, tasks, paths } = createRunManifest({
|
|
155
|
-
cwd:
|
|
204
|
+
cwd: resolvedCtx.cwd,
|
|
156
205
|
team,
|
|
157
206
|
workflow,
|
|
158
207
|
goal,
|
|
@@ -169,7 +218,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
169
218
|
atomicWriteJson(paths.manifestPath, updatedManifest);
|
|
170
219
|
registerActiveRun(updatedManifest);
|
|
171
220
|
|
|
172
|
-
const loadedConfig = loadConfig(
|
|
221
|
+
const loadedConfig = loadConfig(resolvedCtx.cwd);
|
|
173
222
|
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
174
223
|
const runtime = await resolveCrewRuntime(executedConfig);
|
|
175
224
|
const runtimeResolution = runtimeResolutionState(runtime);
|
|
@@ -205,11 +254,12 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
205
254
|
const asyncManifest = { ...effectiveManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
|
|
206
255
|
atomicWriteJson(paths.manifestPath, asyncManifest);
|
|
207
256
|
void appendEventAsync(effectiveManifest.eventsPath, { type: "async.spawned", runId: effectiveManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
|
|
208
|
-
|
|
257
|
+
ctx.onRunStarted?.(effectiveManifest.runId);
|
|
258
|
+
scheduleBackgroundEarlyExitGuard(resolvedCtx.cwd, effectiveManifest.runId, spawned.pid, spawned.logPath);
|
|
209
259
|
// Wait for the async run to complete and return actual results.
|
|
210
260
|
try {
|
|
211
|
-
const completed = await waitForRun(updatedManifest.runId,
|
|
212
|
-
const metrics = collectRunMetrics(
|
|
261
|
+
const completed = await waitForRun(updatedManifest.runId, resolvedCtx.cwd, { timeoutMs: 3600000 });
|
|
262
|
+
const metrics = collectRunMetrics(resolvedCtx.cwd, completed.manifest.runId);
|
|
213
263
|
const lines: string[] = [
|
|
214
264
|
`pi-crew run ${completed.manifest.status}: ${completed.manifest.runId} (${team.name})`,
|
|
215
265
|
`Goal: ${goal.slice(0, 100)}`,
|
|
@@ -292,7 +342,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
292
342
|
}
|
|
293
343
|
|
|
294
344
|
const runFailed = completed.manifest.status === "failed" || completed.manifest.status === "blocked";
|
|
295
|
-
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot }, runFailed);
|
|
345
|
+
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot, metrics }, runFailed);
|
|
296
346
|
} catch (waitError: unknown) {
|
|
297
347
|
const errorMessage = waitError instanceof Error ? waitError.message : String(waitError);
|
|
298
348
|
return result(
|
|
@@ -341,8 +391,8 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
341
391
|
|
|
342
392
|
// Wait for the foreground run to complete and return actual results.
|
|
343
393
|
try {
|
|
344
|
-
const completed = await waitForRun(updatedManifest.runId,
|
|
345
|
-
const metrics = collectRunMetrics(
|
|
394
|
+
const completed = await waitForRun(updatedManifest.runId, resolvedCtx.cwd, { timeoutMs: 3600000 });
|
|
395
|
+
const metrics = collectRunMetrics(resolvedCtx.cwd, completed.manifest.runId);
|
|
346
396
|
const lines: string[] = [
|
|
347
397
|
`pi-crew run ${completed.manifest.status}: ${completed.manifest.runId} (${team.name})`,
|
|
348
398
|
`Goal: ${goal.slice(0, 100)}`,
|
|
@@ -425,7 +475,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
425
475
|
}
|
|
426
476
|
|
|
427
477
|
const runFailed = completed.manifest.status === "failed" || completed.manifest.status === "blocked";
|
|
428
|
-
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot }, runFailed);
|
|
478
|
+
return result(lines.join("\n"), { action: "run", status: runFailed ? "error" : "ok", runId: completed.manifest.runId, artifactsRoot: completed.manifest.artifactsRoot, metrics }, runFailed);
|
|
429
479
|
} catch (waitError: unknown) {
|
|
430
480
|
const errorMessage = waitError instanceof Error ? waitError.message : String(waitError);
|
|
431
481
|
return result(
|
|
@@ -465,5 +515,5 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
465
515
|
? "Experimental live-session worker execution was enabled."
|
|
466
516
|
: "Safe scaffold mode: child Pi workers were not launched because runtime.mode=scaffold or executeWorkers=false was configured.",
|
|
467
517
|
].join("\n");
|
|
468
|
-
return result(text, { action: "run", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
|
|
518
|
+
return result(text, { action: "run", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot, metrics: collectRunMetrics(resolvedCtx.cwd, executed.manifest.runId) }, executed.manifest.status === "failed");
|
|
469
519
|
}
|
|
@@ -18,7 +18,7 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
|
|
|
18
18
|
if (!params.runId) return result("Status requires runId.", { action: "status", status: "error" }, true);
|
|
19
19
|
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
20
20
|
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "status", status: "error" }, true);
|
|
21
|
-
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
21
|
+
const loaded = loadRunManifestById(runCwd, params.runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
|
|
22
22
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "status", status: "error" }, true);
|
|
23
23
|
let { manifest, tasks } = loaded;
|
|
24
24
|
let asyncLivenessLine: string | undefined;
|
|
@@ -10,6 +10,8 @@ export interface TeamToolDetails {
|
|
|
10
10
|
resumedIds?: string[];
|
|
11
11
|
retriedTaskIds?: string[];
|
|
12
12
|
mailboxIds?: string[];
|
|
13
|
+
/** Run metrics for compact display in TUI tool result rendering. */
|
|
14
|
+
metrics?: { taskCount?: number; completedCount?: number; totalTokens?: number; totalCost?: number; durationMs?: number; consistencyScore?: number };
|
|
13
15
|
/** Structured data for programmatic consumption (e.g. TUI widgets). */
|
|
14
16
|
data?: Record<string, unknown>;
|
|
15
17
|
}
|