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,11 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
|
|
4
|
+
import { activeRunEntries } from "../state/active-run-registry.ts";
|
|
5
|
+
import type { TeamRunManifest } from "../state/types.ts";
|
|
3
6
|
import { closeWatcher, watchWithErrorHandler } from "../utils/fs-watch.ts";
|
|
4
7
|
import { findRepoRoot, projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
5
|
-
import { activeRunEntries } from "../state/active-run-registry.ts";
|
|
6
8
|
import { isSafePathId, resolveContainedRelativePath, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
7
|
-
import type { TeamRunManifest } from "../state/types.ts";
|
|
8
|
-
import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
|
|
9
9
|
|
|
10
10
|
export interface ManifestCache {
|
|
11
11
|
list(limit?: number): TeamRunManifest[];
|
|
@@ -74,7 +74,14 @@ function validateManifestForRoot(root: string, runId: string, manifest: TeamRunM
|
|
|
74
74
|
const stateRoot = resolveContainedRelativePath(root, runId, "runId");
|
|
75
75
|
const crewRoot = path.dirname(path.dirname(root));
|
|
76
76
|
const artifactsRoot = resolveContainedRelativePath(path.join(crewRoot, DEFAULT_PATHS.state.artifactsSubdir), runId, "runId");
|
|
77
|
-
if (
|
|
77
|
+
if (
|
|
78
|
+
manifest.runId !== runId ||
|
|
79
|
+
!sameFilesystemPath(manifest.stateRoot, stateRoot) ||
|
|
80
|
+
!sameFilesystemPath(manifest.tasksPath, path.join(stateRoot, DEFAULT_PATHS.state.tasksFile)) ||
|
|
81
|
+
!sameFilesystemPath(manifest.eventsPath, path.join(stateRoot, DEFAULT_PATHS.state.eventsFile)) ||
|
|
82
|
+
!sameFilesystemPath(manifest.artifactsRoot, artifactsRoot)
|
|
83
|
+
)
|
|
84
|
+
return false;
|
|
78
85
|
if (fs.existsSync(artifactsRoot)) {
|
|
79
86
|
if (fs.lstatSync(artifactsRoot).isSymbolicLink()) return false;
|
|
80
87
|
resolveRealContainedPath(path.dirname(artifactsRoot), path.basename(artifactsRoot));
|
|
@@ -125,7 +132,10 @@ function collectRoots(root: string): ParsedEntry[] {
|
|
|
125
132
|
}
|
|
126
133
|
return entries
|
|
127
134
|
.filter((entry) => entry.length > 0 && isSafePathId(entry))
|
|
128
|
-
.map((entry) => ({
|
|
135
|
+
.map((entry) => ({
|
|
136
|
+
runId: entry,
|
|
137
|
+
path: manifestPathForRun(root, entry),
|
|
138
|
+
}))
|
|
129
139
|
.filter((entry): entry is ParsedEntry => entry.path !== undefined);
|
|
130
140
|
}
|
|
131
141
|
|
|
@@ -201,7 +211,10 @@ export function createManifestCache(cwd: string, options: ManifestCacheOptions =
|
|
|
201
211
|
}
|
|
202
212
|
const parsedEntries = [
|
|
203
213
|
...roots.flatMap((root) => collectRoots(root)),
|
|
204
|
-
...activeRunEntries().map((entry) => ({
|
|
214
|
+
...activeRunEntries().map((entry) => ({
|
|
215
|
+
runId: entry.runId,
|
|
216
|
+
path: entry.manifestPath,
|
|
217
|
+
})),
|
|
205
218
|
];
|
|
206
219
|
const unique = new Map<string, CachedManifest | undefined>();
|
|
207
220
|
for (const entry of parsedEntries) {
|
|
@@ -216,7 +229,6 @@ export function createManifestCache(cwd: string, options: ManifestCacheOptions =
|
|
|
216
229
|
if (cached) unique.set(entry.runId, cached);
|
|
217
230
|
}
|
|
218
231
|
|
|
219
|
-
|
|
220
232
|
const runs = [...unique.values()].filter((value): value is CachedManifest => value !== undefined).map((value) => value.manifest);
|
|
221
233
|
const sorted = runs.sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""));
|
|
222
234
|
const limited = sorted.slice(0, Math.max(0, limit));
|
|
@@ -238,11 +250,15 @@ export function createManifestCache(cwd: string, options: ManifestCacheOptions =
|
|
|
238
250
|
|
|
239
251
|
if (options.watch ?? true) {
|
|
240
252
|
for (const root of roots) {
|
|
241
|
-
const watcher = watchWithErrorHandler(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
253
|
+
const watcher = watchWithErrorHandler(
|
|
254
|
+
root,
|
|
255
|
+
() => {
|
|
256
|
+
scheduleListRefresh();
|
|
257
|
+
},
|
|
258
|
+
() => {
|
|
259
|
+
scheduleListRefresh();
|
|
260
|
+
},
|
|
261
|
+
);
|
|
246
262
|
if (watcher) {
|
|
247
263
|
watcher.unref();
|
|
248
264
|
watchers.push(watcher);
|
package/src/runtime/mcp-proxy.ts
CHANGED
|
@@ -34,10 +34,7 @@ export interface McpProxyConfig {
|
|
|
34
34
|
* @param options.parentMcpTools — MCP tool names from the parent session (if available)
|
|
35
35
|
* @param options.shareMcp — Whether to share MCP connections (default: true)
|
|
36
36
|
*/
|
|
37
|
-
export function buildMcpProxyConfig(options: {
|
|
38
|
-
parentMcpTools?: string[];
|
|
39
|
-
shareMcp?: boolean;
|
|
40
|
-
}): McpProxyConfig {
|
|
37
|
+
export function buildMcpProxyConfig(options: { parentMcpTools?: string[]; shareMcp?: boolean }): McpProxyConfig {
|
|
41
38
|
if (options.shareMcp === false) {
|
|
42
39
|
return { enableMcp: true, proxyTools: [], proxyToolNames: [] };
|
|
43
40
|
}
|
|
@@ -74,9 +71,7 @@ export function buildMcpProxyConfig(options: {
|
|
|
74
71
|
* In a future iteration, these can be wired to the actual MCP connections
|
|
75
72
|
* via an inter-process bridge.
|
|
76
73
|
*/
|
|
77
|
-
function createMcpProxyTools(
|
|
78
|
-
toolNames: string[],
|
|
79
|
-
): Array<ToolDefinition<TSchema, unknown>> {
|
|
74
|
+
function createMcpProxyTools(toolNames: string[]): Array<ToolDefinition<TSchema, unknown>> {
|
|
80
75
|
// For now, we don't create individual proxy tools because we can't
|
|
81
76
|
// forward MCP calls without the parent's MCP manager reference.
|
|
82
77
|
//
|
|
@@ -93,10 +88,7 @@ function createMcpProxyTools(
|
|
|
93
88
|
*/
|
|
94
89
|
export function discoverMcpToolNames(activeToolNames: string[]): string[] {
|
|
95
90
|
return activeToolNames.filter(
|
|
96
|
-
(name) =>
|
|
97
|
-
name.startsWith("mcp__") ||
|
|
98
|
-
name.startsWith("mcp-") ||
|
|
99
|
-
(name.includes("__") && !name.startsWith("submit_result")),
|
|
91
|
+
(name) => name.startsWith("mcp__") || name.startsWith("mcp-") || (name.includes("__") && !name.startsWith("submit_result")),
|
|
100
92
|
);
|
|
101
93
|
}
|
|
102
94
|
|
|
@@ -104,10 +96,10 @@ export function discoverMcpToolNames(activeToolNames: string[]): string[] {
|
|
|
104
96
|
* Build MCP proxy config from a real Pi SDK session's active tools.
|
|
105
97
|
* This is the preferred way — inspect what the parent session has available.
|
|
106
98
|
*/
|
|
107
|
-
export function buildMcpProxyFromSession(
|
|
108
|
-
activeToolNames: string[],
|
|
109
|
-
options?: { shareMcp?: boolean },
|
|
110
|
-
): McpProxyConfig {
|
|
99
|
+
export function buildMcpProxyFromSession(activeToolNames: string[], options?: { shareMcp?: boolean }): McpProxyConfig {
|
|
111
100
|
const mcpTools = discoverMcpToolNames(activeToolNames);
|
|
112
|
-
return buildMcpProxyConfig({
|
|
101
|
+
return buildMcpProxyConfig({
|
|
102
|
+
parentMcpTools: mcpTools,
|
|
103
|
+
shareMcp: options?.shareMcp,
|
|
104
|
+
});
|
|
113
105
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Metric names that are denied to prevent prototype pollution.
|
|
3
3
|
*/
|
|
4
|
-
export const DENIED_METRIC_NAMES: ReadonlySet<string> = new Set([
|
|
5
|
-
"__proto__",
|
|
6
|
-
"constructor",
|
|
7
|
-
"prototype",
|
|
8
|
-
]);
|
|
4
|
+
export const DENIED_METRIC_NAMES: ReadonlySet<string> = new Set(["__proto__", "constructor", "prototype"]);
|
|
9
5
|
|
|
10
6
|
const METRIC_LINE_RE = /^CREW_METRIC\s+(\w+)=(\S+)$/;
|
|
11
7
|
|
|
@@ -2,8 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { errors } from "../errors.ts";
|
|
5
|
-
import { checkModelScope } from "./model-scope.ts";
|
|
6
5
|
import { fuzzyResolveModelId } from "./model-resolver.ts";
|
|
6
|
+
import { checkModelScope } from "./model-scope.ts";
|
|
7
7
|
|
|
8
8
|
export interface AvailableModelInfo {
|
|
9
9
|
provider: string;
|
|
@@ -46,13 +46,22 @@ function modelInfoFromUnknown(value: unknown): AvailableModelInfo | undefined {
|
|
|
46
46
|
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
47
47
|
const record = value as ModelLike;
|
|
48
48
|
if (typeof record.provider !== "string" || typeof record.id !== "string") return undefined;
|
|
49
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
provider: record.provider,
|
|
51
|
+
id: record.id,
|
|
52
|
+
fullId: `${record.provider}/${record.id}`,
|
|
53
|
+
};
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
export function availableModelInfosFromRegistry(registry: unknown): AvailableModelInfo[] | undefined {
|
|
53
57
|
if (!registry || typeof registry !== "object" || Array.isArray(registry)) return undefined;
|
|
54
58
|
const candidate = registry as ModelRegistryLike;
|
|
55
|
-
const raw =
|
|
59
|
+
const raw =
|
|
60
|
+
typeof candidate.getAvailable === "function"
|
|
61
|
+
? candidate.getAvailable()
|
|
62
|
+
: typeof candidate.getAll === "function"
|
|
63
|
+
? candidate.getAll()
|
|
64
|
+
: undefined;
|
|
56
65
|
if (!Array.isArray(raw)) return undefined;
|
|
57
66
|
return raw.map(modelInfoFromUnknown).filter((entry): entry is AvailableModelInfo => entry !== undefined);
|
|
58
67
|
}
|
|
@@ -74,7 +83,7 @@ function readJsonObject(filePath: string): Record<string, unknown> | undefined {
|
|
|
74
83
|
try {
|
|
75
84
|
if (!fs.existsSync(filePath)) return undefined;
|
|
76
85
|
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
77
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : undefined;
|
|
86
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : undefined;
|
|
78
87
|
} catch {
|
|
79
88
|
return undefined;
|
|
80
89
|
}
|
|
@@ -92,7 +101,11 @@ function piAgentDir(): string {
|
|
|
92
101
|
|
|
93
102
|
function settingsModelInfo(settings: PiSettingsLike | undefined): AvailableModelInfo | undefined {
|
|
94
103
|
if (typeof settings?.defaultProvider !== "string" || typeof settings.defaultModel !== "string") return undefined;
|
|
95
|
-
return {
|
|
104
|
+
return {
|
|
105
|
+
provider: settings.defaultProvider,
|
|
106
|
+
id: settings.defaultModel,
|
|
107
|
+
fullId: `${settings.defaultProvider}/${settings.defaultModel}`,
|
|
108
|
+
};
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
function modelsJsonInfos(modelsJson: PiModelsJsonLike | undefined): AvailableModelInfo[] {
|
|
@@ -118,8 +131,11 @@ function modelsJsonInfos(modelsJson: PiModelsJsonLike | undefined): AvailableMod
|
|
|
118
131
|
export function configuredModelInfosFromPiConfig(cwd?: string): AvailableModelInfo[] {
|
|
119
132
|
const agentDir = piAgentDir();
|
|
120
133
|
const globalSettings = readJsonObject(path.join(agentDir, "settings.json")) as PiSettingsLike | undefined;
|
|
121
|
-
const projectSettings = cwd ? readJsonObject(path.join(cwd, ".pi", "settings.json")) as PiSettingsLike | undefined : undefined;
|
|
122
|
-
const effectiveSettings = {
|
|
134
|
+
const projectSettings = cwd ? (readJsonObject(path.join(cwd, ".pi", "settings.json")) as PiSettingsLike | undefined) : undefined;
|
|
135
|
+
const effectiveSettings = {
|
|
136
|
+
...(globalSettings ?? {}),
|
|
137
|
+
...(projectSettings ?? {}),
|
|
138
|
+
};
|
|
123
139
|
const defaultModel = settingsModelInfo(effectiveSettings);
|
|
124
140
|
return uniqueModelInfos([
|
|
125
141
|
...(defaultModel ? [defaultModel] : []),
|
|
@@ -127,7 +143,10 @@ export function configuredModelInfosFromPiConfig(cwd?: string): AvailableModelIn
|
|
|
127
143
|
]);
|
|
128
144
|
}
|
|
129
145
|
|
|
130
|
-
export function splitThinkingSuffix(model: string): {
|
|
146
|
+
export function splitThinkingSuffix(model: string): {
|
|
147
|
+
baseModel: string;
|
|
148
|
+
thinkingSuffix: string;
|
|
149
|
+
} {
|
|
131
150
|
const colonIdx = model.lastIndexOf(":");
|
|
132
151
|
if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
|
|
133
152
|
return {
|
|
@@ -251,7 +270,9 @@ export function isRetryableModelFailure(error: string | undefined): boolean {
|
|
|
251
270
|
|
|
252
271
|
export function formatModelAttemptNote(attempt: ModelAttemptSummary, nextModel?: string): string {
|
|
253
272
|
const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
|
|
254
|
-
return nextModel
|
|
273
|
+
return nextModel
|
|
274
|
+
? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.`
|
|
275
|
+
: `[fallback] ${attempt.model} failed: ${failure}.`;
|
|
255
276
|
}
|
|
256
277
|
|
|
257
278
|
export function buildModelCandidates(
|
|
@@ -321,16 +342,31 @@ export function buildConfiguredModelRouting(input: {
|
|
|
321
342
|
}): ConfiguredModelRouting {
|
|
322
343
|
const registryModels = availableModelInfosFromRegistry(input.modelRegistry);
|
|
323
344
|
const configModels = configuredModelInfosFromPiConfig(input.cwd);
|
|
324
|
-
const availableModels =
|
|
345
|
+
const availableModels =
|
|
346
|
+
registryModels && registryModels.length > 0 ? registryModels : configModels.length > 0 ? configModels : registryModels;
|
|
325
347
|
const parentModel = modelStringFromUnknown(input.parentModel);
|
|
326
348
|
const preferredProvider = parentModel?.split("/")[0] ?? availableModels?.[0]?.provider;
|
|
327
349
|
// B3: Parent model inheritance — when agent has no model specified,
|
|
328
350
|
// inherit from parent session model before falling back to defaults.
|
|
329
351
|
const effectiveAgentModel = input.agentModel?.trim() ? input.agentModel : parentModel;
|
|
330
|
-
const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find((model): model is string =>
|
|
331
|
-
|
|
352
|
+
const requested = [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel].find((model): model is string =>
|
|
353
|
+
Boolean(model?.trim()),
|
|
354
|
+
);
|
|
355
|
+
if (availableModels && availableModels.length === 0)
|
|
356
|
+
return {
|
|
357
|
+
requested,
|
|
358
|
+
candidates: [],
|
|
359
|
+
reason: "no configured Pi models available",
|
|
360
|
+
};
|
|
332
361
|
const rawModels = availableModels
|
|
333
|
-
? [
|
|
362
|
+
? [
|
|
363
|
+
input.overrideModel,
|
|
364
|
+
input.stepModel,
|
|
365
|
+
input.teamRoleModel,
|
|
366
|
+
effectiveAgentModel,
|
|
367
|
+
...(input.fallbackModels ?? []),
|
|
368
|
+
...availableModels.map((model) => model.fullId),
|
|
369
|
+
]
|
|
334
370
|
: [input.overrideModel, input.stepModel, input.teamRoleModel, effectiveAgentModel, ...(input.fallbackModels ?? []), parentModel];
|
|
335
371
|
// Fix (Round 18): when an agent has `model: false` (frontmatter) the
|
|
336
372
|
// inherited `parentModel` (= session chính's model, e.g. minimax-M3) IS the
|
|
@@ -345,11 +381,12 @@ export function buildConfiguredModelRouting(input: {
|
|
|
345
381
|
return isAvailableModel(model.trim(), availableModels);
|
|
346
382
|
});
|
|
347
383
|
const candidates = buildModelCandidates(configuredModels[0], configuredModels.slice(1), availableModels, preferredProvider);
|
|
348
|
-
const reason =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
384
|
+
const reason =
|
|
385
|
+
requested && candidates[0] && resolveModelCandidate(requested, availableModels, preferredProvider) !== candidates[0]
|
|
386
|
+
? "requested model unavailable; selected configured Pi fallback"
|
|
387
|
+
: candidates.length > 1
|
|
388
|
+
? "configured Pi fallback chain"
|
|
389
|
+
: undefined;
|
|
353
390
|
// F7 scope gate: when `scopeModelsPatterns` is configured, check the
|
|
354
391
|
// resolved model. Caller-supplied (override/step/team role) out-of-scope
|
|
355
392
|
// is a HARD ERROR (we surface it via the verdict AND throw, so spawn aborts
|
|
@@ -60,9 +60,7 @@ export function resolveModel(input: string, registry: ModelRegistry): Model | st
|
|
|
60
60
|
} else if (name.includes(query)) {
|
|
61
61
|
score = 40 + (query.length / name.length) * 20;
|
|
62
62
|
} else if (
|
|
63
|
-
query
|
|
64
|
-
.split(/[\s\-/]+/)
|
|
65
|
-
.every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
|
|
63
|
+
query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
|
|
66
64
|
) {
|
|
67
65
|
score = 20;
|
|
68
66
|
}
|
|
@@ -113,9 +111,7 @@ export function fuzzyResolveModelId(input: string, models: SimpleModelEntry[]):
|
|
|
113
111
|
} else if (name.includes(query)) {
|
|
114
112
|
score = 40 + (query.length / (name.length || 1)) * 20;
|
|
115
113
|
} else if (
|
|
116
|
-
query
|
|
117
|
-
.split(/[\s\-/]+/)
|
|
118
|
-
.every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
|
|
114
|
+
query.split(/[\s\-/]+/).every((part) => id.includes(part) || name.includes(part) || m.provider.toLowerCase().includes(part))
|
|
119
115
|
) {
|
|
120
116
|
score = 20;
|
|
121
117
|
}
|
|
@@ -92,7 +92,12 @@ export function checkModelScope(
|
|
|
92
92
|
source: ModelScopeSource,
|
|
93
93
|
): ModelScopeCheck {
|
|
94
94
|
if (!modelId) {
|
|
95
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
inScope: true,
|
|
97
|
+
source,
|
|
98
|
+
model: "",
|
|
99
|
+
reason: "no model specified",
|
|
100
|
+
};
|
|
96
101
|
}
|
|
97
102
|
if (!patterns || patterns.length === 0) {
|
|
98
103
|
// No allowlist → not enforcing. The toggle is opt-in; the user hasn't
|
|
@@ -101,7 +106,12 @@ export function checkModelScope(
|
|
|
101
106
|
}
|
|
102
107
|
for (const pattern of patterns) {
|
|
103
108
|
if (matchesModelPattern(modelId, pattern)) {
|
|
104
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
inScope: true,
|
|
111
|
+
source,
|
|
112
|
+
model: modelId,
|
|
113
|
+
matchedPattern: pattern,
|
|
114
|
+
};
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
return {
|
|
@@ -131,7 +141,13 @@ export async function readEnabledModelsPatterns(cwd: string, agentDir?: string):
|
|
|
131
141
|
// LAZY: defer dynamic import of @earendil-works/pi-coding-agent to its call site.
|
|
132
142
|
const mod = await import("@earendil-works/pi-coding-agent" as string).catch(() => null);
|
|
133
143
|
if (!mod) return [];
|
|
134
|
-
const SettingsManagerCtor = (
|
|
144
|
+
const SettingsManagerCtor = (
|
|
145
|
+
mod as {
|
|
146
|
+
SettingsManager?: {
|
|
147
|
+
create?: (cwd: string, agentDir?: string) => { getEnabledModels?: () => string[] | undefined };
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
).SettingsManager;
|
|
135
151
|
if (!SettingsManagerCtor?.create) return [];
|
|
136
152
|
const sm = SettingsManagerCtor.create(cwd, agentDir);
|
|
137
153
|
const patterns = sm.getEnabledModels?.();
|
|
@@ -1,90 +1,88 @@
|
|
|
1
1
|
export interface NotebookCell {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
index: number;
|
|
3
|
+
cellType: "code" | "markdown" | "raw";
|
|
4
|
+
source: string;
|
|
5
|
+
outputs?: unknown[];
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface Notebook {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
cells: NotebookCell[];
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
nbformat: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** Check if a file path is a notebook */
|
|
16
16
|
export function isNotebookPath(filePath: string): boolean {
|
|
17
|
-
|
|
17
|
+
return filePath.toLowerCase().endsWith(".ipynb");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function normalizeSource(source: unknown): string {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
if (typeof source === "string") return source;
|
|
22
|
+
if (Array.isArray(source)) return source.join("");
|
|
23
|
+
return "";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Parse a .ipynb JSON file into a Notebook structure */
|
|
27
27
|
export function parseNotebook(content: string): Notebook {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
28
|
+
let raw: unknown;
|
|
29
|
+
try {
|
|
30
|
+
raw = JSON.parse(content);
|
|
31
|
+
} catch {
|
|
32
|
+
return { cells: [], nbformat: 4 };
|
|
33
|
+
}
|
|
34
|
+
if (typeof raw !== "object" || raw === null) return { cells: [], nbformat: 4 };
|
|
35
|
+
const obj = raw as Record<string, unknown>;
|
|
36
|
+
const nbformat = typeof obj.nbformat === "number" ? obj.nbformat : 4;
|
|
37
|
+
const rawCells = Array.isArray(obj.cells) ? obj.cells : [];
|
|
38
|
+
const cells: NotebookCell[] = rawCells
|
|
39
|
+
.map((c: unknown, i: number): NotebookCell | null => {
|
|
40
|
+
if (typeof c !== "object" || c === null) return null;
|
|
41
|
+
const cell = c as Record<string, unknown>;
|
|
42
|
+
const cellType = cell.cell_type;
|
|
43
|
+
if (cellType !== "code" && cellType !== "markdown" && cellType !== "raw") return null;
|
|
44
|
+
return {
|
|
45
|
+
index: i,
|
|
46
|
+
cellType,
|
|
47
|
+
source: normalizeSource(cell.source),
|
|
48
|
+
outputs: Array.isArray(cell.outputs) ? cell.outputs : undefined,
|
|
49
|
+
metadata:
|
|
50
|
+
cell.metadata && typeof cell.metadata === "object" && !Array.isArray(cell.metadata)
|
|
51
|
+
? (cell.metadata as Record<string, unknown>)
|
|
52
|
+
: undefined,
|
|
53
|
+
};
|
|
54
|
+
})
|
|
55
|
+
.filter((c: NotebookCell | null): c is NotebookCell => c !== null);
|
|
56
|
+
const metadata =
|
|
57
|
+
obj.metadata && typeof obj.metadata === "object" && !Array.isArray(obj.metadata)
|
|
58
|
+
? (obj.metadata as Record<string, unknown>)
|
|
59
|
+
: undefined;
|
|
60
|
+
return { cells, metadata, nbformat };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/** Get a specific cell by index */
|
|
64
64
|
export function getCell(notebook: Notebook, index: number): NotebookCell | undefined {
|
|
65
|
-
|
|
65
|
+
return notebook.cells.find((c) => c.index === index);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/** Update a cell's source content, returning a new Notebook */
|
|
69
69
|
export function updateCell(notebook: Notebook, index: number, source: string): Notebook {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
return { ...notebook, cells };
|
|
70
|
+
const cells = notebook.cells.map((c) => (c.index === index ? { ...c, source } : c));
|
|
71
|
+
return { ...notebook, cells };
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
/** Serialize a Notebook back to .ipynb JSON string */
|
|
77
75
|
export function serializeNotebook(notebook: Notebook): string {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
const raw = {
|
|
77
|
+
nbformat: notebook.nbformat,
|
|
78
|
+
nbformat_minor: 5,
|
|
79
|
+
metadata: notebook.metadata ?? {},
|
|
80
|
+
cells: notebook.cells.map((c) => ({
|
|
81
|
+
cell_type: c.cellType,
|
|
82
|
+
source: c.source,
|
|
83
|
+
metadata: c.metadata ?? {},
|
|
84
|
+
...(c.cellType === "code" ? { outputs: c.outputs ?? [], execution_count: null } : {}),
|
|
85
|
+
})),
|
|
86
|
+
};
|
|
87
|
+
return JSON.stringify(raw, null, 2) + "\n";
|
|
90
88
|
}
|
|
@@ -21,13 +21,14 @@
|
|
|
21
21
|
* cleanupOrphanWorkers) are protected by file locking to prevent concurrent
|
|
22
22
|
* writes from causing lost updates.
|
|
23
23
|
*/
|
|
24
|
+
|
|
25
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
24
26
|
import * as fs from "node:fs";
|
|
25
27
|
import * as path from "node:path";
|
|
26
|
-
import { execSync, execFileSync } from "node:child_process";
|
|
27
|
-
import { userPiRoot } from "../utils/paths.ts";
|
|
28
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
29
|
-
import { withFileLockSync } from "../state/locks.ts";
|
|
30
28
|
import { isSymlinkSafePath } from "../state/atomic-write.ts";
|
|
29
|
+
import { withFileLockSync } from "../state/locks.ts";
|
|
30
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
31
|
+
import { userPiRoot } from "../utils/paths.ts";
|
|
31
32
|
|
|
32
33
|
const STALE_REGISTRATION_MS = 60 * 60 * 1000; // 1 hour
|
|
33
34
|
// Grace period before a fresh worker can be cleaned up when parent is dead.
|
|
@@ -95,7 +96,10 @@ function getProcessStartTimeLinux(pid: number): number | undefined {
|
|
|
95
96
|
// utime(12) stime(13) cutime(14) cstime(15) priority(16) nice(17)
|
|
96
97
|
// num_threads(18) itrealvalue(19) starttime(20) vsize(21) rss(22)
|
|
97
98
|
// ...but we only need starttime which is field 22 (index 21 after comm)
|
|
98
|
-
const fieldsAfterComm = stat
|
|
99
|
+
const fieldsAfterComm = stat
|
|
100
|
+
.slice(lastParen + 1)
|
|
101
|
+
.trim()
|
|
102
|
+
.split(/\s+/);
|
|
99
103
|
// starttime is at index 19 (the 20th field after comm)
|
|
100
104
|
const startTimeClockTicks = Number(fieldsAfterComm[19]);
|
|
101
105
|
if (!Number.isFinite(startTimeClockTicks)) return undefined;
|
|
@@ -139,11 +143,10 @@ function getProcessStartTimeWindows(pid: number): number | undefined {
|
|
|
139
143
|
// For Node.js without native modules, use tasklist /v and parse output
|
|
140
144
|
try {
|
|
141
145
|
// /v verbose, /fo csv, /nh no header
|
|
142
|
-
const output = execFileSync(
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
).trim();
|
|
146
|
+
const output = execFileSync("powershell", ["-Command", `Get-Process -Id ${pid} | Select-Object -ExpandProperty StartTime`], {
|
|
147
|
+
encoding: "utf-8",
|
|
148
|
+
timeout: 5000,
|
|
149
|
+
}).trim();
|
|
147
150
|
if (!output) return undefined;
|
|
148
151
|
const date = new Date(output);
|
|
149
152
|
if (Number.isNaN(date.getTime())) return undefined;
|
|
@@ -153,7 +156,6 @@ function getProcessStartTimeWindows(pid: number): number | undefined {
|
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
|
|
156
|
-
|
|
157
159
|
let REGISTRY_PATH = path.join(userPiRoot(), "state", "orphan-workers.json");
|
|
158
160
|
|
|
159
161
|
/** @internal Test-only: override the registry path. */
|
|
@@ -213,7 +215,11 @@ function writeRegistry(entries: OrphanWorkerEntry[]): void {
|
|
|
213
215
|
// writing to disk.
|
|
214
216
|
for (const entry of entries) {
|
|
215
217
|
if (!isValidId(entry.sessionId) || !isValidId(entry.runId)) {
|
|
216
|
-
logInternalError(
|
|
218
|
+
logInternalError(
|
|
219
|
+
"orphan-worker-registry.write",
|
|
220
|
+
new Error("Refusing to write: invalid sessionId or runId"),
|
|
221
|
+
`sessionId=${entry.sessionId} runId=${entry.runId}`,
|
|
222
|
+
);
|
|
217
223
|
return;
|
|
218
224
|
}
|
|
219
225
|
}
|
|
@@ -224,12 +230,20 @@ function writeRegistry(entries: OrphanWorkerEntry[]): void {
|
|
|
224
230
|
// isSymlinkSafePath walks the ancestor chain to detect any symlinks,
|
|
225
231
|
// preventing attacks where an intermediate ancestor is a symlink.
|
|
226
232
|
if (!isSymlinkSafePath(p)) {
|
|
227
|
-
logInternalError(
|
|
233
|
+
logInternalError(
|
|
234
|
+
"orphan-worker-registry.write",
|
|
235
|
+
new Error("Refusing to write: target is a symlink or inside untrusted directory"),
|
|
236
|
+
`path=${p}`,
|
|
237
|
+
);
|
|
228
238
|
return;
|
|
229
239
|
}
|
|
230
240
|
// Issue 2 fix: Check parent directory safety immediately before creating it.
|
|
231
241
|
if (!isSymlinkSafePath(dir)) {
|
|
232
|
-
logInternalError(
|
|
242
|
+
logInternalError(
|
|
243
|
+
"orphan-worker-registry.write",
|
|
244
|
+
new Error("Refusing to create: parent directory is a symlink or inside untrusted directory"),
|
|
245
|
+
`dir=${dir}`,
|
|
246
|
+
);
|
|
233
247
|
return;
|
|
234
248
|
}
|
|
235
249
|
// Ensure parent directory exists to serialize directory
|
|
@@ -240,11 +254,7 @@ function writeRegistry(entries: OrphanWorkerEntry[]): void {
|
|
|
240
254
|
try {
|
|
241
255
|
fs.writeFileSync(p, JSON.stringify(entries, null, 2), { mode: 0o600 });
|
|
242
256
|
} catch (error) {
|
|
243
|
-
logInternalError(
|
|
244
|
-
"orphan-worker-registry.write",
|
|
245
|
-
error,
|
|
246
|
-
`path=${p} entries=${entries.length}`,
|
|
247
|
-
);
|
|
257
|
+
logInternalError("orphan-worker-registry.write", error, `path=${p} entries=${entries.length}`);
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
|
|
@@ -266,9 +276,7 @@ export function registerWorker(
|
|
|
266
276
|
// Issue 3 fix: Validate sessionId and runId to prevent path traversal attacks.
|
|
267
277
|
if (!isValidId(sessionId) || !isValidId(runId)) return;
|
|
268
278
|
const startTime = getProcessStartTime(pid) ?? 0;
|
|
269
|
-
const parentPidStartTime = Number.isFinite(parentPid) && parentPid > 0
|
|
270
|
-
? (getProcessStartTime(parentPid) ?? 0)
|
|
271
|
-
: 0;
|
|
279
|
+
const parentPidStartTime = Number.isFinite(parentPid) && parentPid > 0 ? (getProcessStartTime(parentPid) ?? 0) : 0;
|
|
272
280
|
withFileLockSync(getRegistryPath(), () => {
|
|
273
281
|
const entries = readRegistry();
|
|
274
282
|
// Dedupe by PID
|
|
@@ -322,10 +330,13 @@ export interface CleanupOrphanWorkersResult {
|
|
|
322
330
|
* ALWAYS kept regardless of age. This protects concurrent sessions.
|
|
323
331
|
* Pass undefined for unconditional cleanup (e.g. from `pi-crew cleanup`).
|
|
324
332
|
*/
|
|
325
|
-
export function cleanupOrphanWorkers(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
333
|
+
export function cleanupOrphanWorkers(currentSessionId?: string): CleanupOrphanWorkersResult {
|
|
334
|
+
let result: CleanupOrphanWorkersResult = {
|
|
335
|
+
scanned: 0,
|
|
336
|
+
killed: 0,
|
|
337
|
+
pruned: 0,
|
|
338
|
+
kept: 0,
|
|
339
|
+
};
|
|
329
340
|
withFileLockSync(getRegistryPath(), () => {
|
|
330
341
|
const entries = readRegistry();
|
|
331
342
|
const now = Date.now();
|