pi-crew 0.3.7 → 0.3.9
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 +17 -0
- package/package.json +1 -1
- package/src/agents/discover-agents.ts +354 -15
- package/src/config/config.ts +732 -208
- package/src/config/types.ts +34 -5
- package/src/extension/help.ts +1 -0
- package/src/extension/register.ts +1173 -257
- package/src/extension/registration/commands.ts +15 -2
- package/src/extension/registration/team-tool.ts +1 -1
- package/src/extension/session-summary.ts +11 -1
- package/src/extension/team-tool/api.ts +4 -1
- package/src/extension/team-tool/cache-control.ts +23 -0
- package/src/extension/team-tool/cancel.ts +15 -5
- package/src/extension/team-tool/context.ts +2 -0
- package/src/extension/team-tool/handle-settings.ts +2 -0
- package/src/extension/team-tool/health-monitor.ts +563 -0
- package/src/extension/team-tool/inspect.ts +10 -3
- package/src/extension/team-tool/respond.ts +5 -2
- package/src/extension/team-tool/status.ts +4 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +901 -177
- package/src/runtime/adaptive-plan.ts +1 -1
- package/src/runtime/foreground-watchdog.ts +129 -0
- package/src/runtime/manifest-cache.ts +4 -2
- package/src/runtime/run-tracker.ts +11 -0
- package/src/runtime/runtime-policy.ts +15 -2
- package/src/runtime/skill-instructions.ts +8 -2
- package/src/runtime/stale-reconciler.ts +322 -18
- package/src/runtime/task-packet.ts +48 -1
- package/src/runtime/task-runner.ts +6 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +204 -76
- package/src/state/state-store.ts +9 -1
- package/src/teams/discover-teams.ts +2 -1
- package/src/ui/run-event-bus.ts +2 -1
- package/src/ui/settings-overlay.ts +2 -0
- package/src/workflows/discover-workflows.ts +5 -1
|
@@ -221,11 +221,24 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
221
221
|
] as const) {
|
|
222
222
|
pi.registerCommand(name, { description, handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
223
223
|
const runId = args.trim() || undefined;
|
|
224
|
-
const result = await handleTeamTool({ action, runId }, teamCommandContext(ctx));
|
|
224
|
+
const result = await handleTeamTool({ action, runId }, { ...teamCommandContext(ctx), getRunSnapshotCache: deps.getRunSnapshotCache });
|
|
225
225
|
await notifyCommandResult(ctx, commandText(result));
|
|
226
226
|
} });
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
pi.registerCommand("team-invalidate", {
|
|
230
|
+
description: "Invalidate the snapshot cache for a run so the UI refreshes immediately: <runId>",
|
|
231
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
232
|
+
const runId = args.trim() || undefined;
|
|
233
|
+
if (!runId) {
|
|
234
|
+
await notifyCommandResult(ctx, "Usage: /team-invalidate <runId>");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const result = await handleTeamTool({ action: "invalidate", runId }, { ...teamCommandContext(ctx), getRunSnapshotCache: deps.getRunSnapshotCache });
|
|
238
|
+
await notifyCommandResult(ctx, commandText(result));
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
229
242
|
pi.registerCommand("team-retry", {
|
|
230
243
|
description: "Retry failed/cancelled pi-crew tasks: <runId> [taskId]",
|
|
231
244
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
@@ -236,7 +249,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
|
|
|
236
249
|
await notifyCommandResult(ctx, "Usage: /team-retry <runId> [taskId]");
|
|
237
250
|
return;
|
|
238
251
|
}
|
|
239
|
-
const retryResult = await handleTeamTool({ action: "retry", runId, taskId }, teamCommandContext(ctx));
|
|
252
|
+
const retryResult = await handleTeamTool({ action: "retry", runId, taskId }, { ...teamCommandContext(ctx), getRunSnapshotCache: deps.getRunSnapshotCache });
|
|
240
253
|
await notifyCommandResult(ctx, commandText(retryResult));
|
|
241
254
|
},
|
|
242
255
|
});
|
|
@@ -81,7 +81,7 @@ export function registerTeamTool(pi: ExtensionAPI, deps: RegisterTeamToolDeps):
|
|
|
81
81
|
const runLabel = resolved.team ?? resolved.agent ?? "direct";
|
|
82
82
|
pi.setSessionName(`pi-crew: ${runLabel}/${resolved.workflow ?? "default"} — ${resolved.goal.slice(0, 60)}`);
|
|
83
83
|
}
|
|
84
|
-
const output = await handleTeamTool(resolved, { ...toolCtx, signal: controller.signal, metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(toolCtx, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: (runId) => { stopProgress.attach(toolCtx.cwd, runId); deps.openLiveSidebar(toolCtx, runId); }, onJsonEvent: deps.onJsonEvent });
|
|
84
|
+
const output = await handleTeamTool(resolved, { ...toolCtx, signal: controller.signal, metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(toolCtx, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: (runId) => { stopProgress.attach(toolCtx.cwd, runId); deps.openLiveSidebar(toolCtx, runId); }, onJsonEvent: deps.onJsonEvent, getRunSnapshotCache: deps.getRunSnapshotCache });
|
|
85
85
|
if (resolved.action === "run" && !output.isError && typeof output.details?.runId === "string") {
|
|
86
86
|
pi.appendEntry("crew:run-started", {
|
|
87
87
|
runId: output.details.runId,
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { isDisplayActiveRun } from "../runtime/process-status.ts";
|
|
2
3
|
import { listRuns } from "./run-index.ts";
|
|
4
|
+
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
3
5
|
|
|
4
6
|
export function notifyActiveRuns(ctx: ExtensionContext): void {
|
|
5
|
-
const active = listRuns(ctx.cwd)
|
|
7
|
+
const active = listRuns(ctx.cwd)
|
|
8
|
+
.filter((run) => {
|
|
9
|
+
if (run.status !== "queued" && run.status !== "planning" && run.status !== "running") return false;
|
|
10
|
+
// Use the same display filter as the widget/powerbar — runs without
|
|
11
|
+
// real agent evidence (e.g. integration test fixtures) must not appear.
|
|
12
|
+
const agents = readCrewAgents(run);
|
|
13
|
+
return isDisplayActiveRun(run, agents);
|
|
14
|
+
})
|
|
15
|
+
.slice(0, 5);
|
|
6
16
|
if (active.length === 0) return;
|
|
7
17
|
ctx.ui.notify(`pi-crew active runs: ${active.map((run) => `${run.runId} [${run.status}]`).join(", ")}`, "info");
|
|
8
18
|
}
|
|
@@ -21,6 +21,7 @@ import { liveControlRealtimeMessage, publishLiveControlRealtime } from "../../su
|
|
|
21
21
|
import { buildCapabilityInventory } from "../../runtime/capability-inventory.ts";
|
|
22
22
|
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
23
23
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
24
|
+
import { locateRunCwd } from "../team-tool.ts";
|
|
24
25
|
import { configRecord, result, type TeamContext } from "./context.ts";
|
|
25
26
|
|
|
26
27
|
function globMatch(value: string, pattern: string): boolean {
|
|
@@ -83,7 +84,9 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
|
|
|
83
84
|
return result(JSON.stringify(inventory, null, 2), { action: "api", status: "ok" });
|
|
84
85
|
}
|
|
85
86
|
if (!params.runId) return result("API requires runId.", { action: "api", status: "error" }, true);
|
|
86
|
-
const
|
|
87
|
+
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
88
|
+
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "api", status: "error" }, true);
|
|
89
|
+
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
87
90
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "api", status: "error" }, true);
|
|
88
91
|
if (operation === "read-manifest") {
|
|
89
92
|
return result(JSON.stringify(loaded.manifest, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { runEventBus } from "../../ui/run-event-bus.ts";
|
|
2
|
+
import type { RunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
3
|
+
|
|
4
|
+
export interface CacheControlDeps {
|
|
5
|
+
getRunSnapshotCache: (cwd: string) => RunSnapshotCache;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Invalidate the run snapshot cache for a specific runId and emit
|
|
10
|
+
* a runEventBus event so the render scheduler coalescer fires immediately.
|
|
11
|
+
* Call this after any state mutation that changes task/agent status.
|
|
12
|
+
*/
|
|
13
|
+
export function invalidateSnapshot(
|
|
14
|
+
runId: string,
|
|
15
|
+
runCwd: string,
|
|
16
|
+
deps: CacheControlDeps,
|
|
17
|
+
): void {
|
|
18
|
+
// 1. Invalidate snapshot cache entry
|
|
19
|
+
deps.getRunSnapshotCache(runCwd).invalidate(runId);
|
|
20
|
+
|
|
21
|
+
// 2. Emit runEventBus event so renderScheduler coalescer fires
|
|
22
|
+
runEventBus.emit({ runId, type: "run.cache_invalidated" });
|
|
23
|
+
}
|
|
@@ -10,8 +10,10 @@ import { killProcessPid } from "../../runtime/child-pi.ts";
|
|
|
10
10
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
11
11
|
import { executeHook, appendHookEvent } from "../../hooks/registry.ts";
|
|
12
12
|
import type { PiTeamsToolResult } from "../tool-result.ts";
|
|
13
|
+
import { locateRunCwd } from "../team-tool.ts";
|
|
13
14
|
import { result, type TeamContext } from "./context.ts";
|
|
14
15
|
import { enforceDestructiveIntent, intentFromConfig } from "./intent-policy.ts";
|
|
16
|
+
import { invalidateSnapshot, type CacheControlDeps } from "./cache-control.ts";
|
|
15
17
|
|
|
16
18
|
export interface AbortOwnedResult {
|
|
17
19
|
abortedIds: string[];
|
|
@@ -37,7 +39,9 @@ export function abortOwned(
|
|
|
37
39
|
ctx: TeamContext,
|
|
38
40
|
force?: boolean,
|
|
39
41
|
): AbortOwnedResult {
|
|
40
|
-
const
|
|
42
|
+
const runCwd = locateRunCwd(runId, ctx.cwd);
|
|
43
|
+
if (!runCwd) return { abortedIds: [], missingIds: taskIds ?? [], foreignIds: [] };
|
|
44
|
+
const loaded = loadRunManifestById(runCwd, runId);
|
|
41
45
|
if (!loaded) return { abortedIds: [], missingIds: taskIds ?? [], foreignIds: [] };
|
|
42
46
|
|
|
43
47
|
const result: AbortOwnedResult = { abortedIds: [], missingIds: [], foreignIds: [] };
|
|
@@ -73,9 +77,11 @@ function cancelReasonFromParams(params: TeamToolParamsValue): CancellationReason
|
|
|
73
77
|
return { code: reason.code, message: reason.message };
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
export async function handleRetry(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
80
|
+
export async function handleRetry(params: TeamToolParamsValue, ctx: TeamContext, deps?: CacheControlDeps): Promise<PiTeamsToolResult> {
|
|
77
81
|
if (!params.runId) return result("Retry requires runId.", { action: "retry", status: "error" }, true);
|
|
78
|
-
const
|
|
82
|
+
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
83
|
+
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "retry", status: "error" }, true);
|
|
84
|
+
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
79
85
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "retry", status: "error" }, true);
|
|
80
86
|
|
|
81
87
|
// Pre-lock ownership check: reject foreign-owned runs unless force is set
|
|
@@ -123,6 +129,7 @@ export async function handleRetry(params: TeamToolParamsValue, ctx: TeamContext)
|
|
|
123
129
|
appendEvent(loaded.manifest.eventsPath, { type: "task.retried", runId: loaded.manifest.runId, taskId, message: `Task ${taskId} queued for retry.` });
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
if (deps) invalidateSnapshot(loaded.manifest.runId, runCwd, deps);
|
|
126
133
|
return result(`Retried ${retriedTaskIds.length} task(s) in run ${loaded.manifest.runId}.`, {
|
|
127
134
|
action: "retry",
|
|
128
135
|
status: "ok",
|
|
@@ -133,11 +140,13 @@ export async function handleRetry(params: TeamToolParamsValue, ctx: TeamContext)
|
|
|
133
140
|
});
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
|
|
143
|
+
export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext, deps?: CacheControlDeps): Promise<PiTeamsToolResult> {
|
|
137
144
|
const intentError = enforceDestructiveIntent("cancel", params, ctx.config);
|
|
138
145
|
if (intentError) return intentError;
|
|
139
146
|
if (!params.runId) return result("Cancel requires runId.", { action: "cancel", status: "error" }, true);
|
|
140
|
-
const
|
|
147
|
+
const runCwd = locateRunCwd(params.runId, ctx.cwd);
|
|
148
|
+
if (!runCwd) return result(`Run '${params.runId}' not found.`, { action: "cancel", status: "error" }, true);
|
|
149
|
+
const loaded = loadRunManifestById(runCwd, params.runId);
|
|
141
150
|
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cancel", status: "error" }, true);
|
|
142
151
|
|
|
143
152
|
// Pre-lock ownership check: reject foreign-owned runs unless force is set
|
|
@@ -212,6 +221,7 @@ export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext
|
|
|
212
221
|
if (abortResult.foreignIds.length > 0) parts.push(` ${abortResult.foreignIds.length} task(s) belong to another session and were not cancelled: ${abortResult.foreignIds.join(", ")}.`);
|
|
213
222
|
if (abortResult.missingIds.length > 0) parts.push(` ${abortResult.missingIds.length} task ID(s) not found: ${abortResult.missingIds.join(", ")}.`);
|
|
214
223
|
|
|
224
|
+
if (deps) invalidateSnapshot(updated.runId, runCwd, deps);
|
|
215
225
|
return result(parts.join(""), {
|
|
216
226
|
action: "cancel",
|
|
217
227
|
status: "ok",
|
|
@@ -2,6 +2,7 @@ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
import type { PiTeamsConfig } from "../../config/config.ts";
|
|
3
3
|
import type { MetricRegistry } from "../../observability/metric-registry.ts";
|
|
4
4
|
import type { TeamToolDetails } from "../team-tool-types.ts";
|
|
5
|
+
import type { RunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
|
|
5
6
|
import { toolResult, type PiTeamsToolResult } from "../tool-result.ts";
|
|
6
7
|
|
|
7
8
|
export type TeamContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "model">> & {
|
|
@@ -16,6 +17,7 @@ export type TeamContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<Extension
|
|
|
16
17
|
onRunStarted?: (runId: string) => void;
|
|
17
18
|
onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
|
|
18
19
|
config?: PiTeamsConfig;
|
|
20
|
+
getRunSnapshotCache?: (cwd: string) => RunSnapshotCache;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export function withSessionId<T extends Pick<ExtensionContext, "sessionManager">>(ctx: T): T & { sessionId?: string } {
|
|
@@ -40,6 +40,7 @@ const EFFECTIVE_DEFAULTS: Record<string, unknown> = {
|
|
|
40
40
|
"notifierIntervalMs": 5000,
|
|
41
41
|
"reliability.autoRetry": false,
|
|
42
42
|
"reliability.autoRecover": false,
|
|
43
|
+
"reliability.cleanupOrphanedTempDirs": true,
|
|
43
44
|
"telemetry.enabled": false,
|
|
44
45
|
"notifications.enabled": false,
|
|
45
46
|
};
|
|
@@ -192,6 +193,7 @@ const KNOWN_KEYS = new Set([
|
|
|
192
193
|
// reliability
|
|
193
194
|
"reliability.autoRetry",
|
|
194
195
|
"reliability.autoRecover",
|
|
196
|
+
"reliability.cleanupOrphanedTempDirs",
|
|
195
197
|
"reliability.deadletterThreshold",
|
|
196
198
|
"reliability.retryPolicy.maxAttempts",
|
|
197
199
|
"reliability.retryPolicy.backoffMs",
|