pi-crew 0.2.3 → 0.2.5
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/AGENTS.md +57 -32
- package/CHANGELOG.md +466 -448
- package/LICENSE +21 -21
- package/NOTICE.md +16 -16
- package/README.md +323 -323
- package/docs/FEATURE_INTAKE.md +126 -0
- package/docs/HARNESS.md +86 -0
- package/docs/HARNESS_BACKLOG.md +41 -0
- package/docs/TEST_MATRIX.md +49 -0
- package/docs/actions-reference.md +595 -595
- package/docs/architecture.md +180 -180
- package/docs/code-review-2026-05-11.md +592 -592
- package/docs/commands-reference.md +347 -347
- package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
- package/docs/decisions/0001-durable-state.md +41 -0
- package/docs/decisions/0002-child-process-for-async.md +42 -0
- package/docs/decisions/0003-depth-guard.md +36 -0
- package/docs/decisions/0004-execfile-over-exec.md +34 -0
- package/docs/decisions/0005-no-parameter-properties.md +49 -0
- package/docs/decisions/0006-publish-bundled-esm.md +63 -0
- package/docs/decisions/0007-active-run-binary-index.md +54 -0
- package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
- package/docs/decisions/README.md +23 -0
- package/docs/followup-review-round4-2026-05-13.md +107 -0
- package/docs/implementation-plan-top3.md +333 -0
- package/docs/live-mailbox-runtime.md +36 -36
- package/docs/next-upgrade-roadmap.md +808 -808
- package/docs/oh-my-pi-research.md +509 -0
- package/docs/perf/baseline-2026-05.md +113 -0
- package/docs/perf/final-report-2026-05.md +206 -0
- package/docs/perf/sprint-1-report.md +71 -0
- package/docs/perf/sprint-2-report.md +81 -0
- package/docs/perf/sprint-2.5-report.md +53 -0
- package/docs/perf/sprint-3-report.md +36 -0
- package/docs/perf/sprint-4-report.md +47 -0
- package/docs/perf/sprint-5-report.md +51 -0
- package/docs/perf/sprint-6-report.md +94 -0
- package/docs/perf/sprint-7-report.md +74 -0
- package/docs/perf/upgrade-plan-2026-05.md +147 -0
- package/docs/pi-subagents3-deep-analysis.md +508 -0
- package/docs/product/README.md +31 -0
- package/docs/product/platform.md +27 -0
- package/docs/product/runtime-safety.md +37 -0
- package/docs/product/team-run.md +39 -0
- package/docs/product/team-tool.md +37 -0
- package/docs/publishing.md +65 -65
- package/docs/resource-formats.md +134 -134
- package/docs/runtime-analysis-child-vs-live.md +171 -0
- package/docs/runtime-flow.md +148 -148
- package/docs/runtime-migration-in-process-analysis.md +250 -0
- package/docs/stories/README.md +30 -0
- package/docs/stories/backlog.md +36 -0
- package/docs/templates/decision.md +27 -0
- package/docs/templates/story.md +44 -0
- package/docs/templates/validation-report.md +32 -0
- package/docs/usage.md +238 -238
- package/index.ts +7 -6
- package/install.mjs +65 -65
- package/package.json +107 -100
- package/schema.json +222 -222
- package/skills/child-pi-spawning/SKILL.md +213 -0
- package/skills/context-artifact-hygiene/SKILL.md +32 -0
- package/skills/event-log-tracing/SKILL.md +299 -0
- package/skills/git-master/SKILL.md +225 -24
- package/skills/live-agent-lifecycle/SKILL.md +192 -0
- package/skills/mailbox-interactive/SKILL.md +300 -19
- package/skills/model-routing-context/SKILL.md +94 -0
- package/skills/multi-perspective-review/SKILL.md +88 -0
- package/skills/read-only-explorer/SKILL.md +250 -26
- package/skills/safe-bash/SKILL.md +307 -21
- package/skills/verification-before-done/SKILL.md +11 -2
- package/skills/widget-rendering/SKILL.md +258 -0
- package/skills/workspace-isolation/SKILL.md +202 -0
- package/skills/worktree-isolation/SKILL.md +202 -18
- package/src/adapters/claude-adapter.ts +25 -25
- package/src/adapters/codex-adapter.ts +21 -21
- package/src/adapters/cursor-adapter.ts +17 -17
- package/src/adapters/export-util.ts +137 -137
- package/src/adapters/index.ts +15 -15
- package/src/adapters/registry.ts +18 -18
- package/src/adapters/types.ts +23 -23
- package/src/agents/agent-config.ts +38 -38
- package/src/agents/agent-serializer.ts +38 -38
- package/src/agents/discover-agents.ts +121 -118
- package/src/config/config.ts +740 -858
- package/src/config/defaults.ts +96 -96
- package/src/config/drift-detector.ts +211 -211
- package/src/config/markers.ts +327 -327
- package/src/config/resilient-parser.ts +109 -108
- package/src/config/suggestions.ts +74 -74
- package/src/config/types.ts +199 -0
- package/src/extension/async-notifier.ts +123 -89
- package/src/extension/autonomous-policy.ts +169 -169
- package/src/extension/cross-extension-rpc.ts +104 -104
- package/src/extension/help.ts +47 -47
- package/src/extension/import-index.ts +69 -69
- package/src/extension/management.ts +395 -382
- package/src/extension/notification-router.ts +116 -116
- package/src/extension/notification-sink.ts +51 -51
- package/src/extension/project-init.ts +168 -168
- package/src/extension/register.ts +859 -668
- package/src/extension/registration/artifact-cleanup.ts +15 -15
- package/src/extension/registration/command-utils.ts +54 -54
- package/src/extension/registration/commands.ts +559 -452
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +102 -102
- package/src/extension/registration/subagent-tools.ts +220 -159
- package/src/extension/registration/team-tool.ts +159 -99
- package/src/extension/registration/viewers.ts +29 -0
- package/src/extension/result-watcher.ts +128 -128
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +73 -73
- package/src/extension/run-import.ts +84 -84
- package/src/extension/run-index.ts +94 -94
- package/src/extension/run-maintenance.ts +142 -142
- package/src/extension/session-summary.ts +8 -8
- package/src/extension/team-manager-command.ts +96 -96
- package/src/extension/team-recommendation.ts +188 -188
- package/src/extension/team-tool/api.ts +5 -2
- package/src/extension/team-tool/cancel.ts +224 -209
- package/src/extension/team-tool/config-patch.ts +36 -36
- package/src/extension/team-tool/context.ts +60 -60
- package/src/extension/team-tool/doctor.ts +242 -242
- package/src/extension/team-tool/handle-settings.ts +421 -195
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +139 -139
- package/src/extension/team-tool/parallel-dispatch.ts +156 -156
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +112 -111
- package/src/extension/team-tool/run.ts +246 -229
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +344 -344
- package/src/extension/tool-result.ts +16 -16
- package/src/extension/validate-resources.ts +77 -77
- package/src/hooks/registry.ts +61 -61
- package/src/hooks/types.ts +40 -40
- package/src/i18n.ts +184 -184
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +68 -68
- package/src/observability/exporters/adapter.ts +30 -30
- package/src/observability/exporters/otlp-exporter.ts +106 -92
- package/src/observability/exporters/prometheus-exporter.ts +54 -54
- package/src/observability/metric-registry.ts +87 -87
- package/src/observability/metric-retention.ts +54 -54
- package/src/observability/metric-sink.ts +81 -56
- package/src/observability/metrics-primitives.ts +167 -167
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/adaptive-plan.ts +338 -0
- package/src/runtime/agent-control.ts +169 -169
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +153 -153
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi.ts +541 -461
- package/src/runtime/code-summary.ts +247 -247
- package/src/runtime/compaction-summary.ts +271 -271
- package/src/runtime/concurrency.ts +58 -58
- package/src/runtime/crash-recovery.ts +317 -301
- package/src/runtime/crew-agent-records.ts +379 -281
- package/src/runtime/crew-agent-runtime.ts +60 -60
- package/src/runtime/cross-extension-rpc.ts +72 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -201
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
- package/src/runtime/deadletter.ts +47 -47
- package/src/runtime/delivery-coordinator.ts +176 -176
- package/src/runtime/delta-conflict.ts +360 -360
- package/src/runtime/diagnostic-export.ts +102 -102
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +82 -81
- package/src/runtime/errors/crew-errors.ts +166 -0
- package/src/runtime/event-stream-bridge.ts +92 -92
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +234 -106
- package/src/runtime/heartbeat-watcher.ts +145 -124
- package/src/runtime/iteration-hooks.ts +267 -267
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +377 -179
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +676 -600
- package/src/runtime/loop-gates.ts +129 -129
- package/src/runtime/manifest-cache.ts +263 -263
- package/src/runtime/mcp-proxy.ts +113 -113
- package/src/runtime/metric-parser.ts +40 -40
- package/src/runtime/model-fallback.ts +282 -274
- package/src/runtime/model-resolver.ts +118 -0
- package/src/runtime/output-validator.ts +187 -187
- package/src/runtime/overflow-recovery.ts +175 -175
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +156 -156
- package/src/runtime/parent-guard.ts +80 -80
- package/src/runtime/phase-progress.ts +217 -217
- package/src/runtime/pi-args.ts +165 -165
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +167 -167
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +125 -125
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +97 -73
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -81
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/run-tracker.ts +99 -0
- package/src/runtime/runtime-policy.ts +21 -0
- package/src/runtime/runtime-resolver.ts +94 -91
- package/src/runtime/scheduler.ts +294 -0
- package/src/runtime/semaphore.ts +131 -131
- package/src/runtime/sensitive-paths.ts +92 -92
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/settings-store.ts +103 -0
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -222
- package/src/runtime/stale-reconciler.ts +198 -189
- package/src/runtime/streaming-output.ts +47 -0
- package/src/runtime/subagent-manager.ts +404 -400
- package/src/runtime/subprocess-tool-registry.ts +67 -67
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph-scheduler.ts +122 -122
- package/src/runtime/task-graph.ts +207 -207
- package/src/runtime/task-output-context.ts +177 -177
- package/src/runtime/task-packet.ts +93 -93
- package/src/runtime/task-quality.ts +207 -207
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +131 -113
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +139 -139
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +469 -459
- package/src/runtime/team-runner.ts +693 -945
- package/src/runtime/usage-tracker.ts +71 -0
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -187
- package/src/runtime/yield-handler.ts +190 -190
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -126
- package/src/schema/validation-types.ts +151 -148
- package/src/skills/discover-skills.ts +67 -67
- package/src/skills/skill-templates.ts +374 -374
- package/src/state/active-run-registry.ts +227 -191
- package/src/state/artifact-store.ts +130 -129
- package/src/state/atomic-write.ts +262 -195
- package/src/state/blob-store.ts +116 -116
- package/src/state/contracts.ts +111 -111
- package/src/state/event-log-rotation.ts +161 -158
- package/src/state/event-log.ts +383 -303
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -146
- package/src/state/mailbox.ts +446 -405
- package/src/state/state-store.ts +364 -351
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +285 -285
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +116 -116
- package/src/teams/team-config.ts +27 -27
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -144
- package/src/ui/crew-widget.ts +487 -370
- package/src/ui/dashboard-panes/agents-pane.ts +109 -28
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/health-pane.ts +30 -30
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/progress-pane.ts +30 -30
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
- package/src/ui/heartbeat-aggregator.ts +63 -63
- package/src/ui/keybinding-map.ts +97 -94
- package/src/ui/live-conversation-overlay.ts +152 -0
- package/src/ui/live-run-sidebar.ts +180 -180
- package/src/ui/mascot.ts +442 -442
- package/src/ui/overlays/agent-picker-overlay.ts +57 -57
- package/src/ui/overlays/confirm-overlay.ts +58 -58
- package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
- package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
- package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
- package/src/ui/pi-ui-compat.ts +57 -57
- package/src/ui/powerbar-publisher.ts +221 -197
- package/src/ui/render-scheduler.ts +216 -143
- package/src/ui/run-action-dispatcher.ts +118 -118
- package/src/ui/run-dashboard.ts +526 -464
- package/src/ui/run-event-bus.ts +208 -208
- package/src/ui/run-snapshot-cache.ts +826 -777
- package/src/ui/settings-overlay.ts +721 -0
- package/src/ui/snapshot-types.ts +86 -70
- package/src/ui/theme-adapter.ts +190 -190
- package/src/ui/tool-progress-formatter.ts +89 -0
- package/src/ui/transcript-cache.ts +94 -94
- package/src/ui/transcript-viewer.ts +335 -335
- package/src/utils/conflict-detect.ts +662 -0
- package/src/utils/file-coalescer.ts +86 -86
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/fs-watch.ts +88 -31
- package/src/utils/gh-protocol.ts +479 -0
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/internal-error.ts +6 -6
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +102 -63
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sse-parser.ts +134 -134
- package/src/utils/task-name-generator.ts +337 -337
- package/src/utils/timings.ts +33 -33
- package/src/utils/visual.ts +243 -198
- package/src/workflows/discover-workflows.ts +139 -139
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +26 -26
- package/src/workflows/workflow-serializer.ts +32 -32
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +75 -75
- package/src/worktree/worktree-manager.ts +188 -188
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/tsconfig.json +19 -19
- package/workflows/default.workflow.md +30 -30
- package/workflows/fast-fix.workflow.md +23 -23
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/skills/task-packet/SKILL.md +0 -28
- package/skills/verify-evidence/SKILL.md +0 -27
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
-
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
5
|
-
import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
6
|
-
import { activeRunEntries } from "../state/active-run-registry.ts";
|
|
7
|
-
import { isSafePathId, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
8
|
-
import { sharedScanCache } from "../utils/scan-cache.ts";
|
|
9
|
-
import { CancellationToken, createCancellationToken } from "../runtime/cancellation-token.ts";
|
|
10
|
-
|
|
11
|
-
function readManifest(filePath: string): TeamRunManifest | undefined {
|
|
12
|
-
const cached = sharedScanCache.readAndCache("manifests", filePath, filePath);
|
|
13
|
-
if (cached) return cached.raw as TeamRunManifest;
|
|
14
|
-
try {
|
|
15
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TeamRunManifest;
|
|
16
|
-
} catch {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function collectRuns(root: string, maxEntries?: number, signal?: AbortSignal): TeamRunManifest[] {
|
|
22
|
-
const runsRoot = path.join(root, DEFAULT_PATHS.state.runsSubdir);
|
|
23
|
-
if (!fs.existsSync(runsRoot)) return [];
|
|
24
|
-
if (signal?.aborted) return [];
|
|
25
|
-
const token = createCancellationToken({ signal });
|
|
26
|
-
const entries = fs.readdirSync(runsRoot, { withFileTypes: true })
|
|
27
|
-
.filter((entry) => entry.isDirectory() && isSafePathId(entry.name))
|
|
28
|
-
.map((entry) => entry.name)
|
|
29
|
-
.sort((a, b) => b.localeCompare(a));
|
|
30
|
-
const selected = maxEntries !== undefined ? entries.slice(0, Math.max(0, maxEntries)) : entries;
|
|
31
|
-
const results: TeamRunManifest[] = [];
|
|
32
|
-
for (let i = 0; i < selected.length; i++) {
|
|
33
|
-
if (i % 10 === 0) token.heartbeat(`collectRuns:${i}/${selected.length}`);
|
|
34
|
-
try {
|
|
35
|
-
const manifest = readManifest(path.join(resolveRealContainedPath(runsRoot, selected[i]), DEFAULT_PATHS.state.manifestFile));
|
|
36
|
-
if (!manifest) continue;
|
|
37
|
-
// Filter out ghost runs: active status but CWD no longer exists.
|
|
38
|
-
// These are deadletter/replay/temp runs whose temp dirs were cleaned up.
|
|
39
|
-
if ((manifest.status === "queued" || manifest.status === "running" || manifest.status === "planning") && manifest.cwd && !fs.existsSync(manifest.cwd)) continue;
|
|
40
|
-
results.push(manifest);
|
|
41
|
-
} catch { /* skip unreadable manifests */ }
|
|
42
|
-
}
|
|
43
|
-
return results;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function mergeRuns(runSets: TeamRunManifest[][], max?: number): TeamRunManifest[] {
|
|
47
|
-
const byId = new Map<string, TeamRunManifest>();
|
|
48
|
-
for (const runs of runSets) for (const run of runs) byId.set(run.runId, run);
|
|
49
|
-
const sorted = [...byId.values()].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
50
|
-
return max !== undefined ? sorted.slice(0, Math.max(0, max)) : sorted;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function scopedRunRoots(cwd: string): string[] {
|
|
54
|
-
const roots = new Set<string>();
|
|
55
|
-
roots.add(userCrewRoot());
|
|
56
|
-
const projectRoot = findRepoRoot(cwd);
|
|
57
|
-
if (projectRoot) roots.add(projectCrewRoot(cwd));
|
|
58
|
-
return [...roots];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function collectActiveRuns(): TeamRunManifest[] {
|
|
62
|
-
return activeRunEntries()
|
|
63
|
-
.map((entry) => readManifest(entry.manifestPath))
|
|
64
|
-
.filter((manifest): manifest is TeamRunManifest => manifest !== undefined);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function listRuns(cwd: string, signal?: AbortSignal): TeamRunManifest[] {
|
|
68
|
-
const roots = scopedRunRoots(cwd);
|
|
69
|
-
return mergeRuns([...roots.map((root) => collectRuns(root, undefined, signal)), collectActiveRuns()]);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function listRecentRuns(cwd: string, max = 20, signal?: AbortSignal): TeamRunManifest[] {
|
|
73
|
-
const roots = scopedRunRoots(cwd);
|
|
74
|
-
return mergeRuns([...roots.map((root) => collectRuns(root, max, signal)), collectActiveRuns()], max);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* List runs filtered to a specific scope.
|
|
79
|
-
* - "project": only runs in the project crew root
|
|
80
|
-
* - "user": only runs in the user crew root
|
|
81
|
-
* - "all" (default): merge both scopes (current behavior)
|
|
82
|
-
*/
|
|
83
|
-
export function listRunsByScope(cwd: string, scope: "project" | "user" | "all" = "all", max?: number, signal?: AbortSignal): TeamRunManifest[] {
|
|
84
|
-
const projectRoot = findRepoRoot(cwd);
|
|
85
|
-
switch (scope) {
|
|
86
|
-
case "project":
|
|
87
|
-
return projectRoot ? collectRuns(projectCrewRoot(cwd), max, signal) : [];
|
|
88
|
-
case "user":
|
|
89
|
-
return collectRuns(userCrewRoot(), max, signal);
|
|
90
|
-
case "all":
|
|
91
|
-
default:
|
|
92
|
-
return max !== undefined ? listRecentRuns(cwd, max, signal) : listRuns(cwd, signal);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
+
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
5
|
+
import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
6
|
+
import { activeRunEntries } from "../state/active-run-registry.ts";
|
|
7
|
+
import { isSafePathId, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
8
|
+
import { sharedScanCache } from "../utils/scan-cache.ts";
|
|
9
|
+
import { CancellationToken, createCancellationToken } from "../runtime/cancellation-token.ts";
|
|
10
|
+
|
|
11
|
+
function readManifest(filePath: string): TeamRunManifest | undefined {
|
|
12
|
+
const cached = sharedScanCache.readAndCache("manifests", filePath, filePath);
|
|
13
|
+
if (cached) return cached.raw as TeamRunManifest;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8")) as TeamRunManifest;
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function collectRuns(root: string, maxEntries?: number, signal?: AbortSignal): TeamRunManifest[] {
|
|
22
|
+
const runsRoot = path.join(root, DEFAULT_PATHS.state.runsSubdir);
|
|
23
|
+
if (!fs.existsSync(runsRoot)) return [];
|
|
24
|
+
if (signal?.aborted) return [];
|
|
25
|
+
const token = createCancellationToken({ signal });
|
|
26
|
+
const entries = fs.readdirSync(runsRoot, { withFileTypes: true })
|
|
27
|
+
.filter((entry) => entry.isDirectory() && isSafePathId(entry.name))
|
|
28
|
+
.map((entry) => entry.name)
|
|
29
|
+
.sort((a, b) => b.localeCompare(a));
|
|
30
|
+
const selected = maxEntries !== undefined ? entries.slice(0, Math.max(0, maxEntries)) : entries;
|
|
31
|
+
const results: TeamRunManifest[] = [];
|
|
32
|
+
for (let i = 0; i < selected.length; i++) {
|
|
33
|
+
if (i % 10 === 0) token.heartbeat(`collectRuns:${i}/${selected.length}`);
|
|
34
|
+
try {
|
|
35
|
+
const manifest = readManifest(path.join(resolveRealContainedPath(runsRoot, selected[i]), DEFAULT_PATHS.state.manifestFile));
|
|
36
|
+
if (!manifest) continue;
|
|
37
|
+
// Filter out ghost runs: active status but CWD no longer exists.
|
|
38
|
+
// These are deadletter/replay/temp runs whose temp dirs were cleaned up.
|
|
39
|
+
if ((manifest.status === "queued" || manifest.status === "running" || manifest.status === "planning") && manifest.cwd && !fs.existsSync(manifest.cwd)) continue;
|
|
40
|
+
results.push(manifest);
|
|
41
|
+
} catch { /* skip unreadable manifests */ }
|
|
42
|
+
}
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mergeRuns(runSets: TeamRunManifest[][], max?: number): TeamRunManifest[] {
|
|
47
|
+
const byId = new Map<string, TeamRunManifest>();
|
|
48
|
+
for (const runs of runSets) for (const run of runs) byId.set(run.runId, run);
|
|
49
|
+
const sorted = [...byId.values()].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
50
|
+
return max !== undefined ? sorted.slice(0, Math.max(0, max)) : sorted;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scopedRunRoots(cwd: string): string[] {
|
|
54
|
+
const roots = new Set<string>();
|
|
55
|
+
roots.add(userCrewRoot());
|
|
56
|
+
const projectRoot = findRepoRoot(cwd);
|
|
57
|
+
if (projectRoot) roots.add(projectCrewRoot(cwd));
|
|
58
|
+
return [...roots];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function collectActiveRuns(): TeamRunManifest[] {
|
|
62
|
+
return activeRunEntries()
|
|
63
|
+
.map((entry) => readManifest(entry.manifestPath))
|
|
64
|
+
.filter((manifest): manifest is TeamRunManifest => manifest !== undefined);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function listRuns(cwd: string, signal?: AbortSignal): TeamRunManifest[] {
|
|
68
|
+
const roots = scopedRunRoots(cwd);
|
|
69
|
+
return mergeRuns([...roots.map((root) => collectRuns(root, undefined, signal)), collectActiveRuns()]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function listRecentRuns(cwd: string, max = 20, signal?: AbortSignal): TeamRunManifest[] {
|
|
73
|
+
const roots = scopedRunRoots(cwd);
|
|
74
|
+
return mergeRuns([...roots.map((root) => collectRuns(root, max, signal)), collectActiveRuns()], max);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* List runs filtered to a specific scope.
|
|
79
|
+
* - "project": only runs in the project crew root
|
|
80
|
+
* - "user": only runs in the user crew root
|
|
81
|
+
* - "all" (default): merge both scopes (current behavior)
|
|
82
|
+
*/
|
|
83
|
+
export function listRunsByScope(cwd: string, scope: "project" | "user" | "all" = "all", max?: number, signal?: AbortSignal): TeamRunManifest[] {
|
|
84
|
+
const projectRoot = findRepoRoot(cwd);
|
|
85
|
+
switch (scope) {
|
|
86
|
+
case "project":
|
|
87
|
+
return projectRoot ? collectRuns(projectCrewRoot(cwd), max, signal) : [];
|
|
88
|
+
case "user":
|
|
89
|
+
return collectRuns(userCrewRoot(), max, signal);
|
|
90
|
+
case "all":
|
|
91
|
+
default:
|
|
92
|
+
return max !== undefined ? listRecentRuns(cwd, max, signal) : listRuns(cwd, signal);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
-
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
5
|
-
import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
6
|
-
import { listRuns } from "./run-index.ts";
|
|
7
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
8
|
-
import { redactSecrets } from "../utils/redaction.ts";
|
|
9
|
-
import { createCancellationToken } from "../runtime/cancellation-token.ts";
|
|
10
|
-
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
11
|
-
import { isSafePathId } from "../utils/safe-paths.ts";
|
|
12
|
-
|
|
13
|
-
export interface PruneRunsResult {
|
|
14
|
-
kept: string[];
|
|
15
|
-
removed: string[];
|
|
16
|
-
auditPath?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface PruneRunsOptions {
|
|
20
|
-
intent?: string;
|
|
21
|
-
signal?: AbortSignal;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isFinished(run: TeamRunManifest): boolean {
|
|
25
|
-
return run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "blocked";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isSafeToPrune(cwd: string, run: TeamRunManifest): boolean {
|
|
29
|
-
try {
|
|
30
|
-
const crewRoot = run.stateRoot.startsWith(userCrewRoot() + path.sep) ? userCrewRoot() : projectCrewRoot(cwd);
|
|
31
|
-
resolveRealContainedPath(crewRoot, run.stateRoot);
|
|
32
|
-
resolveRealContainedPath(crewRoot, run.artifactsRoot);
|
|
33
|
-
return true;
|
|
34
|
-
} catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function appendPruneAudit(cwd: string, payload: Record<string, unknown>): string | undefined {
|
|
40
|
-
try {
|
|
41
|
-
const filePath = path.join(projectCrewRoot(cwd), "audit", "prune.jsonl");
|
|
42
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
43
|
-
fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ ...payload, auditedAt: new Date().toISOString() }))}\n`, "utf-8");
|
|
44
|
-
return filePath;
|
|
45
|
-
} catch (error) {
|
|
46
|
-
logInternalError("prune.audit-write", error, `cwd=${cwd}`);
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function pruneFinishedRuns(cwd: string, keep: number, options: PruneRunsOptions = {}): PruneRunsResult {
|
|
52
|
-
const token = createCancellationToken({ signal: options.signal });
|
|
53
|
-
const finished = listRuns(cwd, options.signal).filter((run) => run.cwd === cwd && isFinished(run)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
54
|
-
const kept = finished.slice(0, keep).map((run) => run.runId);
|
|
55
|
-
const removed: string[] = [];
|
|
56
|
-
const toRemove = finished.slice(keep);
|
|
57
|
-
for (let i = 0; i < toRemove.length; i++) {
|
|
58
|
-
if (i % 5 === 0) token.heartbeat(`prune:${i}/${toRemove.length}`);
|
|
59
|
-
const run = toRemove[i];
|
|
60
|
-
if (!isSafeToPrune(cwd, run)) {
|
|
61
|
-
logInternalError("prune.path-unsafe", new Error(`Skipping unsafe prune: stateRoot=${run.stateRoot}, artifactsRoot=${run.artifactsRoot}`), `runId=${run.runId}`);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
fs.rmSync(run.stateRoot, { recursive: true, force: true });
|
|
65
|
-
fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
|
|
66
|
-
removed.push(run.runId);
|
|
67
|
-
}
|
|
68
|
-
const auditPath = appendPruneAudit(cwd, { action: "prune", keep, intent: options.intent, kept, removed });
|
|
69
|
-
return { kept, removed, auditPath };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Prune finished run directories at the user level (~/.pi/agent/extensions/pi-crew/state/runs/).
|
|
74
|
-
*
|
|
75
|
-
* This handles runs created without a project root (e.g. `team action='run'` from home directory)
|
|
76
|
-
* that would otherwise accumulate forever.
|
|
77
|
-
*
|
|
78
|
-
* @param keep Number of most recent finished runs to retain
|
|
79
|
-
* @returns kept and removed run IDs
|
|
80
|
-
*/
|
|
81
|
-
export function pruneUserLevelRuns(keep: number): PruneRunsResult {
|
|
82
|
-
const crewRoot = userCrewRoot();
|
|
83
|
-
const runsRoot = path.join(crewRoot, DEFAULT_PATHS.state.runsSubdir);
|
|
84
|
-
if (!fs.existsSync(runsRoot)) return { kept: [], removed: [] };
|
|
85
|
-
|
|
86
|
-
// Read all run directories, parse manifests, filter to finished
|
|
87
|
-
const MAX_DIRS = 500;
|
|
88
|
-
const finished: Array<{ runId: string; updatedAt: string; stateRoot: string; artifactsRoot: string }> = [];
|
|
89
|
-
const ghostRemoved: string[] = [];
|
|
90
|
-
const dirs = fs.readdirSync(runsRoot, { withFileTypes: true })
|
|
91
|
-
.filter((entry) => entry.isDirectory() && isSafePathId(entry.name))
|
|
92
|
-
.slice(0, MAX_DIRS)
|
|
93
|
-
.map((entry) => entry.name);
|
|
94
|
-
|
|
95
|
-
for (const dir of dirs) {
|
|
96
|
-
const manifestPath = path.join(runsRoot, dir, DEFAULT_PATHS.state.manifestFile);
|
|
97
|
-
let manifest: TeamRunManifest | undefined;
|
|
98
|
-
try {
|
|
99
|
-
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as TeamRunManifest;
|
|
100
|
-
} catch {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Ghost run cleanup: active status but CWD no longer exists.
|
|
105
|
-
// These are deadletter/replay/temp runs from dead Pi sessions.
|
|
106
|
-
const isActive = manifest.status === "queued" || manifest.status === "running" || manifest.status === "planning";
|
|
107
|
-
if (isActive && manifest.cwd && !fs.existsSync(manifest.cwd)) {
|
|
108
|
-
fs.rmSync(path.join(runsRoot, dir), { recursive: true, force: true });
|
|
109
|
-
ghostRemoved.push(manifest.runId);
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!isFinished(manifest)) continue;
|
|
114
|
-
|
|
115
|
-
// Safety check: ensure stateRoot and artifactsRoot are contained within user crew root
|
|
116
|
-
try {
|
|
117
|
-
resolveRealContainedPath(crewRoot, manifest.stateRoot);
|
|
118
|
-
resolveRealContainedPath(crewRoot, manifest.artifactsRoot);
|
|
119
|
-
} catch {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
finished.push({
|
|
124
|
-
runId: manifest.runId,
|
|
125
|
-
updatedAt: manifest.updatedAt,
|
|
126
|
-
stateRoot: manifest.stateRoot,
|
|
127
|
-
artifactsRoot: manifest.artifactsRoot,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Sort newest first, keep top N, remove the rest
|
|
132
|
-
finished.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
133
|
-
const kept = finished.slice(0, keep).map((r) => r.runId);
|
|
134
|
-
const removed: string[] = [];
|
|
135
|
-
for (const run of finished.slice(keep)) {
|
|
136
|
-
fs.rmSync(run.stateRoot, { recursive: true, force: true });
|
|
137
|
-
fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
|
|
138
|
-
removed.push(run.runId);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return { kept, removed: [...removed, ...ghostRemoved] };
|
|
142
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
4
|
+
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
5
|
+
import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
6
|
+
import { listRuns } from "./run-index.ts";
|
|
7
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
8
|
+
import { redactSecrets } from "../utils/redaction.ts";
|
|
9
|
+
import { createCancellationToken } from "../runtime/cancellation-token.ts";
|
|
10
|
+
import { DEFAULT_PATHS } from "../config/defaults.ts";
|
|
11
|
+
import { isSafePathId } from "../utils/safe-paths.ts";
|
|
12
|
+
|
|
13
|
+
export interface PruneRunsResult {
|
|
14
|
+
kept: string[];
|
|
15
|
+
removed: string[];
|
|
16
|
+
auditPath?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PruneRunsOptions {
|
|
20
|
+
intent?: string;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isFinished(run: TeamRunManifest): boolean {
|
|
25
|
+
return run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "blocked";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isSafeToPrune(cwd: string, run: TeamRunManifest): boolean {
|
|
29
|
+
try {
|
|
30
|
+
const crewRoot = run.stateRoot.startsWith(userCrewRoot() + path.sep) ? userCrewRoot() : projectCrewRoot(cwd);
|
|
31
|
+
resolveRealContainedPath(crewRoot, run.stateRoot);
|
|
32
|
+
resolveRealContainedPath(crewRoot, run.artifactsRoot);
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appendPruneAudit(cwd: string, payload: Record<string, unknown>): string | undefined {
|
|
40
|
+
try {
|
|
41
|
+
const filePath = path.join(projectCrewRoot(cwd), "audit", "prune.jsonl");
|
|
42
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
43
|
+
fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ ...payload, auditedAt: new Date().toISOString() }))}\n`, "utf-8");
|
|
44
|
+
return filePath;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logInternalError("prune.audit-write", error, `cwd=${cwd}`);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function pruneFinishedRuns(cwd: string, keep: number, options: PruneRunsOptions = {}): PruneRunsResult {
|
|
52
|
+
const token = createCancellationToken({ signal: options.signal });
|
|
53
|
+
const finished = listRuns(cwd, options.signal).filter((run) => run.cwd === cwd && isFinished(run)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
54
|
+
const kept = finished.slice(0, keep).map((run) => run.runId);
|
|
55
|
+
const removed: string[] = [];
|
|
56
|
+
const toRemove = finished.slice(keep);
|
|
57
|
+
for (let i = 0; i < toRemove.length; i++) {
|
|
58
|
+
if (i % 5 === 0) token.heartbeat(`prune:${i}/${toRemove.length}`);
|
|
59
|
+
const run = toRemove[i];
|
|
60
|
+
if (!isSafeToPrune(cwd, run)) {
|
|
61
|
+
logInternalError("prune.path-unsafe", new Error(`Skipping unsafe prune: stateRoot=${run.stateRoot}, artifactsRoot=${run.artifactsRoot}`), `runId=${run.runId}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
fs.rmSync(run.stateRoot, { recursive: true, force: true });
|
|
65
|
+
fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
|
|
66
|
+
removed.push(run.runId);
|
|
67
|
+
}
|
|
68
|
+
const auditPath = appendPruneAudit(cwd, { action: "prune", keep, intent: options.intent, kept, removed });
|
|
69
|
+
return { kept, removed, auditPath };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prune finished run directories at the user level (~/.pi/agent/extensions/pi-crew/state/runs/).
|
|
74
|
+
*
|
|
75
|
+
* This handles runs created without a project root (e.g. `team action='run'` from home directory)
|
|
76
|
+
* that would otherwise accumulate forever.
|
|
77
|
+
*
|
|
78
|
+
* @param keep Number of most recent finished runs to retain
|
|
79
|
+
* @returns kept and removed run IDs
|
|
80
|
+
*/
|
|
81
|
+
export function pruneUserLevelRuns(keep: number): PruneRunsResult {
|
|
82
|
+
const crewRoot = userCrewRoot();
|
|
83
|
+
const runsRoot = path.join(crewRoot, DEFAULT_PATHS.state.runsSubdir);
|
|
84
|
+
if (!fs.existsSync(runsRoot)) return { kept: [], removed: [] };
|
|
85
|
+
|
|
86
|
+
// Read all run directories, parse manifests, filter to finished
|
|
87
|
+
const MAX_DIRS = 500;
|
|
88
|
+
const finished: Array<{ runId: string; updatedAt: string; stateRoot: string; artifactsRoot: string }> = [];
|
|
89
|
+
const ghostRemoved: string[] = [];
|
|
90
|
+
const dirs = fs.readdirSync(runsRoot, { withFileTypes: true })
|
|
91
|
+
.filter((entry) => entry.isDirectory() && isSafePathId(entry.name))
|
|
92
|
+
.slice(0, MAX_DIRS)
|
|
93
|
+
.map((entry) => entry.name);
|
|
94
|
+
|
|
95
|
+
for (const dir of dirs) {
|
|
96
|
+
const manifestPath = path.join(runsRoot, dir, DEFAULT_PATHS.state.manifestFile);
|
|
97
|
+
let manifest: TeamRunManifest | undefined;
|
|
98
|
+
try {
|
|
99
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as TeamRunManifest;
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Ghost run cleanup: active status but CWD no longer exists.
|
|
105
|
+
// These are deadletter/replay/temp runs from dead Pi sessions.
|
|
106
|
+
const isActive = manifest.status === "queued" || manifest.status === "running" || manifest.status === "planning";
|
|
107
|
+
if (isActive && manifest.cwd && !fs.existsSync(manifest.cwd)) {
|
|
108
|
+
fs.rmSync(path.join(runsRoot, dir), { recursive: true, force: true });
|
|
109
|
+
ghostRemoved.push(manifest.runId);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!isFinished(manifest)) continue;
|
|
114
|
+
|
|
115
|
+
// Safety check: ensure stateRoot and artifactsRoot are contained within user crew root
|
|
116
|
+
try {
|
|
117
|
+
resolveRealContainedPath(crewRoot, manifest.stateRoot);
|
|
118
|
+
resolveRealContainedPath(crewRoot, manifest.artifactsRoot);
|
|
119
|
+
} catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
finished.push({
|
|
124
|
+
runId: manifest.runId,
|
|
125
|
+
updatedAt: manifest.updatedAt,
|
|
126
|
+
stateRoot: manifest.stateRoot,
|
|
127
|
+
artifactsRoot: manifest.artifactsRoot,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Sort newest first, keep top N, remove the rest
|
|
132
|
+
finished.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
133
|
+
const kept = finished.slice(0, keep).map((r) => r.runId);
|
|
134
|
+
const removed: string[] = [];
|
|
135
|
+
for (const run of finished.slice(keep)) {
|
|
136
|
+
fs.rmSync(run.stateRoot, { recursive: true, force: true });
|
|
137
|
+
fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
|
|
138
|
+
removed.push(run.runId);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { kept, removed: [...removed, ...ghostRemoved] };
|
|
142
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { listRuns } from "./run-index.ts";
|
|
3
|
-
|
|
4
|
-
export function notifyActiveRuns(ctx: ExtensionContext): void {
|
|
5
|
-
const active = listRuns(ctx.cwd).filter((run) => run.status === "queued" || run.status === "planning" || run.status === "running").slice(0, 5);
|
|
6
|
-
if (active.length === 0) return;
|
|
7
|
-
ctx.ui.notify(`pi-crew active runs: ${active.map((run) => `${run.runId} [${run.status}]`).join(", ")}`, "info");
|
|
8
|
-
}
|
|
1
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { listRuns } from "./run-index.ts";
|
|
3
|
+
|
|
4
|
+
export function notifyActiveRuns(ctx: ExtensionContext): void {
|
|
5
|
+
const active = listRuns(ctx.cwd).filter((run) => run.status === "queued" || run.status === "planning" || run.status === "running").slice(0, 5);
|
|
6
|
+
if (active.length === 0) return;
|
|
7
|
+
ctx.ui.notify(`pi-crew active runs: ${active.map((run) => `${run.runId} [${run.status}]`).join(", ")}`, "info");
|
|
8
|
+
}
|