pi-crew 0.9.53 → 0.9.55
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 +45 -0
- package/package.json +6 -3
- package/scripts/clean-strip-types.mjs +60 -0
- package/src/adapters/claude-adapter.ts +23 -0
- package/src/adapters/codex-adapter.ts +21 -0
- package/src/adapters/cursor-adapter.ts +17 -0
- package/src/adapters/export-util.ts +143 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +18 -0
- package/src/adapters/types.ts +23 -0
- package/src/agents/agent-config.ts +194 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -0
- package/src/agents/discover-agents.ts +623 -0
- package/src/benchmark/benchmark-runner.ts +313 -0
- package/src/benchmark/feedback-loop.ts +73 -0
- package/src/config/config.ts +1270 -0
- package/src/config/defaults.ts +193 -0
- package/src/config/drift-detector.ts +290 -0
- package/src/config/markers.ts +330 -0
- package/src/config/resilient-parser.ts +117 -0
- package/src/config/role-tools.ts +118 -0
- package/src/config/suggestions.ts +75 -0
- package/src/config/types.ts +280 -0
- package/src/errors.ts +194 -0
- package/src/extension/action-suggestions.ts +117 -0
- package/src/extension/async-notifier.ts +179 -0
- package/src/extension/autonomous-policy.ts +209 -0
- package/src/extension/command-completions.ts +120 -0
- package/src/extension/context-status-injection.ts +185 -0
- package/src/extension/crew-autocomplete.ts +131 -0
- package/src/extension/crew-cleanup.ts +168 -0
- package/src/extension/crew-input-router.ts +93 -0
- package/src/extension/crew-shortcuts.ts +87 -0
- package/src/extension/crew-vibes/cat-frames.ts +18 -0
- package/src/extension/crew-vibes/config.ts +195 -0
- package/src/extension/crew-vibes/figures.ts +101 -0
- package/src/extension/crew-vibes/font-detect.ts +71 -0
- package/src/extension/crew-vibes/footer.ts +292 -0
- package/src/extension/crew-vibes/index.ts +432 -0
- package/src/extension/crew-vibes/provider-usage.ts +396 -0
- package/src/extension/crew-vibes/render.ts +206 -0
- package/src/extension/crew-vibes/speed.ts +286 -0
- package/src/extension/cross-extension-rpc.ts +350 -0
- package/src/extension/help.ts +62 -0
- package/src/extension/import-index.ts +74 -0
- package/src/extension/knowledge-injection.ts +402 -0
- package/src/extension/management.ts +571 -0
- package/src/extension/message-renderers.ts +108 -0
- package/src/extension/notification-router.ts +152 -0
- package/src/extension/notification-sink.ts +54 -0
- package/src/extension/pi-api.ts +56 -0
- package/src/extension/plan-orchestrate.ts +302 -0
- package/src/extension/project-init.ts +170 -0
- package/src/extension/register.ts +113 -0
- package/src/extension/registration/artifact-cleanup.ts +20 -0
- package/src/extension/registration/command-registration.ts +58 -0
- package/src/extension/registration/command-utils.ts +58 -0
- package/src/extension/registration/commands.ts +1228 -0
- package/src/extension/registration/compaction-guard.ts +333 -0
- package/src/extension/registration/context-builder.ts +135 -0
- package/src/extension/registration/crash-recovery-cache.ts +54 -0
- package/src/extension/registration/foreground-run-controller.ts +238 -0
- package/src/extension/registration/hook-registration.ts +119 -0
- package/src/extension/registration/lazy-configurers.ts +124 -0
- package/src/extension/registration/lifecycle-handlers.ts +923 -0
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +322 -0
- package/src/extension/registration/registration-types.ts +158 -0
- package/src/extension/registration/runtime-cleanup.ts +230 -0
- package/src/extension/registration/subagent-helpers.ts +125 -0
- package/src/extension/registration/subagent-manager-setup.ts +330 -0
- package/src/extension/registration/subagent-tools.ts +456 -0
- package/src/extension/registration/team-tool.ts +225 -0
- package/src/extension/registration/tool-registration.ts +50 -0
- package/src/extension/registration/ui.ts +172 -0
- package/src/extension/registration/viewers.ts +121 -0
- package/src/extension/registration/wire-cross-extension.ts +37 -0
- package/src/extension/result-watcher.ts +139 -0
- package/src/extension/rpc-hmac.ts +256 -0
- package/src/extension/run-bundle-schema.ts +104 -0
- package/src/extension/run-export.ts +97 -0
- package/src/extension/run-import.ts +168 -0
- package/src/extension/run-index.ts +131 -0
- package/src/extension/run-maintenance.ts +190 -0
- package/src/extension/session-summary.ts +18 -0
- package/src/extension/team-manager-command.ts +153 -0
- package/src/extension/team-onboard.ts +180 -0
- package/src/extension/team-recommendation.ts +288 -0
- package/src/extension/team-tool/anchor.ts +172 -0
- package/src/extension/team-tool/api.ts +1244 -0
- package/src/extension/team-tool/auto-summarize.ts +142 -0
- package/src/extension/team-tool/cache-control.ts +19 -0
- package/src/extension/team-tool/cancel.ts +337 -0
- package/src/extension/team-tool/chain-dispatch.ts +95 -0
- package/src/extension/team-tool/chain-executor.ts +379 -0
- package/src/extension/team-tool/config-patch.ts +50 -0
- package/src/extension/team-tool/context.ts +149 -0
- package/src/extension/team-tool/destructive-gate.ts +52 -0
- package/src/extension/team-tool/dispatch/automate.ts +80 -0
- package/src/extension/team-tool/dispatch/control.ts +42 -0
- package/src/extension/team-tool/dispatch/index.ts +95 -0
- package/src/extension/team-tool/dispatch/manage.ts +161 -0
- package/src/extension/team-tool/dispatch/run.ts +53 -0
- package/src/extension/team-tool/dispatch/status.ts +187 -0
- package/src/extension/team-tool/doctor.ts +431 -0
- package/src/extension/team-tool/explain.ts +282 -0
- package/src/extension/team-tool/failure-patterns.ts +118 -0
- package/src/extension/team-tool/goal-wrap.ts +330 -0
- package/src/extension/team-tool/goal.ts +556 -0
- package/src/extension/team-tool/handle-schedule.ts +332 -0
- package/src/extension/team-tool/handle-settings.ts +520 -0
- package/src/extension/team-tool/health-monitor.ts +481 -0
- package/src/extension/team-tool/inspect.ts +88 -0
- package/src/extension/team-tool/intent-policy.ts +45 -0
- package/src/extension/team-tool/lifecycle-actions.ts +615 -0
- package/src/extension/team-tool/orchestrate.ts +98 -0
- package/src/extension/team-tool/parallel-dispatch.ts +183 -0
- package/src/extension/team-tool/plan.ts +50 -0
- package/src/extension/team-tool/respond.ts +150 -0
- package/src/extension/team-tool/run-deadline.ts +66 -0
- package/src/extension/team-tool/run-not-found.ts +49 -0
- package/src/extension/team-tool/run.ts +1051 -0
- package/src/extension/team-tool/status.ts +260 -0
- package/src/extension/team-tool/workflow-manage.ts +261 -0
- package/src/extension/team-tool-types.ts +26 -0
- package/src/extension/team-tool.ts +750 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/extension/validate-resources.ts +108 -0
- package/src/hooks/registry.ts +200 -0
- package/src/hooks/types.ts +69 -0
- package/src/i18n.ts +212 -0
- package/src/observability/correlation.ts +50 -0
- package/src/observability/event-bus.ts +86 -0
- package/src/observability/event-to-metric.ts +170 -0
- package/src/observability/exporters/adapter.ts +27 -0
- package/src/observability/exporters/otlp-exporter.ts +356 -0
- package/src/observability/exporters/prometheus-exporter.ts +54 -0
- package/src/observability/metric-registry.ts +100 -0
- package/src/observability/metric-retention.ts +64 -0
- package/src/observability/metric-sink.ts +99 -0
- package/src/observability/metrics-primitives.ts +219 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +10 -0
- package/src/plugins/plugins/vitest.ts +9 -0
- package/src/prompt/prompt-runtime.ts +380 -0
- package/src/runtime/adaptive-plan.ts +563 -0
- package/src/runtime/agent-control.ts +215 -0
- package/src/runtime/agent-memory.ts +81 -0
- package/src/runtime/agent-observability.ts +122 -0
- package/src/runtime/anchor-manager.ts +475 -0
- package/src/runtime/async-marker.ts +32 -0
- package/src/runtime/async-runner.ts +381 -0
- package/src/runtime/attention-events.ts +28 -0
- package/src/runtime/auto-summarize.ts +344 -0
- package/src/runtime/background-runner.ts +840 -0
- package/src/runtime/batch-barrier.ts +147 -0
- package/src/runtime/broker-issuer.ts +39 -0
- package/src/runtime/cancellation-token.ts +99 -0
- package/src/runtime/cancellation.ts +99 -0
- package/src/runtime/capability-inventory.ts +137 -0
- package/src/runtime/chain-parser.ts +237 -0
- package/src/runtime/chain-runner.ts +606 -0
- package/src/runtime/checkpoint.ts +291 -0
- package/src/runtime/child-pi-constants.ts +42 -0
- package/src/runtime/child-pi-kill.ts +180 -0
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi-spawn.ts +287 -0
- package/src/runtime/child-pi-steering.ts +128 -0
- package/src/runtime/child-pi-streams.ts +296 -0
- package/src/runtime/child-pi-transcript.ts +169 -0
- package/src/runtime/child-pi.ts +1105 -0
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +292 -0
- package/src/runtime/command-trace.ts +105 -0
- package/src/runtime/compact-pipeline.ts +56 -0
- package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
- package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
- package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
- package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
- package/src/runtime/compact-stages/index.ts +23 -0
- package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
- package/src/runtime/compact-stages/truncation-stage.ts +74 -0
- package/src/runtime/compaction-summary.ts +278 -0
- package/src/runtime/completion-guard.ts +207 -0
- package/src/runtime/concurrency.ts +58 -0
- package/src/runtime/crash-classification.ts +229 -0
- package/src/runtime/crash-recovery.ts +594 -0
- package/src/runtime/crew-agent-records.ts +595 -0
- package/src/runtime/crew-agent-runtime.ts +62 -0
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +159 -0
- package/src/runtime/crew-broker.ts +1304 -0
- package/src/runtime/crew-hooks.ts +227 -0
- package/src/runtime/cross-extension-rpc.ts +143 -0
- package/src/runtime/custom-tools/irc-tool.ts +282 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
- package/src/runtime/deadletter.ts +47 -0
- package/src/runtime/delivery-coordinator.ts +211 -0
- package/src/runtime/delta-conflict.ts +348 -0
- package/src/runtime/deterministic-ast.ts +161 -0
- package/src/runtime/diagnostic-export.ts +171 -0
- package/src/runtime/direct-run.ts +45 -0
- package/src/runtime/dwf-state-store.ts +102 -0
- package/src/runtime/dynamic-workflow-context.ts +1013 -0
- package/src/runtime/dynamic-workflow-runner.ts +325 -0
- package/src/runtime/effectiveness.ts +90 -0
- package/src/runtime/errors/crew-errors.ts +162 -0
- package/src/runtime/event-stream-bridge.ts +98 -0
- package/src/runtime/foreground-control.ts +193 -0
- package/src/runtime/foreground-watchdog.ts +132 -0
- package/src/runtime/global-worker-cap.ts +96 -0
- package/src/runtime/goal-achievement.ts +148 -0
- package/src/runtime/goal-evaluator.ts +365 -0
- package/src/runtime/goal-loop-runner.ts +825 -0
- package/src/runtime/goal-state-store.ts +219 -0
- package/src/runtime/green-contract.ts +55 -0
- package/src/runtime/group-join.ts +267 -0
- package/src/runtime/handoff-manager.ts +598 -0
- package/src/runtime/heartbeat-gradient.ts +36 -0
- package/src/runtime/heartbeat-watcher.ts +239 -0
- package/src/runtime/hidden-handoff.ts +407 -0
- package/src/runtime/important-line-classifier.ts +140 -0
- package/src/runtime/intercom-bridge.ts +187 -0
- package/src/runtime/iteration-hooks.ts +305 -0
- package/src/runtime/live-agent-control.ts +136 -0
- package/src/runtime/live-agent-manager.ts +704 -0
- package/src/runtime/live-control-realtime.ts +56 -0
- package/src/runtime/live-extension-bridge.ts +148 -0
- package/src/runtime/live-irc.ts +97 -0
- package/src/runtime/live-session-health.ts +108 -0
- package/src/runtime/live-session-runtime.ts +1146 -0
- package/src/runtime/loop-gates.ts +128 -0
- package/src/runtime/manifest-cache.ts +375 -0
- package/src/runtime/mcp-proxy.ts +105 -0
- package/src/runtime/metric-parser.ts +36 -0
- package/src/runtime/model-fallback.ts +409 -0
- package/src/runtime/model-resolver.ts +126 -0
- package/src/runtime/model-scope.ts +158 -0
- package/src/runtime/orphan-worker-registry.ts +465 -0
- package/src/runtime/output-validator.ts +202 -0
- package/src/runtime/overflow-recovery.ts +206 -0
- package/src/runtime/parallel-research.ts +68 -0
- package/src/runtime/parallel-utils.ts +160 -0
- package/src/runtime/parent-guard.ts +134 -0
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +292 -0
- package/src/runtime/per-write-validator.ts +181 -0
- package/src/runtime/phase-progress.ts +217 -0
- package/src/runtime/phase-tracker.ts +385 -0
- package/src/runtime/pi-args.ts +655 -0
- package/src/runtime/pi-json-output.ts +170 -0
- package/src/runtime/pi-spawn.ts +273 -0
- package/src/runtime/pipeline-runner.ts +523 -0
- package/src/runtime/plan-templates.ts +201 -0
- package/src/runtime/policy-engine.ts +115 -0
- package/src/runtime/post-checks.ts +142 -0
- package/src/runtime/post-exit-stdio-guard.ts +109 -0
- package/src/runtime/process-lifecycle.ts +491 -0
- package/src/runtime/process-status.ts +154 -0
- package/src/runtime/progress-event-coalescer.ts +44 -0
- package/src/runtime/progress-tracker.ts +124 -0
- package/src/runtime/prose-compressor.ts +162 -0
- package/src/runtime/recovery-recipes.ts +203 -0
- package/src/runtime/replace.ts +570 -0
- package/src/runtime/resilient-edit.ts +153 -0
- package/src/runtime/result-extractor.ts +217 -0
- package/src/runtime/retry-executor.ts +109 -0
- package/src/runtime/retry-runner.ts +336 -0
- package/src/runtime/role-permission.ts +59 -0
- package/src/runtime/run-coalesced-task-group.ts +308 -0
- package/src/runtime/run-drift.ts +219 -0
- package/src/runtime/run-tracker.ts +116 -0
- package/src/runtime/run-worker.ts +78 -0
- package/src/runtime/runtime-policy.ts +29 -0
- package/src/runtime/runtime-resolver.ts +172 -0
- package/src/runtime/runtime-warmup.ts +160 -0
- package/src/runtime/scheduler.ts +353 -0
- package/src/runtime/semaphore.ts +141 -0
- package/src/runtime/sensitive-paths.ts +93 -0
- package/src/runtime/session-resources.ts +25 -0
- package/src/runtime/session-snapshot.ts +59 -0
- package/src/runtime/session-usage.ts +79 -0
- package/src/runtime/settings-store.ts +155 -0
- package/src/runtime/sidechain-output.ts +35 -0
- package/src/runtime/single-agent-compose.ts +84 -0
- package/src/runtime/skill-effectiveness.ts +524 -0
- package/src/runtime/skill-instructions.ts +394 -0
- package/src/runtime/stale-reconciler.ts +680 -0
- package/src/runtime/stream-preview.ts +184 -0
- package/src/runtime/streaming-output.ts +50 -0
- package/src/runtime/subagent-manager.ts +561 -0
- package/src/runtime/subprocess-tool-registry.ts +70 -0
- package/src/runtime/supervisor-contact.ts +64 -0
- package/src/runtime/task-display.ts +52 -0
- package/src/runtime/task-graph-scheduler.ts +227 -0
- package/src/runtime/task-graph.ts +290 -0
- package/src/runtime/task-health.ts +83 -0
- package/src/runtime/task-id.ts +161 -0
- package/src/runtime/task-output-context.ts +602 -0
- package/src/runtime/task-packet.ts +268 -0
- package/src/runtime/task-quality.ts +199 -0
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/child-executor.ts +790 -0
- package/src/runtime/task-runner/context-retrieval.ts +163 -0
- package/src/runtime/task-runner/live-executor.ts +227 -0
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/post-execution.ts +480 -0
- package/src/runtime/task-runner/pre-execution.ts +365 -0
- package/src/runtime/task-runner/progress.ts +157 -0
- package/src/runtime/task-runner/prompt-builder.ts +278 -0
- package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
- package/src/runtime/task-runner/result-utils.ts +16 -0
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +125 -0
- package/src/runtime/task-runner/scaffold-executor.ts +25 -0
- package/src/runtime/task-runner/state-helpers.ts +176 -0
- package/src/runtime/task-runner/tail-read.ts +34 -0
- package/src/runtime/task-runner.ts +216 -0
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +2396 -0
- package/src/runtime/tool-output-pruner.ts +333 -0
- package/src/runtime/tool-progress.ts +278 -0
- package/src/runtime/usage-tracker.ts +73 -0
- package/src/runtime/verification-gates.ts +579 -0
- package/src/runtime/verification-integrity.ts +108 -0
- package/src/runtime/verification-worktree.ts +186 -0
- package/src/runtime/worker-heartbeat.ts +25 -0
- package/src/runtime/worker-startup.ts +83 -0
- package/src/runtime/workflow-state.ts +195 -0
- package/src/runtime/workspace-lock.ts +443 -0
- package/src/runtime/workspace-tree.ts +312 -0
- package/src/runtime/yield-handler.ts +224 -0
- package/src/runtime/zombie-scanner.ts +298 -0
- package/src/schema/config-schema.ts +327 -0
- package/src/schema/team-tool-schema.ts +492 -0
- package/src/schema/validation-types.ts +159 -0
- package/src/skills/discover-skills.ts +203 -0
- package/src/skills/skill-templates.ts +456 -0
- package/src/skills/validate.ts +285 -0
- package/src/state/active-run-registry.ts +439 -0
- package/src/state/artifact-store.ts +176 -0
- package/src/state/atomic-write.ts +970 -0
- package/src/state/blob-store.ts +308 -0
- package/src/state/contracts.ts +160 -0
- package/src/state/crew-init.ts +269 -0
- package/src/state/decision-ledger.ts +372 -0
- package/src/state/event-log-rotation.ts +399 -0
- package/src/state/event-log.ts +1465 -0
- package/src/state/event-reconstructor.ts +229 -0
- package/src/state/gitignore-manager.ts +46 -0
- package/src/state/health-store.ts +79 -0
- package/src/state/hook-instinct-bridge.ts +94 -0
- package/src/state/hook-integrations.ts +51 -0
- package/src/state/instinct-store.ts +275 -0
- package/src/state/jsonl-writer.ts +107 -0
- package/src/state/locks.ts +562 -0
- package/src/state/mailbox.ts +916 -0
- package/src/state/observation-store.ts +176 -0
- package/src/state/run-cache.ts +193 -0
- package/src/state/run-graph.ts +183 -0
- package/src/state/run-metrics.ts +165 -0
- package/src/state/schedule.ts +166 -0
- package/src/state/session-state-map.ts +51 -0
- package/src/state/state-store.ts +973 -0
- package/src/state/task-claims.ts +61 -0
- package/src/state/tiered-eval.ts +480 -0
- package/src/state/types-eval.ts +58 -0
- package/src/state/types.ts +447 -0
- package/src/state/usage.ts +138 -0
- package/src/state/worker-atomic-writer.ts +198 -0
- package/src/subagents/async-entry.ts +1 -0
- package/src/subagents/index.ts +3 -0
- package/src/subagents/live/control.ts +1 -0
- package/src/subagents/live/manager.ts +1 -0
- package/src/subagents/live/realtime.ts +1 -0
- package/src/subagents/live/session-runtime.ts +1 -0
- package/src/subagents/manager.ts +1 -0
- package/src/subagents/spawn.ts +1 -0
- package/src/teams/discover-teams.ts +187 -0
- package/src/teams/team-config.ts +27 -0
- package/src/teams/team-serializer.ts +38 -0
- package/src/tools/safe-bash-extension.ts +54 -0
- package/src/tools/safe-bash.ts +505 -0
- package/src/types/diff.d.ts +18 -0
- package/src/types/new-api-types.ts +35 -0
- package/src/ui/agent-management-overlay.ts +160 -0
- package/src/ui/card-colors.ts +139 -0
- package/src/ui/crew-footer.ts +102 -0
- package/src/ui/crew-select-list.ts +112 -0
- package/src/ui/dashboard-panes/agents-pane.ts +164 -0
- package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
- package/src/ui/dashboard-panes/capability-pane.ts +77 -0
- package/src/ui/dashboard-panes/health-pane.ts +31 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
- package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
- package/src/ui/dashboard-panes/progress-pane.ts +38 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
- package/src/ui/deploy-bundled-themes.ts +71 -0
- package/src/ui/dwf-phase-display.ts +151 -0
- package/src/ui/dynamic-border.ts +35 -0
- package/src/ui/format-helpers.ts +31 -0
- package/src/ui/heartbeat-aggregator.ts +74 -0
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +248 -0
- package/src/ui/layout-primitives.ts +106 -0
- package/src/ui/live-conversation-overlay.ts +183 -0
- package/src/ui/live-duration.ts +55 -0
- package/src/ui/live-run-sidebar.ts +293 -0
- package/src/ui/loaders.ts +6 -0
- package/src/ui/mascot.ts +449 -0
- package/src/ui/overlays/agent-picker-overlay.ts +66 -0
- package/src/ui/overlays/confirm-overlay.ts +60 -0
- package/src/ui/overlays/help-overlay.ts +177 -0
- package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
- package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
- package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
- package/src/ui/pi-ui-compat.ts +68 -0
- package/src/ui/powerbar-publisher.ts +459 -0
- package/src/ui/render-coalescer.ts +101 -0
- package/src/ui/render-diff.ts +160 -0
- package/src/ui/render-scheduler.ts +278 -0
- package/src/ui/run-action-dispatcher.ts +182 -0
- package/src/ui/run-dashboard.ts +910 -0
- package/src/ui/run-event-bus.ts +381 -0
- package/src/ui/run-snapshot-cache.ts +1057 -0
- package/src/ui/settings-overlay.ts +1067 -0
- package/src/ui/shared-overlay-scheduler.ts +96 -0
- package/src/ui/snapshot-types.ts +90 -0
- package/src/ui/spinner.ts +17 -0
- package/src/ui/status-colors.ts +143 -0
- package/src/ui/syntax-highlight.ts +136 -0
- package/src/ui/terminal-status.ts +269 -0
- package/src/ui/theme-adapter.ts +271 -0
- package/src/ui/theme-discovery.ts +199 -0
- package/src/ui/tool-progress-formatter.ts +93 -0
- package/src/ui/tool-renderers/brief-mode.ts +225 -0
- package/src/ui/tool-renderers/index.ts +726 -0
- package/src/ui/transcript-cache.ts +128 -0
- package/src/ui/transcript-entries.ts +256 -0
- package/src/ui/transcript-viewer.ts +424 -0
- package/src/ui/widget/index.ts +418 -0
- package/src/ui/widget/widget-formatters.ts +182 -0
- package/src/ui/widget/widget-model.ts +110 -0
- package/src/ui/widget/widget-renderer.ts +182 -0
- package/src/ui/widget/widget-types.ts +36 -0
- package/src/utils/bm25-search.ts +235 -0
- package/src/utils/completion-dedupe.ts +63 -0
- package/src/utils/conflict-detect.ts +668 -0
- package/src/utils/env-allowlist.ts +30 -0
- package/src/utils/env-filter.ts +155 -0
- package/src/utils/file-coalescer.ts +98 -0
- package/src/utils/fingerprint.ts +180 -0
- package/src/utils/frontmatter.ts +70 -0
- package/src/utils/fs-watch.ts +37 -0
- package/src/utils/gh-protocol.ts +556 -0
- package/src/utils/git.ts +260 -0
- package/src/utils/guards.ts +107 -0
- package/src/utils/ids.ts +24 -0
- package/src/utils/incremental-reader.ts +220 -0
- package/src/utils/internal-error.ts +10 -0
- package/src/utils/names.ts +36 -0
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/paths.ts +227 -0
- package/src/utils/project-detector.ts +160 -0
- package/src/utils/redaction.ts +370 -0
- package/src/utils/resolve-shell.ts +36 -0
- package/src/utils/run-watcher-registry.ts +152 -0
- package/src/utils/safe-paths.ts +393 -0
- package/src/utils/scan-cache.ts +144 -0
- package/src/utils/session-utils.ts +110 -0
- package/src/utils/sleep.ts +54 -0
- package/src/utils/socket-path.ts +164 -0
- package/src/utils/sse-parser.ts +131 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/timings.ts +33 -0
- package/src/utils/token-counter.ts +200 -0
- package/src/utils/visual.ts +207 -0
- package/src/workflows/cost-estimator.ts +34 -0
- package/src/workflows/discover-workflows.ts +308 -0
- package/src/workflows/intermediate-store.ts +166 -0
- package/src/workflows/preflight-validator.ts +168 -0
- package/src/workflows/topology-analyzer.ts +203 -0
- package/src/workflows/validate-workflow.ts +50 -0
- package/src/workflows/workflow-config.ts +75 -0
- package/src/workflows/workflow-serializer.ts +32 -0
- package/src/worktree/branch-freshness.ts +112 -0
- package/src/worktree/cleanup.ts +341 -0
- package/src/worktree/worktree-manager.ts +1204 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* path-overlap.ts — write-path overlap detection for sequential scheduling (M5).
|
|
3
|
+
*
|
|
4
|
+
* Implements the writes-only path-intersection heuristic (per plan §11 decision
|
|
5
|
+
* #5). A pair of steps is considered overlapping ONLY when both declare the
|
|
6
|
+
* same write target (via `step.output`). Reads are intentionally ignored to
|
|
7
|
+
* avoid false positives (parallel reads are safe).
|
|
8
|
+
*
|
|
9
|
+
* Scope:
|
|
10
|
+
* - export `detectWriteOverlap(stepA, stepB): boolean` — pair check
|
|
11
|
+
* - export `selectNonOverlapping(steps, maxCount): WorkflowStep[]` — greedy
|
|
12
|
+
* packer that returns at most `maxCount` steps such that no two returned
|
|
13
|
+
* steps have overlapping write paths.
|
|
14
|
+
* - export `filterReadyByWriteOverlap(taskIds, tasks, workflow, maxCount)` —
|
|
15
|
+
* scheduler-side wrapper that translates TeamTaskState IDs to WorkflowSteps
|
|
16
|
+
* and applies the overlap filter.
|
|
17
|
+
*
|
|
18
|
+
* Default greedy strategy: preserve declaration order; first step always wins
|
|
19
|
+
* because (a) deterministic for tests, (b) declaration order typically reflects
|
|
20
|
+
* author intent, (c) a future PR can swap to priority-based when M5 defaults
|
|
21
|
+
* flip to `true` in production.
|
|
22
|
+
*
|
|
23
|
+
* Out of scope: reads overlap detection (decision #5); deferral cap (decision
|
|
24
|
+
* #6; M5 first ship treats repeated deferral as best-effort; cap warning logged
|
|
25
|
+
* but not enforced, since M5 default = false means production isn't gated).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { TeamTaskState } from "../state/types.ts";
|
|
29
|
+
import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extract the canonical write-target path of a step.
|
|
33
|
+
*
|
|
34
|
+
* Returns:
|
|
35
|
+
* - `step.output` if it is a non-empty string (the file path produced).
|
|
36
|
+
* - `undefined` otherwise (no declared write target → step has no write
|
|
37
|
+
* overlap risk).
|
|
38
|
+
*
|
|
39
|
+
* `step.output === false` means "explicitly no output"; treated as no write.
|
|
40
|
+
*/
|
|
41
|
+
function extractWritePath(step: WorkflowStep): string | undefined {
|
|
42
|
+
if (typeof step.output === "string" && step.output.length > 0) return step.output;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pairwise check: do stepA and stepB both write to the same path?
|
|
48
|
+
*
|
|
49
|
+
* O(1). Both steps must declare an `output` string; otherwise no overlap.
|
|
50
|
+
*
|
|
51
|
+
* Examples:
|
|
52
|
+
* detectWriteOverlap({output: 'a.md'}, {output: 'a.md'}) → true
|
|
53
|
+
* detectWriteOverlap({output: 'a.md'}, {output: 'b.md'}) → false
|
|
54
|
+
* detectWriteOverlap({output: 'a.md'}, {output: false}) → false
|
|
55
|
+
* detectWriteOverlap({output: 'a.md'}, {}) → false
|
|
56
|
+
*/
|
|
57
|
+
export function detectWriteOverlap(stepA: WorkflowStep, stepB: WorkflowStep): boolean {
|
|
58
|
+
const pathA = extractWritePath(stepA);
|
|
59
|
+
const pathB = extractWritePath(stepB);
|
|
60
|
+
if (!pathA || !pathB) return false;
|
|
61
|
+
return pathA === pathB;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Greedy packer: pick up to `maxCount` steps from `steps` such that no two
|
|
66
|
+
* PICKED steps have overlapping write paths.
|
|
67
|
+
*
|
|
68
|
+
* Algorithm:
|
|
69
|
+
* - Iterates over `steps` in order.
|
|
70
|
+
* - First step is always picked.
|
|
71
|
+
* - Each subsequent step is picked iff (a) it doesn't overlap with any
|
|
72
|
+
* already-picked step AND (b) we haven't reached `maxCount`.
|
|
73
|
+
*
|
|
74
|
+
* Returns the picked subset (a NEW array; does not mutate input).
|
|
75
|
+
*
|
|
76
|
+
* Used by scheduler when `limits.serializeOnPathOverlap = true`.
|
|
77
|
+
*/
|
|
78
|
+
export function selectNonOverlapping(steps: WorkflowStep[], maxCount: number): WorkflowStep[] {
|
|
79
|
+
const picked: WorkflowStep[] = [];
|
|
80
|
+
if (maxCount <= 0 || steps.length === 0) return picked;
|
|
81
|
+
for (const step of steps) {
|
|
82
|
+
if (picked.length >= maxCount) break;
|
|
83
|
+
let conflict = false;
|
|
84
|
+
for (const existing of picked) {
|
|
85
|
+
if (detectWriteOverlap(step, existing)) {
|
|
86
|
+
conflict = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!conflict) picked.push(step);
|
|
91
|
+
}
|
|
92
|
+
return picked;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Scheduler-side wrapper: given a list of ready task IDs (from the task-graph
|
|
97
|
+
* ready queue), filter them by write-path overlap using the workflow step
|
|
98
|
+
* definitions.
|
|
99
|
+
*
|
|
100
|
+
* Behavior:
|
|
101
|
+
* - Resolves each task ID → `TeamTaskState` → `WorkflowStep` via
|
|
102
|
+
* `task.stepId` matching `workflow.steps[i].id`.
|
|
103
|
+
* - Tasks whose step is missing from the workflow (e.g. ad-hoc tasks) are
|
|
104
|
+
* always included (treated as having no declared write path).
|
|
105
|
+
* - Returns the same array (identity-equal) when `enabled` is false, so
|
|
106
|
+
* callers can skip the filter cheaply.
|
|
107
|
+
*
|
|
108
|
+
* Used by the team-runner scheduler when `limits.serializeOnPathOverlap`
|
|
109
|
+
* is `true` (per plan §11 decision #6, default is `false` so this is a
|
|
110
|
+
* no-op in v0.9.17 unless the workflow author opts in).
|
|
111
|
+
*/
|
|
112
|
+
export function filterReadyByWriteOverlap(
|
|
113
|
+
readyTaskIds: string[],
|
|
114
|
+
tasks: TeamTaskState[],
|
|
115
|
+
workflow: WorkflowConfig,
|
|
116
|
+
maxCount: number,
|
|
117
|
+
enabled: boolean,
|
|
118
|
+
): string[] {
|
|
119
|
+
if (!enabled || maxCount <= 0 || readyTaskIds.length === 0) return readyTaskIds;
|
|
120
|
+
const taskById = new Map<string, TeamTaskState>(tasks.map((task) => [task.id, task]));
|
|
121
|
+
const stepById = new Map<string, WorkflowStep>(workflow.steps.map((step) => [step.id, step]));
|
|
122
|
+
const pickedSteps: WorkflowStep[] = [];
|
|
123
|
+
const pickedTaskIds: string[] = [];
|
|
124
|
+
for (const taskId of readyTaskIds) {
|
|
125
|
+
if (pickedTaskIds.length >= maxCount) break;
|
|
126
|
+
const task = taskById.get(taskId);
|
|
127
|
+
if (!task?.stepId) {
|
|
128
|
+
// No step reference: treat as no declared write path → never conflicts.
|
|
129
|
+
pickedTaskIds.push(taskId);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const step = stepById.get(task.stepId);
|
|
133
|
+
if (!step) {
|
|
134
|
+
pickedTaskIds.push(taskId);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
let conflict = false;
|
|
138
|
+
for (const existing of pickedSteps) {
|
|
139
|
+
if (detectWriteOverlap(step, existing)) {
|
|
140
|
+
conflict = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!conflict) {
|
|
145
|
+
pickedTaskIds.push(taskId);
|
|
146
|
+
pickedSteps.push(step);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return pickedTaskIds;
|
|
150
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robust resolution + async loading of the @earendil-works/pi-coding-agent
|
|
3
|
+
* peer dependency. Fixes the "Cannot find module '@earendil-works/pi-coding-agent'"
|
|
4
|
+
* crash that blocks ALL team runs when pi-crew and pi are installed in
|
|
5
|
+
* SEPARATE node_modules trees.
|
|
6
|
+
*
|
|
7
|
+
* PROBLEM (Windows / global installs — reported 2026-06-17)
|
|
8
|
+
* pi-crew is a pi EXTENSION. pi installs extensions under
|
|
9
|
+
* `~/.pi/agent/npm/node_modules/<ext>/`, but pi itself (the
|
|
10
|
+
* @earendil-works/pi-coding-agent package that extensions import from)
|
|
11
|
+
* usually lives in a DIFFERENT node_modules tree — a global one (nvm,
|
|
12
|
+
* %APPDATA%\npm, Volta, fnm, pnpm-global). Node's resolver only walks UP
|
|
13
|
+
* through ancestor `node_modules` of the importing file, so a file under
|
|
14
|
+
* `~/.pi/agent/npm/node_modules/pi-crew/...` CANNOT resolve a peer dep
|
|
15
|
+
* installed under `~/.nvm/.../lib/node_modules/`. Every static
|
|
16
|
+
* `import { X } from "@earendil-works/pi-coding-agent"` that executes inside
|
|
17
|
+
* a SPAWNED CHILD PROCESS (the detached background team runner started by
|
|
18
|
+
* async-runner.spawnBackgroundTeamRun) therefore crashes at module load,
|
|
19
|
+
* leaving all team runs permanently `queued`.
|
|
20
|
+
*
|
|
21
|
+
* ADDITIONAL CONSTRAINT (verified empirically 2026-06-17)
|
|
22
|
+
* pi-coding-agent ships as ESM-only (`"type":"module"`, exports map has only
|
|
23
|
+
* an `import` condition). CJS `require()` / `createRequire(dir)(name)` fails
|
|
24
|
+
* with ERR_PACKAGE_PATH_NOT_EXPORTED under plain node AND under jiti/tsx. The
|
|
25
|
+
* ONLY working load mechanism is a dynamic `import()` of the resolved ESM
|
|
26
|
+
* entry file URL. Hence: sync resolution of the DIR, async load of the MODULE.
|
|
27
|
+
*
|
|
28
|
+
* APPROACH
|
|
29
|
+
* - resolvePeerDep() (sync) — find the install dir across many layouts.
|
|
30
|
+
* - primePeerDep() (async) — dynamic-import the resolved entry, cache
|
|
31
|
+
* the module namespace. Memoized. Called
|
|
32
|
+
* once per process during bootstrap.
|
|
33
|
+
* - getAgentDir() (sync) — read the cached module's getAgentDir.
|
|
34
|
+
* Falls back to a computed default if the
|
|
35
|
+
* cache was never primed, so it NEVER throws.
|
|
36
|
+
*/
|
|
37
|
+
import * as fs from "node:fs";
|
|
38
|
+
import * as os from "node:os";
|
|
39
|
+
import * as path from "node:path";
|
|
40
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
41
|
+
import { resolveNpmGlobalRoot } from "./pi-spawn.ts";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The pi-coding-agent peer dependency package name(s) we can be loaded by.
|
|
45
|
+
* @earendil-works is the canonical scope; @mariozechner is the historical fork.
|
|
46
|
+
*/
|
|
47
|
+
export const PEER_DEP_NAMES = ["@earendil-works/pi-coding-agent", "@mariozechner/pi-coding-agent"] as const;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Env var a parent pi-crew process sets on spawned children so they can resolve
|
|
51
|
+
* the peer dep WITHOUT running `npm root -g` (~200ms probe). The resolver
|
|
52
|
+
* checks this FIRST. Absent (older parent, direct invocation, tests) → falls
|
|
53
|
+
* through to the probing strategies. Also lets users override the resolution
|
|
54
|
+
* explicitly as a last-resort fix.
|
|
55
|
+
*/
|
|
56
|
+
export const PEER_DEP_DIR_ENV = "PI_CREW_PEER_DEP_DIR";
|
|
57
|
+
|
|
58
|
+
type PeerDepModule = typeof import("@earendil-works/pi-coding-agent");
|
|
59
|
+
|
|
60
|
+
interface ResolvedPeerDep {
|
|
61
|
+
dir: string;
|
|
62
|
+
name: string;
|
|
63
|
+
/** file:// URL of the ESM entry (exports["."].import || main). */
|
|
64
|
+
mainUrl: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let cachedResolve: ResolvedPeerDep | undefined | null = null;
|
|
68
|
+
let cachedModule: PeerDepModule | undefined;
|
|
69
|
+
let primingPromise: Promise<PeerDepModule> | undefined;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build the ordered list of "resolution bases" — paths to seed
|
|
73
|
+
* `createRequire(...).resolve()` from. Node walks UP `node_modules` from each
|
|
74
|
+
* base's directory, so any base inside (or beside) the peer dep's package
|
|
75
|
+
* tree will find it. Pure given env/process inputs; exported for unit tests.
|
|
76
|
+
*/
|
|
77
|
+
export function peerDepResolutionBases(): string[] {
|
|
78
|
+
const bases: string[] = [];
|
|
79
|
+
|
|
80
|
+
// 0. Parent-provided hint (fastest — no probe). Set by async-runner.
|
|
81
|
+
const envHint = process.env[PEER_DEP_DIR_ENV]?.trim();
|
|
82
|
+
if (envHint) bases.push(path.resolve(envHint));
|
|
83
|
+
|
|
84
|
+
// 1. This file's location — works when pi-crew and pi-coding-agent share a
|
|
85
|
+
// node_modules ancestor (the common co-located install).
|
|
86
|
+
bases.push(fileURLToPath(import.meta.url));
|
|
87
|
+
|
|
88
|
+
// 2. The entry script. In the PARENT (main pi process) argv[1] is pi's CLI
|
|
89
|
+
// script, which lives INSIDE pi-coding-agent's package → resolves. In a
|
|
90
|
+
// SPAWNED CHILD argv[1] is a pi-crew script → cheap miss, falls through.
|
|
91
|
+
const argv1 = process.argv[1];
|
|
92
|
+
if (argv1) bases.push(path.resolve(argv1));
|
|
93
|
+
|
|
94
|
+
// 3. The Node binary's global node_modules. Covers nvm / nvm-windows /
|
|
95
|
+
// Volta / fnm where pi-coding-agent is `npm i -g`'d: node is at
|
|
96
|
+
// <prefix>/bin/node and globals live at <prefix>/lib/node_modules.
|
|
97
|
+
try {
|
|
98
|
+
const execDir = path.dirname(fs.realpathSync.native(process.execPath));
|
|
99
|
+
bases.push(path.join(path.dirname(execDir), "lib", "node_modules"));
|
|
100
|
+
// Some layouts (Windows global, or a bare node_modules sibling of bin).
|
|
101
|
+
bases.push(path.join(execDir, "node_modules"));
|
|
102
|
+
} catch {
|
|
103
|
+
/* realpath best-effort */
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4. `npm root -g` — the canonical cross-layout global root (memoized in
|
|
107
|
+
// pi-spawn.ts, ~200ms once). Derive the scoped package dirs from it.
|
|
108
|
+
const npmRoot = resolveNpmGlobalRoot();
|
|
109
|
+
if (npmRoot) {
|
|
110
|
+
for (const pkgName of PEER_DEP_NAMES) {
|
|
111
|
+
bases.push(path.join(npmRoot, ...pkgName.split("/")));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 5. Windows %APPDATA%\npm static layout (legacy npm-global, pre-npm-root-g).
|
|
116
|
+
if (process.env.APPDATA) {
|
|
117
|
+
bases.push(path.join(process.env.APPDATA, "npm", "node_modules"));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return bases;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Pull the ESM entry path out of package.json (exports import || main). */
|
|
124
|
+
function extractEsmMain(pkg: unknown): string | undefined {
|
|
125
|
+
if (!pkg || typeof pkg !== "object") return undefined;
|
|
126
|
+
const p = pkg as Record<string, unknown>;
|
|
127
|
+
const exp = p.exports;
|
|
128
|
+
if (exp && typeof exp === "object") {
|
|
129
|
+
const dot = (exp as Record<string, unknown>)["."];
|
|
130
|
+
if (dot && typeof dot === "object") {
|
|
131
|
+
const d = dot as Record<string, unknown>;
|
|
132
|
+
const rel = d.import ?? d.default ?? d.module;
|
|
133
|
+
if (typeof rel === "string") return rel;
|
|
134
|
+
} else if (typeof dot === "string") {
|
|
135
|
+
return dot;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const main = p.main;
|
|
139
|
+
return typeof main === "string" ? main : undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Walk the node_modules resolution algorithm MANUALLY from `start` looking for
|
|
144
|
+
* any of `names`. We do NOT use createRequire/require.resolve here because
|
|
145
|
+
* pi-coding-agent ships an ESM-only package with a restrictive exports map
|
|
146
|
+
* (only the `.` import condition) — `require.resolve("<pkg>/package.json")`
|
|
147
|
+
* and `require.resolve("<pkg>")` both throw ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
148
|
+
* Reading package.json directly from the walked dir sidesteps the exports map
|
|
149
|
+
* entirely (exports only governs subpath IMPORTS, not raw file reads).
|
|
150
|
+
*
|
|
151
|
+
* At each directory we check BOTH `<dir>/node_modules/<pkg>` (the standard
|
|
152
|
+
* container case) AND `<dir>/<pkg>` (handles a base that IS a node_modules
|
|
153
|
+
* dir, e.g. the output of `npm root -g`), then walk up to root.
|
|
154
|
+
*/
|
|
155
|
+
function findPackageDir(start: string, names: readonly string[]): { dir: string; name: string } | undefined {
|
|
156
|
+
let dir = path.resolve(start);
|
|
157
|
+
try {
|
|
158
|
+
if (fs.statSync(dir).isFile()) dir = path.dirname(dir);
|
|
159
|
+
} catch {
|
|
160
|
+
/* treat as directory */
|
|
161
|
+
}
|
|
162
|
+
while (true) {
|
|
163
|
+
for (const name of names) {
|
|
164
|
+
const segs = name.split("/");
|
|
165
|
+
const candidates = [path.join(dir, "node_modules", ...segs, "package.json"), path.join(dir, ...segs, "package.json")];
|
|
166
|
+
for (const pkgJson of candidates) {
|
|
167
|
+
try {
|
|
168
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf-8"));
|
|
169
|
+
if (pkg?.name === name) {
|
|
170
|
+
return { dir: path.dirname(pkgJson), name };
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
/* not present at this candidate */
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const parent = path.dirname(dir);
|
|
178
|
+
if (parent === dir) break; // reached filesystem root
|
|
179
|
+
dir = parent;
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function tryResolveFrom(base: string): ResolvedPeerDep | undefined {
|
|
185
|
+
const found = findPackageDir(base, PEER_DEP_NAMES);
|
|
186
|
+
if (!found) return undefined;
|
|
187
|
+
try {
|
|
188
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(found.dir, "package.json"), "utf-8"));
|
|
189
|
+
const mainRel = extractEsmMain(pkg);
|
|
190
|
+
if (!mainRel) return undefined;
|
|
191
|
+
const mainAbs = path.resolve(found.dir, mainRel);
|
|
192
|
+
if (!fs.existsSync(mainAbs)) return undefined;
|
|
193
|
+
return {
|
|
194
|
+
dir: found.dir,
|
|
195
|
+
name: found.name,
|
|
196
|
+
mainUrl: pathToFileURL(mainAbs).href,
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Resolve the peer dep install dir + ESM entry URL. Memoized (sync). */
|
|
204
|
+
export function resolvePeerDep(): ResolvedPeerDep | undefined {
|
|
205
|
+
if (cachedResolve !== null) return cachedResolve ?? undefined;
|
|
206
|
+
for (const base of peerDepResolutionBases()) {
|
|
207
|
+
const found = tryResolveFrom(base);
|
|
208
|
+
if (found) {
|
|
209
|
+
cachedResolve = found;
|
|
210
|
+
return found;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
cachedResolve = null; // mark attempted-and-failed; don't re-probe per call
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Just the install directory (for env-hint propagation to children). */
|
|
218
|
+
export function resolvePeerDepDir(): string | undefined {
|
|
219
|
+
return resolvePeerDep()?.dir;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Dynamic-import the peer dep module, caching the namespace. Memoized via a
|
|
224
|
+
* shared promise so concurrent callers share one load. On failure the promise
|
|
225
|
+
* is cleared so a later caller can retry. Safe to call repeatedly.
|
|
226
|
+
*/
|
|
227
|
+
export function primePeerDep(): Promise<PeerDepModule> {
|
|
228
|
+
if (cachedModule) return Promise.resolve(cachedModule);
|
|
229
|
+
if (primingPromise) return primingPromise;
|
|
230
|
+
primingPromise = (async () => {
|
|
231
|
+
const resolved = resolvePeerDep();
|
|
232
|
+
if (!resolved) {
|
|
233
|
+
throw new Error(buildMissingMessage());
|
|
234
|
+
}
|
|
235
|
+
// LAZY: defer dynamic import of module to its call site.
|
|
236
|
+
cachedModule = (await import(resolved.mainUrl)) as PeerDepModule;
|
|
237
|
+
return cachedModule;
|
|
238
|
+
})();
|
|
239
|
+
// Clear on failure so a later caller can retry (e.g. after env fix).
|
|
240
|
+
primingPromise.catch(() => {
|
|
241
|
+
primingPromise = undefined;
|
|
242
|
+
});
|
|
243
|
+
return primingPromise;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Async module accessor (primes if needed). */
|
|
247
|
+
export async function loadPeerDep(): Promise<PeerDepModule> {
|
|
248
|
+
return primePeerDep();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function buildMissingMessage(): string {
|
|
252
|
+
return (
|
|
253
|
+
`pi-crew could not resolve the @earendil-works/pi-coding-agent peer dependency.\n` +
|
|
254
|
+
`This usually means pi-crew and pi are installed in separate node_modules trees\n` +
|
|
255
|
+
`(e.g. pi-crew under ~/.pi/agent/npm/ but pi under an nvm/Volta/fnm global scope).\n` +
|
|
256
|
+
`Resolution bases tried:\n` +
|
|
257
|
+
peerDepResolutionBases()
|
|
258
|
+
.map((b) => ` - ${b}`)
|
|
259
|
+
.join("\n") +
|
|
260
|
+
`\nFix: install pi-crew in the SAME scope as pi, e.g.\n` +
|
|
261
|
+
` npm install -g @earendil-works/pi-crew\n` +
|
|
262
|
+
`or set the env var ${PEER_DEP_DIR_ENV}=<path to the pi-coding-agent package dir>.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Read the user agent dir via the REAL peer-dep getAgentDir (fork-aware:
|
|
268
|
+
* correct for pi, tau, and renamed forks). Sync; reads the primed cache.
|
|
269
|
+
*
|
|
270
|
+
* If the cache was never primed (e.g. called before bootstrap completes, or
|
|
271
|
+
* prime failed), falls back to a computed default so it NEVER throws. The
|
|
272
|
+
* default matches standard pi (`~/.pi/agent`) and respects the
|
|
273
|
+
* `PI_CODING_AGENT_DIR` override — correct for the overwhelmingly common
|
|
274
|
+
* case. Forks rely on the primed real function (register.ts primes at startup).
|
|
275
|
+
*/
|
|
276
|
+
export function getAgentDir(): string {
|
|
277
|
+
if (cachedModule?.getAgentDir) {
|
|
278
|
+
try {
|
|
279
|
+
return cachedModule.getAgentDir();
|
|
280
|
+
} catch {
|
|
281
|
+
/* fall through to computed default */
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** @internal — reset all caches for unit tests. */
|
|
288
|
+
export function __resetPeerDepCacheForTest(): void {
|
|
289
|
+
cachedResolve = null;
|
|
290
|
+
cachedModule = undefined;
|
|
291
|
+
primingPromise = undefined;
|
|
292
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-write validator — real-time feedback on file writes/edits (T5).
|
|
3
|
+
*
|
|
4
|
+
* Distilled from pi-lens (apmantza) — the "inline channel": on every
|
|
5
|
+
* `write`/`edit` tool result, run a CHEAP synchronous validator for the file
|
|
6
|
+
* type and, on failure, append a `🔴` blocker block to the tool result the
|
|
7
|
+
* agent sees next. This catches silent-breaking errors (malformed JSON
|
|
8
|
+
* config) at the moment they're introduced instead of at the next load.
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL LATENCY-SAFETY DESIGN (the reason this is a careful slice, not the
|
|
11
|
+
* full pi-lens pipeline): pi-lens runs LSP servers + linters per write. That
|
|
12
|
+
* is expensive and would cause latency storms if naively ported (seconds of
|
|
13
|
+
* spawn per edit, firing in the main session AND every worker). This module's
|
|
14
|
+
* v1 deliberately ships ONLY zero-cost, zero-spawn, synchronous validators:
|
|
15
|
+
*
|
|
16
|
+
* - `json` → `JSON.parse` (nanoseconds, built-in, no process spawn).
|
|
17
|
+
*
|
|
18
|
+
* The registry is extensible — future validators (`.js` → `node --check`,
|
|
19
|
+
* `.sh` → `bash -n`, `.py` → `py_compile`) are process-spawning and MUST be
|
|
20
|
+
* added behind an explicit opt-in (never default-on) to preserve the
|
|
21
|
+
* latency guarantee. A process-spawning validator would also need to be async
|
|
22
|
+
* and debounced (pi-lens's `inFlightPipelines` / debounce-window pattern),
|
|
23
|
+
* which the current sync contract intentionally avoids.
|
|
24
|
+
*
|
|
25
|
+
* Contract guarantees for v1:
|
|
26
|
+
* - Synchronous. No `await`, no `spawn`, no disk write.
|
|
27
|
+
* - One disk READ per validated file (after a cheap extension check, so
|
|
28
|
+
* non-validated files cost nothing).
|
|
29
|
+
* - Dedup by content: the same path+content is validated at most once per
|
|
30
|
+
* process (a repeated identical write doesn't re-report).
|
|
31
|
+
* - Silent on success; appends exactly one TextContent block on failure.
|
|
32
|
+
* - Best-effort: any internal error is swallowed (never breaks a write).
|
|
33
|
+
*
|
|
34
|
+
* @module per-write-validator
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { readFileSync } from "node:fs";
|
|
38
|
+
import { extname as pathExtname } from "node:path";
|
|
39
|
+
|
|
40
|
+
/** Outcome of validating a file's content. */
|
|
41
|
+
export interface ValidationResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
/** Human-readable error message when `ok` is false. */
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A synchronous validator: content + path → result. */
|
|
48
|
+
export type PerWriteValidator = (content: string, filePath: string) => ValidationResult;
|
|
49
|
+
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
51
|
+
// Validators (zero-cost, synchronous, dependency-free for v1)
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/** JSON: parse with `JSON.parse`. Catches malformed config/manifests instantly. */
|
|
55
|
+
export function validateJson(content: string, _filePath: string): ValidationResult {
|
|
56
|
+
if (content.trim() === "") return { ok: true }; // empty file is valid JSON absence, not a parse error
|
|
57
|
+
try {
|
|
58
|
+
JSON.parse(content);
|
|
59
|
+
return { ok: true };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
return { ok: false, error: `Invalid JSON: ${message}` };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Registry of default-on validators, keyed by extension (lowercase, no dot).
|
|
68
|
+
* ONLY zero-cost synchronous validators belong here. Process-spawning
|
|
69
|
+
* validators must be registered via a future opt-in path (see module doc).
|
|
70
|
+
*/
|
|
71
|
+
const DEFAULT_VALIDATORS: ReadonlyMap<string, PerWriteValidator> = new Map([["json", validateJson]]);
|
|
72
|
+
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Dedup cache (path → last-validated content). Bounded; small.
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
const MAX_DEDUP_ENTRIES = 256;
|
|
78
|
+
const seenContent = new Map<string, string>();
|
|
79
|
+
|
|
80
|
+
function rememberSeen(path: string, content: string): void {
|
|
81
|
+
if (seenContent.has(path)) seenContent.delete(path); // refresh LRU position
|
|
82
|
+
seenContent.set(path, content);
|
|
83
|
+
while (seenContent.size > MAX_DEDUP_ENTRIES) {
|
|
84
|
+
const oldest = seenContent.keys().next().value;
|
|
85
|
+
if (oldest === undefined) break;
|
|
86
|
+
seenContent.delete(oldest);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Test seam: reset the dedup cache between tests. */
|
|
91
|
+
export function resetPerWriteValidatorCache(): void {
|
|
92
|
+
seenContent.clear();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Replace the validator registry (test seam). Production uses
|
|
97
|
+
* DEFAULT_VALIDATORS; tests inject a custom map to exercise specific extensions.
|
|
98
|
+
*/
|
|
99
|
+
let validators: ReadonlyMap<string, PerWriteValidator> = DEFAULT_VALIDATORS;
|
|
100
|
+
|
|
101
|
+
export function setPerWriteValidatorsForTest(map: ReadonlyMap<string, PerWriteValidator> | undefined): void {
|
|
102
|
+
validators = map ?? DEFAULT_VALIDATORS;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Normalise an extension to the registry key form (lowercase, no leading dot).
|
|
107
|
+
* "" for files with no extension.
|
|
108
|
+
*/
|
|
109
|
+
export function extensionKey(filePath: string): string {
|
|
110
|
+
return pathExtname(filePath).replace(/^\./, "").toLowerCase();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
114
|
+
// Path extraction from a tool_result event input (defensive — pi-ai types
|
|
115
|
+
// aren't exported here, so accept a record and probe common field names).
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
const PATH_FIELDS = ["filePath", "path", "file"] as const;
|
|
119
|
+
|
|
120
|
+
/** Extract the written/edited path from a tool result input, if present. */
|
|
121
|
+
export function extractPathFromInput(input: unknown): string | undefined {
|
|
122
|
+
if (!input || typeof input !== "object") return undefined;
|
|
123
|
+
const record = input as Record<string, unknown>;
|
|
124
|
+
for (const field of PATH_FIELDS) {
|
|
125
|
+
const value = record[field];
|
|
126
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
132
|
+
// Core entry point
|
|
133
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate a just-written/edited file. Returns `null` when there is nothing
|
|
137
|
+
* to report (no validator for the extension, dedup hit, file unreadable, or
|
|
138
|
+
* the content is valid). Returns a `ValidationResult` with `ok:false` when the
|
|
139
|
+
* content fails validation.
|
|
140
|
+
*
|
|
141
|
+
* Reads the file from disk (it's already written by `tool_result` time) so the
|
|
142
|
+
* logic is uniform across `write` (full content) and `edit` (patch). The disk
|
|
143
|
+
* read happens ONLY after a cheap extension check, so non-validated files cost
|
|
144
|
+
* nothing.
|
|
145
|
+
*/
|
|
146
|
+
export function validateWrittenFile(filePath: string): ValidationResult | null {
|
|
147
|
+
const key = extensionKey(filePath);
|
|
148
|
+
const validator = validators.get(key);
|
|
149
|
+
if (!validator) return null; // cheap skip: no validator for this file type
|
|
150
|
+
let content: string;
|
|
151
|
+
try {
|
|
152
|
+
content = readFileSync(filePath, "utf-8");
|
|
153
|
+
} catch {
|
|
154
|
+
// Unreadable / missing / permission denied — can't validate; never block.
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
// Dedup: identical content already validated this process → don't re-report.
|
|
158
|
+
if (seenContent.get(filePath) === content) return null;
|
|
159
|
+
rememberSeen(filePath, content);
|
|
160
|
+
const result = validator(content, filePath);
|
|
161
|
+
return result.ok ? null : result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build the TextContent block to append to a tool_result on validation failure.
|
|
166
|
+
* Uses a strong `🔴` prefix so the agent treats it as a real signal and fixes
|
|
167
|
+
* the file before continuing.
|
|
168
|
+
*/
|
|
169
|
+
export function buildValidationBlocker(filePath: string, error: string): { type: "text"; text: string } {
|
|
170
|
+
return {
|
|
171
|
+
type: "text",
|
|
172
|
+
text: [
|
|
173
|
+
"",
|
|
174
|
+
"🔴 pi-crew per-write check FAILED",
|
|
175
|
+
` ${filePath}`,
|
|
176
|
+
` ${error}`,
|
|
177
|
+
" The file you just wrote is malformed. Fix it now — a broken file here will",
|
|
178
|
+
" silently fail the next load/parse. Re-write the file with valid content before continuing.",
|
|
179
|
+
].join("\n"),
|
|
180
|
+
};
|
|
181
|
+
}
|