pi-crew 0.9.15 → 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 +145 -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 +193 -40
- 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 +86 -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
package/src/utils/redaction.ts
CHANGED
|
@@ -120,7 +120,7 @@ export function isSecretKey(keyName: string): boolean {
|
|
|
120
120
|
// Boundary chars that may precede an "authorization:" or "Bearer " keyword.
|
|
121
121
|
// Includes '-' so prefixed headers (Proxy-Authorization, X-Authorization) and
|
|
122
122
|
// '\t' so tab-indented headers are recognized. See security review L5.
|
|
123
|
-
const AUTH_HEADER_BOUNDARY_CHARS = new Set([" ", ",", "{", "[", "
|
|
123
|
+
const AUTH_HEADER_BOUNDARY_CHARS = new Set([" ", ",", "{", "[", '"', "\r", "\n", "-", "\t"]);
|
|
124
124
|
function isAuthHeaderBoundary(ch: string | undefined): boolean {
|
|
125
125
|
return ch !== undefined && AUTH_HEADER_BOUNDARY_CHARS.has(ch);
|
|
126
126
|
}
|
|
@@ -133,8 +133,8 @@ function isAuthHeaderBoundary(ch: string | undefined): boolean {
|
|
|
133
133
|
export function redactAuthHeader(line: string): string {
|
|
134
134
|
const lower = line.toLowerCase();
|
|
135
135
|
let result = "";
|
|
136
|
-
let i = 0;
|
|
137
|
-
let searchFrom = 0;
|
|
136
|
+
let i = 0; // emit cursor into the original `line`
|
|
137
|
+
let searchFrom = 0; // cursor for the next indexOf scan
|
|
138
138
|
for (;;) {
|
|
139
139
|
const authIdx = lower.indexOf("authorization:", searchFrom);
|
|
140
140
|
if (authIdx === -1) {
|
|
@@ -172,7 +172,7 @@ export function redactBearerTokens(line: string): string {
|
|
|
172
172
|
const upper = line.toUpperCase();
|
|
173
173
|
const result: string[] = [];
|
|
174
174
|
let i = 0;
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
while (i < line.length) {
|
|
177
177
|
if (upper.startsWith("BEARER ", i)) {
|
|
178
178
|
// Check word boundary: start-of-string or a boundary char. Includes '-'
|
|
@@ -182,7 +182,7 @@ export function redactBearerTokens(line: string): string {
|
|
|
182
182
|
i++;
|
|
183
183
|
continue;
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
// Found "Bearer " - now find the token
|
|
187
187
|
const bearerPrefix = line.substring(i, i + 7); // "Bearer "
|
|
188
188
|
let j = i + 7;
|
|
@@ -191,7 +191,7 @@ export function redactBearerTokens(line: string): string {
|
|
|
191
191
|
j++;
|
|
192
192
|
tokenLen++;
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
if (tokenLen >= 8) {
|
|
196
196
|
// Replace with Bearer + *** (redact the token)
|
|
197
197
|
result.push(bearerPrefix + "***");
|
|
@@ -202,29 +202,30 @@ export function redactBearerTokens(line: string): string {
|
|
|
202
202
|
result.push(line[i]);
|
|
203
203
|
i++;
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
return result.join("");
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
210
210
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
211
|
-
if (value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Map || value instanceof Set)
|
|
211
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Map || value instanceof Set)
|
|
212
|
+
return false;
|
|
212
213
|
return true;
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
export function redactSecretString(value: string): string {
|
|
216
217
|
let result = value;
|
|
217
|
-
|
|
218
|
+
|
|
218
219
|
// Replace PEM private keys
|
|
219
220
|
result = result.replace(PEM_PRIVATE_KEY_PATTERN, "***");
|
|
220
|
-
|
|
221
|
+
|
|
221
222
|
// Replace Authorization headers (non-Bearer format)
|
|
222
223
|
result = redactAuthHeader(result);
|
|
223
|
-
|
|
224
|
+
|
|
224
225
|
// Replace Bearer tokens (run before structured-token patterns so a
|
|
225
226
|
// "Bearer <jwt>" pair is collapsed first; bare tokens are caught below).
|
|
226
227
|
result = redactBearerTokens(result);
|
|
227
|
-
|
|
228
|
+
|
|
228
229
|
// P1f: structured secret tokens (JWT / GitHub PAT / AWS keys + optional
|
|
229
230
|
// Slack/Google/Stripe). Best-effort vs adversarial workers (see note above).
|
|
230
231
|
result = result
|
|
@@ -234,10 +235,10 @@ export function redactSecretString(value: string): string {
|
|
|
234
235
|
.replace(SLACK_TOKEN_PATTERN, "***")
|
|
235
236
|
.replace(GOOGLE_API_KEY_PATTERN, "***")
|
|
236
237
|
.replace(STRIPE_KEY_PATTERN, "***");
|
|
237
|
-
|
|
238
|
+
|
|
238
239
|
// Replace inline secrets: key=value or key:value patterns
|
|
239
240
|
result = redactInlineSecrets(result);
|
|
240
|
-
|
|
241
|
+
|
|
241
242
|
return result;
|
|
242
243
|
}
|
|
243
244
|
|
|
@@ -254,8 +255,8 @@ export function redactSecretString(value: string): string {
|
|
|
254
255
|
function isKeyChar(c: string): boolean {
|
|
255
256
|
const code = c.charCodeAt(0);
|
|
256
257
|
return (
|
|
257
|
-
(code >= 48 && code <= 57) ||
|
|
258
|
-
(code >= 65 && code <= 90) ||
|
|
258
|
+
(code >= 48 && code <= 57) || // 0-9
|
|
259
|
+
(code >= 65 && code <= 90) || // A-Z
|
|
259
260
|
(code >= 97 && code <= 122) || // a-z
|
|
260
261
|
code === 95 || // _
|
|
261
262
|
code === 45 // -
|
|
@@ -275,7 +276,7 @@ function redactInlineSecrets(value: string): string {
|
|
|
275
276
|
const keyLen = j - i;
|
|
276
277
|
|
|
277
278
|
let redacted = false;
|
|
278
|
-
if (keyLen > 0 && j < value.length && (value[j] ===
|
|
279
|
+
if (keyLen > 0 && j < value.length && (value[j] === "=" || value[j] === ":")) {
|
|
279
280
|
const key = value.substring(i, j);
|
|
280
281
|
|
|
281
282
|
// Check if this is a secret key
|
|
@@ -284,7 +285,16 @@ function redactInlineSecrets(value: string): string {
|
|
|
284
285
|
const sep = value[j];
|
|
285
286
|
let k = j + 1;
|
|
286
287
|
let valLen = 0;
|
|
287
|
-
while (
|
|
288
|
+
while (
|
|
289
|
+
k < value.length &&
|
|
290
|
+
valLen < 500 &&
|
|
291
|
+
value[k] !== " " &&
|
|
292
|
+
value[k] !== "," &&
|
|
293
|
+
value[k] !== ";" &&
|
|
294
|
+
value[k] !== '"' &&
|
|
295
|
+
value[k] !== "\r" &&
|
|
296
|
+
value[k] !== "\n"
|
|
297
|
+
) {
|
|
288
298
|
k++;
|
|
289
299
|
valLen++;
|
|
290
300
|
}
|
|
@@ -334,4 +344,4 @@ export function redactJsonLine(line: string): string {
|
|
|
334
344
|
} catch {
|
|
335
345
|
return redactSecretString(line);
|
|
336
346
|
}
|
|
337
|
-
}
|
|
347
|
+
}
|
|
@@ -5,11 +5,7 @@ import * as fs from "node:fs";
|
|
|
5
5
|
*/
|
|
6
6
|
export function resolveBashCmd(): string {
|
|
7
7
|
if (process.platform !== "win32") return "bash";
|
|
8
|
-
const candidates = [
|
|
9
|
-
process.env.SHELL,
|
|
10
|
-
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
11
|
-
"C:\\Program Files (x86)\\Git\\bin\\bash.exe",
|
|
12
|
-
];
|
|
8
|
+
const candidates = [process.env.SHELL, "C:\\Program Files\\Git\\bin\\bash.exe", "C:\\Program Files (x86)\\Git\\bin\\bash.exe"];
|
|
13
9
|
for (const cand of candidates) {
|
|
14
10
|
if (cand && fs.existsSync(cand)) return cand;
|
|
15
11
|
}
|
|
@@ -20,14 +16,20 @@ export function resolveBashCmd(): string {
|
|
|
20
16
|
* Choose the right shell command and arguments for a script path.
|
|
21
17
|
* On Windows, powershell (.ps1) and batch (.cmd/.bat) run natively.
|
|
22
18
|
*/
|
|
23
|
-
export function resolveShellForScript(scriptPath: string): {
|
|
19
|
+
export function resolveShellForScript(scriptPath: string): {
|
|
20
|
+
command: string;
|
|
21
|
+
args: string[];
|
|
22
|
+
} {
|
|
24
23
|
if (process.platform === "win32") {
|
|
25
24
|
if (scriptPath.endsWith(".ps1")) {
|
|
26
25
|
return { command: "powershell", args: ["-File", scriptPath] };
|
|
27
26
|
}
|
|
28
27
|
if (scriptPath.endsWith(".cmd") || scriptPath.endsWith(".bat")) {
|
|
29
28
|
// Node >= 20 blocks direct spawn of .bat/.cmd without shell (CVE-2024-27980)
|
|
30
|
-
return {
|
|
29
|
+
return {
|
|
30
|
+
command: process.env.ComSpec ?? "cmd.exe",
|
|
31
|
+
args: ["/d", "/s", "/c", scriptPath],
|
|
32
|
+
};
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
return { command: resolveBashCmd(), args: [scriptPath] };
|
|
@@ -46,29 +46,26 @@ export class RunWatcherRegistry {
|
|
|
46
46
|
* brand-new run, because `crew.run.created` is never emitted by the runtime
|
|
47
47
|
* (confirmed: only `crew.run.completed/failed/cancelled` are emitted).
|
|
48
48
|
*/
|
|
49
|
-
setRootWatcher(
|
|
50
|
-
runsDir: string,
|
|
51
|
-
onNewRun: RunChangeCallback,
|
|
52
|
-
onError?: ErrorCallback,
|
|
53
|
-
): void {
|
|
49
|
+
setRootWatcher(runsDir: string, onNewRun: RunChangeCallback, onError?: ErrorCallback): void {
|
|
54
50
|
if (this.closed) return;
|
|
55
51
|
// Replace any prior root watcher.
|
|
56
52
|
closeWatcher(this.rootWatcher);
|
|
57
|
-
this.rootWatcher =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
53
|
+
this.rootWatcher =
|
|
54
|
+
watchWithErrorHandler(
|
|
55
|
+
runsDir,
|
|
56
|
+
(_eventType, filename) => {
|
|
57
|
+
if (typeof filename !== "string" || filename.length === 0) return;
|
|
58
|
+
// fs.watch reports directory entries as bare names (no slash on Linux).
|
|
59
|
+
// A new run dir appears as `runs/<runId>` → filename = "<runId>".
|
|
60
|
+
// Filter obviously-not-run-id noise (files, temp, etc.) defensively.
|
|
61
|
+
const candidate = filename.replace(/\\/g, "/").split("/")[0];
|
|
62
|
+
if (candidate.length === 0) return;
|
|
63
|
+
onNewRun(candidate);
|
|
64
|
+
},
|
|
65
|
+
(error) => {
|
|
66
|
+
if (onError) onError(error);
|
|
67
|
+
},
|
|
68
|
+
) ?? undefined;
|
|
72
69
|
}
|
|
73
70
|
|
|
74
71
|
/**
|
|
@@ -76,12 +73,7 @@ export class RunWatcherRegistry {
|
|
|
76
73
|
* inotify watch. If a watcher for this runId already exists, close + replace.
|
|
77
74
|
* Returns true if a watcher is now active for this runId.
|
|
78
75
|
*/
|
|
79
|
-
addRunWatcher(
|
|
80
|
-
runId: string,
|
|
81
|
-
runDir: string,
|
|
82
|
-
onChange: RunChangeCallback,
|
|
83
|
-
onError?: ErrorCallback,
|
|
84
|
-
): boolean {
|
|
76
|
+
addRunWatcher(runId: string, runDir: string, onChange: RunChangeCallback, onError?: ErrorCallback): boolean {
|
|
85
77
|
if (this.closed) return false;
|
|
86
78
|
const existing = this.runWatchers.get(runId);
|
|
87
79
|
if (existing) closeWatcher(existing);
|
|
@@ -121,11 +113,7 @@ export class RunWatcherRegistry {
|
|
|
121
113
|
* not yet watched, remove watchers for runs that left the active set. Returns
|
|
122
114
|
* which runIds were added / removed (useful for logging + tests).
|
|
123
115
|
*/
|
|
124
|
-
reconcile(
|
|
125
|
-
activeRuns: ActiveRun[],
|
|
126
|
-
onChange: RunChangeCallback,
|
|
127
|
-
onError?: ErrorCallback,
|
|
128
|
-
): ReconcileResult {
|
|
116
|
+
reconcile(activeRuns: ActiveRun[], onChange: RunChangeCallback, onError?: ErrorCallback): ReconcileResult {
|
|
129
117
|
if (this.closed) return { added: [], removed: [] };
|
|
130
118
|
const activeIds = new Set(activeRuns.map((r) => r.runId));
|
|
131
119
|
const added: string[] = [];
|
package/src/utils/safe-paths.ts
CHANGED
|
@@ -11,7 +11,7 @@ export function assertSafePathId(kind: string, value: string): string {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function resolveContainedPath(baseDir: string, targetPath: string): string {
|
|
14
|
-
if (targetPath.includes(
|
|
14
|
+
if (targetPath.includes("\0")) {
|
|
15
15
|
throw new Error(`Security: path contains null byte`);
|
|
16
16
|
}
|
|
17
17
|
const base = path.resolve(baseDir);
|
|
@@ -23,9 +23,10 @@ export function resolveContainedPath(baseDir: string, targetPath: string): strin
|
|
|
23
23
|
// OS-managed symlink is wrongly rejected as "outside" the base.
|
|
24
24
|
const baseNorm = resolveCanonicalPath(base);
|
|
25
25
|
const resolvedNorm = resolveCanonicalPath(resolved);
|
|
26
|
-
const relative =
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const relative =
|
|
27
|
+
process.platform === "win32"
|
|
28
|
+
? path.relative(baseNorm.toLowerCase(), resolvedNorm.toLowerCase())
|
|
29
|
+
: path.relative(baseNorm, resolvedNorm);
|
|
29
30
|
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Path is outside ${baseDir}: ${targetPath}`);
|
|
30
31
|
return resolved;
|
|
31
32
|
}
|
|
@@ -57,7 +58,9 @@ function resolveCanonicalPath(p: string): string {
|
|
|
57
58
|
let acc = real;
|
|
58
59
|
for (let i = parts.length - 1; i >= 0; i--) acc = path.join(acc, parts[i]);
|
|
59
60
|
return acc;
|
|
60
|
-
} catch {
|
|
61
|
+
} catch {
|
|
62
|
+
/* keep walking up */
|
|
63
|
+
}
|
|
61
64
|
parts.push(path.basename(current));
|
|
62
65
|
current = path.dirname(current);
|
|
63
66
|
}
|
|
@@ -91,14 +94,20 @@ function resolveWindowsCanonical(p: string): string {
|
|
|
91
94
|
try {
|
|
92
95
|
const real = fs.realpathSync(p);
|
|
93
96
|
return real;
|
|
94
|
-
} catch {
|
|
97
|
+
} catch {
|
|
98
|
+
/* proceed to ancestor walk */
|
|
99
|
+
}
|
|
95
100
|
// Walk up to find the deepest existing ancestor
|
|
96
101
|
const parts: string[] = [];
|
|
97
102
|
let current = p;
|
|
98
103
|
while (current !== path.dirname(current)) {
|
|
99
104
|
try {
|
|
100
105
|
let real: string;
|
|
101
|
-
try {
|
|
106
|
+
try {
|
|
107
|
+
real = fs.realpathSync.native(current);
|
|
108
|
+
} catch {
|
|
109
|
+
real = fs.realpathSync(current);
|
|
110
|
+
}
|
|
102
111
|
// Guard against NTFS internal paths
|
|
103
112
|
if (real.includes("$Extend") || real.includes("$Deleted")) throw new Error("NTFS internal path");
|
|
104
113
|
// Found existing ancestor — join with remaining parts in reverse order
|
|
@@ -107,7 +116,9 @@ function resolveWindowsCanonical(p: string): string {
|
|
|
107
116
|
real = path.join(real, parts[i]);
|
|
108
117
|
}
|
|
109
118
|
return real;
|
|
110
|
-
} catch {
|
|
119
|
+
} catch {
|
|
120
|
+
/* keep walking */
|
|
121
|
+
}
|
|
111
122
|
parts.push(path.basename(current));
|
|
112
123
|
current = path.dirname(current);
|
|
113
124
|
}
|
|
@@ -161,7 +172,7 @@ function resolveWindowsCanonical(p: string): string {
|
|
|
161
172
|
* operation is atomic and prevents TOCTOU attacks.
|
|
162
173
|
*/
|
|
163
174
|
export function resolveRealContainedPath(baseDir: string, targetPath: string): string {
|
|
164
|
-
if (targetPath.includes(
|
|
175
|
+
if (targetPath.includes("\0")) {
|
|
165
176
|
throw new Error(`Security: path contains null byte`);
|
|
166
177
|
}
|
|
167
178
|
const resolved = resolveContainedPath(baseDir, targetPath);
|
|
@@ -179,7 +190,9 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
179
190
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
180
191
|
baseFd = fs.openSync(baseDir, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
181
192
|
} catch (retryError) {
|
|
182
|
-
throw new Error(
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Cannot open base directory ${baseDir}: ${retryError instanceof Error ? retryError.message : String(retryError)}`,
|
|
195
|
+
);
|
|
183
196
|
}
|
|
184
197
|
} else if (errCode === "ELOOP") {
|
|
185
198
|
// On macOS, system directories like /var → /private/var contain symlinks.
|
|
@@ -192,7 +205,9 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
192
205
|
baseDir = realBaseDir; // update for later use
|
|
193
206
|
// Fall through to fstatSync below
|
|
194
207
|
}
|
|
195
|
-
} catch {
|
|
208
|
+
} catch {
|
|
209
|
+
/* throw original */
|
|
210
|
+
}
|
|
196
211
|
}
|
|
197
212
|
if (baseFd === undefined) throw new Error("Refusing to resolve: baseDir path contains a symlink: " + baseDir);
|
|
198
213
|
} else {
|
|
@@ -235,15 +250,15 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
235
250
|
fs.closeSync(fd);
|
|
236
251
|
} catch (error) {
|
|
237
252
|
if ((error as NodeJS.ErrnoException).code === "ELOOP") {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
253
|
+
// On macOS, /var → /private/var, /tmp → /private/tmp, /etc → /private/etc
|
|
254
|
+
// are system symlinks managed by the OS. Allow them.
|
|
255
|
+
if (process.platform === "darwin") {
|
|
256
|
+
const resolvedSymlink = resolvedAccumulated;
|
|
257
|
+
const knownDarwinSymlinks = ["/var", "/tmp", "/etc", "/private/var", "/private/tmp", "/private/etc"];
|
|
258
|
+
if (knownDarwinSymlinks.includes(resolvedSymlink)) continue;
|
|
259
|
+
}
|
|
260
|
+
throw new Error("Refusing to resolve: target path ancestor is a symlink: " + resolvedAccumulated);
|
|
244
261
|
}
|
|
245
|
-
throw new Error("Refusing to resolve: target path ancestor is a symlink: " + resolvedAccumulated);
|
|
246
|
-
}
|
|
247
262
|
// EPERM on Windows when opening a directory — skip validation
|
|
248
263
|
if ((error as NodeJS.ErrnoException).code === "EPERM" && process.platform === "win32") continue;
|
|
249
264
|
// ENOENT means component doesn't exist — that's OK. Only existing symlinks
|
|
@@ -265,7 +280,8 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
265
280
|
try {
|
|
266
281
|
targetFd = fs.openSync(resolved, O_RDONLY | O_NOFOLLOW);
|
|
267
282
|
} catch (error) {
|
|
268
|
-
if ((error as NodeJS.ErrnoException).code === "ELOOP")
|
|
283
|
+
if ((error as NodeJS.ErrnoException).code === "ELOOP")
|
|
284
|
+
throw new Error("Refusing to resolve: target path is a symlink: " + resolved);
|
|
269
285
|
// EPERM on Windows when opening a directory — treat as non-existent
|
|
270
286
|
if ((error as NodeJS.ErrnoException).code === "EPERM" && process.platform === "win32") return resolved;
|
|
271
287
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
@@ -288,7 +304,9 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
288
304
|
// We already validated all ancestors are not symlinks above.
|
|
289
305
|
return resolved;
|
|
290
306
|
}
|
|
291
|
-
throw new Error(
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Cannot resolve real path of ${resolved}: ${targetError instanceof Error ? targetError.message : String(targetError)}`,
|
|
309
|
+
);
|
|
292
310
|
} finally {
|
|
293
311
|
fs.closeSync(targetFd);
|
|
294
312
|
}
|
|
@@ -330,7 +348,9 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
330
348
|
// ENOENT on an ancestor of realTarget after realpathSync is concerning
|
|
331
349
|
// — the path existed when we validated it but now doesn't. This could
|
|
332
350
|
// indicate a race or attack. For safety, treat this as an error.
|
|
333
|
-
throw new Error(
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Cannot validate resolved path: ${ancestor} disappeared after realpathSync: ${error instanceof Error ? error.message : String(error)}`,
|
|
353
|
+
);
|
|
334
354
|
}
|
|
335
355
|
ancestor = path.dirname(ancestor);
|
|
336
356
|
}
|
|
@@ -354,12 +374,13 @@ export function resolveRealContainedPath(baseDir: string, targetPath: string): s
|
|
|
354
374
|
}
|
|
355
375
|
|
|
356
376
|
export function resolveContainedRelativePath(baseDir: string, relativePath: string, kind = "path"): string {
|
|
357
|
-
if (relativePath.includes(
|
|
377
|
+
if (relativePath.includes("\0")) {
|
|
358
378
|
throw new Error(`Security: path contains null byte: ${kind}`);
|
|
359
379
|
}
|
|
360
380
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
361
381
|
// Detect Windows absolute paths (C:\, \\server\share) that path.isAbsolute may miss after normalization
|
|
362
382
|
if (/^[A-Za-z]:/.test(normalized)) throw new Error(`Invalid ${kind}: ${relativePath}`);
|
|
363
|
-
if (!normalized || normalized.split("/").some((segment) => segment === "..") || path.isAbsolute(normalized))
|
|
383
|
+
if (!normalized || normalized.split("/").some((segment) => segment === "..") || path.isAbsolute(normalized))
|
|
384
|
+
throw new Error(`Invalid ${kind}: ${relativePath}`);
|
|
364
385
|
return resolveContainedPath(baseDir, path.resolve(baseDir, normalized));
|
|
365
386
|
}
|
package/src/utils/scan-cache.ts
CHANGED
|
@@ -106,7 +106,14 @@ export class SharedScanCache {
|
|
|
106
106
|
if (cached && cached.mtimeMs >= stat.mtimeMs && cached.sizeBytes === stat.size) return cached;
|
|
107
107
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
108
108
|
const raw = parseJson ? JSON.parse(content) : content;
|
|
109
|
-
const entry: ScanEntry = {
|
|
109
|
+
const entry: ScanEntry = {
|
|
110
|
+
key,
|
|
111
|
+
path: filePath,
|
|
112
|
+
raw,
|
|
113
|
+
mtimeMs: stat.mtimeMs,
|
|
114
|
+
sizeBytes: stat.size,
|
|
115
|
+
loadedAtMs: this.#now(),
|
|
116
|
+
};
|
|
110
117
|
this.set(bucket, entry);
|
|
111
118
|
return entry;
|
|
112
119
|
} catch {
|
|
@@ -134,4 +141,4 @@ export class SharedScanCache {
|
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
/** Global shared scan cache instance. */
|
|
137
|
-
export const sharedScanCache = new SharedScanCache();
|
|
144
|
+
export const sharedScanCache = new SharedScanCache();
|
|
@@ -14,9 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export function assertValidSessionId(id: string): void {
|
|
16
16
|
if (!id || !/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(id)) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`Invalid session id: must be non-empty, alphanumeric with '-', '_', '.' and start/end with alphanumeric`,
|
|
19
|
-
);
|
|
17
|
+
throw new Error(`Invalid session id: must be non-empty, alphanumeric with '-', '_', '.' and start/end with alphanumeric`);
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
20
|
|
|
@@ -79,4 +77,4 @@ export function extractSessionId(ctx: unknown): string | undefined {
|
|
|
79
77
|
}
|
|
80
78
|
if (typeof raw !== "string" || raw.length === 0) return undefined;
|
|
81
79
|
return raw;
|
|
82
|
-
}
|
|
80
|
+
}
|
package/src/utils/sleep.ts
CHANGED
|
@@ -16,7 +16,10 @@ export function sleepSync(ms: number): void {
|
|
|
16
16
|
// On Unix, try spawning the `sleep` command to avoid CPU busy-wait.
|
|
17
17
|
if (process.platform !== "win32" && ms >= 10) {
|
|
18
18
|
try {
|
|
19
|
-
execFileSync("sleep", [(ms / 1000).toFixed(3)], {
|
|
19
|
+
execFileSync("sleep", [(ms / 1000).toFixed(3)], {
|
|
20
|
+
timeout: ms + 1000,
|
|
21
|
+
stdio: "pipe",
|
|
22
|
+
});
|
|
20
23
|
return;
|
|
21
24
|
} catch {
|
|
22
25
|
// sleep command unavailable — fall through to busy-wait
|
|
@@ -39,9 +42,13 @@ export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
|
39
42
|
if (signal?.aborted) return Promise.reject(new Error("aborted"));
|
|
40
43
|
return new Promise<void>((resolve, reject) => {
|
|
41
44
|
const timer = setTimeout(resolve, ms);
|
|
42
|
-
signal?.addEventListener(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
signal?.addEventListener(
|
|
46
|
+
"abort",
|
|
47
|
+
() => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
reject(new Error("aborted"));
|
|
50
|
+
},
|
|
51
|
+
{ once: true },
|
|
52
|
+
);
|
|
46
53
|
});
|
|
47
54
|
}
|
package/src/utils/sse-parser.ts
CHANGED
|
@@ -12,10 +12,7 @@ const MAX_EVENT_LINES = 1000;
|
|
|
12
12
|
const MAX_DATA_SIZE = 100000; // 100KB per line
|
|
13
13
|
|
|
14
14
|
/** Read newline-delimited lines from a text ReadableStream, buffering partial chunks. */
|
|
15
|
-
async function* readLines(
|
|
16
|
-
stream: ReadableStream<string>,
|
|
17
|
-
signal?: AbortSignal,
|
|
18
|
-
): AsyncGenerator<string> {
|
|
15
|
+
async function* readLines(stream: ReadableStream<string>, signal?: AbortSignal): AsyncGenerator<string> {
|
|
19
16
|
const reader = stream.getReader();
|
|
20
17
|
let buffer = "";
|
|
21
18
|
try {
|
|
@@ -29,9 +26,7 @@ async function* readLines(
|
|
|
29
26
|
buffer += value;
|
|
30
27
|
let idx: number;
|
|
31
28
|
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
32
|
-
const line = buffer.endsWith("\r\n", idx + 1)
|
|
33
|
-
? buffer.slice(0, idx - 1)
|
|
34
|
-
: buffer.slice(0, idx);
|
|
29
|
+
const line = buffer.endsWith("\r\n", idx + 1) ? buffer.slice(0, idx - 1) : buffer.slice(0, idx);
|
|
35
30
|
yield line;
|
|
36
31
|
buffer = buffer.slice(idx + 1);
|
|
37
32
|
}
|
|
@@ -42,10 +37,7 @@ async function* readLines(
|
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
/** Parse SSE events from a byte stream */
|
|
45
|
-
export async function* readSseEvents(
|
|
46
|
-
stream: ReadableStream<Uint8Array>,
|
|
47
|
-
signal?: AbortSignal,
|
|
48
|
-
): AsyncGenerator<ServerSentEvent> {
|
|
40
|
+
export async function* readSseEvents(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<ServerSentEvent> {
|
|
49
41
|
const textStream = stream.pipeThrough(new TextDecoderStream() as unknown as TransformStream<Uint8Array, string>);
|
|
50
42
|
let currentEvent: string | null = null;
|
|
51
43
|
let currentData: string[] = [];
|
|
@@ -127,15 +119,11 @@ export async function* readSseEvents(
|
|
|
127
119
|
}
|
|
128
120
|
|
|
129
121
|
/** Parse SSE events and yield parsed JSON data objects */
|
|
130
|
-
export async function* readSseJson<T>(
|
|
131
|
-
stream: ReadableStream<Uint8Array>,
|
|
132
|
-
signal?: AbortSignal,
|
|
133
|
-
): AsyncGenerator<T> {
|
|
122
|
+
export async function* readSseJson<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<T> {
|
|
134
123
|
for await (const evt of readSseEvents(stream, signal)) {
|
|
135
124
|
try {
|
|
136
125
|
const parsed: T = JSON.parse(evt.data) as T;
|
|
137
126
|
yield parsed;
|
|
138
|
-
} catch {
|
|
139
|
-
}
|
|
127
|
+
} catch {}
|
|
140
128
|
}
|
|
141
129
|
}
|