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
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: widget-rendering
|
|
3
|
+
description: Pi TUI crew widget data sources, display priority, and rendering performance. Use when debugging empty agents, ghost runs, or widget timing issues.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# widget-rendering
|
|
7
|
+
|
|
8
|
+
The crew widget (`src/ui/crew-widget.ts`) displays active runs and their agents in the Pi TUI. It must render synchronously at TTY refresh rate without blocking. Understanding the data sources and timing rules is essential for debugging display issues.
|
|
9
|
+
|
|
10
|
+
## Three Data Sources
|
|
11
|
+
|
|
12
|
+
The widget has three sources, used in priority order:
|
|
13
|
+
|
|
14
|
+
### 1. `liveAgents` Map (real-time, highest priority)
|
|
15
|
+
|
|
16
|
+
In-memory map from `live-agent-manager.ts`. Provides:
|
|
17
|
+
- Real-time tool names: `activeTools` Map (toolName → description)
|
|
18
|
+
- Turn count, response text, compaction count
|
|
19
|
+
- Session stats: context %, token usage
|
|
20
|
+
- Status from the handle
|
|
21
|
+
|
|
22
|
+
**When used:** Agents with `liveHandle && liveHandle.status === "running"` get the live activity description (tool labels, response text, turn counter).
|
|
23
|
+
|
|
24
|
+
**When NOT used:** After `evictStaleLiveAgentHandles()` removes a handle, widget falls back to agent records on disk.
|
|
25
|
+
|
|
26
|
+
### 2. Snapshot cache (500ms TTL)
|
|
27
|
+
|
|
28
|
+
`RunSnapshotCache` from `run-snapshot-cache.ts` caches parsed manifests and agents for 500ms. Reduces disk reads during rapid refresh.
|
|
29
|
+
|
|
30
|
+
**When used:** As the fallback when no live handle exists. Prevents excessive disk reads on every render tick.
|
|
31
|
+
|
|
32
|
+
**Invalidation:** Cache is invalidated when:
|
|
33
|
+
- `invalidate()` is called on a specific run
|
|
34
|
+
- An empty result is returned (forces refresh on next tick)
|
|
35
|
+
- TTL expires (500ms)
|
|
36
|
+
|
|
37
|
+
### 3. `agents.json` on disk (durables, lowest priority)
|
|
38
|
+
|
|
39
|
+
`readCrewAgents(run)` reads `artifactsRoot/agents.json`. Provides:
|
|
40
|
+
- Final agent status (completed/failed/cancelled)
|
|
41
|
+
- Tool count, token usage from final record
|
|
42
|
+
- Error messages
|
|
43
|
+
- Timestamps (startedAt, completedAt)
|
|
44
|
+
|
|
45
|
+
**When used:** For completed agents, or when snapshot cache misses.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Display Priority
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
for each active run:
|
|
53
|
+
for each agent in run:
|
|
54
|
+
if liveAgents has this agent (by agentId or taskId):
|
|
55
|
+
→ use live activity description (tool labels, response text)
|
|
56
|
+
→ use live status (running/queued/waiting)
|
|
57
|
+
→ use live session stats (context %, turns, tokens)
|
|
58
|
+
else if snapshot cache has fresh data:
|
|
59
|
+
→ use cached agent status
|
|
60
|
+
→ use cached tool count, tokens, progress
|
|
61
|
+
else:
|
|
62
|
+
→ read agents.json from disk
|
|
63
|
+
→ use disk agent status
|
|
64
|
+
|
|
65
|
+
if status is completed/failed/cancelled:
|
|
66
|
+
→ apply linger rules (finishedAgents: 1min, errors: 2min)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Active Runs Filtering
|
|
72
|
+
|
|
73
|
+
`activeWidgetRuns()` determines which runs to show. Key filter: `isDisplayActiveRun(manifest, tasks)` from `process-status.ts`.
|
|
74
|
+
|
|
75
|
+
**Rule: `hasStaleAsyncProcess()`**
|
|
76
|
+
|
|
77
|
+
A run with an async PID is considered stale (hidden) if:
|
|
78
|
+
1. PID is recorded but process is dead, AND
|
|
79
|
+
2. The run is more than 30 minutes old (`STALE_ACTIVE_RUN_MS = 30 * 60 * 1000`)
|
|
80
|
+
|
|
81
|
+
**Rule: `isDisplayActiveRun()`**
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
export function isDisplayActiveRun(manifest: TeamRunManifest, tasks: TeamTaskState[]): boolean {
|
|
85
|
+
if (manifest.status === "running" || manifest.status === "waiting") {
|
|
86
|
+
if (manifest.async?.pid) {
|
|
87
|
+
if (hasStaleAsyncProcess(manifest.async.pid, manifest.updatedAt)) return false;
|
|
88
|
+
}
|
|
89
|
+
const hasActiveTask = tasks.some((t) => t.status === "running" || t.status === "queued" || t.status === "waiting");
|
|
90
|
+
if (!hasActiveTask) return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This filters out ghost runs (PID dead, manifest still "running") that are more than 30 minutes old.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Stale Handle Eviction
|
|
102
|
+
|
|
103
|
+
**On every widget refresh**, `evictStaleLiveAgentHandles()` is called at the start of `activeWidgetRuns()`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export function activeWidgetRuns(...): WidgetRun[] {
|
|
107
|
+
evictStaleLiveAgentHandles(); // prevent memory leaks
|
|
108
|
+
const runs = preloadedManifests ?? ...;
|
|
109
|
+
// ...
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Eviction rule:** Remove handles where:
|
|
114
|
+
- Status is terminal (not running/queued/waiting), AND
|
|
115
|
+
- `updatedAt` is more than 10 minutes ago
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const STALE_HANDLE_MS = 10 * 60 * 1000;
|
|
119
|
+
if (handle.status !== "running" && handle.status !== "queued" && handle.status !== "waiting") {
|
|
120
|
+
const age = now - new Date(handle.updatedAt).getTime();
|
|
121
|
+
if (age > STALE_HANDLE_MS) {
|
|
122
|
+
liveAgents.delete(agentId);
|
|
123
|
+
safeDisposeLiveSession(handle);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Why called on every refresh:** Ensures the in-memory Map stays bounded even during long Pi sessions. Completed agents linger for 10 minutes (for visibility), then get evicted.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Frame Timing Rules
|
|
133
|
+
|
|
134
|
+
### `renderTick()` must be non-blocking
|
|
135
|
+
|
|
136
|
+
Every render cycle (`renderTick` / `requestAnimationFrame`) must complete in <16ms to maintain 60fps. The widget must not:
|
|
137
|
+
- Call `fs.readFileSync` on hot paths
|
|
138
|
+
- Call `loadConfig()` during render
|
|
139
|
+
- Scan directories (`readdirSync`)
|
|
140
|
+
- Make network calls
|
|
141
|
+
|
|
142
|
+
**Solution:** Preload everything async before the first render.
|
|
143
|
+
|
|
144
|
+
### Widget refresh intervals
|
|
145
|
+
|
|
146
|
+
| Scenario | Interval |
|
|
147
|
+
|---|---|
|
|
148
|
+
| Live agents running | 160ms (`LIVE_REFRESH_MS`) |
|
|
149
|
+
| No live agents, recent activity | 2s |
|
|
150
|
+
| Idle | 10s |
|
|
151
|
+
|
|
152
|
+
### TTL interactions
|
|
153
|
+
|
|
154
|
+
- Snapshot cache TTL = 500ms
|
|
155
|
+
- Preload interval must be < TTL to avoid render-time gaps
|
|
156
|
+
- If preload interval ≥ TTL, the cache always has fresh data for render
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Agent Activity Description
|
|
161
|
+
|
|
162
|
+
`agentActivity(agent, liveHandle?)` generates the activity string shown in the widget:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
function agentActivity(agent: CrewAgentRecord, liveHandle?: LiveAgentHandle): string {
|
|
166
|
+
if (liveHandle && liveHandle.status === "running") {
|
|
167
|
+
const live = describeLiveActivity(liveHandle);
|
|
168
|
+
// Prefer richer agent.progress data if live is just the fallback
|
|
169
|
+
if (live === "thinking…" && agent.progress?.currentTool)
|
|
170
|
+
return `${TOOL_LABELS[agent.progress.currentTool] ?? agent.progress.currentTool}…`;
|
|
171
|
+
return live;
|
|
172
|
+
}
|
|
173
|
+
// Fallback chain from agent records
|
|
174
|
+
if (agent.progress?.currentTool) return `${TOOL_LABELS[agent.progress.currentTool]}…`;
|
|
175
|
+
if (recent output) return lastOutput line;
|
|
176
|
+
if (activityState === "needs_attention") return "needs attention";
|
|
177
|
+
if (status === "queued") return "queued";
|
|
178
|
+
if (status === "running") {
|
|
179
|
+
if (age < 5s && no tool) return "spawning…";
|
|
180
|
+
return "thinking…";
|
|
181
|
+
}
|
|
182
|
+
if (status === "failed") return agent.error ?? "failed";
|
|
183
|
+
return "done";
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Tool name extraction:** `TOOL_LABELS` maps tool names to readable labels:
|
|
188
|
+
```typescript
|
|
189
|
+
const TOOL_LABELS = {
|
|
190
|
+
read: "reading",
|
|
191
|
+
bash: "running command",
|
|
192
|
+
edit: "editing",
|
|
193
|
+
write: "writing",
|
|
194
|
+
grep: "searching",
|
|
195
|
+
find: "finding files",
|
|
196
|
+
ls: "listing",
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Ghost Run Display Bug Patterns
|
|
203
|
+
|
|
204
|
+
### Bug: Agent shows "running" in widget but process is dead
|
|
205
|
+
|
|
206
|
+
**Root cause:** `agents.json` still has status "running" while the actual PID is dead.
|
|
207
|
+
|
|
208
|
+
**Fix path:** `reconcileAllStaleRuns` now calls `upsertCrewAgent` when repairing tasks, syncing agent status files. Also `purgeStaleActiveRunIndex` and `cancelOrphanedRuns` now sync agent records.
|
|
209
|
+
|
|
210
|
+
### Bug: Widget shows empty agent (no name, no status)
|
|
211
|
+
|
|
212
|
+
**Root cause:** `agentActivity` fallback chain returned `"done"` with no name. The agent description construction had fallbacks to empty strings.
|
|
213
|
+
|
|
214
|
+
**Fix applied:** `agentActivity` now uses `handle.agent ?? handle.role ?? agent.agent ?? agent.role ?? "Agent"` as the description base. Plus `(running)` suffix uses `handle.status` not `agent.status`.
|
|
215
|
+
|
|
216
|
+
### Bug: Ghost runs appear after Pi restart
|
|
217
|
+
|
|
218
|
+
**Root cause:** `active-run-index.json` is missing on restart, so `purgeStaleActiveRunIndex()` doesn't know about orphaned runs. `reconcileAllStaleRuns` (disk scan) was never called.
|
|
219
|
+
|
|
220
|
+
**Fix applied:** `reconcileAllStaleRuns` is now called at session start in `register.ts`.
|
|
221
|
+
|
|
222
|
+
### Bug: "worker blinks" — agent appears and disappears in 1 frame
|
|
223
|
+
|
|
224
|
+
**Root cause:** Worker spawns and crashes in <1 frame. Structured logs (`worker.spawned` + rapid `worker.exit`) expose the crash cause.
|
|
225
|
+
|
|
226
|
+
**Fix:** Event log tracing skill documents this pattern.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Anti-patterns
|
|
231
|
+
|
|
232
|
+
- **Blocking render with fs calls**: Every `readFileSync`, `readdirSync`, `fs.statSync` in the render path causes frame drops. Preload everything async.
|
|
233
|
+
- **Stale cache in hot path**: If snapshot cache TTL is too long, widget shows outdated state. Keep TTL at 500ms or less.
|
|
234
|
+
- **No invalidation on empty**: When `readCrewAgents` returns `[]` (no agents yet), the cache must be invalidated on next tick to prevent showing empty for too long.
|
|
235
|
+
- **Expired handles accumulating**: Without `evictStaleLiveAgentHandles`, the Map grows indefinitely. Call it on every refresh.
|
|
236
|
+
- **Widget showing stale health warnings**: Completed/cancelled/failed runs should not show health warnings. Filter by status.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Source patterns
|
|
241
|
+
|
|
242
|
+
- `src/ui/crew-widget.ts` — render, refresh, activeWidgetRuns, evictStaleLiveAgentHandles, agentActivity, describeLiveActivity
|
|
243
|
+
- `src/ui/run-snapshot-cache.ts` — SnapshotCache, get, refreshIfStale, TTL=500ms
|
|
244
|
+
- `src/runtime/crew-agent-records.ts` — readCrewAgents, agents.json
|
|
245
|
+
- `src/runtime/process-status.ts` — hasStaleAsyncProcess, isDisplayActiveRun
|
|
246
|
+
- `src/runtime/background-runner.ts` — active run filtering with async PID check
|
|
247
|
+
- `src/runtime/active-run-registry.ts` — purgeStaleActiveRunIndex
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Verification
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
cd pi-crew
|
|
255
|
+
npx tsc --noEmit
|
|
256
|
+
node --experimental-strip-types --test test/unit/crew-widget.test.ts test/unit/run-snapshot-cache.test.ts test/unit/live-agent-manager.test.ts
|
|
257
|
+
npm test
|
|
258
|
+
```
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workspace-isolation
|
|
3
|
+
description: Workspace isolation boundaries in pi-crew. Use when ensuring agents from workspace A cannot access workspace B, or when implementing worktree-based parallel execution.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# workspace-isolation
|
|
7
|
+
|
|
8
|
+
pi-crew enforces workspace isolation so that agents, runs, and live sessions from one project folder cannot be accessed from another. The workspace boundary is `manifest.cwd` — the directory where a run was initiated.
|
|
9
|
+
|
|
10
|
+
## Workspace Boundary Definition
|
|
11
|
+
|
|
12
|
+
**`manifest.cwd`** is the canonical workspace root. Every run record carries the directory where it was created.
|
|
13
|
+
|
|
14
|
+
**Why it matters:** Pi can have multiple workspace folders open simultaneously. Without isolation, an agent from workspace A could be steered/controlled from workspace B.
|
|
15
|
+
|
|
16
|
+
**Rules:**
|
|
17
|
+
- Every run's `manifest.cwd` is set at creation time
|
|
18
|
+
- Every live agent handle carries `workspaceId = manifest.cwd`
|
|
19
|
+
- Widget queries filter by `manifest.cwd`
|
|
20
|
+
- API operations reject cross-workspace access
|
|
21
|
+
|
|
22
|
+
## Live Agent Workspace Check
|
|
23
|
+
|
|
24
|
+
`LiveAgentHandle.workspaceId` field (added to prevent cross-workspace access):
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
interface LiveAgentHandle {
|
|
28
|
+
// ... other fields
|
|
29
|
+
/** Workspace where this agent was spawned — used for session-scoped visibility. */
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Enforcement in `api.ts`** (team-tool operations):
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// list-active-live-agents: filter by workspace
|
|
38
|
+
listActiveLiveAgentsByWorkspace(manifest.cwd);
|
|
39
|
+
|
|
40
|
+
// steer-agent, follow-up-agent, stop-agent, resume-agent:
|
|
41
|
+
const live = getLiveAgent(agentId);
|
|
42
|
+
if (live && live.workspaceId !== manifest.cwd)
|
|
43
|
+
return result(`Live agent '${agentId}' does not belong to workspace ${manifest.cwd}.`, { status: "error" }, true);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Enforcement in `live-agent-manager.ts`**:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// listLiveAgentsByWorkspace(workspaceId): filter by workspaceId
|
|
50
|
+
export function listLiveAgentsByWorkspace(workspaceId: string): LiveAgentHandle[] {
|
|
51
|
+
return listLiveAgents().filter((a) => a.workspaceId === workspaceId);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Team Workspace Modes
|
|
56
|
+
|
|
57
|
+
### `single` (default)
|
|
58
|
+
|
|
59
|
+
- All agents run in the project root (`manifest.cwd`)
|
|
60
|
+
- No worktree creation
|
|
61
|
+
- Simpler, but all workers share the same git state
|
|
62
|
+
|
|
63
|
+
### `worktree` (parallel isolation)
|
|
64
|
+
|
|
65
|
+
- Each task (or phase) gets its own git worktree
|
|
66
|
+
- Worktree path: `<repo-root>/.worktrees/<runId>/<taskId>/`
|
|
67
|
+
- Branch name: `crew/<runId>-<taskId>` (sanitized)
|
|
68
|
+
- Allows parallel code-changing tasks without git conflicts
|
|
69
|
+
|
|
70
|
+
**Entry point in `team-runner.ts`:**
|
|
71
|
+
```typescript
|
|
72
|
+
const worktree = workspaceMode === "worktree" && task.worktree !== undefined
|
|
73
|
+
? { path: task.worktree.path, branch: task.worktree.branch, reused: task.worktree.reused }
|
|
74
|
+
: undefined;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Worktree lifecycle:**
|
|
78
|
+
|
|
79
|
+
1. **Creation** (`prepareTaskWorkspace` in `worktree-manager.ts`):
|
|
80
|
+
- Check leader repo is clean (`assertCleanLeader`)
|
|
81
|
+
- `git worktree add <path> <branch>`
|
|
82
|
+
- Link `node_modules` if present
|
|
83
|
+
- Mark reused if already exists
|
|
84
|
+
|
|
85
|
+
2. **Naming convention**:
|
|
86
|
+
```
|
|
87
|
+
Branch: crew/<sanitized-runId>-<sanitized-taskId>
|
|
88
|
+
Path: .worktrees/<runId>/<taskId>/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
3. **Cleanup** (on task/run completion):
|
|
92
|
+
- Check dirty state
|
|
93
|
+
- `git worktree remove <path> --force` (only if force=true)
|
|
94
|
+
- Preserve dirty worktrees unless explicitly forced
|
|
95
|
+
|
|
96
|
+
**Safety rules:**
|
|
97
|
+
- Leader repo must be clean before creating worktrees
|
|
98
|
+
- One owner per file/symbol/migration path
|
|
99
|
+
- Branch names derived deterministically from run/task IDs (no user-controlled path fragments)
|
|
100
|
+
|
|
101
|
+
## Cross-Workspace Prevention
|
|
102
|
+
|
|
103
|
+
**In api.ts (`handleTeamToolCall`):**
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
if (operation === "list-live-agents") {
|
|
107
|
+
return result(JSON.stringify(
|
|
108
|
+
listActiveLiveAgentsByWorkspace(loaded.manifest.cwd), // ← filtered by workspace
|
|
109
|
+
null, 2
|
|
110
|
+
), { action: "api", status: "ok", runId: loaded.manifest.runId });
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**In cancel.ts:**
|
|
115
|
+
- Verifies run ownership before allowing cancel
|
|
116
|
+
- Cross-session cancel rejected unless force=true
|
|
117
|
+
|
|
118
|
+
**In respond.ts:**
|
|
119
|
+
- Verifies task ownership before responding
|
|
120
|
+
- Cross-session respond rejected unless force=true
|
|
121
|
+
|
|
122
|
+
**In crash-recovery.ts:**
|
|
123
|
+
- `purgeStaleActiveRunIndex` only affects runs in the current workspace (cwd)
|
|
124
|
+
- `reconcileAllStaleRuns` only scans the current workspace's `.crew/state/runs/`
|
|
125
|
+
|
|
126
|
+
## Live Session Workspace
|
|
127
|
+
|
|
128
|
+
`LiveSessionConfig` carries workspaceId:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
interface LiveSessionConfig {
|
|
132
|
+
// ... other fields
|
|
133
|
+
/** Workspace directory — used for path containment and isolation. */
|
|
134
|
+
workspaceId: string;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Propagation chain:**
|
|
139
|
+
```
|
|
140
|
+
team-tool.ts (handleTeamToolCall)
|
|
141
|
+
→ TeamContext { workspaceId: cwd }
|
|
142
|
+
→ LiveSessionConfig { workspaceId }
|
|
143
|
+
→ registerLiveAgent({ workspaceId })
|
|
144
|
+
→ LiveAgentHandle { workspaceId }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Configuration
|
|
148
|
+
|
|
149
|
+
**defaults.ts isolation settings:**
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const DEFAULT_PATHS = {
|
|
153
|
+
crewRoot: ".crew", // under project root
|
|
154
|
+
stateRoot: ".crew/state", // under project root
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
All paths are resolved relative to `manifest.cwd`, ensuring state stays under the project root.
|
|
159
|
+
|
|
160
|
+
## Anti-patterns
|
|
161
|
+
|
|
162
|
+
- **Passing raw cwd without validation**: Always use `resolveContainedPath` to ensure paths stay under workspace root.
|
|
163
|
+
- **Cross-workspace respond/cancel**: Even with force=true, foreign session operations should be rejected. Check `ownerSessionId`.
|
|
164
|
+
- **Symlink traversal**: Use `resolveRealContainedPath` to resolve symlinks and detect escape attempts.
|
|
165
|
+
- **Worktree name collision**: Use deterministic names from run/task IDs. Never accept user-controlled branch names.
|
|
166
|
+
- **Dirty worktree removal**: Never force-remove worktrees with uncommitted changes unless explicitly confirmed.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Source patterns
|
|
171
|
+
|
|
172
|
+
- `src/extension/team-tool/api.ts` — workspaceId filter in list-live-agents, steer-agent, follow-up-agent, stop-agent, resume-agent
|
|
173
|
+
- `src/runtime/live-agent-manager.ts` — workspaceId in LiveAgentHandle, listLiveAgentsByWorkspace, listActiveLiveAgentsByWorkspace
|
|
174
|
+
- `src/runtime/live-session-runtime.ts` — LiveSessionConfig, workspaceId in session creation
|
|
175
|
+
- `src/runtime/team-runner.ts` — workspaceId passed through executeTeamRun
|
|
176
|
+
- `src/state/state-store.ts` — initRunManifest with cwd, manifest.cwd
|
|
177
|
+
- `src/worktree/worktree-manager.ts` — prepareTaskWorkspace, assertCleanLeader, linkNodeModulesIfPresent
|
|
178
|
+
- `src/config/defaults.ts` — DEFAULT_PATHS (state under project root)
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Verification
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
cd pi-crew
|
|
186
|
+
# Verify workspace filter in list-live-agents
|
|
187
|
+
node --experimental-strip-types -e "
|
|
188
|
+
import { listLiveAgentsByWorkspace, listActiveLiveAgentsByWorkspace } from './src/runtime/live-agent-manager.ts';
|
|
189
|
+
console.log('By workspace:', listLiveAgentsByWorkspace(process.cwd()).length);
|
|
190
|
+
console.log('Active by workspace:', listActiveLiveAgentsByWorkspace(process.cwd()).length);
|
|
191
|
+
"
|
|
192
|
+
|
|
193
|
+
# Verify worktree creation
|
|
194
|
+
node --experimental-strip-types -e "
|
|
195
|
+
import { prepareTaskWorkspace } from './src/worktree/worktree-manager.ts';
|
|
196
|
+
// Requires a clean git repo and workspaceMode='worktree'
|
|
197
|
+
"
|
|
198
|
+
|
|
199
|
+
npx tsc --noEmit
|
|
200
|
+
node --experimental-strip-types --test test/unit/worktree-manager.test.ts test/unit/isolation-policy.test.ts
|
|
201
|
+
npm test
|
|
202
|
+
```
|
|
@@ -5,35 +5,219 @@ description: Conflict-safe git worktree workflow. Use when running parallel impl
|
|
|
5
5
|
|
|
6
6
|
# worktree-isolation
|
|
7
7
|
|
|
8
|
-
Use this skill for worktree-based execution or cleanup.
|
|
8
|
+
Use this skill for worktree-based execution or cleanup. Git worktrees create isolated working directories that allow parallel code-changing tasks without git conflicts.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## How Worktrees Work
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
A git worktree is a separate working directory linked to the same repository. It has its own:
|
|
13
|
+
- Working directory (different path)
|
|
14
|
+
- HEAD (can be on a different branch)
|
|
15
|
+
- Staged/unstaged changes
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
But it shares:
|
|
18
|
+
- Object database (`.git/objects`)
|
|
19
|
+
- Refs (branches, tags)
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
21
|
+
This means creating a worktree is cheap (no clone needed) and fast.
|
|
22
|
+
|
|
23
|
+
## When to Use Worktrees
|
|
24
|
+
|
|
25
|
+
**Use worktree mode when:**
|
|
26
|
+
- Running parallel implementation workers that modify the same repo
|
|
27
|
+
- Isolating risky changes that might need to be discarded
|
|
28
|
+
- Running multiple agents on the same codebase simultaneously
|
|
29
|
+
- Running a long task that would block other work
|
|
30
|
+
|
|
31
|
+
**Don't use worktree mode when:**
|
|
32
|
+
- The task is read-only (use scaffold mode instead)
|
|
33
|
+
- Only one agent needs to work at a time
|
|
34
|
+
- The repository has uncommitted changes (must be clean)
|
|
35
|
+
|
|
36
|
+
## Worktree Lifecycle
|
|
37
|
+
|
|
38
|
+
### 1. Creation
|
|
39
|
+
|
|
40
|
+
**Prerequisites:**
|
|
41
|
+
- Leader repository must be clean (`git status` empty)
|
|
42
|
+
- Sufficient disk space for worktree directory
|
|
43
|
+
|
|
44
|
+
**Creation flow:**
|
|
45
|
+
```
|
|
46
|
+
team-runner.ts (workspaceMode: "worktree")
|
|
47
|
+
→ prepareTaskWorkspace(manifest, task)
|
|
48
|
+
→ assertCleanLeader(repoRoot)
|
|
49
|
+
→ git worktree add <path> <branch>
|
|
50
|
+
→ linkNodeModulesIfPresent(repoRoot, worktreePath)
|
|
51
|
+
→ return { cwd: worktreePath, worktreePath, branch }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Naming convention:**
|
|
55
|
+
- Branch: `crew/<sanitized-runId>-<sanitized-taskId>`
|
|
56
|
+
- Path: `.worktrees/<runId>/<taskId>/`
|
|
57
|
+
- Deterministic from run/task IDs — no user-controlled fragments
|
|
58
|
+
|
|
59
|
+
**Example:**
|
|
60
|
+
```
|
|
61
|
+
Run: team_20260514092752_218fe358085d7115
|
|
62
|
+
Task: 01_explore
|
|
63
|
+
|
|
64
|
+
Branch: crew/team-20260514092752-218fe358085d7115/01-explore
|
|
65
|
+
Path: .worktrees/team-20260514092752-218fe358085d7115/01-explore/
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Reuse
|
|
69
|
+
|
|
70
|
+
If a worktree with the same branch already exists, it is reused instead of recreated:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Check if worktree already exists
|
|
74
|
+
const existing = git(cwd, ["worktree", "list", "--porcelain"]);
|
|
75
|
+
if (existing.includes(branch)) {
|
|
76
|
+
return { reused: true, worktreePath: parsePath(existing) };
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Reuse is safe when the worktree's base branch hasn't diverged (checked via `branch-freshness.ts`).
|
|
81
|
+
|
|
82
|
+
### 3. Work in worktree
|
|
83
|
+
|
|
84
|
+
Each task works in its own worktree directory:
|
|
85
|
+
- `cwd` = worktree path
|
|
86
|
+
- `git status` shows only that task's changes
|
|
87
|
+
- Changes are isolated from other worktrees and the leader
|
|
88
|
+
|
|
89
|
+
### 4. Cleanup
|
|
90
|
+
|
|
91
|
+
**On task completion:**
|
|
92
|
+
1. Check dirty state (uncommitted changes)
|
|
93
|
+
2. If dirty and not forced → preserve (report to operator)
|
|
94
|
+
3. If clean → `git worktree remove <path>`
|
|
95
|
+
|
|
96
|
+
**On force cleanup:**
|
|
97
|
+
- `git worktree remove <path> --force`
|
|
98
|
+
- Removes even if there are changes (but logs a warning)
|
|
99
|
+
|
|
100
|
+
**Safety rules:**
|
|
101
|
+
- Never force-remove dirty worktrees by default
|
|
102
|
+
- Always check `git status` before cleanup
|
|
103
|
+
- Report worktree paths in events/artifacts for recovery
|
|
104
|
+
|
|
105
|
+
## Stale Worktree Detection
|
|
106
|
+
|
|
107
|
+
Worktrees can become stale when:
|
|
108
|
+
- The base branch has moved
|
|
109
|
+
- The run was abandoned mid-task
|
|
110
|
+
- Node modules are out of date
|
|
111
|
+
|
|
112
|
+
**Detection approach:**
|
|
113
|
+
```typescript
|
|
114
|
+
// Check if base branch has diverged
|
|
115
|
+
function isStaleWorktree(worktreePath: string, baseBranch: string): boolean {
|
|
116
|
+
const current = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
117
|
+
const ahead = git(worktreePath, ["rev-list", "--count", `${baseBranch}..HEAD`]);
|
|
118
|
+
const behind = git(worktreePath, ["rev-list", "--count", `HEAD..${baseBranch}`]);
|
|
119
|
+
return Number(ahead) > 10 || Number(behind) > 0;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Cleanup stale worktrees:**
|
|
124
|
+
```bash
|
|
125
|
+
# List all worktrees
|
|
126
|
+
git worktree list
|
|
127
|
+
|
|
128
|
+
# Remove stale worktree
|
|
129
|
+
git worktree remove .worktrees/stale-task --force
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Merge Conflict Strategy
|
|
133
|
+
|
|
134
|
+
When worktrees complete and changes need to be merged back:
|
|
135
|
+
|
|
136
|
+
1. **One owner per file/symbol**: Assign each file to exactly one worktree. No two worktrees modify the same file.
|
|
137
|
+
2. **Merge order**: If multiple worktrees produce changes, merge in reverse creation order.
|
|
138
|
+
3. **Conflict detection**: `git status --porcelain` shows conflicts.
|
|
139
|
+
4. **Conflict resolution**: Resolve in the leader branch, then continue.
|
|
140
|
+
|
|
141
|
+
**If conflicts occur:**
|
|
142
|
+
```bash
|
|
143
|
+
git merge --no-commit <branch>
|
|
144
|
+
# Resolve conflicts manually
|
|
145
|
+
git add <resolved-files>
|
|
146
|
+
git commit -m "Merge branch and resolve conflicts"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Branch Freshness Check
|
|
150
|
+
|
|
151
|
+
Before reusing a worktree, verify the base branch hasn't diverged:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
function checkBranchFreshness(worktreePath: string, baseBranch: string): {
|
|
155
|
+
fresh: boolean;
|
|
156
|
+
ahead: number;
|
|
157
|
+
behind: number;
|
|
158
|
+
} {
|
|
159
|
+
const status = git(worktreePath, ["status", "--porcelain"]);
|
|
160
|
+
if (status.trim()) return { fresh: false, ahead: 0, behind: 0 }; // dirty
|
|
161
|
+
|
|
162
|
+
const current = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]).trim();
|
|
163
|
+
if (current !== baseBranch) return { fresh: false, ahead: 0, behind: 0 }; // different branch
|
|
164
|
+
|
|
165
|
+
// Check divergence
|
|
166
|
+
const ahead = Number(git(worktreePath, ["rev-list", "--count", `${baseBranch}..HEAD`]));
|
|
167
|
+
const behind = Number(git(worktreePath, ["rev-list", "--count", `HEAD..${baseBranch}`]));
|
|
168
|
+
return { fresh: ahead === 0 && behind === 0, ahead, behind };
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Crash Recovery
|
|
173
|
+
|
|
174
|
+
If a task crashes mid-worktree:
|
|
175
|
+
1. Find orphaned worktrees: `git worktree list`
|
|
176
|
+
2. Check for abandoned runs in `.crew/state/runs/`
|
|
177
|
+
3. If run is failed/cancelled and worktree is dirty → report to operator
|
|
178
|
+
4. If run is completed → safe to clean up
|
|
24
179
|
|
|
25
180
|
## Anti-patterns
|
|
26
181
|
|
|
27
|
-
- Parallel editing
|
|
28
|
-
- Force-removing dirty worktrees
|
|
29
|
-
- Reusing stale worktrees
|
|
30
|
-
- Storing worktrees outside
|
|
182
|
+
- **Parallel editing same file**: Assign one owner per file. Use the task ID in branch names to track ownership.
|
|
183
|
+
- **Force-removing dirty worktrees**: Always report dirty state to operator before cleanup.
|
|
184
|
+
- **Reusing stale worktrees**: Check `branch-freshness.ts` before reuse. If base branch moved, recreate instead.
|
|
185
|
+
- **Storing worktrees outside workspace root**: All worktrees must be under `<repo-root>/.worktrees/`. Never store outside.
|
|
186
|
+
- **Worktree name collision**: Use deterministic naming from run/task IDs, not user input.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Source patterns
|
|
191
|
+
|
|
192
|
+
- `src/worktree/worktree-manager.ts` — prepareTaskWorkspace, assertCleanLeader, linkNodeModulesIfPresent, sanitizeBranchPart
|
|
193
|
+
- `src/worktree/cleanup.ts` — worktree cleanup logic, dirty state detection
|
|
194
|
+
- `src/worktree/branch-freshness.ts` — branch divergence detection
|
|
195
|
+
- `src/runtime/team-runner.ts` — workspaceMode handling, worktree passed to task
|
|
196
|
+
- `src/runtime/task-runner.ts` — worktreePath in task context
|
|
197
|
+
|
|
198
|
+
---
|
|
31
199
|
|
|
32
200
|
## Verification
|
|
33
201
|
|
|
34
202
|
```bash
|
|
35
203
|
cd pi-crew
|
|
204
|
+
|
|
205
|
+
# List all worktrees
|
|
206
|
+
git worktree list
|
|
207
|
+
|
|
208
|
+
# Check leader repo is clean
|
|
209
|
+
git status --short
|
|
210
|
+
|
|
211
|
+
# Verify worktree creation
|
|
212
|
+
node --experimental-strip-types -e "
|
|
213
|
+
import { prepareTaskWorkspace } from './src/worktree/worktree-manager.ts';
|
|
214
|
+
// Requires clean repo and workspaceMode='worktree'
|
|
215
|
+
"
|
|
216
|
+
|
|
217
|
+
# TypeScript
|
|
36
218
|
npx tsc --noEmit
|
|
37
|
-
|
|
219
|
+
|
|
220
|
+
# Tests
|
|
221
|
+
node --experimental-strip-types --test test/unit/worktree-manager.test.ts test/integration/worktree-mode.test.ts
|
|
38
222
|
npm test
|
|
39
|
-
```
|
|
223
|
+
```
|