pi-crew 0.9.16 → 0.9.17
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 +81 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21790 -0
- package/dist/index.mjs +83070 -0
- package/dist/index.mjs.map +7 -0
- package/docs/A +358 -0
- package/docs/M-A +357 -0
- package/docs/REVIEW-FINDINGS-2026-06 +357 -0
- package/docs/Y +357 -0
- package/docs/a +357 -0
- package/docs/aA +358 -0
- package/docs/archive/README.md +91 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/skills/REFERENCE.md +0 -17
- package/docs//303/242mmaAAA/303/242 +357 -0
- package/index.bundle.ts +25 -0
- package/index.ts +95 -4
- package/package.json +15 -4
- package/skills/iterative-audit/SKILL.md +0 -1
- package/skills/widget-rendering/SKILL.md +17 -0
- package/src/adapters/claude-adapter.ts +1 -3
- package/src/adapters/export-util.ts +16 -16
- package/src/adapters/index.ts +5 -5
- package/src/agents/agent-config.ts +20 -14
- package/src/agents/agent-serializer.ts +1 -1
- package/src/agents/discover-agents.ts +83 -39
- package/src/benchmark/benchmark-runner.ts +51 -51
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +166 -590
- package/src/config/defaults.ts +28 -2
- package/src/config/drift-detector.ts +11 -14
- package/src/config/markers.ts +201 -203
- package/src/config/resilient-parser.ts +14 -6
- package/src/config/role-tools.ts +3 -3
- package/src/config/suggestions.ts +5 -12
- package/src/config/types.ts +9 -25
- package/src/errors.ts +151 -157
- package/src/extension/action-suggestions.ts +54 -9
- package/src/extension/async-notifier.ts +43 -13
- package/src/extension/autonomous-policy.ts +77 -37
- package/src/extension/command-completions.ts +9 -8
- package/src/extension/context-status-injection.ts +14 -12
- package/src/extension/crew-autocomplete.ts +8 -16
- package/src/extension/crew-cleanup.ts +2 -1
- package/src/extension/crew-shortcuts.ts +9 -6
- package/src/extension/cross-extension-rpc.ts +136 -42
- package/src/extension/help.ts +1 -1
- package/src/extension/import-index.ts +10 -5
- package/src/extension/knowledge-injection.ts +88 -15
- package/src/extension/management.ts +89 -349
- package/src/extension/message-renderers.ts +3 -4
- package/src/extension/notification-router.ts +22 -5
- package/src/extension/notification-sink.ts +6 -3
- package/src/extension/pi-api.ts +17 -8
- package/src/extension/plan-orchestrate.ts +8 -27
- package/src/extension/project-init.ts +21 -6
- package/src/extension/register.ts +329 -975
- package/src/extension/registration/artifact-cleanup.ts +9 -4
- package/src/extension/registration/command-utils.ts +5 -1
- package/src/extension/registration/commands.ts +806 -314
- package/src/extension/registration/compaction-guard.ts +15 -15
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +293 -0
- package/src/extension/registration/subagent-helpers.ts +29 -7
- package/src/extension/registration/subagent-tools.ts +253 -44
- package/src/extension/registration/team-tool.ts +19 -84
- package/src/extension/registration/ui.ts +168 -0
- package/src/extension/registration/viewers.ts +64 -24
- package/src/extension/result-watcher.ts +18 -7
- package/src/extension/run-bundle-schema.ts +21 -6
- package/src/extension/run-export.ts +15 -6
- package/src/extension/run-import.ts +64 -29
- package/src/extension/run-index.ts +28 -9
- package/src/extension/run-maintenance.ts +32 -12
- package/src/extension/session-summary.ts +1 -1
- package/src/extension/team-manager-command.ts +64 -7
- package/src/extension/team-onboard.ts +151 -150
- package/src/extension/team-recommendation.ts +121 -21
- package/src/extension/team-tool/anchor.ts +31 -64
- package/src/extension/team-tool/api.ts +929 -141
- package/src/extension/team-tool/auto-summarize.ts +14 -26
- package/src/extension/team-tool/cache-control.ts +1 -5
- package/src/extension/team-tool/cancel.ts +138 -38
- package/src/extension/team-tool/chain-dispatch.ts +5 -13
- package/src/extension/team-tool/chain-executor.ts +29 -51
- package/src/extension/team-tool/config-patch.ts +1 -1
- package/src/extension/team-tool/context.ts +40 -20
- package/src/extension/team-tool/destructive-gate.ts +10 -5
- package/src/extension/team-tool/doctor.ts +170 -51
- package/src/extension/team-tool/explain.ts +228 -214
- package/src/extension/team-tool/failure-patterns.ts +3 -9
- package/src/extension/team-tool/goal-wrap.ts +71 -29
- package/src/extension/team-tool/goal.ts +185 -35
- package/src/extension/team-tool/handle-schedule.ts +34 -15
- package/src/extension/team-tool/handle-settings.ts +94 -56
- package/src/extension/team-tool/health-monitor.ts +27 -108
- package/src/extension/team-tool/inspect.ts +42 -9
- package/src/extension/team-tool/intent-policy.ts +4 -1
- package/src/extension/team-tool/lifecycle-actions.ts +244 -48
- package/src/extension/team-tool/orchestrate.ts +7 -18
- package/src/extension/team-tool/parallel-dispatch.ts +51 -29
- package/src/extension/team-tool/plan.ts +29 -5
- package/src/extension/team-tool/respond.ts +49 -16
- package/src/extension/team-tool/run-not-found.ts +3 -8
- package/src/extension/team-tool/run.ts +80 -317
- package/src/extension/team-tool/status.ts +135 -43
- package/src/extension/team-tool/workflow-manage.ts +92 -25
- package/src/extension/team-tool-types.ts +8 -1
- package/src/extension/team-tool.ts +142 -551
- package/src/extension/validate-resources.ts +39 -8
- package/src/hooks/registry.ts +66 -17
- package/src/hooks/types.ts +1 -1
- package/src/i18n.ts +25 -11
- package/src/observability/correlation.ts +17 -3
- package/src/observability/event-bus.ts +51 -56
- package/src/observability/event-to-metric.ts +117 -15
- package/src/observability/exporters/otlp-exporter.ts +56 -17
- package/src/observability/exporters/prometheus-exporter.ts +1 -1
- package/src/observability/metric-registry.ts +16 -4
- package/src/observability/metric-retention.ts +8 -2
- package/src/observability/metric-sink.ts +11 -3
- package/src/observability/metrics-primitives.ts +42 -9
- package/src/plugins/plugin-define.ts +2 -2
- package/src/plugins/plugin-registry.ts +25 -25
- package/src/plugins/plugins/index.ts +1 -1
- package/src/plugins/plugins/nextjs.ts +16 -16
- package/src/plugins/plugins/vite.ts +7 -11
- package/src/plugins/plugins/vitest.ts +6 -11
- package/src/runtime/adaptive-plan.ts +225 -40
- package/src/runtime/agent-control.ts +58 -15
- package/src/runtime/agent-memory.ts +15 -9
- package/src/runtime/agent-observability.ts +10 -2
- package/src/runtime/anchor-manager.ts +27 -25
- package/src/runtime/async-marker.ts +7 -1
- package/src/runtime/async-runner.ts +27 -23
- package/src/runtime/auto-summarize.ts +7 -13
- package/src/runtime/background-runner.ts +189 -251
- package/src/runtime/batch-barrier.ts +18 -16
- package/src/runtime/cancellation-token.ts +16 -6
- package/src/runtime/cancellation.ts +46 -8
- package/src/runtime/capability-inventory.ts +6 -4
- package/src/runtime/chain-parser.ts +44 -11
- package/src/runtime/chain-runner.ts +63 -69
- package/src/runtime/checkpoint.ts +12 -56
- package/src/runtime/child-pi.ts +343 -78
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +71 -26
- package/src/runtime/compact-stages/index.ts +15 -5
- package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
- package/src/runtime/compact-stages/truncation-stage.ts +4 -1
- package/src/runtime/compaction-summary.ts +17 -10
- package/src/runtime/completion-guard.ts +33 -16
- package/src/runtime/crash-classification.ts +28 -7
- package/src/runtime/crash-recovery.ts +177 -37
- package/src/runtime/crew-agent-records.ts +145 -47
- package/src/runtime/crew-hooks.ts +9 -24
- package/src/runtime/cross-extension-rpc.ts +25 -23
- package/src/runtime/custom-tools/irc-tool.ts +43 -15
- package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
- package/src/runtime/delivery-coordinator.ts +34 -7
- package/src/runtime/delta-conflict.ts +9 -21
- package/src/runtime/deterministic-ast.ts +1 -1
- package/src/runtime/diagnostic-export.ts +53 -20
- package/src/runtime/direct-run.ts +12 -2
- package/src/runtime/dwf-state-store.ts +1 -1
- package/src/runtime/dynamic-workflow-context.ts +186 -58
- package/src/runtime/dynamic-workflow-runner.ts +42 -20
- package/src/runtime/effectiveness.ts +16 -8
- package/src/runtime/errors/crew-errors.ts +7 -11
- package/src/runtime/event-stream-bridge.ts +9 -3
- package/src/runtime/foreground-control.ts +54 -14
- package/src/runtime/foreground-watchdog.ts +9 -6
- package/src/runtime/goal-achievement.ts +27 -10
- package/src/runtime/goal-evaluator.ts +54 -19
- package/src/runtime/goal-loop-runner.ts +280 -99
- package/src/runtime/goal-state-store.ts +27 -17
- package/src/runtime/green-contract.ts +11 -2
- package/src/runtime/group-join.ts +64 -31
- package/src/runtime/handoff-manager.ts +37 -42
- package/src/runtime/heartbeat-gradient.ts +10 -2
- package/src/runtime/heartbeat-watcher.ts +32 -6
- package/src/runtime/hidden-handoff.ts +12 -30
- package/src/runtime/important-line-classifier.ts +12 -2
- package/src/runtime/iteration-hooks.ts +25 -15
- package/src/runtime/live-agent-control.ts +55 -7
- package/src/runtime/live-agent-manager.ts +130 -27
- package/src/runtime/live-control-realtime.ts +23 -3
- package/src/runtime/live-irc.ts +4 -1
- package/src/runtime/live-session-health.ts +12 -4
- package/src/runtime/live-session-runtime.ts +379 -117
- package/src/runtime/manifest-cache.ts +28 -12
- package/src/runtime/mcp-proxy.ts +8 -16
- package/src/runtime/metric-parser.ts +1 -5
- package/src/runtime/model-fallback.ts +55 -18
- package/src/runtime/model-resolver.ts +2 -6
- package/src/runtime/model-scope.ts +19 -3
- package/src/runtime/notebook-helpers.ts +60 -62
- package/src/runtime/orphan-worker-registry.ts +37 -26
- package/src/runtime/output-validator.ts +18 -3
- package/src/runtime/overflow-recovery.ts +5 -4
- package/src/runtime/parallel-research.ts +29 -5
- package/src/runtime/parallel-utils.ts +9 -11
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +12 -17
- package/src/runtime/per-write-validator.ts +1 -3
- package/src/runtime/phase-tracker.ts +342 -330
- package/src/runtime/pi-args.ts +26 -8
- package/src/runtime/pi-json-output.ts +1 -1
- package/src/runtime/pi-spawn.ts +30 -19
- package/src/runtime/pipeline-runner.ts +56 -55
- package/src/runtime/plan-templates.ts +5 -6
- package/src/runtime/policy-engine.ts +43 -7
- package/src/runtime/post-checks.ts +12 -4
- package/src/runtime/post-exit-stdio-guard.ts +2 -2
- package/src/runtime/process-lifecycle.ts +20 -10
- package/src/runtime/process-status.ts +16 -5
- package/src/runtime/progress-event-coalescer.ts +2 -1
- package/src/runtime/progress-tracker.ts +103 -103
- package/src/runtime/prose-compressor.ts +9 -11
- package/src/runtime/recovery-recipes.ts +118 -24
- package/src/runtime/replace.ts +25 -10
- package/src/runtime/resilient-edit.ts +10 -25
- package/src/runtime/result-extractor.ts +1 -2
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +26 -50
- package/src/runtime/role-permission.ts +6 -1
- package/src/runtime/run-coalesced-task-group.ts +256 -0
- package/src/runtime/run-drift.ts +14 -15
- package/src/runtime/run-tracker.ts +6 -28
- package/src/runtime/runtime-policy.ts +1 -6
- package/src/runtime/runtime-resolver.ts +80 -15
- package/src/runtime/scheduler.ts +47 -18
- package/src/runtime/semaphore.ts +5 -7
- package/src/runtime/sensitive-paths.ts +2 -1
- package/src/runtime/session-usage.ts +1 -1
- package/src/runtime/settings-store.ts +16 -12
- package/src/runtime/sidechain-output.ts +6 -2
- package/src/runtime/single-agent-compose.ts +1 -4
- package/src/runtime/skill-effectiveness.ts +29 -97
- package/src/runtime/skill-instructions.ts +40 -83
- package/src/runtime/stale-reconciler.ts +73 -197
- package/src/runtime/stream-preview.ts +1 -1
- package/src/runtime/streaming-output.ts +1 -1
- package/src/runtime/subagent-manager.ts +44 -169
- package/src/runtime/subprocess-tool-registry.ts +4 -1
- package/src/runtime/supervisor-contact.ts +10 -5
- package/src/runtime/task-display.ts +19 -5
- package/src/runtime/task-graph-scheduler.ts +95 -21
- package/src/runtime/task-graph.ts +5 -11
- package/src/runtime/task-health.ts +54 -47
- package/src/runtime/task-id.ts +11 -18
- package/src/runtime/task-output-context.ts +265 -81
- package/src/runtime/task-packet.ts +12 -20
- package/src/runtime/task-quality.ts +6 -14
- package/src/runtime/task-runner/context-retrieval.ts +4 -13
- package/src/runtime/task-runner/live-executor.ts +114 -36
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/progress.ts +50 -12
- package/src/runtime/task-runner/prompt-builder.ts +31 -7
- package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
- package/src/runtime/task-runner/result-utils.ts +3 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +27 -8
- package/src/runtime/task-runner/state-helpers.ts +29 -8
- package/src/runtime/task-runner/tail-read.ts +2 -6
- package/src/runtime/task-runner.ts +180 -326
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +456 -974
- package/src/runtime/tool-output-pruner.ts +6 -9
- package/src/runtime/tool-progress.ts +11 -21
- package/src/runtime/verification-gates.ts +33 -24
- package/src/runtime/verification-integrity.ts +1 -4
- package/src/runtime/verification-worktree.ts +62 -12
- package/src/runtime/worker-heartbeat.ts +5 -1
- package/src/runtime/worker-startup.ts +29 -5
- package/src/runtime/workflow-state.ts +17 -9
- package/src/runtime/workspace-lock.ts +30 -35
- package/src/runtime/workspace-tree.ts +20 -32
- package/src/runtime/yield-handler.ts +43 -9
- package/src/runtime/zombie-scanner.ts +5 -4
- package/src/schema/config-schema.ts +266 -172
- package/src/schema/team-tool-schema.ts +29 -74
- package/src/schema/validation-types.ts +25 -17
- package/src/skills/discover-skills.ts +35 -10
- package/src/skills/skill-templates.ts +109 -27
- package/src/skills/validate.ts +26 -8
- package/src/state/active-run-registry.ts +87 -28
- package/src/state/artifact-store.ts +9 -5
- package/src/state/atomic-write-v2.ts +85 -63
- package/src/state/atomic-write.ts +105 -26
- package/src/state/blob-store.ts +47 -20
- package/src/state/contracts.ts +20 -5
- package/src/state/crew-init.ts +5 -22
- package/src/state/decision-ledger.ts +19 -73
- package/src/state/event-log-rotation.ts +41 -15
- package/src/state/event-log.ts +168 -53
- package/src/state/event-reconstructor.ts +11 -1
- package/src/state/gitignore-manager.ts +2 -8
- package/src/state/health-store.ts +57 -57
- package/src/state/hook-instinct-bridge.ts +1 -1
- package/src/state/hook-integrations.ts +1 -1
- package/src/state/instinct-store.ts +17 -5
- package/src/state/jsonl-writer.ts +1 -1
- package/src/state/locks.ts +23 -9
- package/src/state/mailbox.ts +151 -28
- package/src/state/observation-store.ts +20 -14
- package/src/state/run-cache.ts +142 -135
- package/src/state/run-graph.ts +6 -15
- package/src/state/run-metrics.ts +5 -18
- package/src/state/schedule.ts +15 -9
- package/src/state/state-store.ts +129 -45
- package/src/state/task-claims.ts +12 -2
- package/src/state/tiered-eval.ts +52 -43
- package/src/state/types-eval.ts +2 -2
- package/src/state/types.ts +24 -20
- package/src/state/usage.ts +20 -4
- package/src/state/worker-atomic-writer.ts +6 -3
- package/src/subagents/index.ts +2 -2
- package/src/teams/discover-teams.ts +21 -6
- package/src/tools/safe-bash-extension.ts +5 -6
- package/src/tools/safe-bash.ts +10 -11
- package/src/types/new-api-types.ts +6 -10
- package/src/ui/agent-management-overlay.ts +52 -40
- package/src/ui/card-colors.ts +13 -4
- package/src/ui/crew-footer.ts +8 -7
- package/src/ui/crew-select-list.ts +1 -1
- package/src/ui/dashboard-panes/agents-pane.ts +41 -25
- package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
- package/src/ui/dashboard-panes/capability-pane.ts +32 -15
- package/src/ui/dashboard-panes/health-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
- package/src/ui/dashboard-panes/progress-pane.ts +12 -9
- package/src/ui/heartbeat-aggregator.ts +15 -4
- package/src/ui/keybinding-map.ts +40 -7
- package/src/ui/live-conversation-overlay.ts +36 -20
- package/src/ui/live-duration.ts +1 -4
- package/src/ui/live-run-sidebar.ts +88 -25
- package/src/ui/loaders.ts +2 -8
- package/src/ui/mascot.ts +20 -36
- package/src/ui/overlays/agent-picker-overlay.ts +11 -3
- package/src/ui/overlays/confirm-overlay.ts +4 -2
- package/src/ui/overlays/help-overlay.ts +25 -14
- package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
- package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
- package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
- package/src/ui/pi-ui-compat.ts +7 -7
- package/src/ui/powerbar-publisher.ts +120 -42
- package/src/ui/render-diff.ts +10 -3
- package/src/ui/render-scheduler.ts +12 -4
- package/src/ui/run-action-dispatcher.ts +81 -18
- package/src/ui/run-dashboard.ts +172 -76
- package/src/ui/run-event-bus.ts +59 -37
- package/src/ui/run-snapshot-cache.ts +276 -86
- package/src/ui/settings-overlay.ts +361 -74
- package/src/ui/status-colors.ts +12 -1
- package/src/ui/syntax-highlight.ts +1 -1
- package/src/ui/theme-adapter.ts +16 -14
- package/src/ui/theme-discovery.ts +13 -2
- package/src/ui/tool-progress-formatter.ts +7 -7
- package/src/ui/tool-render.ts +128 -56
- package/src/ui/tool-renderers/brief-mode.ts +45 -27
- package/src/ui/tool-renderers/index.ts +109 -43
- package/src/ui/transcript-cache.ts +32 -6
- package/src/ui/transcript-entries.ts +25 -23
- package/src/ui/transcript-viewer.ts +78 -29
- package/src/ui/widget/index.ts +99 -40
- package/src/ui/widget/widget-formatters.ts +13 -6
- package/src/ui/widget/widget-model.ts +19 -8
- package/src/ui/widget/widget-renderer.ts +23 -13
- package/src/ui/widget/widget-types.ts +1 -1
- package/src/utils/bm25-search.ts +199 -199
- package/src/utils/conflict-detect.ts +22 -21
- package/src/utils/env-filter.ts +14 -7
- package/src/utils/file-coalescer.ts +5 -1
- package/src/utils/fingerprint.ts +3 -6
- package/src/utils/frontmatter.ts +3 -1
- package/src/utils/fs-watch.ts +2 -6
- package/src/utils/gh-protocol.ts +119 -44
- package/src/utils/git.ts +13 -15
- package/src/utils/guards.ts +2 -5
- package/src/utils/ids.ts +9 -2
- package/src/utils/incremental-reader.ts +14 -3
- package/src/utils/internal-error.ts +2 -1
- package/src/utils/names.ts +12 -3
- package/src/utils/paths.ts +49 -5
- package/src/utils/project-detector.ts +2 -2
- package/src/utils/redaction.ts +29 -19
- package/src/utils/resolve-shell.ts +9 -7
- package/src/utils/run-watcher-registry.ts +19 -31
- package/src/utils/safe-paths.ts +45 -24
- package/src/utils/scan-cache.ts +9 -2
- package/src/utils/session-utils.ts +2 -4
- package/src/utils/sleep.ts +12 -5
- package/src/utils/sse-parser.ts +5 -17
- package/src/utils/visual.ts +52 -32
- package/src/workflows/discover-workflows.ts +41 -109
- package/src/workflows/intermediate-store.ts +5 -21
- package/src/workflows/preflight-validator.ts +6 -33
- package/src/workflows/topology-analyzer.ts +5 -21
- package/src/workflows/workflow-config.ts +7 -6
- package/src/worktree/branch-freshness.ts +66 -8
- package/src/worktree/cleanup.ts +130 -20
- package/src/worktree/worktree-manager.ts +212 -69
- package/skills/artifact-analysis-loop/SKILL.md +0 -303
- package/skills/detection-pipeline-design/SKILL.md +0 -286
- package/skills/hunting-investigation-loop/SKILL.md +0 -402
- package/skills/incident-playbook-construction/SKILL.md +0 -384
- package/skills/security-review/SKILL.md +0 -561
- package/skills/threat-hypothesis-framework/SKILL.md +0 -176
- package/skills/ui-render-performance/SKILL.md +0 -58
- /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
- /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
- /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
- /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
- /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
- /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
- /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
- /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
- /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
- /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
- /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
- /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
- /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
- /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
|
@@ -1,80 +1,40 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import type {
|
|
5
|
-
ExtensionAPI,
|
|
6
|
-
ExtensionContext,
|
|
7
|
-
} from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
5
|
import { asRecord, loadConfig } from "../config/config.ts";
|
|
9
|
-
import { applyCrewSettingsToConfig, loadCrewSettings } from "../runtime/settings-store.ts";
|
|
10
|
-
// 2.7: Lazy-load LiveRunSidebar — only constructed when the user actually opens
|
|
11
|
-
// a live run sidebar overlay. The class pulls in transcript-viewer and other
|
|
12
|
-
// heavy UI modules.
|
|
13
|
-
import type { LiveRunSidebar as LiveRunSidebarType } from "../ui/live-run-sidebar.ts";
|
|
14
|
-
import {
|
|
15
|
-
type AsyncNotifierState,
|
|
16
|
-
startAsyncRunNotifier,
|
|
17
|
-
stopAsyncRunNotifier,
|
|
18
|
-
} from "./async-notifier.ts";
|
|
19
|
-
import { registerAutonomousPolicy } from "./autonomous-policy.ts";
|
|
20
|
-
import { registerKnowledgeInjection } from "./knowledge-injection.ts";
|
|
21
|
-
import { registerCleanupHandler } from "./crew-cleanup.ts";
|
|
22
|
-
import type { ScheduledJob } from "../runtime/scheduler.ts";
|
|
23
|
-
import { clearHooksScoped } from "../hooks/registry.ts";
|
|
24
|
-
import { uninstallCrewGlobalRegistry } from "./team-tool.ts";
|
|
25
|
-
import { notifyActiveRuns } from "./session-summary.ts";
|
|
26
|
-
|
|
27
|
-
let _cachedLiveRunSidebar: typeof LiveRunSidebarType | undefined;
|
|
28
|
-
async function importLiveRunSidebar(): Promise<typeof LiveRunSidebarType> {
|
|
29
|
-
if (!_cachedLiveRunSidebar) {
|
|
30
|
-
// LAZY: defer LiveRunSidebar import until the user opens a sidebar overlay.
|
|
31
|
-
const mod = await import("../ui/live-run-sidebar.ts");
|
|
32
|
-
_cachedLiveRunSidebar = mod.LiveRunSidebar;
|
|
33
|
-
}
|
|
34
|
-
return _cachedLiveRunSidebar;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
6
|
import { DEFAULT_NOTIFICATIONS, DEFAULT_UI } from "../config/defaults.ts";
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} from "../
|
|
50
|
-
import {
|
|
51
|
-
createMetricFileSink,
|
|
52
|
-
type MetricSink,
|
|
53
|
-
} from "../observability/metric-sink.ts";
|
|
7
|
+
import { clearHooksScoped } from "../hooks/registry.ts";
|
|
8
|
+
// 2.7: Lazy-load OTLPExporter — moved to registration/observability.ts (H3-L2 split).
|
|
9
|
+
import { BatchBarrier, type BatchMember } from "../runtime/batch-barrier.ts";
|
|
10
|
+
import type {
|
|
11
|
+
cancelOrphanedRuns as CancelOrphanedRunsFn,
|
|
12
|
+
detectInterruptedRuns as DetectInterruptedRunsFn,
|
|
13
|
+
purgeStaleActiveRunIndex as PurgeStaleActiveRunIndexFn,
|
|
14
|
+
} from "../runtime/crash-recovery.ts";
|
|
15
|
+
// 2.7: Lazy-load crash-recovery helpers — only invoked from session_start
|
|
16
|
+
// deferred cleanup and cleanupRuntime. Each function is awaited inside an
|
|
17
|
+
// async context that already runs after registration completes.
|
|
18
|
+
import { reconcileAllStaleRuns } from "../runtime/crash-recovery.ts";
|
|
19
|
+
import { appendDeadletter } from "../runtime/deadletter.ts";
|
|
54
20
|
import { listLiveAgents } from "../runtime/live-agent-manager.ts";
|
|
55
21
|
import { createManifestCache } from "../runtime/manifest-cache.ts";
|
|
22
|
+
import { cleanupOrphanWorkers } from "../runtime/orphan-worker-registry.ts";
|
|
23
|
+
import { primePeerDep } from "../runtime/peer-dep.ts";
|
|
24
|
+
import { buildValidationBlocker, extractPathFromInput, validateWrittenFile } from "../runtime/per-write-validator.ts";
|
|
25
|
+
import { cleanupLegacyOrphanTempDirs, cleanupOrphanTempDirs } from "../runtime/pi-args.ts";
|
|
26
|
+
import { startRuntimeWarmup } from "../runtime/runtime-warmup.ts";
|
|
27
|
+
import type { ScheduledJob } from "../runtime/scheduler.ts";
|
|
56
28
|
import { CrewScheduler } from "../runtime/scheduler.ts";
|
|
29
|
+
import { applyCrewSettingsToConfig, loadCrewSettings } from "../runtime/settings-store.ts";
|
|
30
|
+
import { reconcileOrphanedTempWorkspaces } from "../runtime/stale-reconciler.ts";
|
|
57
31
|
import { loadRunManifestById, updateRunStatus } from "../state/state-store.ts";
|
|
58
32
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
59
|
-
import {
|
|
60
|
-
SubagentManager,
|
|
61
|
-
readPersistedSubagentRecord,
|
|
62
|
-
} from "../subagents/manager.ts";
|
|
63
|
-
import { BatchBarrier, type BatchMember } from "../runtime/batch-barrier.ts";
|
|
33
|
+
import { readPersistedSubagentRecord, SubagentManager } from "../subagents/manager.ts";
|
|
64
34
|
import { terminateActiveChildPiProcesses } from "../subagents/spawn.ts";
|
|
65
|
-
import {
|
|
66
|
-
type CrewWidgetState,
|
|
67
|
-
stopCrewWidget,
|
|
68
|
-
updateCrewWidget,
|
|
69
|
-
} from "../ui/widget/index.ts";
|
|
70
|
-
import { summarizeHeartbeats } from "../ui/heartbeat-aggregator.ts";
|
|
71
35
|
import { deployBundledThemes } from "../ui/deploy-bundled-themes.ts";
|
|
72
|
-
import {
|
|
73
|
-
|
|
74
|
-
setExtensionWidget,
|
|
75
|
-
setWorkingIndicator,
|
|
76
|
-
showCustom,
|
|
77
|
-
} from "../ui/pi-ui-compat.ts";
|
|
36
|
+
import { summarizeHeartbeats } from "../ui/heartbeat-aggregator.ts";
|
|
37
|
+
import { requestRender, setExtensionWidget, setWorkingIndicator } from "../ui/pi-ui-compat.ts";
|
|
78
38
|
import {
|
|
79
39
|
clearPiCrewPowerbar,
|
|
80
40
|
disposePowerbarCoalescer,
|
|
@@ -85,76 +45,56 @@ import {
|
|
|
85
45
|
} from "../ui/powerbar-publisher.ts";
|
|
86
46
|
import { RenderScheduler } from "../ui/render-scheduler.ts";
|
|
87
47
|
import { runEventBus } from "../ui/run-event-bus.ts";
|
|
88
|
-
import { createTerminalStatusController, type TerminalStatusController } from "../ui/terminal-status.ts";
|
|
89
|
-
import { extractPathFromInput, validateWrittenFile, buildValidationBlocker } from "../runtime/per-write-validator.ts";
|
|
90
|
-
import { startRuntimeWarmup } from "../runtime/runtime-warmup.ts";
|
|
91
|
-
import { primePeerDep } from "../runtime/peer-dep.ts";
|
|
92
48
|
import { createRunSnapshotCache } from "../ui/run-snapshot-cache.ts";
|
|
49
|
+
import { createTerminalStatusController, type TerminalStatusController } from "../ui/terminal-status.ts";
|
|
50
|
+
import { type CrewWidgetState, stopCrewWidget, updateCrewWidget } from "../ui/widget/index.ts";
|
|
93
51
|
import { closeWatcher } from "../utils/fs-watch.ts";
|
|
94
|
-
import { RunWatcherRegistry } from "../utils/run-watcher-registry.ts";
|
|
95
52
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
96
|
-
import {
|
|
97
|
-
|
|
98
|
-
projectCrewRoot,
|
|
99
|
-
userCrewRoot,
|
|
100
|
-
} from "../utils/paths.ts";
|
|
53
|
+
import { clearProjectRootCache, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
54
|
+
import { RunWatcherRegistry } from "../utils/run-watcher-registry.ts";
|
|
101
55
|
import { resolveContainedPath } from "../utils/safe-paths.ts";
|
|
102
56
|
import { extractSessionId } from "../utils/session-utils.ts";
|
|
103
57
|
import { resetTimings, time } from "../utils/timings.ts";
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} from "./
|
|
108
|
-
import {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
} from "./
|
|
112
|
-
import {
|
|
58
|
+
// 2.7: Lazy-load LiveRunSidebar — moved to registration/ui.ts (H3-L2 split).
|
|
59
|
+
// The class pulls in transcript-viewer and other heavy UI modules.
|
|
60
|
+
import { type AsyncNotifierState, startAsyncRunNotifier, stopAsyncRunNotifier } from "./async-notifier.ts";
|
|
61
|
+
import { registerAutonomousPolicy } from "./autonomous-policy.ts";
|
|
62
|
+
import { registerContextStatusInjection } from "./context-status-injection.ts";
|
|
63
|
+
import { registerCrewAutocomplete } from "./crew-autocomplete.ts";
|
|
64
|
+
import { registerCleanupHandler } from "./crew-cleanup.ts";
|
|
65
|
+
import { registerCrewInputRouter } from "./crew-input-router.ts";
|
|
66
|
+
import { registerCrewShortcuts } from "./crew-shortcuts.ts";
|
|
67
|
+
import { type PiCrewRpcHandle, registerPiCrewRpc } from "./cross-extension-rpc.ts";
|
|
68
|
+
import { registerKnowledgeInjection } from "./knowledge-injection.ts";
|
|
69
|
+
import { registerCrewMessageRenderers } from "./message-renderers.ts";
|
|
70
|
+
import { type NotificationDescriptor } from "./notification-router.ts";
|
|
113
71
|
import { runArtifactCleanup } from "./registration/artifact-cleanup.ts";
|
|
114
72
|
import { registerTeamCommands } from "./registration/commands.ts";
|
|
115
73
|
import { registerCompactionGuard } from "./registration/compaction-guard.ts";
|
|
74
|
+
// H3-L2 split: lifecycle installer extracted to registration/lifecycle.ts.
|
|
75
|
+
// This module owns the async-run notifier, notification router, delivery coordinator, and run-lifecycle subscriptions.
|
|
116
76
|
import {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
77
|
+
configureDeliveryCoordinator as configureDeliveryCoordinatorFromRegistration,
|
|
78
|
+
configureNotifications as configureNotificationsFromRegistration,
|
|
79
|
+
disposeDeliveryCoordinator,
|
|
80
|
+
disposeNotifications,
|
|
81
|
+
type LifecycleState,
|
|
82
|
+
startLifecycleWatchers,
|
|
83
|
+
stopLifecycleWatchers,
|
|
84
|
+
} from "./registration/lifecycle.ts";
|
|
85
|
+
// H3-L2 split: observability installer extracted to registration/observability.ts.
|
|
86
|
+
// This module owns metric registry, OTLP, heartbeat-watcher, and auto-repair timers.
|
|
87
|
+
import { configureObservability, disposeObservability, type ObservabilityState } from "./registration/observability.ts";
|
|
88
|
+
import { __test__subagentSpawnParams, sendAgentWakeUp, sendFollowUp } from "./registration/subagent-helpers.ts";
|
|
121
89
|
import { registerSubagentTools } from "./registration/subagent-tools.ts";
|
|
122
|
-
import { registerCrewMessageRenderers } from "./message-renderers.ts";
|
|
123
|
-
import { registerCrewInputRouter } from "./crew-input-router.ts";
|
|
124
|
-
import { registerCrewAutocomplete } from "./crew-autocomplete.ts";
|
|
125
|
-
import { registerCrewShortcuts } from "./crew-shortcuts.ts";
|
|
126
|
-
import { registerContextStatusInjection } from "./context-status-injection.ts";
|
|
127
90
|
import { registerTeamTool } from "./registration/team-tool.ts";
|
|
128
|
-
|
|
129
|
-
|
|
91
|
+
// H3-L2 split: ui installer extracted to registration/ui.ts.
|
|
92
|
+
// This module owns live-run sidebar + powerbar overlay logic.
|
|
93
|
+
import { clearDashboardPowerbar, installLiveSidebar, pushPowerbarUpdate, type UiState } from "./registration/ui.ts";
|
|
94
|
+
import { notifyActiveRuns } from "./session-summary.ts";
|
|
130
95
|
import { shouldBlockDestructiveTeamAction } from "./team-tool/destructive-gate.ts";
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
async function importOTLPExporter(): Promise<typeof OTLPExporterType> {
|
|
134
|
-
if (!_cachedOTLPExporter) {
|
|
135
|
-
// LAZY: opt-in OTLP metric export — load only when otlp.enabled=true.
|
|
136
|
-
const mod = await import("../observability/exporters/otlp-exporter.ts");
|
|
137
|
-
_cachedOTLPExporter = mod.OTLPExporter;
|
|
138
|
-
}
|
|
139
|
-
return _cachedOTLPExporter;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
import type {
|
|
143
|
-
cancelOrphanedRuns as CancelOrphanedRunsFn,
|
|
144
|
-
detectInterruptedRuns as DetectInterruptedRunsFn,
|
|
145
|
-
purgeStaleActiveRunIndex as PurgeStaleActiveRunIndexFn,
|
|
146
|
-
} from "../runtime/crash-recovery.ts";
|
|
147
|
-
// 2.7: Lazy-load crash-recovery helpers — only invoked from session_start
|
|
148
|
-
// deferred cleanup and cleanupRuntime. Each function is awaited inside an
|
|
149
|
-
// async context that already runs after registration completes.
|
|
150
|
-
import {
|
|
151
|
-
reconcileAllStaleRuns,
|
|
152
|
-
} from "../runtime/crash-recovery.ts";
|
|
153
|
-
import { appendDeadletter } from "../runtime/deadletter.ts";
|
|
154
|
-
import { HeartbeatWatcher } from "../runtime/heartbeat-watcher.ts";
|
|
155
|
-
import { cleanupOrphanTempDirs, cleanupLegacyOrphanTempDirs } from "../runtime/pi-args.ts";
|
|
156
|
-
import { cleanupOrphanWorkers } from "../runtime/orphan-worker-registry.ts";
|
|
157
|
-
import { reconcileOrphanedTempWorkspaces } from "../runtime/stale-reconciler.ts";
|
|
96
|
+
import { persistScheduledJobUpdate } from "./team-tool/handle-schedule.ts";
|
|
97
|
+
import { handleTeamTool, uninstallCrewGlobalRegistry } from "./team-tool.ts";
|
|
158
98
|
|
|
159
99
|
let _cachedCrashRecovery:
|
|
160
100
|
| {
|
|
@@ -163,9 +103,7 @@ let _cachedCrashRecovery:
|
|
|
163
103
|
purgeStaleActiveRunIndex: typeof PurgeStaleActiveRunIndexFn;
|
|
164
104
|
}
|
|
165
105
|
| undefined;
|
|
166
|
-
async function importCrashRecovery(): Promise<
|
|
167
|
-
NonNullable<typeof _cachedCrashRecovery>
|
|
168
|
-
> {
|
|
106
|
+
async function importCrashRecovery(): Promise<NonNullable<typeof _cachedCrashRecovery>> {
|
|
169
107
|
if (!_cachedCrashRecovery) {
|
|
170
108
|
// LAZY: defer crash-recovery (~14 KB) until session_start cleanup runs.
|
|
171
109
|
const mod = await import("../runtime/crash-recovery.ts");
|
|
@@ -189,13 +127,9 @@ function purgeStaleActiveRunIndexSyncIfLoaded(): void {
|
|
|
189
127
|
}
|
|
190
128
|
}
|
|
191
129
|
|
|
192
|
-
import {
|
|
193
|
-
pruneFinishedRuns,
|
|
194
|
-
pruneUserLevelRuns,
|
|
195
|
-
} from "../extension/run-maintenance.ts";
|
|
130
|
+
import { pruneFinishedRuns, pruneUserLevelRuns } from "../extension/run-maintenance.ts";
|
|
196
131
|
import { initI18n } from "../i18n.ts";
|
|
197
|
-
|
|
198
|
-
import { OverflowRecoveryTracker } from "../runtime/overflow-recovery.ts";
|
|
132
|
+
// H3-L2 split: DeliveryCoordinator + OverflowRecoveryTracker moved to registration/lifecycle.ts.
|
|
199
133
|
import { tryRegisterSessionCleanup } from "../runtime/session-resources.ts";
|
|
200
134
|
import { createSessionSnapshot } from "../runtime/session-snapshot.ts";
|
|
201
135
|
|
|
@@ -245,9 +179,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
245
179
|
let manifestCache = createManifestCache(process.cwd());
|
|
246
180
|
let runSnapshotCache = createRunSnapshotCache(process.cwd());
|
|
247
181
|
let cacheCwd = process.cwd();
|
|
248
|
-
const getManifestCache = (
|
|
249
|
-
cwd: string,
|
|
250
|
-
): ReturnType<typeof createManifestCache> => {
|
|
182
|
+
const getManifestCache = (cwd: string): ReturnType<typeof createManifestCache> => {
|
|
251
183
|
if (manifestCache && cacheCwd === cwd) return manifestCache;
|
|
252
184
|
if (manifestCache) manifestCache.dispose();
|
|
253
185
|
if (runSnapshotCache) runSnapshotCache.dispose?.();
|
|
@@ -256,299 +188,76 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
256
188
|
runSnapshotCache = createRunSnapshotCache(cwd);
|
|
257
189
|
return manifestCache;
|
|
258
190
|
};
|
|
259
|
-
const getRunSnapshotCache = (
|
|
260
|
-
cwd: string,
|
|
261
|
-
): ReturnType<typeof createRunSnapshotCache> => {
|
|
191
|
+
const getRunSnapshotCache = (cwd: string): ReturnType<typeof createRunSnapshotCache> => {
|
|
262
192
|
if (cacheCwd !== cwd) getManifestCache(cwd);
|
|
263
193
|
return runSnapshotCache;
|
|
264
194
|
};
|
|
265
|
-
const telemetryEnabled = (): boolean =>
|
|
266
|
-
loadConfig(currentCtx?.cwd ?? process.cwd()).config.telemetry
|
|
267
|
-
?.enabled !== false;
|
|
195
|
+
const telemetryEnabled = (): boolean => loadConfig(currentCtx?.cwd ?? process.cwd()).config.telemetry?.enabled !== false;
|
|
268
196
|
const widgetState: CrewWidgetState = { frame: 0 };
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
197
|
+
// H3-L2 split: notification sink + router moved to registration/lifecycle.ts state.
|
|
198
|
+
// H3-L2 split: Observability state lives in registration/observability.ts;
|
|
199
|
+
// orchestrator keeps a holder so cleanupRuntime can dispose in place.
|
|
200
|
+
const observabilityState: ObservabilityState = {
|
|
201
|
+
metricRegistry: undefined,
|
|
202
|
+
eventMetricSub: undefined,
|
|
203
|
+
metricSink: undefined,
|
|
204
|
+
heartbeatWatcher: undefined,
|
|
205
|
+
autoRepairTimer: undefined,
|
|
206
|
+
tempReconcileTimer: undefined,
|
|
207
|
+
otlpExporter: undefined,
|
|
208
|
+
};
|
|
209
|
+
// H3-L2 split: UI state lives in registration/ui.ts; orchestrator keeps
|
|
210
|
+
// a holder for the live sidebar runId and dashboard-opened flag.
|
|
211
|
+
const uiState: UiState = { liveSidebarRunId: undefined, dashboardOpened: false };
|
|
212
|
+
// H3-L2 split: Lifecycle state lives in registration/lifecycle.ts.
|
|
213
|
+
const lifecycleState: LifecycleState = {
|
|
214
|
+
notifierStarted: false,
|
|
215
|
+
notificationSink: undefined,
|
|
216
|
+
notificationRouter: undefined,
|
|
217
|
+
deliveryCoordinator: undefined,
|
|
218
|
+
overflowTracker: undefined,
|
|
219
|
+
};
|
|
280
220
|
const configureNotifications = (ctx: ExtensionContext): void => {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
notificationRouter = undefined;
|
|
284
|
-
notificationSink = undefined;
|
|
285
|
-
const config = loadConfig(ctx.cwd).config;
|
|
286
|
-
if (config.notifications?.enabled === false) return;
|
|
287
|
-
if (config.telemetry?.enabled !== false)
|
|
288
|
-
notificationSink = createJsonlSink(
|
|
289
|
-
projectCrewRoot(ctx.cwd),
|
|
290
|
-
config.notifications?.sinkRetentionDays ??
|
|
291
|
-
DEFAULT_NOTIFICATIONS.sinkRetentionDays,
|
|
292
|
-
);
|
|
293
|
-
notificationRouter = new NotificationRouter(
|
|
294
|
-
{
|
|
295
|
-
dedupWindowMs:
|
|
296
|
-
config.notifications?.dedupWindowMs ??
|
|
297
|
-
DEFAULT_NOTIFICATIONS.dedupWindowMs,
|
|
298
|
-
batchWindowMs:
|
|
299
|
-
config.notifications?.batchWindowMs ??
|
|
300
|
-
DEFAULT_NOTIFICATIONS.batchWindowMs,
|
|
301
|
-
quietHours: config.notifications?.quietHours,
|
|
302
|
-
severityFilter: config.notifications?.severityFilter ?? [
|
|
303
|
-
...DEFAULT_NOTIFICATIONS.severityFilter,
|
|
304
|
-
],
|
|
305
|
-
sink: (notification) => notificationSink?.write(notification),
|
|
306
|
-
},
|
|
307
|
-
(notification) => {
|
|
308
|
-
widgetState.notificationCount =
|
|
309
|
-
(widgetState.notificationCount ?? 0) + 1;
|
|
310
|
-
sendFollowUp(
|
|
311
|
-
pi,
|
|
312
|
-
[
|
|
313
|
-
notification.title,
|
|
314
|
-
notification.body,
|
|
315
|
-
notification.runId
|
|
316
|
-
? `Run: ${notification.runId}`
|
|
317
|
-
: undefined,
|
|
318
|
-
]
|
|
319
|
-
.filter((line): line is string => Boolean(line))
|
|
320
|
-
.join("\n"),
|
|
321
|
-
);
|
|
322
|
-
if (currentCtx) {
|
|
323
|
-
const uiConfig = loadConfig(currentCtx.cwd).config.ui;
|
|
324
|
-
updateCrewWidget(
|
|
325
|
-
currentCtx,
|
|
326
|
-
widgetState,
|
|
327
|
-
uiConfig,
|
|
328
|
-
getManifestCache(currentCtx.cwd),
|
|
329
|
-
getRunSnapshotCache(currentCtx.cwd),
|
|
330
|
-
);
|
|
331
|
-
requestPowerbarUpdate(
|
|
332
|
-
pi.events,
|
|
333
|
-
currentCtx.cwd,
|
|
334
|
-
uiConfig,
|
|
335
|
-
getManifestCache(currentCtx.cwd),
|
|
336
|
-
getRunSnapshotCache(currentCtx.cwd),
|
|
337
|
-
currentCtx,
|
|
338
|
-
widgetState.notificationCount ?? 0,
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
);
|
|
221
|
+
// H3-L2 split: notifications wiring delegated to registration/lifecycle.ts.
|
|
222
|
+
void configureNotificationsFromRegistration(ctx);
|
|
343
223
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
metricSink?.dispose();
|
|
355
|
-
eventMetricSub?.dispose();
|
|
356
|
-
otlpExporter?.dispose();
|
|
357
|
-
metricRegistry?.dispose();
|
|
358
|
-
heartbeatWatcher = undefined;
|
|
359
|
-
metricSink = undefined;
|
|
360
|
-
eventMetricSub = undefined;
|
|
361
|
-
otlpExporter = undefined;
|
|
362
|
-
metricRegistry = undefined;
|
|
363
|
-
const config = loadConfig(ctx.cwd).config;
|
|
364
|
-
if (config.observability?.enabled === false) return;
|
|
365
|
-
metricRegistry = createMetricRegistry();
|
|
366
|
-
eventMetricSub = wireEventToMetrics(pi.events, metricRegistry);
|
|
367
|
-
if (config.telemetry?.enabled !== false)
|
|
368
|
-
metricSink = createMetricFileSink({
|
|
369
|
-
crewRoot: projectCrewRoot(ctx.cwd),
|
|
370
|
-
registry: metricRegistry,
|
|
371
|
-
retentionDays: config.observability?.metricRetentionDays ?? 7,
|
|
372
|
-
});
|
|
373
|
-
if (config.otlp?.enabled === true && config.otlp.endpoint) {
|
|
374
|
-
const otlpEndpoint = config.otlp.endpoint;
|
|
375
|
-
const otlpHeaders = config.otlp.headers;
|
|
376
|
-
const otlpInterval = config.otlp.intervalMs;
|
|
377
|
-
const owningRegistry = metricRegistry;
|
|
378
|
-
// LAZY: opt-in OTLP export — load the exporter module on first enable.
|
|
379
|
-
void importOTLPExporter()
|
|
380
|
-
.then((Ctor) => {
|
|
381
|
-
if (
|
|
382
|
-
cleanedUp ||
|
|
383
|
-
metricRegistry !== owningRegistry ||
|
|
384
|
-
!owningRegistry
|
|
385
|
-
)
|
|
386
|
-
return;
|
|
387
|
-
otlpExporter = new Ctor(
|
|
388
|
-
{
|
|
389
|
-
endpoint: otlpEndpoint,
|
|
390
|
-
headers: otlpHeaders,
|
|
391
|
-
intervalMs: otlpInterval,
|
|
392
|
-
},
|
|
393
|
-
owningRegistry,
|
|
394
|
-
);
|
|
395
|
-
otlpExporter.start();
|
|
396
|
-
})
|
|
397
|
-
.catch((error: unknown) =>
|
|
398
|
-
logInternalError("register.otlp-lazy-import", error),
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
heartbeatWatcher = new HeartbeatWatcher({
|
|
402
|
-
cwd: ctx.cwd,
|
|
403
|
-
pollIntervalMs: config.observability?.pollIntervalMs ?? 5000,
|
|
404
|
-
manifestCache: getManifestCache(ctx.cwd),
|
|
405
|
-
registry: metricRegistry,
|
|
406
|
-
router: {
|
|
407
|
-
enqueue: (notification) => {
|
|
408
|
-
notifyOperator(notification);
|
|
409
|
-
return true;
|
|
410
|
-
},
|
|
411
|
-
},
|
|
412
|
-
deadletterTickThreshold:
|
|
413
|
-
config.reliability?.deadletterThreshold ?? 3,
|
|
414
|
-
onDeadletterTrigger: (manifest, taskId) => {
|
|
415
|
-
appendDeadletter(manifest, {
|
|
416
|
-
taskId,
|
|
417
|
-
runId: manifest.runId,
|
|
418
|
-
reason: "heartbeat-dead",
|
|
419
|
-
attempts: 0,
|
|
420
|
-
timestamp: new Date().toISOString(),
|
|
421
|
-
});
|
|
422
|
-
metricRegistry
|
|
423
|
-
?.counter(
|
|
424
|
-
"crew.task.deadletter_total",
|
|
425
|
-
"Deadletter triggers by reason",
|
|
426
|
-
)
|
|
427
|
-
.inc({ reason: "heartbeat-dead" });
|
|
428
|
-
pi.events?.emit?.("crew.task.deadletter", {
|
|
429
|
-
runId: manifest.runId,
|
|
430
|
-
taskId,
|
|
431
|
-
reason: "heartbeat-dead",
|
|
432
|
-
});
|
|
433
|
-
},
|
|
224
|
+
async function configureNotificationsFromRegistration(ctx: ExtensionContext): Promise<void> {
|
|
225
|
+
// LAZY: registration/lifecycle is heavy (notification-router + sink)
|
|
226
|
+
const lifecycleModule = await import("./registration/lifecycle.ts");
|
|
227
|
+
await lifecycleModule.configureNotifications(ctx, lifecycleState, {
|
|
228
|
+
pi,
|
|
229
|
+
widgetState,
|
|
230
|
+
getCurrentCtx: () => currentCtx,
|
|
231
|
+
getManifestCache,
|
|
232
|
+
getRunSnapshotCache,
|
|
233
|
+
requestPowerbarUpdate,
|
|
434
234
|
});
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
//
|
|
438
|
-
// This
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
clearInterval(autoRepairTimer);
|
|
442
|
-
autoRepairTimer = undefined;
|
|
443
|
-
}
|
|
444
|
-
if (tempReconcileTimer) {
|
|
445
|
-
clearInterval(tempReconcileTimer);
|
|
446
|
-
tempReconcileTimer = undefined;
|
|
447
|
-
}
|
|
448
|
-
const autoRepairIntervalMs =
|
|
449
|
-
config.reliability?.autoRepairIntervalMs ?? 60_000;
|
|
450
|
-
if (autoRepairIntervalMs > 0) {
|
|
451
|
-
autoRepairTimer = setInterval(() => {
|
|
452
|
-
if (cleanedUp || !currentCtx) return;
|
|
453
|
-
try {
|
|
454
|
-
const staleResults = reconcileAllStaleRuns(
|
|
455
|
-
currentCtx.cwd,
|
|
456
|
-
getManifestCache(currentCtx.cwd),
|
|
457
|
-
);
|
|
458
|
-
if (staleResults.length > 0) {
|
|
459
|
-
for (const result of staleResults) {
|
|
460
|
-
if (result.repaired) {
|
|
461
|
-
notifyOperator({
|
|
462
|
-
id: `auto_repair_${result.runId}`,
|
|
463
|
-
severity: "info",
|
|
464
|
-
source: "auto-repair",
|
|
465
|
-
runId: result.runId,
|
|
466
|
-
title: `Auto-repaired stale run`,
|
|
467
|
-
body: result.detail,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
} catch (error) {
|
|
473
|
-
logInternalError("register.autoRepair", error);
|
|
474
|
-
}
|
|
475
|
-
}, autoRepairIntervalMs);
|
|
476
|
-
autoRepairTimer.unref();
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Auto-repair: also scan /tmp/ for orphaned pi-crew-* workspaces.
|
|
480
|
-
// This catches zombie runs from tests or crashed sessions.
|
|
481
|
-
if (autoRepairIntervalMs > 0) {
|
|
482
|
-
tempReconcileTimer = setInterval(() => {
|
|
483
|
-
if (cleanedUp) return;
|
|
484
|
-
try {
|
|
485
|
-
reconcileOrphanedTempWorkspaces(Date.now(), {
|
|
486
|
-
cleanupOrphanedTempDirs:
|
|
487
|
-
config.reliability?.cleanupOrphanedTempDirs,
|
|
488
|
-
});
|
|
489
|
-
// Layer 4: also clean orphan temp dirs under
|
|
490
|
-
// ~/.pi/agent/pi-crew/tmp/ that the SIGKILL'd parent
|
|
491
|
-
// processes left behind. Catches anything Layers 1-3 missed.
|
|
492
|
-
const orphanResult = cleanupOrphanTempDirs();
|
|
493
|
-
if (orphanResult.cleaned > 0) {
|
|
494
|
-
notifyOperator({
|
|
495
|
-
id: `layer4_temp_cleanup_${Date.now()}`,
|
|
496
|
-
severity: "info",
|
|
497
|
-
source: "temp-cleanup",
|
|
498
|
-
title: `Layer 4: cleaned ${orphanResult.cleaned} orphan temp dir(s)`,
|
|
499
|
-
body: `~/.pi/agent/pi-crew/tmp/ orphans older than 24h removed (scanned ${orphanResult.scanned}, failed ${orphanResult.failed}).`,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
// Layer 5: clean legacy /tmp/pi-crew-* prompt/task orphans
|
|
503
|
-
// from before commit 8ba270d moved temp dirs out of /tmp.
|
|
504
|
-
// The existing reconcileOrphanedTempWorkspaces only cleans
|
|
505
|
-
// dirs containing .crew/state/runs/ (run-state dirs), so
|
|
506
|
-
// prompt/task orphans are never touched by Layer 3.
|
|
507
|
-
const legacyResult = cleanupLegacyOrphanTempDirs();
|
|
508
|
-
if (legacyResult.cleaned > 0) {
|
|
509
|
-
notifyOperator({
|
|
510
|
-
id: `layer5_legacy_temp_cleanup_${Date.now()}`,
|
|
511
|
-
severity: "info",
|
|
512
|
-
source: "temp-cleanup",
|
|
513
|
-
title: `Layer 5: cleaned ${legacyResult.cleaned} legacy /tmp/pi-crew-* orphan(s)`,
|
|
514
|
-
body: `Pre-fix /tmp/pi-crew-* prompt/task orphans (no .crew/state/runs/, >24h) removed (scanned ${legacyResult.scanned}, failed ${legacyResult.failed}).`,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
} catch (error) {
|
|
518
|
-
logInternalError("register.tempAutoRepair", error);
|
|
519
|
-
}
|
|
520
|
-
}, autoRepairIntervalMs * 5); // Less frequent: every 5 min by default
|
|
521
|
-
tempReconcileTimer.unref();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (config.reliability?.autoRecover === true) {
|
|
525
|
-
const cwdSnapshot = ctx.cwd;
|
|
526
|
-
const cacheSnapshot = getManifestCache(cwdSnapshot);
|
|
527
|
-
void importCrashRecovery()
|
|
528
|
-
.then(({ detectInterruptedRuns }) => {
|
|
529
|
-
if (cleanedUp) return;
|
|
530
|
-
for (const plan of detectInterruptedRuns(
|
|
531
|
-
cwdSnapshot,
|
|
532
|
-
cacheSnapshot,
|
|
533
|
-
)) {
|
|
534
|
-
notifyOperator({
|
|
535
|
-
id: `recovery_prompt_${plan.runId}`,
|
|
536
|
-
severity: "warning",
|
|
537
|
-
source: "crash-recovery",
|
|
538
|
-
runId: plan.runId,
|
|
539
|
-
title: `Run ${plan.runId} was interrupted`,
|
|
540
|
-
body: `${plan.resumableTasks.length} tasks pending recovery. Open dashboard to inspect before resuming.`,
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
})
|
|
544
|
-
.catch((error: unknown) =>
|
|
545
|
-
logInternalError(
|
|
546
|
-
"register.crash-recovery-lazy-import",
|
|
547
|
-
error,
|
|
548
|
-
),
|
|
549
|
-
);
|
|
550
|
-
}
|
|
235
|
+
}
|
|
236
|
+
const configureObservability = (ctx: ExtensionContext): void => {
|
|
237
|
+
// H3-L2 split: observability installation delegated to registration/observability.ts.
|
|
238
|
+
// This keeps the orchestrator thin while preserving the same lifecycle:
|
|
239
|
+
// dispose prior → install fresh → gate on config flags.
|
|
240
|
+
void configureObservabilityFromRegistration(ctx);
|
|
551
241
|
};
|
|
242
|
+
// H3-L2 split: wraps the registration/observability.configureObservability call.
|
|
243
|
+
// Pulled out as a method so we can build the deps bag once at registration time
|
|
244
|
+
// and reuse it across session_start cycles.
|
|
245
|
+
async function configureObservabilityFromRegistration(ctx: ExtensionContext): Promise<void> {
|
|
246
|
+
// LAZY: registration/observability is heavy (HeartbeatWatcher + metric stack)
|
|
247
|
+
const observabilityModule = await import("./registration/observability.ts");
|
|
248
|
+
await observabilityModule.configureObservability(ctx, observabilityState, {
|
|
249
|
+
pi,
|
|
250
|
+
getManifestCache,
|
|
251
|
+
notifyOperator,
|
|
252
|
+
isCleanedUp: () => cleanedUp,
|
|
253
|
+
reconcileStaleRuns: (cwd, cache) => reconcileAllStaleRuns(cwd, cache),
|
|
254
|
+
reconcileOrphanedTempWorkspaces: (now, opts) => reconcileOrphanedTempWorkspaces(now, opts),
|
|
255
|
+
cleanupOrphanTempDirs,
|
|
256
|
+
cleanupLegacyOrphanTempDirs,
|
|
257
|
+
appendDeadletter: (manifest, entry) => appendDeadletter(manifest, entry as Parameters<typeof appendDeadletter>[1]),
|
|
258
|
+
importCrashRecovery,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
552
261
|
const autoRecoveryLast = new Map<string, { insertedAt: number; lastAccessAt: number }>();
|
|
553
262
|
// FIX (Round 22, defensive cap): Bound the cooldown-gate Map. Each run
|
|
554
263
|
// contributes up to 4 keys (one per maybeNotifyHealth kind). Without a cap,
|
|
@@ -560,85 +269,28 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
560
269
|
// being re-accessed.
|
|
561
270
|
const AUTO_RECOVERY_LAST_MAX_ENTRIES = 1000;
|
|
562
271
|
const configureDeliveryCoordinator = (): void => {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
},
|
|
571
|
-
sendFollowUp: (title, body) => {
|
|
572
|
-
sendFollowUp(
|
|
573
|
-
pi,
|
|
574
|
-
[title, body]
|
|
575
|
-
.filter((line): line is string => Boolean(line))
|
|
576
|
-
.join("\n"),
|
|
577
|
-
);
|
|
578
|
-
},
|
|
579
|
-
sendWakeUp: (message) => {
|
|
580
|
-
sendAgentWakeUp(pi, message);
|
|
581
|
-
},
|
|
582
|
-
});
|
|
583
|
-
overflowTracker = new OverflowRecoveryTracker({
|
|
584
|
-
onPhaseChange: (state, previousPhase) => {
|
|
585
|
-
if (metricRegistry) {
|
|
586
|
-
metricRegistry
|
|
587
|
-
.counter(
|
|
588
|
-
"crew.task.overflow_recovery_total",
|
|
589
|
-
"Overflow recovery phase transitions",
|
|
590
|
-
)
|
|
591
|
-
.inc({
|
|
592
|
-
phase: state.phase,
|
|
593
|
-
previous_phase: previousPhase,
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
pi.events?.emit?.("crew.task.overflow", {
|
|
597
|
-
runId: state.runId,
|
|
598
|
-
taskId: state.taskId,
|
|
599
|
-
phase: state.phase,
|
|
600
|
-
previousPhase,
|
|
601
|
-
});
|
|
602
|
-
},
|
|
603
|
-
onTimeout: (state) => {
|
|
604
|
-
notifyOperator({
|
|
605
|
-
id: `overflow_timeout_${state.taskId}`,
|
|
606
|
-
severity: "warning",
|
|
607
|
-
source: "overflow-recovery",
|
|
608
|
-
runId: state.runId,
|
|
609
|
-
title: `Task ${state.taskId} overflow recovery timed out`,
|
|
610
|
-
body: `Phase: ${state.phase}, compaction_count: ${state.compactionCount}, retry_count: ${state.retryCount}. The task may be stuck.`,
|
|
611
|
-
});
|
|
612
|
-
},
|
|
272
|
+
// H3-L2 split: delivery coordinator + overflow tracker moved to registration/lifecycle.ts.
|
|
273
|
+
void configureDeliveryCoordinatorFromRegistration(lifecycleState, {
|
|
274
|
+
pi,
|
|
275
|
+
observabilityState,
|
|
276
|
+
notifyOperator,
|
|
277
|
+
sendFollowUp,
|
|
278
|
+
sendAgentWakeUp,
|
|
613
279
|
});
|
|
614
280
|
};
|
|
615
281
|
const notifyOperator = (notification: NotificationDescriptor): void => {
|
|
616
282
|
try {
|
|
617
|
-
notificationRouter?.enqueue(notification);
|
|
283
|
+
lifecycleState.notificationRouter?.enqueue(notification);
|
|
618
284
|
} catch (error) {
|
|
619
285
|
logInternalError("register.notification", error);
|
|
620
|
-
sendFollowUp(
|
|
621
|
-
pi,
|
|
622
|
-
[notification.title, notification.body]
|
|
623
|
-
.filter((line): line is string => Boolean(line))
|
|
624
|
-
.join("\n"),
|
|
625
|
-
);
|
|
286
|
+
sendFollowUp(pi, [notification.title, notification.body].filter((line): line is string => Boolean(line)).join("\n"));
|
|
626
287
|
}
|
|
627
288
|
};
|
|
628
289
|
const captureSessionGeneration = (): number => sessionGeneration;
|
|
629
|
-
const isOwnerSessionCurrent = (
|
|
630
|
-
ownerGeneration
|
|
631
|
-
): boolean =>
|
|
632
|
-
!cleanedUp &&
|
|
633
|
-
(ownerGeneration === undefined ||
|
|
634
|
-
ownerGeneration === sessionGeneration);
|
|
635
|
-
const isContextCurrent = (
|
|
636
|
-
ctx: ExtensionContext,
|
|
637
|
-
ownerGeneration: number,
|
|
638
|
-
): boolean =>
|
|
639
|
-
!cleanedUp &&
|
|
640
|
-
currentCtx === ctx &&
|
|
641
|
-
sessionGeneration === ownerGeneration;
|
|
290
|
+
const isOwnerSessionCurrent = (ownerGeneration: number | undefined): boolean =>
|
|
291
|
+
!cleanedUp && (ownerGeneration === undefined || ownerGeneration === sessionGeneration);
|
|
292
|
+
const isContextCurrent = (ctx: ExtensionContext, ownerGeneration: number): boolean =>
|
|
293
|
+
!cleanedUp && currentCtx === ctx && sessionGeneration === ownerGeneration;
|
|
642
294
|
const batchBarrier = new BatchBarrier();
|
|
643
295
|
const subagentManager = new SubagentManager(
|
|
644
296
|
4,
|
|
@@ -685,13 +337,10 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
685
337
|
setTimeout(() => {
|
|
686
338
|
if (cleanedUp) return;
|
|
687
339
|
const fresh = subagentManager.getRecord(agentId);
|
|
688
|
-
const persisted = currentCtx
|
|
689
|
-
? readPersistedSubagentRecord(currentCtx.cwd, agentId)
|
|
690
|
-
: undefined;
|
|
340
|
+
const persisted = currentCtx ? readPersistedSubagentRecord(currentCtx.cwd, agentId) : undefined;
|
|
691
341
|
// Leader already joined the result -> suppress redundant notify.
|
|
692
342
|
if (fresh?.resultConsumed || persisted?.resultConsumed) return;
|
|
693
|
-
if (!isOwnerSessionCurrent(fresh?.ownerSessionGeneration ?? ownerGen))
|
|
694
|
-
return;
|
|
343
|
+
if (!isOwnerSessionCurrent(fresh?.ownerSessionGeneration ?? ownerGen)) return;
|
|
695
344
|
// Rule 1 (batch coalescing): if this agent belongs to a batch, never
|
|
696
345
|
// emit an individual notification. Instead record its terminal state
|
|
697
346
|
// in the barrier; emit ONE consolidated notification only when ALL
|
|
@@ -707,10 +356,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
707
356
|
if (snap.allDone && !snap.notified) {
|
|
708
357
|
batchBarrier.markNotified(agentBatchId);
|
|
709
358
|
const roster = snap.terminal
|
|
710
|
-
.map(
|
|
711
|
-
(m) =>
|
|
712
|
-
`- ${m.id} [${m.status}] (${m.type ?? "agent"}): ${m.description ?? ""}`,
|
|
713
|
-
)
|
|
359
|
+
.map((m) => `- ${m.id} [${m.status}] (${m.type ?? "agent"}): ${m.description ?? ""}`)
|
|
714
360
|
.join("\n");
|
|
715
361
|
const joinInstruction = [
|
|
716
362
|
`All ${snap.terminal.length} background subagents in batch "${agentBatchId}" have finished.`,
|
|
@@ -765,26 +411,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
765
411
|
},
|
|
766
412
|
1000,
|
|
767
413
|
(event, payload) => {
|
|
768
|
-
const ownerGeneration =
|
|
769
|
-
|
|
770
|
-
? payload.ownerSessionGeneration
|
|
771
|
-
: undefined;
|
|
772
|
-
if (
|
|
773
|
-
ownerGeneration !== undefined &&
|
|
774
|
-
!isOwnerSessionCurrent(ownerGeneration)
|
|
775
|
-
)
|
|
776
|
-
return;
|
|
414
|
+
const ownerGeneration = typeof payload.ownerSessionGeneration === "number" ? payload.ownerSessionGeneration : undefined;
|
|
415
|
+
if (ownerGeneration !== undefined && !isOwnerSessionCurrent(ownerGeneration)) return;
|
|
777
416
|
if (event === "subagent.stuck-blocked") {
|
|
778
|
-
const id =
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
typeof payload.runId === "string"
|
|
782
|
-
? payload.runId
|
|
783
|
-
: "unknown";
|
|
784
|
-
const durationMs =
|
|
785
|
-
typeof payload.durationMs === "number"
|
|
786
|
-
? payload.durationMs
|
|
787
|
-
: 0;
|
|
417
|
+
const id = typeof payload.id === "string" ? payload.id : "unknown";
|
|
418
|
+
const runId = typeof payload.runId === "string" ? payload.runId : "unknown";
|
|
419
|
+
const durationMs = typeof payload.durationMs === "number" ? payload.durationMs : 0;
|
|
788
420
|
notifyOperator({
|
|
789
421
|
id: `subagent-stuck:${id}:${runId}`,
|
|
790
422
|
severity: "warning",
|
|
@@ -798,7 +430,6 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
798
430
|
},
|
|
799
431
|
);
|
|
800
432
|
const foregroundControllers = new Map<string | symbol, AbortController>();
|
|
801
|
-
let liveSidebarRunId: string | undefined;
|
|
802
433
|
let renderScheduler: RenderScheduler | undefined;
|
|
803
434
|
const renderSchedulerUnsubscribers: Array<() => void> = [];
|
|
804
435
|
// T4 (v0.8.3): terminal tab title + Ghostty native progress bar. Lazily
|
|
@@ -834,21 +465,15 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
834
465
|
// P0 fix: stopSessionBoundSubagents must NOT abort foreground team runs on session switch.
|
|
835
466
|
// Foreground team runs run in the same process as the session; they naturally clean up
|
|
836
467
|
// when the session context is torn down. Only subagents need explicit abort on switch.
|
|
837
|
-
const foregroundTeamRunControllers = new Map<
|
|
838
|
-
string | symbol,
|
|
839
|
-
AbortController
|
|
840
|
-
>();
|
|
468
|
+
const foregroundTeamRunControllers = new Map<string | symbol, AbortController>();
|
|
841
469
|
|
|
842
470
|
const stopSessionBoundSubagents = (): void => {
|
|
843
471
|
// Only abort subagent controllers — NOT foreground team runs.
|
|
844
472
|
// Foreground team runs are bound to the session lifecycle; they will be aborted
|
|
845
473
|
// by cleanupRuntime during session_shutdown.
|
|
846
|
-
for (const controller of foregroundControllers.values())
|
|
847
|
-
controller.abort();
|
|
474
|
+
for (const controller of foregroundControllers.values()) controller.abort();
|
|
848
475
|
foregroundControllers.clear();
|
|
849
|
-
subagentManager.abortAll(
|
|
850
|
-
"Session switching — foreground subagents cancelled.",
|
|
851
|
-
);
|
|
476
|
+
subagentManager.abortAll("Session switching — foreground subagents cancelled.");
|
|
852
477
|
terminateActiveChildPiProcesses();
|
|
853
478
|
disposeRenderSchedulerSubscriptions();
|
|
854
479
|
renderScheduler?.dispose();
|
|
@@ -856,95 +481,28 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
856
481
|
terminalStatus?.dispose();
|
|
857
482
|
terminalStatus = undefined;
|
|
858
483
|
terminalStatusActive = false;
|
|
859
|
-
liveSidebarRunId = undefined;
|
|
860
|
-
if (currentCtx)
|
|
861
|
-
stopCrewWidget(
|
|
862
|
-
currentCtx,
|
|
863
|
-
widgetState,
|
|
864
|
-
loadConfig(currentCtx.cwd).config.ui,
|
|
865
|
-
);
|
|
484
|
+
uiState.liveSidebarRunId = undefined;
|
|
485
|
+
if (currentCtx) stopCrewWidget(currentCtx, widgetState, loadConfig(currentCtx.cwd).config.ui);
|
|
866
486
|
clearPiCrewPowerbar(pi.events);
|
|
867
487
|
};
|
|
868
488
|
const openLiveSidebar = (ctx: ExtensionContext, runId: string): void => {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const foregroundAutoOpen =
|
|
872
|
-
uiConfig?.autoOpenDashboardForForegroundRuns ??
|
|
873
|
-
DEFAULT_UI.autoOpenDashboardForForegroundRuns;
|
|
874
|
-
if (
|
|
875
|
-
!ctx.hasUI ||
|
|
876
|
-
!autoOpen ||
|
|
877
|
-
!foregroundAutoOpen ||
|
|
878
|
-
(uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) !==
|
|
879
|
-
"right"
|
|
880
|
-
)
|
|
881
|
-
return;
|
|
882
|
-
if (liveSidebarRunId === runId) return;
|
|
883
|
-
liveSidebarRunId = runId;
|
|
884
|
-
const widgetPlacement =
|
|
885
|
-
uiConfig?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
886
|
-
setExtensionWidget(ctx, "pi-crew", undefined, {
|
|
887
|
-
placement: widgetPlacement,
|
|
888
|
-
});
|
|
889
|
-
setExtensionWidget(ctx, "pi-crew-active", undefined, {
|
|
890
|
-
placement: widgetPlacement,
|
|
891
|
-
});
|
|
892
|
-
widgetState.lastVisibility = "hidden";
|
|
893
|
-
widgetState.lastPlacement = widgetPlacement;
|
|
894
|
-
widgetState.lastKey = "pi-crew-active";
|
|
895
|
-
widgetState.model = undefined;
|
|
896
|
-
const width = Math.min(
|
|
897
|
-
90,
|
|
898
|
-
Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth),
|
|
899
|
-
);
|
|
900
|
-
void importLiveRunSidebar()
|
|
901
|
-
.then((LiveRunSidebar) => {
|
|
902
|
-
if (cleanedUp || !currentCtx) return;
|
|
903
|
-
void showCustom<undefined>(
|
|
904
|
-
ctx,
|
|
905
|
-
(_tui, theme, _keybindings, done) =>
|
|
906
|
-
new LiveRunSidebar({
|
|
907
|
-
cwd: ctx.cwd,
|
|
908
|
-
runId,
|
|
909
|
-
done,
|
|
910
|
-
theme,
|
|
911
|
-
config: uiConfig,
|
|
912
|
-
snapshotCache: getRunSnapshotCache(ctx.cwd),
|
|
913
|
-
}),
|
|
914
|
-
{
|
|
915
|
-
overlay: true,
|
|
916
|
-
overlayOptions: {
|
|
917
|
-
width,
|
|
918
|
-
minWidth: 40,
|
|
919
|
-
maxHeight: "100%",
|
|
920
|
-
anchor: "top-right",
|
|
921
|
-
offsetX: 0,
|
|
922
|
-
offsetY: 0,
|
|
923
|
-
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
924
|
-
visible: (termWidth: number) => termWidth >= 100,
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
).finally(() => {
|
|
928
|
-
if (liveSidebarRunId === runId)
|
|
929
|
-
liveSidebarRunId = undefined;
|
|
930
|
-
updateCrewWidget(
|
|
931
|
-
ctx,
|
|
932
|
-
widgetState,
|
|
933
|
-
loadConfig(ctx.cwd).config.ui,
|
|
934
|
-
getManifestCache(ctx.cwd),
|
|
935
|
-
getRunSnapshotCache(ctx.cwd),
|
|
936
|
-
);
|
|
937
|
-
});
|
|
938
|
-
})
|
|
939
|
-
.catch((error: unknown) =>
|
|
940
|
-
logInternalError("register.live-sidebar-lazy-import", error),
|
|
941
|
-
);
|
|
489
|
+
// H3-L2 split: live sidebar delegated to registration/ui.ts.
|
|
490
|
+
void installLiveSidebarFromRegistration(ctx, runId);
|
|
942
491
|
};
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
492
|
+
// H3-L2 split: wraps registration/ui.ts installLiveSidebar call.
|
|
493
|
+
async function installLiveSidebarFromRegistration(ctx: ExtensionContext, runId: string): Promise<void> {
|
|
494
|
+
// LAZY: registration/ui only imported when a live sidebar is actually opened.
|
|
495
|
+
const uiModule = await import("./registration/ui.ts");
|
|
496
|
+
await uiModule.installLiveSidebar(ctx, runId, uiState, {
|
|
497
|
+
pi,
|
|
498
|
+
widgetState,
|
|
499
|
+
getManifestCache,
|
|
500
|
+
getRunSnapshotCache,
|
|
501
|
+
isCleanedUp: () => cleanedUp,
|
|
502
|
+
getCurrentCtx: () => currentCtx,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
const startForegroundRun = (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string): void => {
|
|
948
506
|
const ownerGeneration = captureSessionGeneration();
|
|
949
507
|
const controller = new AbortController();
|
|
950
508
|
const key = runId ?? Symbol();
|
|
@@ -954,11 +512,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
954
512
|
frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
|
|
955
513
|
intervalMs: 80,
|
|
956
514
|
});
|
|
957
|
-
ctx.ui.setWorkingMessage(
|
|
958
|
-
runId
|
|
959
|
-
? `pi-crew foreground run ${runId}...`
|
|
960
|
-
: "pi-crew foreground run...",
|
|
961
|
-
);
|
|
515
|
+
ctx.ui.setWorkingMessage(runId ? `pi-crew foreground run ${runId}...` : "pi-crew foreground run...");
|
|
962
516
|
}
|
|
963
517
|
// Start watchdog for foreground run — periodic health check that
|
|
964
518
|
// auto-notifies the assistant if the run appears hung or completes.
|
|
@@ -974,8 +528,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
974
528
|
setImmediate(() => {
|
|
975
529
|
void runner(controller.signal)
|
|
976
530
|
.catch((error) => {
|
|
977
|
-
const message =
|
|
978
|
-
error instanceof Error ? error.message : String(error);
|
|
531
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
979
532
|
if (runId) {
|
|
980
533
|
try {
|
|
981
534
|
const loaded = loadRunManifestById(ctx.cwd, runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency. Post-run status updates tolerate slight staleness.
|
|
@@ -986,30 +539,13 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
986
539
|
loaded.manifest.status !== "cancelled" &&
|
|
987
540
|
loaded.manifest.status !== "blocked"
|
|
988
541
|
)
|
|
989
|
-
updateRunStatus(
|
|
990
|
-
loaded.manifest,
|
|
991
|
-
"failed",
|
|
992
|
-
message,
|
|
993
|
-
);
|
|
542
|
+
updateRunStatus(loaded.manifest, "failed", message);
|
|
994
543
|
} catch (statusError) {
|
|
995
|
-
logInternalError(
|
|
996
|
-
"register.foreground-run-failure",
|
|
997
|
-
statusError,
|
|
998
|
-
`runId=${runId}`,
|
|
999
|
-
);
|
|
544
|
+
logInternalError("register.foreground-run-failure", statusError, `runId=${runId}`);
|
|
1000
545
|
}
|
|
1001
546
|
}
|
|
1002
|
-
if (isContextCurrent(ctx, ownerGeneration))
|
|
1003
|
-
|
|
1004
|
-
`pi-crew foreground run failed: ${message}`,
|
|
1005
|
-
"error",
|
|
1006
|
-
);
|
|
1007
|
-
else
|
|
1008
|
-
logInternalError(
|
|
1009
|
-
"register.foreground-run-failure",
|
|
1010
|
-
error,
|
|
1011
|
-
`runId=${runId} context disposed`,
|
|
1012
|
-
);
|
|
547
|
+
if (isContextCurrent(ctx, ownerGeneration)) ctx.ui.notify(`pi-crew foreground run failed: ${message}`, "error");
|
|
548
|
+
else logInternalError("register.foreground-run-failure", error, `runId=${runId} context disposed`);
|
|
1013
549
|
})
|
|
1014
550
|
.finally(() => {
|
|
1015
551
|
foregroundTeamRunControllers.delete(key);
|
|
@@ -1034,12 +570,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1034
570
|
if (ownerCurrent && runId) {
|
|
1035
571
|
const loaded = loadRunManifestById(ctx.cwd, runId); // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency. Post-run status updates tolerate slight staleness.
|
|
1036
572
|
const status = loaded?.manifest.status ?? "finished";
|
|
1037
|
-
const level =
|
|
1038
|
-
status === "failed" || status === "blocked"
|
|
1039
|
-
? "error"
|
|
1040
|
-
: status === "cancelled"
|
|
1041
|
-
? "warning"
|
|
1042
|
-
: "info";
|
|
573
|
+
const level = status === "failed" || status === "blocked" ? "error" : status === "cancelled" ? "warning" : "info";
|
|
1043
574
|
ctx.ui.notify(
|
|
1044
575
|
`pi-crew run ${runId} ${status}. Use /team-summary ${runId} or /team-status ${runId}.`,
|
|
1045
576
|
level as "info" | "warning" | "error",
|
|
@@ -1101,9 +632,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1101
632
|
registerKnowledgeInjection(pi);
|
|
1102
633
|
time("register.knowledge");
|
|
1103
634
|
time("register.rpc");
|
|
1104
|
-
function getPiEvents():
|
|
1105
|
-
| Parameters<typeof registerPiCrewRpc>[0]
|
|
1106
|
-
| undefined {
|
|
635
|
+
function getPiEvents(): Parameters<typeof registerPiCrewRpc>[0] | undefined {
|
|
1107
636
|
if (pi && typeof pi === "object" && "events" in pi) {
|
|
1108
637
|
// pi.events may not be typed in the original pi type, so cast through unknown
|
|
1109
638
|
const events = (pi as { events?: Parameters<typeof registerPiCrewRpc>[0] }).events;
|
|
@@ -1116,105 +645,58 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1116
645
|
// Register global RPC registry for cross-extension access (mirrors pi-subagents3's Symbol.for pattern)
|
|
1117
646
|
// Uses lazy import to avoid pulling team-tool.ts into module load.
|
|
1118
647
|
// Other extensions access via: const reg = globalThis[Symbol.for("pi-crew:registry")];
|
|
1119
|
-
void import("./team-tool.ts").then(
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
] as Record<string, unknown>;
|
|
1147
|
-
validatedRegistry.getRecord = (runId: string) =>
|
|
1148
|
-
manifestCacheForRegistry.get(runId);
|
|
1149
|
-
validatedRegistry.listRuns = () =>
|
|
1150
|
-
manifestCacheForRegistry
|
|
1151
|
-
.list(100)
|
|
1152
|
-
.map(
|
|
1153
|
-
(m: {
|
|
1154
|
-
runId: string;
|
|
1155
|
-
status: string;
|
|
1156
|
-
goal: string;
|
|
1157
|
-
}) => ({
|
|
1158
|
-
runId: m.runId,
|
|
1159
|
-
status: m.status,
|
|
1160
|
-
goal: m.goal,
|
|
1161
|
-
}),
|
|
1162
|
-
);
|
|
1163
|
-
validatedRegistry.appendEvent = (
|
|
1164
|
-
runId: string,
|
|
1165
|
-
event: Record<string, unknown>,
|
|
1166
|
-
) => {
|
|
1167
|
-
const manifest = manifestCacheForRegistry.get(runId);
|
|
1168
|
-
if (manifest)
|
|
1169
|
-
void import("../state/event-log.ts").then(
|
|
1170
|
-
({ appendEventFireAndForget }) =>
|
|
1171
|
-
appendEventFireAndForget(
|
|
1172
|
-
manifest.eventsPath,
|
|
1173
|
-
event as Parameters<
|
|
1174
|
-
typeof appendEventFireAndForget
|
|
1175
|
-
>[1],
|
|
1176
|
-
),
|
|
1177
|
-
);
|
|
1178
|
-
};
|
|
1179
|
-
validatedRegistry.waitForAll = async (runId: string) => {
|
|
1180
|
-
// LAZY: state-store only needed for post-completion polling (waitForAll) and sync hasRunning check; avoid at startup.
|
|
1181
|
-
const { loadRunManifestById } = await import(
|
|
1182
|
-
"../state/state-store.ts"
|
|
1183
|
-
);
|
|
1184
|
-
const check = (): boolean => {
|
|
1185
|
-
const loaded = loadRunManifestById(
|
|
1186
|
-
currentCtx?.cwd ?? process.cwd(),
|
|
1187
|
-
runId,
|
|
1188
|
-
);
|
|
1189
|
-
if (!loaded) return true;
|
|
1190
|
-
return !loaded.tasks.some(
|
|
1191
|
-
(t: { status: string }) =>
|
|
1192
|
-
t.status === "running" || t.status === "queued",
|
|
1193
|
-
);
|
|
1194
|
-
};
|
|
1195
|
-
while (!check())
|
|
1196
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1197
|
-
};
|
|
1198
|
-
validatedRegistry.hasRunning = async (runId: string) => {
|
|
1199
|
-
const manifest = manifestCacheForRegistry.get(runId);
|
|
1200
|
-
if (!manifest) return false;
|
|
1201
|
-
// LAZY: state-store only needed in hasRunning; avoid at startup.
|
|
1202
|
-
// Use dynamic import to avoid CJS/ESM mixed module issues.
|
|
1203
|
-
const { loadRunManifestById: loadRunForHasRunning } =
|
|
1204
|
-
// LAZY: defer dynamic import of ../state/state-store.ts to its call site.
|
|
1205
|
-
await import("../state/state-store.ts");
|
|
1206
|
-
const loaded = loadRunForHasRunning(
|
|
1207
|
-
currentCtx?.cwd ?? process.cwd(),
|
|
1208
|
-
runId,
|
|
1209
|
-
);
|
|
1210
|
-
if (!loaded) return false;
|
|
1211
|
-
return loaded.tasks.some(
|
|
1212
|
-
(t: { status: string }) =>
|
|
1213
|
-
t.status === "running" || t.status === "queued",
|
|
648
|
+
void import("./team-tool.ts").then(({ registerCrewGlobalRegistry, installCrewGlobalRegistry }) => {
|
|
649
|
+
// Phase 3b: installCrewGlobalRegistry creates a v2 registry with agent registration API.
|
|
650
|
+
// We then patch the manifest-backed methods with real implementations below.
|
|
651
|
+
const manifestCacheForRegistry = getManifestCache(currentCtx?.cwd ?? process.cwd());
|
|
652
|
+
installCrewGlobalRegistry();
|
|
653
|
+
const CREW_REGISTRY_KEY = Symbol.for("pi-crew:registry");
|
|
654
|
+
const registry = (globalThis as Record<symbol | string, unknown>)[CREW_REGISTRY_KEY] as Record<string, unknown>;
|
|
655
|
+
// Phase 3b (defensive): Validate registry structure before patching methods.
|
|
656
|
+
// If a previous occupant left a non-conforming object, replace it entirely.
|
|
657
|
+
// This prevents runtime failures if getRecord/listRuns/etc. are called on a
|
|
658
|
+
// malformed predecessor value.
|
|
659
|
+
if (registry === null || typeof registry !== "object" || Array.isArray(registry)) {
|
|
660
|
+
(globalThis as Record<symbol | string, unknown>)[CREW_REGISTRY_KEY] = {};
|
|
661
|
+
}
|
|
662
|
+
const validatedRegistry = (globalThis as Record<symbol | string, unknown>)[CREW_REGISTRY_KEY] as Record<string, unknown>;
|
|
663
|
+
validatedRegistry.getRecord = (runId: string) => manifestCacheForRegistry.get(runId);
|
|
664
|
+
validatedRegistry.listRuns = () =>
|
|
665
|
+
manifestCacheForRegistry.list(100).map((m: { runId: string; status: string; goal: string }) => ({
|
|
666
|
+
runId: m.runId,
|
|
667
|
+
status: m.status,
|
|
668
|
+
goal: m.goal,
|
|
669
|
+
}));
|
|
670
|
+
validatedRegistry.appendEvent = (runId: string, event: Record<string, unknown>) => {
|
|
671
|
+
const manifest = manifestCacheForRegistry.get(runId);
|
|
672
|
+
if (manifest)
|
|
673
|
+
void import("../state/event-log.ts").then(({ appendEventFireAndForget }) =>
|
|
674
|
+
appendEventFireAndForget(manifest.eventsPath, event as Parameters<typeof appendEventFireAndForget>[1]),
|
|
1214
675
|
);
|
|
676
|
+
};
|
|
677
|
+
validatedRegistry.waitForAll = async (runId: string) => {
|
|
678
|
+
// LAZY: state-store only needed for post-completion polling (waitForAll) and sync hasRunning check; avoid at startup.
|
|
679
|
+
const { loadRunManifestById } = await import("../state/state-store.ts");
|
|
680
|
+
const check = (): boolean => {
|
|
681
|
+
const loaded = loadRunManifestById(currentCtx?.cwd ?? process.cwd(), runId);
|
|
682
|
+
if (!loaded) return true;
|
|
683
|
+
return !loaded.tasks.some((t: { status: string }) => t.status === "running" || t.status === "queued");
|
|
1215
684
|
};
|
|
1216
|
-
|
|
1217
|
-
|
|
685
|
+
while (!check()) await new Promise((resolve) => setTimeout(resolve, 500));
|
|
686
|
+
};
|
|
687
|
+
validatedRegistry.hasRunning = async (runId: string) => {
|
|
688
|
+
const manifest = manifestCacheForRegistry.get(runId);
|
|
689
|
+
if (!manifest) return false;
|
|
690
|
+
// LAZY: state-store only needed in hasRunning; avoid at startup.
|
|
691
|
+
// Use dynamic import to avoid CJS/ESM mixed module issues.
|
|
692
|
+
const { loadRunManifestById: loadRunForHasRunning } =
|
|
693
|
+
// LAZY: defer dynamic import of ../state/state-store.ts to its call site.
|
|
694
|
+
await import("../state/state-store.ts");
|
|
695
|
+
const loaded = loadRunForHasRunning(currentCtx?.cwd ?? process.cwd(), runId);
|
|
696
|
+
if (!loaded) return false;
|
|
697
|
+
return loaded.tasks.some((t: { status: string }) => t.status === "running" || t.status === "queued");
|
|
698
|
+
};
|
|
699
|
+
});
|
|
1218
700
|
|
|
1219
701
|
const cleanupRuntime = (): void => {
|
|
1220
702
|
if (cleanedUp) return;
|
|
@@ -1230,8 +712,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1230
712
|
stopSessionBoundSubagents();
|
|
1231
713
|
// P0 fix: also abort foreground team runs on session shutdown (not on session switch).
|
|
1232
714
|
// This is the only place where foreground team run controllers should be aborted.
|
|
1233
|
-
for (const controller of foregroundTeamRunControllers.values())
|
|
1234
|
-
controller.abort();
|
|
715
|
+
for (const controller of foregroundTeamRunControllers.values()) controller.abort();
|
|
1235
716
|
foregroundTeamRunControllers.clear();
|
|
1236
717
|
crewScheduler?.stop();
|
|
1237
718
|
stopAsyncRunNotifier(notifierState);
|
|
@@ -1260,37 +741,17 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1260
741
|
// the next session_start will fire the lazy import + purge.
|
|
1261
742
|
purgeStaleActiveRunIndexSyncIfLoaded();
|
|
1262
743
|
|
|
1263
|
-
stopCrewWidget(
|
|
1264
|
-
currentCtx,
|
|
1265
|
-
widgetState,
|
|
1266
|
-
currentCtx ? loadConfig(currentCtx.cwd).config.ui : undefined,
|
|
1267
|
-
);
|
|
744
|
+
stopCrewWidget(currentCtx, widgetState, currentCtx ? loadConfig(currentCtx.cwd).config.ui : undefined);
|
|
1268
745
|
clearPiCrewPowerbar(pi.events);
|
|
1269
746
|
disposePowerbarCoalescer();
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
autoRepairTimer = undefined;
|
|
1274
|
-
}
|
|
1275
|
-
if (tempReconcileTimer) {
|
|
1276
|
-
clearInterval(tempReconcileTimer);
|
|
1277
|
-
tempReconcileTimer = undefined;
|
|
1278
|
-
}
|
|
1279
|
-
metricSink?.dispose();
|
|
1280
|
-
eventMetricSub?.dispose();
|
|
1281
|
-
otlpExporter?.dispose();
|
|
1282
|
-
metricRegistry?.dispose();
|
|
1283
|
-
heartbeatWatcher = undefined;
|
|
1284
|
-
metricSink = undefined;
|
|
1285
|
-
eventMetricSub = undefined;
|
|
1286
|
-
otlpExporter = undefined;
|
|
1287
|
-
metricRegistry = undefined;
|
|
1288
|
-
deliveryCoordinator?.dispose();
|
|
747
|
+
// H3-L2 split: observability disposal delegated to registration/observability.ts.
|
|
748
|
+
disposeObservability(observabilityState, cleanedUp);
|
|
749
|
+
lifecycleState.deliveryCoordinator?.dispose();
|
|
1289
750
|
clearHooksScoped();
|
|
1290
751
|
uninstallCrewGlobalRegistry();
|
|
1291
|
-
overflowTracker?.dispose();
|
|
1292
|
-
deliveryCoordinator = undefined;
|
|
1293
|
-
overflowTracker = undefined;
|
|
752
|
+
lifecycleState.overflowTracker?.dispose();
|
|
753
|
+
lifecycleState.deliveryCoordinator = undefined;
|
|
754
|
+
lifecycleState.overflowTracker = undefined;
|
|
1294
755
|
manifestCache.dispose();
|
|
1295
756
|
runSnapshotCache.dispose?.();
|
|
1296
757
|
// 2.10: drop cached findRepoRoot results when the extension reloads.
|
|
@@ -1298,17 +759,14 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1298
759
|
renderScheduler?.dispose();
|
|
1299
760
|
renderScheduler = undefined;
|
|
1300
761
|
autoRecoveryLast.clear();
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
notificationRouter = undefined;
|
|
1304
|
-
notificationSink = undefined;
|
|
762
|
+
// H3-L2 split: notification disposal delegated to registration/lifecycle.ts.
|
|
763
|
+
disposeNotifications(lifecycleState);
|
|
1305
764
|
rpcHandle?.unsubscribe();
|
|
1306
765
|
rpcHandle = undefined;
|
|
1307
766
|
disposeI18n();
|
|
1308
767
|
sessionGeneration += 1;
|
|
1309
768
|
currentCtx = undefined;
|
|
1310
|
-
if (globalStore[runtimeCleanupStoreKey] === cleanupRuntime)
|
|
1311
|
-
delete globalStore[runtimeCleanupStoreKey];
|
|
769
|
+
if (globalStore[runtimeCleanupStoreKey] === cleanupRuntime) delete globalStore[runtimeCleanupStoreKey];
|
|
1312
770
|
};
|
|
1313
771
|
globalStore[runtimeCleanupStoreKey] = cleanupRuntime;
|
|
1314
772
|
|
|
@@ -1319,11 +777,17 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1319
777
|
try {
|
|
1320
778
|
const entries = ctx.sessionManager?.getEntries?.();
|
|
1321
779
|
if (entries) {
|
|
1322
|
-
import("../ui/tool-renderers/brief-mode.ts")
|
|
1323
|
-
restoreBriefState
|
|
1324
|
-
|
|
780
|
+
import("../ui/tool-renderers/brief-mode.ts")
|
|
781
|
+
.then(({ restoreBriefState }) => {
|
|
782
|
+
restoreBriefState(entries);
|
|
783
|
+
})
|
|
784
|
+
.catch(() => {
|
|
785
|
+
/* non-critical */
|
|
786
|
+
});
|
|
1325
787
|
}
|
|
1326
|
-
} catch {
|
|
788
|
+
} catch {
|
|
789
|
+
/* non-critical */
|
|
790
|
+
}
|
|
1327
791
|
|
|
1328
792
|
time("register.session-start");
|
|
1329
793
|
cleanedUp = false;
|
|
@@ -1355,32 +819,20 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1355
819
|
|
|
1356
820
|
// 2.7: load crash-recovery lazily once per session_start cleanup batch.
|
|
1357
821
|
void (async () => {
|
|
1358
|
-
let crashRecovery:
|
|
1359
|
-
| Awaited<ReturnType<typeof importCrashRecovery>>
|
|
1360
|
-
| undefined;
|
|
822
|
+
let crashRecovery: Awaited<ReturnType<typeof importCrashRecovery>> | undefined;
|
|
1361
823
|
try {
|
|
1362
824
|
crashRecovery = await importCrashRecovery();
|
|
1363
825
|
} catch (error) {
|
|
1364
|
-
logInternalError(
|
|
1365
|
-
"register.sessionStart.lazyCrashRecovery",
|
|
1366
|
-
error,
|
|
1367
|
-
);
|
|
826
|
+
logInternalError("register.sessionStart.lazyCrashRecovery", error);
|
|
1368
827
|
return;
|
|
1369
828
|
}
|
|
1370
829
|
if (cleanedUp || sessionGeneration !== ownerGeneration) return;
|
|
1371
|
-
const {
|
|
1372
|
-
cancelOrphanedRuns: cancelOrphanedRunsFn,
|
|
1373
|
-
purgeStaleActiveRunIndex: purgeStaleActiveRunIndexFn,
|
|
1374
|
-
} = crashRecovery;
|
|
830
|
+
const { cancelOrphanedRuns: cancelOrphanedRunsFn, purgeStaleActiveRunIndex: purgeStaleActiveRunIndexFn } = crashRecovery;
|
|
1375
831
|
|
|
1376
832
|
// Auto-cancel orphaned runs
|
|
1377
833
|
if (currentSessionId) {
|
|
1378
834
|
try {
|
|
1379
|
-
const { cancelled } = cancelOrphanedRunsFn(
|
|
1380
|
-
ctx.cwd,
|
|
1381
|
-
getManifestCache(ctx.cwd),
|
|
1382
|
-
currentSessionId,
|
|
1383
|
-
);
|
|
835
|
+
const { cancelled } = cancelOrphanedRunsFn(ctx.cwd, getManifestCache(ctx.cwd), currentSessionId);
|
|
1384
836
|
if (cancelled.length > 0) {
|
|
1385
837
|
notifyOperator({
|
|
1386
838
|
id: `orphan_cleanup`,
|
|
@@ -1391,10 +843,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1391
843
|
});
|
|
1392
844
|
}
|
|
1393
845
|
} catch (error) {
|
|
1394
|
-
logInternalError(
|
|
1395
|
-
"register.sessionStart.orphanCleanup",
|
|
1396
|
-
error,
|
|
1397
|
-
);
|
|
846
|
+
logInternalError("register.sessionStart.orphanCleanup", error);
|
|
1398
847
|
}
|
|
1399
848
|
}
|
|
1400
849
|
|
|
@@ -1416,10 +865,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1416
865
|
});
|
|
1417
866
|
}
|
|
1418
867
|
} catch (error) {
|
|
1419
|
-
logInternalError(
|
|
1420
|
-
"register.sessionStart.startupTempCleanup",
|
|
1421
|
-
error,
|
|
1422
|
-
);
|
|
868
|
+
logInternalError("register.sessionStart.startupTempCleanup", error);
|
|
1423
869
|
}
|
|
1424
870
|
|
|
1425
871
|
// Orphan worker cleanup (Fix B): kill stale background-runner
|
|
@@ -1440,10 +886,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1440
886
|
});
|
|
1441
887
|
}
|
|
1442
888
|
} catch (error) {
|
|
1443
|
-
logInternalError(
|
|
1444
|
-
"register.sessionStart.orphanWorkers",
|
|
1445
|
-
error,
|
|
1446
|
-
);
|
|
889
|
+
logInternalError("register.sessionStart.orphanWorkers", error);
|
|
1447
890
|
}
|
|
1448
891
|
|
|
1449
892
|
// Global purge of stale active-run-index entries
|
|
@@ -1459,31 +902,21 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1459
902
|
});
|
|
1460
903
|
}
|
|
1461
904
|
} catch (error) {
|
|
1462
|
-
logInternalError(
|
|
1463
|
-
"register.sessionStart.globalIndexPurge",
|
|
1464
|
-
error,
|
|
1465
|
-
);
|
|
905
|
+
logInternalError("register.sessionStart.globalIndexPurge", error);
|
|
1466
906
|
}
|
|
1467
907
|
})();
|
|
1468
908
|
|
|
1469
909
|
// Reconcile stale runs found on disk (not in active-run-index)
|
|
1470
910
|
// These are ghost runs from crashed processes that were never cleaned up.
|
|
1471
911
|
try {
|
|
1472
|
-
const staleResults =
|
|
1473
|
-
reconcileAllStaleRuns(ctx.cwd, getManifestCache(ctx.cwd)) ??
|
|
1474
|
-
[];
|
|
912
|
+
const staleResults = reconcileAllStaleRuns(ctx.cwd, getManifestCache(ctx.cwd)) ?? [];
|
|
1475
913
|
if (staleResults.length > 0) {
|
|
1476
914
|
notifyOperator({
|
|
1477
915
|
id: "stale_reconcile",
|
|
1478
916
|
severity: "info",
|
|
1479
917
|
source: "crash-recovery",
|
|
1480
|
-
title:
|
|
1481
|
-
|
|
1482
|
-
staleResults.length +
|
|
1483
|
-
" stale run(s)",
|
|
1484
|
-
body:
|
|
1485
|
-
"Found and repaired ghost runs from previous sessions: " +
|
|
1486
|
-
staleResults.map((r) => r.runId).join(", "),
|
|
918
|
+
title: "Reconciled " + staleResults.length + " stale run(s)",
|
|
919
|
+
body: "Found and repaired ghost runs from previous sessions: " + staleResults.map((r) => r.runId).join(", "),
|
|
1487
920
|
});
|
|
1488
921
|
}
|
|
1489
922
|
} catch (error) {
|
|
@@ -1503,10 +936,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1503
936
|
});
|
|
1504
937
|
}
|
|
1505
938
|
} catch (error) {
|
|
1506
|
-
logInternalError(
|
|
1507
|
-
"register.sessionStart.autoPruneProject",
|
|
1508
|
-
error,
|
|
1509
|
-
);
|
|
939
|
+
logInternalError("register.sessionStart.autoPruneProject", error);
|
|
1510
940
|
}
|
|
1511
941
|
|
|
1512
942
|
// Auto-prune finished user-level run directories (keep 10 most recent)
|
|
@@ -1534,9 +964,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1534
964
|
// Resolve sessionId before the scheduler executor closure captures it.
|
|
1535
965
|
const sessionId =
|
|
1536
966
|
ctx.sessionManager?.getSessionId?.() ??
|
|
1537
|
-
(typeof ctx === "object" && ctx !== null && "sessionId" in ctx
|
|
1538
|
-
? (ctx as Record<string, unknown>).sessionId
|
|
1539
|
-
: undefined);
|
|
967
|
+
(typeof ctx === "object" && ctx !== null && "sessionId" in ctx ? (ctx as Record<string, unknown>).sessionId : undefined);
|
|
1540
968
|
crewScheduler = new CrewScheduler();
|
|
1541
969
|
crewScheduler.start({
|
|
1542
970
|
emit: (event) => {
|
|
@@ -1548,14 +976,23 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1548
976
|
try {
|
|
1549
977
|
runParams = JSON.parse(job.prompt);
|
|
1550
978
|
} catch {
|
|
1551
|
-
runParams = {
|
|
979
|
+
runParams = {
|
|
980
|
+
action: "run",
|
|
981
|
+
team: "default",
|
|
982
|
+
goal: job.prompt,
|
|
983
|
+
};
|
|
1552
984
|
}
|
|
1553
985
|
if (runParams.action !== "run") return `scheduled-${job.id}-${Date.now()}`;
|
|
1554
986
|
const agentId = `scheduled-${job.id}-${Date.now()}`;
|
|
1555
987
|
setImmediate(async () => {
|
|
1556
988
|
try {
|
|
1557
989
|
const runResult = await handleTeamTool(
|
|
1558
|
-
{
|
|
990
|
+
{
|
|
991
|
+
action: "run",
|
|
992
|
+
team: runParams.team,
|
|
993
|
+
goal: runParams.goal,
|
|
994
|
+
async: true,
|
|
995
|
+
},
|
|
1559
996
|
{ cwd: ctx.cwd, sessionId },
|
|
1560
997
|
);
|
|
1561
998
|
// Track the runId so remove() can cancel spawned runs
|
|
@@ -1567,7 +1004,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1567
1004
|
const cwd = ctx.cwd ?? process.cwd();
|
|
1568
1005
|
const loaded = loadRunManifestById(cwd, runId);
|
|
1569
1006
|
if (loaded) {
|
|
1570
|
-
|
|
1007
|
+
// LAZY: defer dynamic import of ../state/atomic-write.ts to its call site.
|
|
1571
1008
|
const { atomicWriteJson } = await import("../state/atomic-write.ts");
|
|
1572
1009
|
atomicWriteJson(loaded.manifest.stateRoot + "/manifest.json", {
|
|
1573
1010
|
...loaded.manifest,
|
|
@@ -1575,13 +1012,17 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1575
1012
|
schedulerName: job.name,
|
|
1576
1013
|
});
|
|
1577
1014
|
}
|
|
1578
|
-
} catch {
|
|
1015
|
+
} catch {
|
|
1016
|
+
/* best-effort provenance tracking */
|
|
1017
|
+
}
|
|
1579
1018
|
}
|
|
1580
1019
|
// Persist updated job with spawnedRunIds to settings
|
|
1581
1020
|
try {
|
|
1582
1021
|
const updatedJob = crewScheduler?.list().find((j) => j.id === job.id);
|
|
1583
1022
|
if (updatedJob) persistScheduledJobUpdate(ctx.cwd, updatedJob);
|
|
1584
|
-
} catch {
|
|
1023
|
+
} catch {
|
|
1024
|
+
/* best-effort */
|
|
1025
|
+
}
|
|
1585
1026
|
// Update run count
|
|
1586
1027
|
crewScheduler?.update(job.id, {
|
|
1587
1028
|
runCount: job.runCount + 1,
|
|
@@ -1598,10 +1039,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1598
1039
|
finalizer: () => {},
|
|
1599
1040
|
runCancelFn: (runId: string) => {
|
|
1600
1041
|
try {
|
|
1601
|
-
handleTeamTool(
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
).catch((err) => logInternalError("scheduler.runCancelFn", err, `runId=${runId}`));
|
|
1042
|
+
handleTeamTool({ action: "cancel", runId, confirm: true }, { cwd: ctx.cwd, sessionId }).catch((err) =>
|
|
1043
|
+
logInternalError("scheduler.runCancelFn", err, `runId=${runId}`),
|
|
1044
|
+
);
|
|
1605
1045
|
} catch (err) {
|
|
1606
1046
|
logInternalError("scheduler.runCancelFn.sync", err, `runId=${runId}`);
|
|
1607
1047
|
}
|
|
@@ -1624,34 +1064,18 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1624
1064
|
configureNotifications(ctx);
|
|
1625
1065
|
configureObservability(ctx);
|
|
1626
1066
|
configureDeliveryCoordinator();
|
|
1627
|
-
if (typeof sessionId === "string" && sessionId)
|
|
1628
|
-
deliveryCoordinator?.activate(sessionId);
|
|
1067
|
+
if (typeof sessionId === "string" && sessionId) lifecycleState.deliveryCoordinator?.activate(sessionId);
|
|
1629
1068
|
tryRegisterSessionCleanup(pi, () => {
|
|
1630
1069
|
terminateActiveChildPiProcesses();
|
|
1631
1070
|
cleanupRuntime();
|
|
1632
1071
|
});
|
|
1633
1072
|
registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
|
|
1634
|
-
startAsyncRunNotifier(
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
DEFAULT_UI.notifierIntervalMs,
|
|
1639
|
-
{
|
|
1640
|
-
generation: ownerGeneration,
|
|
1641
|
-
isCurrent: (generation) =>
|
|
1642
|
-
generation === sessionGeneration &&
|
|
1643
|
-
currentCtx === ctx &&
|
|
1644
|
-
!cleanedUp,
|
|
1645
|
-
},
|
|
1646
|
-
);
|
|
1073
|
+
startAsyncRunNotifier(ctx, notifierState, loadedConfig.config.notifierIntervalMs ?? DEFAULT_UI.notifierIntervalMs, {
|
|
1074
|
+
generation: ownerGeneration,
|
|
1075
|
+
isCurrent: (generation) => generation === sessionGeneration && currentCtx === ctx && !cleanedUp,
|
|
1076
|
+
});
|
|
1647
1077
|
const cache = getManifestCache(ctx.cwd);
|
|
1648
|
-
updateCrewWidget(
|
|
1649
|
-
ctx,
|
|
1650
|
-
widgetState,
|
|
1651
|
-
loadedConfig.config.ui,
|
|
1652
|
-
cache,
|
|
1653
|
-
getRunSnapshotCache(ctx.cwd),
|
|
1654
|
-
);
|
|
1078
|
+
updateCrewWidget(ctx, widgetState, loadedConfig.config.ui, cache, getRunSnapshotCache(ctx.cwd));
|
|
1655
1079
|
updatePiCrewPowerbar(
|
|
1656
1080
|
pi.events,
|
|
1657
1081
|
ctx.cwd,
|
|
@@ -1672,12 +1096,8 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1672
1096
|
|
|
1673
1097
|
let lastPreloadedConfig: ReturnType<typeof loadConfig> | undefined;
|
|
1674
1098
|
let lastPreloadedManifests: TeamRunManifest[] = [];
|
|
1675
|
-
let lastFrameManifestCache:
|
|
1676
|
-
|
|
1677
|
-
| undefined;
|
|
1678
|
-
let lastFrameSnapshotCache:
|
|
1679
|
-
| ReturnType<typeof createRunSnapshotCache>
|
|
1680
|
-
| undefined;
|
|
1099
|
+
let lastFrameManifestCache: ReturnType<typeof createManifestCache> | undefined;
|
|
1100
|
+
let lastFrameSnapshotCache: ReturnType<typeof createRunSnapshotCache> | undefined;
|
|
1681
1101
|
|
|
1682
1102
|
const buildFrame = async (): Promise<boolean> => {
|
|
1683
1103
|
if (!currentCtx) return false;
|
|
@@ -1724,10 +1144,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1724
1144
|
});
|
|
1725
1145
|
};
|
|
1726
1146
|
|
|
1727
|
-
const startPreloadLoop = (
|
|
1728
|
-
intervalMs: number,
|
|
1729
|
-
dynamicMs?: () => number,
|
|
1730
|
-
): void => {
|
|
1147
|
+
const startPreloadLoop = (intervalMs: number, dynamicMs?: () => number): void => {
|
|
1731
1148
|
if (preloadTimer) clearTimeout(preloadTimer);
|
|
1732
1149
|
const tick = (): void => {
|
|
1733
1150
|
backgroundPreload();
|
|
@@ -1742,10 +1159,8 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1742
1159
|
const renderTick = (): void => {
|
|
1743
1160
|
if (!currentCtx) return;
|
|
1744
1161
|
const config = lastPreloadedConfig?.config.ui;
|
|
1745
|
-
const activeCache =
|
|
1746
|
-
|
|
1747
|
-
const snapshotCache =
|
|
1748
|
-
lastFrameSnapshotCache ?? getRunSnapshotCache(currentCtx.cwd);
|
|
1162
|
+
const activeCache = lastFrameManifestCache ?? getManifestCache(currentCtx.cwd);
|
|
1163
|
+
const snapshotCache = lastFrameSnapshotCache ?? getRunSnapshotCache(currentCtx.cwd);
|
|
1749
1164
|
// 1.1: keep render path zero-fs-IO. Always read from the preloaded
|
|
1750
1165
|
// frame; if it is empty (first tick after session_start, or cwd
|
|
1751
1166
|
// switched), kick off a background preload and render a skeleton
|
|
@@ -1753,22 +1168,13 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1753
1168
|
// frame is ready, avoiding statSync(`runs/`) inside the hot path.
|
|
1754
1169
|
const manifests = lastPreloadedManifests;
|
|
1755
1170
|
if (!lastPreloadedConfig) backgroundPreload();
|
|
1756
|
-
if (liveSidebarRunId) {
|
|
1757
|
-
const placement =
|
|
1758
|
-
|
|
1759
|
-
if (
|
|
1760
|
-
widgetState.lastVisibility !== "hidden" ||
|
|
1761
|
-
widgetState.lastPlacement !== placement
|
|
1762
|
-
) {
|
|
1171
|
+
if (uiState.liveSidebarRunId) {
|
|
1172
|
+
const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
1173
|
+
if (widgetState.lastVisibility !== "hidden" || widgetState.lastPlacement !== placement) {
|
|
1763
1174
|
setExtensionWidget(currentCtx, "pi-crew", undefined, {
|
|
1764
1175
|
placement,
|
|
1765
1176
|
});
|
|
1766
|
-
setExtensionWidget(
|
|
1767
|
-
currentCtx,
|
|
1768
|
-
"pi-crew-active",
|
|
1769
|
-
undefined,
|
|
1770
|
-
{ placement },
|
|
1771
|
-
);
|
|
1177
|
+
setExtensionWidget(currentCtx, "pi-crew-active", undefined, { placement });
|
|
1772
1178
|
widgetState.lastVisibility = "hidden";
|
|
1773
1179
|
widgetState.lastPlacement = placement;
|
|
1774
1180
|
widgetState.lastKey = "pi-crew-active";
|
|
@@ -1776,14 +1182,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1776
1182
|
}
|
|
1777
1183
|
requestRender(currentCtx);
|
|
1778
1184
|
} else {
|
|
1779
|
-
updateCrewWidget(
|
|
1780
|
-
currentCtx,
|
|
1781
|
-
widgetState,
|
|
1782
|
-
config,
|
|
1783
|
-
activeCache,
|
|
1784
|
-
snapshotCache,
|
|
1785
|
-
manifests,
|
|
1786
|
-
);
|
|
1185
|
+
updateCrewWidget(currentCtx, widgetState, config, activeCache, snapshotCache, manifests);
|
|
1787
1186
|
}
|
|
1788
1187
|
requestPowerbarUpdate(
|
|
1789
1188
|
pi.events,
|
|
@@ -1814,20 +1213,11 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1814
1213
|
// Skip if snapshot shows run already completed/failed (stale cache)
|
|
1815
1214
|
if (snapshot.manifest.status !== "running") continue;
|
|
1816
1215
|
const summary = summarizeHeartbeats(snapshot, { now });
|
|
1817
|
-
const maybeNotifyHealth = (
|
|
1818
|
-
kind: string,
|
|
1819
|
-
count: number,
|
|
1820
|
-
title: string,
|
|
1821
|
-
body: string,
|
|
1822
|
-
): void => {
|
|
1216
|
+
const maybeNotifyHealth = (kind: string, count: number, title: string, body: string): void => {
|
|
1823
1217
|
if (count <= 0) return;
|
|
1824
1218
|
const key = `${kind}_${run.runId}`;
|
|
1825
1219
|
const previous = autoRecoveryLast.get(key);
|
|
1826
|
-
if (
|
|
1827
|
-
previous !== undefined &&
|
|
1828
|
-
now - previous.lastAccessAt < 5 * 60_000
|
|
1829
|
-
)
|
|
1830
|
-
return;
|
|
1220
|
+
if (previous !== undefined && now - previous.lastAccessAt < 5 * 60_000) return;
|
|
1831
1221
|
// Defensive cap: evict entry with oldest lastAccessAt before
|
|
1832
1222
|
// inserting/updating when size exceeds the limit. Uses LRU
|
|
1833
1223
|
// semantics so entries that are still being actively
|
|
@@ -1844,7 +1234,10 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1844
1234
|
if (oldestKey === undefined) break;
|
|
1845
1235
|
autoRecoveryLast.delete(oldestKey);
|
|
1846
1236
|
}
|
|
1847
|
-
autoRecoveryLast.set(key, {
|
|
1237
|
+
autoRecoveryLast.set(key, {
|
|
1238
|
+
insertedAt: now,
|
|
1239
|
+
lastAccessAt: now,
|
|
1240
|
+
});
|
|
1848
1241
|
notifyOperator({
|
|
1849
1242
|
id: key,
|
|
1850
1243
|
severity: "warning",
|
|
@@ -1867,35 +1260,22 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1867
1260
|
"Open /team-dashboard → 5 health → inspect health actions.",
|
|
1868
1261
|
);
|
|
1869
1262
|
} catch (error) {
|
|
1870
|
-
logInternalError(
|
|
1871
|
-
"register.health-notification",
|
|
1872
|
-
error,
|
|
1873
|
-
run.runId,
|
|
1874
|
-
);
|
|
1263
|
+
logInternalError("register.health-notification", error, run.runId);
|
|
1875
1264
|
}
|
|
1876
1265
|
}
|
|
1877
1266
|
};
|
|
1878
1267
|
|
|
1879
|
-
const fallbackMs =
|
|
1880
|
-
loadedConfig.config.ui?.dashboardLiveRefreshMs ??
|
|
1881
|
-
DEFAULT_UI.refreshMs;
|
|
1268
|
+
const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? DEFAULT_UI.refreshMs;
|
|
1882
1269
|
// R3: Use faster refresh when live agents OR background runs are running.
|
|
1883
1270
|
// 160ms is aligned with SUBAGENT_SPINNER_FRAME_MS so the spinner advances
|
|
1884
1271
|
// one frame per render tick when a run is active. Falls back to the
|
|
1885
1272
|
// (slower) configured refresh when idle to save CPU.
|
|
1886
1273
|
const liveRefreshMs = 160;
|
|
1887
1274
|
const hasActiveWork = (): boolean => {
|
|
1888
|
-
if (listLiveAgents().some((a) => a.status === "running"))
|
|
1889
|
-
|
|
1890
|
-
return lastPreloadedManifests.some(
|
|
1891
|
-
(r) =>
|
|
1892
|
-
r.status === "running" ||
|
|
1893
|
-
r.status === "queued" ||
|
|
1894
|
-
r.status === "planning",
|
|
1895
|
-
);
|
|
1275
|
+
if (listLiveAgents().some((a) => a.status === "running")) return true;
|
|
1276
|
+
return lastPreloadedManifests.some((r) => r.status === "running" || r.status === "queued" || r.status === "planning");
|
|
1896
1277
|
};
|
|
1897
|
-
const effectiveRefreshMs = () =>
|
|
1898
|
-
hasActiveWork() ? liveRefreshMs : fallbackMs;
|
|
1278
|
+
const effectiveRefreshMs = () => (hasActiveWork() ? liveRefreshMs : fallbackMs);
|
|
1899
1279
|
renderScheduler = new RenderScheduler(pi.events, renderTick, {
|
|
1900
1280
|
// Dynamic fallback: same logic as preload loop so the render timer
|
|
1901
1281
|
// also ticks at spinner frequency while a run is active.
|
|
@@ -1980,39 +1360,23 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
1980
1360
|
});
|
|
1981
1361
|
pi.on("session_before_switch", () => {
|
|
1982
1362
|
sessionGeneration++;
|
|
1983
|
-
const pendingCount = deliveryCoordinator?.getPendingCount() ?? 0;
|
|
1363
|
+
const pendingCount = lifecycleState.deliveryCoordinator?.getPendingCount() ?? 0;
|
|
1984
1364
|
try {
|
|
1985
1365
|
const activeRuns = currentCtx
|
|
1986
1366
|
? getManifestCache(currentCtx.cwd)
|
|
1987
1367
|
.list(50)
|
|
1988
|
-
.filter(
|
|
1989
|
-
(run) =>
|
|
1990
|
-
run.status === "running" ||
|
|
1991
|
-
run.status === "queued" ||
|
|
1992
|
-
run.status === "blocked",
|
|
1993
|
-
)
|
|
1368
|
+
.filter((run) => run.status === "running" || run.status === "queued" || run.status === "blocked")
|
|
1994
1369
|
: [];
|
|
1995
|
-
const snapshot = createSessionSnapshot(
|
|
1996
|
-
activeRuns,
|
|
1997
|
-
pendingCount,
|
|
1998
|
-
sessionGeneration,
|
|
1999
|
-
);
|
|
1370
|
+
const snapshot = createSessionSnapshot(activeRuns, pendingCount, sessionGeneration);
|
|
2000
1371
|
if (pendingCount > 0 || snapshot.activeRunIds.length > 0)
|
|
2001
|
-
logInternalError(
|
|
2002
|
-
"register.session-before-switch",
|
|
2003
|
-
undefined,
|
|
2004
|
-
JSON.stringify(snapshot),
|
|
2005
|
-
);
|
|
1372
|
+
logInternalError("register.session-before-switch", undefined, JSON.stringify(snapshot));
|
|
2006
1373
|
} catch (error) {
|
|
2007
1374
|
logInternalError("register.session-before-switch.snapshot", error);
|
|
2008
1375
|
}
|
|
2009
1376
|
if (pendingCount > 0) {
|
|
2010
|
-
logInternalError(
|
|
2011
|
-
"register.session-before-switch",
|
|
2012
|
-
`Switching session with ${pendingCount} pending deliveries`,
|
|
2013
|
-
);
|
|
1377
|
+
logInternalError("register.session-before-switch", `Switching session with ${pendingCount} pending deliveries`);
|
|
2014
1378
|
}
|
|
2015
|
-
deliveryCoordinator?.deactivate();
|
|
1379
|
+
lifecycleState.deliveryCoordinator?.deactivate();
|
|
2016
1380
|
resetPowerbarDedupState();
|
|
2017
1381
|
stopAsyncRunNotifier(notifierState);
|
|
2018
1382
|
stopSessionBoundSubagents();
|
|
@@ -2024,12 +1388,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
2024
1388
|
pi.on("resources_discover", () => {
|
|
2025
1389
|
const sessionCwd = currentCtx?.cwd ?? process.cwd();
|
|
2026
1390
|
const skillDir = path.resolve(sessionCwd, "skills");
|
|
2027
|
-
const extSkillDir = path.resolve(
|
|
2028
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
2029
|
-
"..",
|
|
2030
|
-
"..",
|
|
2031
|
-
"skills",
|
|
2032
|
-
);
|
|
1391
|
+
const extSkillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
2033
1392
|
const paths: string[] = [];
|
|
2034
1393
|
if (fs.existsSync(extSkillDir)) paths.push(extSkillDir);
|
|
2035
1394
|
if (skillDir !== extSkillDir && fs.existsSync(skillDir)) {
|
|
@@ -2090,7 +1449,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
2090
1449
|
if (!filePath) return;
|
|
2091
1450
|
const result = validateWrittenFile(filePath);
|
|
2092
1451
|
if (!result || result.ok) return;
|
|
2093
|
-
return {
|
|
1452
|
+
return {
|
|
1453
|
+
content: [...event.content, buildValidationBlocker(filePath, result.error ?? "validation failed")],
|
|
1454
|
+
};
|
|
2094
1455
|
} catch {
|
|
2095
1456
|
// best-effort: never break a tool result
|
|
2096
1457
|
}
|
|
@@ -2103,26 +1464,23 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
2103
1464
|
openLiveSidebar,
|
|
2104
1465
|
getManifestCache,
|
|
2105
1466
|
getRunSnapshotCache,
|
|
2106
|
-
getMetricRegistry: () => metricRegistry,
|
|
1467
|
+
getMetricRegistry: () => observabilityState.metricRegistry,
|
|
2107
1468
|
widgetState,
|
|
2108
1469
|
onJsonEvent: (taskId, runId, event) => {
|
|
2109
1470
|
const record = event as Record<string, unknown>;
|
|
2110
|
-
const eventType =
|
|
2111
|
-
|
|
2112
|
-
if (eventType) overflowTracker?.feedEvent(taskId, runId, eventType);
|
|
1471
|
+
const eventType = typeof record.type === "string" ? record.type : undefined;
|
|
1472
|
+
if (eventType) lifecycleState.overflowTracker?.feedEvent(taskId, runId, eventType);
|
|
2113
1473
|
},
|
|
2114
1474
|
});
|
|
2115
1475
|
registerSubagentTools(pi, subagentManager, {
|
|
2116
1476
|
ownerSessionGeneration: captureSessionGeneration,
|
|
2117
|
-
startForegroundRun: (ctx, runner, runId) =>
|
|
2118
|
-
startForegroundRun(ctx as ExtensionContext, runner, runId),
|
|
1477
|
+
startForegroundRun: (ctx, runner, runId) => startForegroundRun(ctx as ExtensionContext, runner, runId),
|
|
2119
1478
|
batchBarrier,
|
|
2120
1479
|
});
|
|
2121
1480
|
time("register.tools");
|
|
2122
1481
|
|
|
2123
1482
|
registerCleanupHandler(pi);
|
|
2124
1483
|
|
|
2125
|
-
|
|
2126
1484
|
// Resilient edit (Phase 2): OPT-IN via CREW_RESILIENT_EDIT=1.
|
|
2127
1485
|
// Wraps the native edit tool with the cascading replace() fallback so that
|
|
2128
1486
|
// slightly-wrong oldText (indentation drift, whitespace) still matches.
|
|
@@ -2132,7 +1490,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
2132
1490
|
.then(({ wrapEditWithResilientReplace }) => {
|
|
2133
1491
|
wrapEditWithResilientReplace(pi);
|
|
2134
1492
|
})
|
|
2135
|
-
.catch(() => {
|
|
1493
|
+
.catch(() => {
|
|
1494
|
+
/* non-critical */
|
|
1495
|
+
});
|
|
2136
1496
|
}
|
|
2137
1497
|
|
|
2138
1498
|
registerTeamCommands(pi, {
|
|
@@ -2141,18 +1501,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
|
|
|
2141
1501
|
openLiveSidebar,
|
|
2142
1502
|
getManifestCache,
|
|
2143
1503
|
getRunSnapshotCache,
|
|
2144
|
-
getMetricRegistry: () => metricRegistry,
|
|
1504
|
+
getMetricRegistry: () => observabilityState.metricRegistry,
|
|
2145
1505
|
dismissNotifications: () => {
|
|
2146
1506
|
widgetState.notificationCount = 0;
|
|
2147
1507
|
if (currentCtx) {
|
|
2148
1508
|
const uiConfig = loadConfig(currentCtx.cwd).config.ui;
|
|
2149
|
-
updateCrewWidget(
|
|
2150
|
-
currentCtx,
|
|
2151
|
-
widgetState,
|
|
2152
|
-
uiConfig,
|
|
2153
|
-
getManifestCache(currentCtx.cwd),
|
|
2154
|
-
getRunSnapshotCache(currentCtx.cwd),
|
|
2155
|
-
);
|
|
1509
|
+
updateCrewWidget(currentCtx, widgetState, uiConfig, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd));
|
|
2156
1510
|
updatePiCrewPowerbar(
|
|
2157
1511
|
pi.events,
|
|
2158
1512
|
currentCtx.cwd,
|