pi-crew 0.1.9 → 0.1.10
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/package.json +1 -1
- package/src/runtime/process-status.ts +18 -0
- package/src/ui/crew-widget.ts +14 -8
- package/src/ui/run-dashboard.ts +13 -8
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import type { CrewAgentRecord } from "./crew-agent-runtime.ts";
|
|
2
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
3
|
+
|
|
1
4
|
export interface ProcessLiveness {
|
|
2
5
|
pid?: number;
|
|
3
6
|
alive: boolean;
|
|
4
7
|
detail: string;
|
|
5
8
|
}
|
|
6
9
|
|
|
10
|
+
const ORPHANED_ACTIVE_RUN_MS = 10 * 60 * 1000;
|
|
11
|
+
|
|
7
12
|
export function checkProcessLiveness(pid: number | undefined): ProcessLiveness {
|
|
8
13
|
if (pid === undefined || !Number.isInteger(pid) || pid <= 0) {
|
|
9
14
|
return { pid, alive: false, detail: "no pid recorded" };
|
|
@@ -23,3 +28,16 @@ export function checkProcessLiveness(pid: number | undefined): ProcessLiveness {
|
|
|
23
28
|
export function isActiveRunStatus(status: string): boolean {
|
|
24
29
|
return status === "queued" || status === "planning" || status === "running";
|
|
25
30
|
}
|
|
31
|
+
|
|
32
|
+
export function isLikelyOrphanedActiveRun(run: TeamRunManifest, agents: CrewAgentRecord[] = [], now = Date.now(), staleMs = ORPHANED_ACTIVE_RUN_MS): boolean {
|
|
33
|
+
if (!isActiveRunStatus(run.status)) return false;
|
|
34
|
+
if (run.async?.pid !== undefined) return false;
|
|
35
|
+
const updatedAt = new Date(run.updatedAt).getTime();
|
|
36
|
+
if (!Number.isFinite(updatedAt) || now - updatedAt < staleMs) return false;
|
|
37
|
+
if (agents.length === 0) return run.summary === "Creating workflow prompts and placeholder results.";
|
|
38
|
+
return agents.every((agent) => agent.status === "queued" && !agent.completedAt && !agent.progress);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isDisplayActiveRun(run: TeamRunManifest, agents: CrewAgentRecord[] = [], now = Date.now()): boolean {
|
|
42
|
+
return isActiveRunStatus(run.status) && !isLikelyOrphanedActiveRun(run, agents, now);
|
|
43
|
+
}
|
package/src/ui/crew-widget.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { listRuns } from "../extension/run-index.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { isDisplayActiveRun, isLikelyOrphanedActiveRun } from "../runtime/process-status.ts";
|
|
4
4
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
5
5
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
6
6
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
@@ -62,6 +62,7 @@ function agentStats(agent: CrewAgentRecord): string {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function runStep(run: TeamRunManifest, agents: CrewAgentRecord[]): string {
|
|
65
|
+
if (isLikelyOrphanedActiveRun(run, agents)) return "stale";
|
|
65
66
|
const running = agents.find((agent) => agent.status === "running");
|
|
66
67
|
if (running) return running.taskId;
|
|
67
68
|
const queued = agents.find((agent) => agent.status === "queued");
|
|
@@ -69,10 +70,15 @@ function runStep(run: TeamRunManifest, agents: CrewAgentRecord[]): string {
|
|
|
69
70
|
return run.status;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function agentsFor(run: TeamRunManifest): CrewAgentRecord[] {
|
|
74
|
+
try { return readCrewAgents(run); } catch { return []; }
|
|
75
|
+
}
|
|
76
|
+
|
|
72
77
|
export function buildCrewWidgetLines(cwd: string, frame = 0, maxLines = 8): string[] {
|
|
73
78
|
const runs = listRuns(cwd).slice(0, 20);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
79
|
+
const runAgents = new Map(runs.map((run) => [run.runId, agentsFor(run)]));
|
|
80
|
+
const activeRuns = runs.filter((run) => isDisplayActiveRun(run, runAgents.get(run.runId) ?? []));
|
|
81
|
+
const recentRuns = runs.filter((run) => !isDisplayActiveRun(run, runAgents.get(run.runId) ?? [])).slice(0, 3);
|
|
76
82
|
const shownRuns = [...activeRuns, ...recentRuns];
|
|
77
83
|
if (!shownRuns.length) return [];
|
|
78
84
|
const runningGlyph = SPINNER[frame % SPINNER.length] ?? "⠋";
|
|
@@ -80,13 +86,13 @@ export function buildCrewWidgetLines(cwd: string, frame = 0, maxLines = 8): stri
|
|
|
80
86
|
const activeCount = activeRuns.length;
|
|
81
87
|
lines.push(`${activeCount ? "●" : "○"} pi-crew · active=${activeCount} recent=${recentRuns.length} · /team-dashboard`);
|
|
82
88
|
for (const run of shownRuns) {
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
const agents = runAgents.get(run.runId) ?? [];
|
|
90
|
+
const stale = isLikelyOrphanedActiveRun(run, agents);
|
|
85
91
|
const counts = new Map<string, number>();
|
|
86
92
|
for (const agent of agents) counts.set(agent.status, (counts.get(agent.status) ?? 0) + 1);
|
|
87
|
-
const countText = [...counts.entries()].map(([status, count]) => `${status}:${count}`).join(" ") || run.status;
|
|
88
|
-
lines.push(`${glyph(run.status, runningGlyph)} ${run.runId.slice(-8)} ${run.team}/${run.workflow ?? "none"} · ${runStep(run, agents)} · ${countText}`);
|
|
89
|
-
for (const agent of agents.filter((item) => item.status === "running" || item.status === "queued").slice(0, 2)) {
|
|
93
|
+
const countText = stale ? "stale queued run" : [...counts.entries()].map(([status, count]) => `${status}:${count}`).join(" ") || run.status;
|
|
94
|
+
lines.push(`${glyph(stale ? "failed" : run.status, runningGlyph)} ${run.runId.slice(-8)} ${run.team}/${run.workflow ?? "none"} · ${runStep(run, agents)} · ${countText}`);
|
|
95
|
+
for (const agent of (stale ? [] : agents.filter((item) => item.status === "running" || item.status === "queued").slice(0, 2))) {
|
|
90
96
|
const stats = agentStats(agent);
|
|
91
97
|
lines.push(` ${glyph(agent.status, runningGlyph)} ${agent.taskId} ${agent.role}→${agent.agent} · ${agentActivity(agent)}${stats ? ` · ${stats}` : ""}`);
|
|
92
98
|
}
|
package/src/ui/run-dashboard.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
3
3
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
4
4
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { isDisplayActiveRun, isLikelyOrphanedActiveRun } from "../runtime/process-status.ts";
|
|
6
6
|
|
|
7
7
|
interface DashboardComponent {
|
|
8
8
|
invalidate(): void;
|
|
@@ -51,7 +51,7 @@ function padVisible(value: string, width: number): string {
|
|
|
51
51
|
|
|
52
52
|
function statusIcon(status: string): string {
|
|
53
53
|
if (status === "completed") return "✓";
|
|
54
|
-
if (status === "failed") return "✗";
|
|
54
|
+
if (status === "failed" || status === "stale") return "✗";
|
|
55
55
|
if (status === "cancelled") return "!";
|
|
56
56
|
if (status === "running") return "▶";
|
|
57
57
|
if (status === "blocked") return "■";
|
|
@@ -104,19 +104,24 @@ function readAgentPreview(run: TeamRunManifest, maxLines = 5): string[] {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
function agentsFor(run: TeamRunManifest): CrewAgentRecord[] {
|
|
108
|
+
try { return readCrewAgents(run); } catch { return []; }
|
|
109
|
+
}
|
|
110
|
+
|
|
107
111
|
function runLabel(run: TeamRunManifest, selected: boolean): string {
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
const agents = agentsFor(run);
|
|
113
|
+
const stale = isLikelyOrphanedActiveRun(run, agents);
|
|
110
114
|
const running = agents.find((agent) => agent.status === "running");
|
|
111
115
|
const queued = agents.find((agent) => agent.status === "queued");
|
|
112
|
-
const step = running ? `step ${running.taskId}` : queued ? `queued ${queued.taskId}` : `agents ${agents.length}`;
|
|
116
|
+
const step = stale ? "orphaned queued run" : running ? `step ${running.taskId}` : queued ? `queued ${queued.taskId}` : `agents ${agents.length}`;
|
|
117
|
+
const status = stale ? "stale" : run.status;
|
|
113
118
|
const marker = selected ? "›" : " ";
|
|
114
|
-
return `${marker} ${statusIcon(
|
|
119
|
+
return `${marker} ${statusIcon(status)} ${run.runId.slice(-8)} ${status} | ${run.team}/${run.workflow ?? "none"} | ${step} | ${run.goal}`;
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
function groupedRuns(runs: TeamRunManifest[]): Array<{ label: string; run?: TeamRunManifest }> {
|
|
118
|
-
const active = runs.filter((run) =>
|
|
119
|
-
const recent = runs.filter((run) => !
|
|
123
|
+
const active = runs.filter((run) => isDisplayActiveRun(run, agentsFor(run)));
|
|
124
|
+
const recent = runs.filter((run) => !isDisplayActiveRun(run, agentsFor(run)));
|
|
120
125
|
const rows: Array<{ label: string; run?: TeamRunManifest }> = [];
|
|
121
126
|
if (active.length) rows.push({ label: "Active" }, ...active.map((run) => ({ label: run.runId, run })));
|
|
122
127
|
if (recent.length) rows.push({ label: "Recent" }, ...recent.map((run) => ({ label: run.runId, run })));
|