pi-crew 0.9.16 → 0.9.18
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 +130 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21784 -0
- package/dist/index.mjs +83169 -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/migration/atomic-write-v2-migration.md +297 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/perf/performance-review-2026-07.md +287 -0
- 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 +303 -591
- 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 +149 -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 +39 -24
- 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 +344 -79
- 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 +187 -59
- 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 +13 -31
- 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 +10 -18
- 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 +2 -6
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +27 -51
- 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 +12 -19
- 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 +50 -14
- 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 +2 -7
- 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 +106 -27
- 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 +137 -47
- 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/terminal-status.ts +1 -3
- 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 +46 -29
- 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/config/config.ts
CHANGED
|
@@ -3,12 +3,9 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { type Static, type TSchema, Type } from "@sinclair/typebox";
|
|
5
5
|
import { Value } from "@sinclair/typebox/value";
|
|
6
|
-
import {
|
|
7
|
-
PiTeamsAutonomyProfileSchema,
|
|
8
|
-
PiTeamsConfigSchema,
|
|
9
|
-
} from "../schema/config-schema.ts";
|
|
10
|
-
import { withFileLockSync } from "../state/locks.ts";
|
|
6
|
+
import { PiTeamsAutonomyProfileSchema, PiTeamsConfigSchema } from "../schema/config-schema.ts";
|
|
11
7
|
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
8
|
+
import { withFileLockSync } from "../state/locks.ts";
|
|
12
9
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
13
10
|
import { projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
|
|
14
11
|
import { suggestConfigKey } from "./suggestions.ts";
|
|
@@ -69,6 +66,108 @@ import type {
|
|
|
69
66
|
UpdateConfigOptions,
|
|
70
67
|
} from "./types.ts";
|
|
71
68
|
|
|
69
|
+
// (F16) loadConfig was called 1 Hz idle / 6 Hz active with 0 cache — added 2s TTL+mtime cache following the manifestCache pattern in state-store.ts:75-130.
|
|
70
|
+
const CONFIG_CACHE_TTL_MS = 2000;
|
|
71
|
+
|
|
72
|
+
interface ConfigCacheEntry {
|
|
73
|
+
value: LoadedPiTeamsConfig;
|
|
74
|
+
mtimes: Record<string, number>;
|
|
75
|
+
cachedAt: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ConfigCacheKeyParts {
|
|
79
|
+
filePath: string;
|
|
80
|
+
legacyPath: string;
|
|
81
|
+
projectPath: string;
|
|
82
|
+
projectPiCrewJsonPath: string;
|
|
83
|
+
cwd: string | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const configCache = new Map<string, ConfigCacheEntry>();
|
|
87
|
+
|
|
88
|
+
/** @internal — TTL override for unit tests (matches __test__setManifestCache pattern in state-store.ts:82). */
|
|
89
|
+
export function __test__setConfigCacheTtlMs(ttlMs: number): void {
|
|
90
|
+
configCacheTtlMsOverride = ttlMs;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @internal — read the effective TTL in use by the cache. */
|
|
94
|
+
export function __test__getConfigCacheTtlMs(): number {
|
|
95
|
+
return configCacheTtlMsOverride ?? CONFIG_CACHE_TTL_MS;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @internal — peek at the cached entry for a given key shape. */
|
|
99
|
+
export function __test__getConfigCacheEntry(parts: ConfigCacheKeyParts): ConfigCacheEntry | undefined {
|
|
100
|
+
return configCache.get(buildConfigCacheKey(parts));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @internal — number of cached entries (for tests/diagnostics). */
|
|
104
|
+
export function __test__configCacheSize(): number {
|
|
105
|
+
return configCache.size;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let configCacheTtlMsOverride: number | null = null;
|
|
109
|
+
|
|
110
|
+
function buildConfigCacheKey(parts: ConfigCacheKeyParts): string {
|
|
111
|
+
return JSON.stringify([parts.filePath, parts.legacyPath, parts.projectPath, parts.projectPiCrewJsonPath, parts.cwd]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function statMtimeMs(filePath: string): number | undefined {
|
|
115
|
+
try {
|
|
116
|
+
return fs.statSync(filePath).mtimeMs;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readCachedConfigParts(filePath: string, legacyPath: string, cwd: string | undefined): ConfigCacheKeyParts {
|
|
124
|
+
const projectPath = cwd ? projectConfigPath(cwd) : "";
|
|
125
|
+
const piCrewJsonPath = cwd ? projectPiCrewJsonPath(cwd) : "";
|
|
126
|
+
return {
|
|
127
|
+
filePath,
|
|
128
|
+
legacyPath,
|
|
129
|
+
projectPath,
|
|
130
|
+
projectPiCrewJsonPath: piCrewJsonPath,
|
|
131
|
+
cwd: cwd ?? null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readCacheMtimes(parts: ConfigCacheKeyParts): Record<string, number> {
|
|
136
|
+
const mtimes: Record<string, number> = {};
|
|
137
|
+
for (const p of [parts.filePath, parts.legacyPath, parts.projectPath, parts.projectPiCrewJsonPath]) {
|
|
138
|
+
if (!p) continue; // skip empty (cwd-undefined project paths)
|
|
139
|
+
const m = statMtimeMs(p);
|
|
140
|
+
if (m !== undefined) mtimes[p] = m;
|
|
141
|
+
}
|
|
142
|
+
return mtimes;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function matchesCachedMtimes(cached: Record<string, number>, current: Record<string, number>): boolean {
|
|
146
|
+
const cachedKeys = Object.keys(cached);
|
|
147
|
+
const currentKeys = Object.keys(current);
|
|
148
|
+
if (cachedKeys.length !== currentKeys.length) return false;
|
|
149
|
+
for (const key of cachedKeys) {
|
|
150
|
+
if (current[key] !== cached[key]) return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function setConfigCache(key: string, value: LoadedPiTeamsConfig, mtimes: Record<string, number>): void {
|
|
156
|
+
if (configCache.has(key)) configCache.delete(key);
|
|
157
|
+
configCache.set(key, { value, mtimes, cachedAt: Date.now() });
|
|
158
|
+
const ttlMs = configCacheTtlMsOverride ?? CONFIG_CACHE_TTL_MS;
|
|
159
|
+
// TTL eviction on insert (mirrors manifestCache eviction in state-store.ts:108-117)
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
for (const [k, entry] of configCache.entries()) {
|
|
162
|
+
if (now - entry.cachedAt > ttlMs) configCache.delete(k);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Drop all cached loadConfig results. Call after config writes or from tests. */
|
|
167
|
+
export function invalidateConfigCache(): void {
|
|
168
|
+
configCache.clear();
|
|
169
|
+
}
|
|
170
|
+
|
|
72
171
|
function resolveHomeDir(): string {
|
|
73
172
|
const envValue = process.env.PI_TEAMS_HOME?.trim();
|
|
74
173
|
const defaultHome = os.homedir();
|
|
@@ -112,14 +211,7 @@ export function configPath(): string {
|
|
|
112
211
|
}
|
|
113
212
|
|
|
114
213
|
export function legacyConfigPath(): string {
|
|
115
|
-
return path.join(
|
|
116
|
-
resolveHomeDir(),
|
|
117
|
-
".pi",
|
|
118
|
-
"agent",
|
|
119
|
-
"extensions",
|
|
120
|
-
"pi-crew",
|
|
121
|
-
"config.json",
|
|
122
|
-
);
|
|
214
|
+
return path.join(resolveHomeDir(), ".pi", "agent", "extensions", "pi-crew", "config.json");
|
|
123
215
|
}
|
|
124
216
|
|
|
125
217
|
export function projectConfigPath(cwd: string): string {
|
|
@@ -134,23 +226,14 @@ export function projectPiCrewJsonPath(cwd: string): string {
|
|
|
134
226
|
return path.join(projectPiRoot(cwd), "pi-crew.json");
|
|
135
227
|
}
|
|
136
228
|
|
|
137
|
-
function withoutUndefined<T extends Record<string, unknown>>(
|
|
138
|
-
value
|
|
139
|
-
): Partial<T> {
|
|
140
|
-
return Object.fromEntries(
|
|
141
|
-
Object.entries(value).filter(([, entry]) => entry !== undefined),
|
|
142
|
-
) as Partial<T>;
|
|
229
|
+
function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
|
|
230
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as Partial<T>;
|
|
143
231
|
}
|
|
144
232
|
|
|
145
233
|
function errorPathFromValidation(error: unknown): string {
|
|
146
234
|
if (error && typeof error === "object") {
|
|
147
|
-
if (typeof (error as { path?: unknown }).path === "string")
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
typeof (error as { instancePath?: unknown }).instancePath ===
|
|
151
|
-
"string"
|
|
152
|
-
)
|
|
153
|
-
return (error as { instancePath: string }).instancePath;
|
|
235
|
+
if (typeof (error as { path?: unknown }).path === "string") return (error as { path: string }).path;
|
|
236
|
+
if (typeof (error as { instancePath?: unknown }).instancePath === "string") return (error as { instancePath: string }).instancePath;
|
|
154
237
|
if (
|
|
155
238
|
typeof (error as { keyword?: unknown }).keyword === "string" &&
|
|
156
239
|
typeof (error as { schemaPath?: unknown }).schemaPath === "string"
|
|
@@ -161,28 +244,18 @@ function errorPathFromValidation(error: unknown): string {
|
|
|
161
244
|
}
|
|
162
245
|
|
|
163
246
|
/** Known top-level config keys from the schema — used for fuzzy suggestions. */
|
|
164
|
-
const KNOWN_TOP_LEVEL_KEYS = Object.keys(
|
|
165
|
-
PiTeamsConfigSchema.properties ?? {},
|
|
166
|
-
) as string[];
|
|
247
|
+
const KNOWN_TOP_LEVEL_KEYS = Object.keys(PiTeamsConfigSchema.properties ?? {}) as string[];
|
|
167
248
|
|
|
168
249
|
function validateConfigWithWarnings(raw: unknown): string[] {
|
|
169
250
|
if (!Value.Check(PiTeamsConfigSchema, raw)) {
|
|
170
251
|
return [...Value.Errors(PiTeamsConfigSchema, raw)].map((error) => {
|
|
171
252
|
const path = errorPathFromValidation(error);
|
|
172
|
-
const message =
|
|
173
|
-
(error as { message?: unknown }).message ?? "invalid value";
|
|
253
|
+
const message = (error as { message?: unknown }).message ?? "invalid value";
|
|
174
254
|
// Enhance "additionalProperties" errors with fuzzy suggestions
|
|
175
|
-
if (
|
|
176
|
-
(error as { keyword?: unknown }).keyword ===
|
|
177
|
-
"additionalProperties"
|
|
178
|
-
) {
|
|
255
|
+
if ((error as { keyword?: unknown }).keyword === "additionalProperties") {
|
|
179
256
|
const offendingKey = path.split("/").pop() ?? path;
|
|
180
|
-
const suggestion = suggestConfigKey(
|
|
181
|
-
|
|
182
|
-
KNOWN_TOP_LEVEL_KEYS,
|
|
183
|
-
);
|
|
184
|
-
if (suggestion)
|
|
185
|
-
return `${path}: ${message} (did you mean '${suggestion}'?)`;
|
|
257
|
+
const suggestion = suggestConfigKey(offendingKey, KNOWN_TOP_LEVEL_KEYS);
|
|
258
|
+
if (suggestion) return `${path}: ${message} (did you mean '${suggestion}'?)`;
|
|
186
259
|
}
|
|
187
260
|
return `${path}: ${message}`;
|
|
188
261
|
});
|
|
@@ -190,18 +263,11 @@ function validateConfigWithWarnings(raw: unknown): string[] {
|
|
|
190
263
|
return [];
|
|
191
264
|
}
|
|
192
265
|
|
|
193
|
-
function projectOverrideWarning(
|
|
194
|
-
projectPath: string,
|
|
195
|
-
dottedPath: string,
|
|
196
|
-
): string {
|
|
266
|
+
function projectOverrideWarning(projectPath: string, dottedPath: string): string {
|
|
197
267
|
return `${projectPath}: project-level sensitive config '${dottedPath}' is ignored; set it in user config to trust it explicitly`;
|
|
198
268
|
}
|
|
199
269
|
|
|
200
|
-
function sanitizeProjectConfig(
|
|
201
|
-
projectPath: string,
|
|
202
|
-
userConfig: PiTeamsConfig,
|
|
203
|
-
config: PiTeamsConfig,
|
|
204
|
-
): ConfigValidationResult {
|
|
270
|
+
function sanitizeProjectConfig(projectPath: string, userConfig: PiTeamsConfig, config: PiTeamsConfig): ConfigValidationResult {
|
|
205
271
|
const sanitized: PiTeamsConfig = { ...config };
|
|
206
272
|
const warnings: string[] = [];
|
|
207
273
|
const dropTopLevel = (key: keyof PiTeamsConfig): void => {
|
|
@@ -214,73 +280,36 @@ function sanitizeProjectConfig(
|
|
|
214
280
|
dropTopLevel("requireCleanWorktreeLeader");
|
|
215
281
|
if (config.runtime) {
|
|
216
282
|
const runtime = { ...config.runtime };
|
|
217
|
-
for (const key of [
|
|
218
|
-
"mode",
|
|
219
|
-
"preferLiveSession",
|
|
220
|
-
"allowChildProcessFallback",
|
|
221
|
-
"inheritContext",
|
|
222
|
-
"isolationPolicy",
|
|
223
|
-
] as const) {
|
|
283
|
+
for (const key of ["mode", "preferLiveSession", "allowChildProcessFallback", "inheritContext", "isolationPolicy"] as const) {
|
|
224
284
|
if (runtime[key] !== undefined) {
|
|
225
285
|
delete runtime[key];
|
|
226
|
-
warnings.push(
|
|
227
|
-
projectOverrideWarning(projectPath, `runtime.${key}`),
|
|
228
|
-
);
|
|
286
|
+
warnings.push(projectOverrideWarning(projectPath, `runtime.${key}`));
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
289
|
if (runtime.requirePlanApproval === false) {
|
|
232
290
|
delete runtime.requirePlanApproval;
|
|
233
|
-
warnings.push(
|
|
234
|
-
projectOverrideWarning(
|
|
235
|
-
projectPath,
|
|
236
|
-
"runtime.requirePlanApproval",
|
|
237
|
-
),
|
|
238
|
-
);
|
|
291
|
+
warnings.push(projectOverrideWarning(projectPath, "runtime.requirePlanApproval"));
|
|
239
292
|
}
|
|
240
|
-
sanitized.runtime = Object.values(runtime).some(
|
|
241
|
-
(entry) => entry !== undefined,
|
|
242
|
-
)
|
|
243
|
-
? runtime
|
|
244
|
-
: undefined;
|
|
293
|
+
sanitized.runtime = Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
|
|
245
294
|
}
|
|
246
295
|
if (config.autonomous) {
|
|
247
296
|
const autonomous = { ...config.autonomous };
|
|
248
|
-
for (const key of [
|
|
249
|
-
"profile",
|
|
250
|
-
"enabled",
|
|
251
|
-
"injectPolicy",
|
|
252
|
-
"preferAsyncForLongTasks",
|
|
253
|
-
"allowWorktreeSuggestion",
|
|
254
|
-
] as const) {
|
|
297
|
+
for (const key of ["profile", "enabled", "injectPolicy", "preferAsyncForLongTasks", "allowWorktreeSuggestion"] as const) {
|
|
255
298
|
if (autonomous[key] !== undefined) {
|
|
256
299
|
delete autonomous[key];
|
|
257
|
-
warnings.push(
|
|
258
|
-
projectOverrideWarning(projectPath, `autonomous.${key}`),
|
|
259
|
-
);
|
|
300
|
+
warnings.push(projectOverrideWarning(projectPath, `autonomous.${key}`));
|
|
260
301
|
}
|
|
261
302
|
}
|
|
262
|
-
sanitized.autonomous = Object.values(autonomous).some(
|
|
263
|
-
(entry) => entry !== undefined,
|
|
264
|
-
)
|
|
265
|
-
? autonomous
|
|
266
|
-
: undefined;
|
|
303
|
+
sanitized.autonomous = Object.values(autonomous).some((entry) => entry !== undefined) ? autonomous : undefined;
|
|
267
304
|
}
|
|
268
305
|
if (config.worktree?.setupHook !== undefined) {
|
|
269
306
|
sanitized.worktree = { ...config.worktree, setupHook: undefined };
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
(entry) => entry !== undefined,
|
|
273
|
-
)
|
|
274
|
-
)
|
|
275
|
-
sanitized.worktree = undefined;
|
|
276
|
-
warnings.push(
|
|
277
|
-
projectOverrideWarning(projectPath, "worktree.setupHook"),
|
|
278
|
-
);
|
|
307
|
+
if (!Object.values(sanitized.worktree).some((entry) => entry !== undefined)) sanitized.worktree = undefined;
|
|
308
|
+
warnings.push(projectOverrideWarning(projectPath, "worktree.setupHook"));
|
|
279
309
|
}
|
|
280
310
|
if (config.otlp?.headers !== undefined) {
|
|
281
311
|
sanitized.otlp = { ...config.otlp, headers: undefined };
|
|
282
|
-
if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined))
|
|
283
|
-
sanitized.otlp = undefined;
|
|
312
|
+
if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined)) sanitized.otlp = undefined;
|
|
284
313
|
warnings.push(projectOverrideWarning(projectPath, "otlp.headers"));
|
|
285
314
|
}
|
|
286
315
|
// FIX: Block project config from setting otlp.endpoint — it controls where
|
|
@@ -288,66 +317,37 @@ function sanitizeProjectConfig(
|
|
|
288
317
|
if (config.otlp?.endpoint !== undefined) {
|
|
289
318
|
if (!sanitized.otlp) sanitized.otlp = { ...config.otlp, endpoint: undefined };
|
|
290
319
|
else sanitized.otlp = { ...sanitized.otlp, endpoint: undefined };
|
|
291
|
-
if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined))
|
|
292
|
-
sanitized.otlp = undefined;
|
|
320
|
+
if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined)) sanitized.otlp = undefined;
|
|
293
321
|
warnings.push(projectOverrideWarning(projectPath, "otlp.endpoint"));
|
|
294
322
|
}
|
|
295
|
-
if (
|
|
296
|
-
config.agents?.disableBuiltins !== undefined ||
|
|
297
|
-
config.agents?.overrides !== undefined
|
|
298
|
-
) {
|
|
323
|
+
if (config.agents?.disableBuiltins !== undefined || config.agents?.overrides !== undefined) {
|
|
299
324
|
const agents = { ...config.agents };
|
|
300
325
|
if (agents.disableBuiltins !== undefined) {
|
|
301
326
|
delete agents.disableBuiltins;
|
|
302
|
-
warnings.push(
|
|
303
|
-
projectOverrideWarning(projectPath, "agents.disableBuiltins"),
|
|
304
|
-
);
|
|
327
|
+
warnings.push(projectOverrideWarning(projectPath, "agents.disableBuiltins"));
|
|
305
328
|
}
|
|
306
329
|
if (agents.overrides !== undefined) {
|
|
307
330
|
delete agents.overrides;
|
|
308
|
-
warnings.push(
|
|
309
|
-
projectOverrideWarning(projectPath, "agents.overrides"),
|
|
310
|
-
);
|
|
331
|
+
warnings.push(projectOverrideWarning(projectPath, "agents.overrides"));
|
|
311
332
|
}
|
|
312
|
-
sanitized.agents = Object.values(agents).some(
|
|
313
|
-
(entry) => entry !== undefined,
|
|
314
|
-
)
|
|
315
|
-
? agents
|
|
316
|
-
: undefined;
|
|
333
|
+
sanitized.agents = Object.values(agents).some((entry) => entry !== undefined) ? agents : undefined;
|
|
317
334
|
}
|
|
318
|
-
if (
|
|
319
|
-
config.tools?.enableSteer !== undefined ||
|
|
320
|
-
config.tools?.terminateOnForeground !== undefined
|
|
321
|
-
) {
|
|
335
|
+
if (config.tools?.enableSteer !== undefined || config.tools?.terminateOnForeground !== undefined) {
|
|
322
336
|
const tools = { ...config.tools };
|
|
323
337
|
if (tools.enableSteer !== undefined) {
|
|
324
338
|
delete tools.enableSteer;
|
|
325
|
-
warnings.push(
|
|
326
|
-
projectOverrideWarning(projectPath, "tools.enableSteer"),
|
|
327
|
-
);
|
|
339
|
+
warnings.push(projectOverrideWarning(projectPath, "tools.enableSteer"));
|
|
328
340
|
}
|
|
329
341
|
if (tools.terminateOnForeground !== undefined) {
|
|
330
342
|
delete tools.terminateOnForeground;
|
|
331
|
-
warnings.push(
|
|
332
|
-
projectOverrideWarning(
|
|
333
|
-
projectPath,
|
|
334
|
-
"tools.terminateOnForeground",
|
|
335
|
-
),
|
|
336
|
-
);
|
|
343
|
+
warnings.push(projectOverrideWarning(projectPath, "tools.terminateOnForeground"));
|
|
337
344
|
}
|
|
338
|
-
sanitized.tools = Object.values(tools).some(
|
|
339
|
-
(entry) => entry !== undefined,
|
|
340
|
-
)
|
|
341
|
-
? tools
|
|
342
|
-
: undefined;
|
|
345
|
+
sanitized.tools = Object.values(tools).some((entry) => entry !== undefined) ? tools : undefined;
|
|
343
346
|
}
|
|
344
347
|
return { config: sanitized, warnings };
|
|
345
348
|
}
|
|
346
349
|
|
|
347
|
-
function mergeConfig(
|
|
348
|
-
base: PiTeamsConfig,
|
|
349
|
-
override: PiTeamsConfig,
|
|
350
|
-
): PiTeamsConfig {
|
|
350
|
+
function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfig {
|
|
351
351
|
const warnings: string[] = [];
|
|
352
352
|
const merged: PiTeamsConfig = {
|
|
353
353
|
...base,
|
|
@@ -356,41 +356,31 @@ function mergeConfig(
|
|
|
356
356
|
if (base.autonomous || override.autonomous) {
|
|
357
357
|
merged.autonomous = {
|
|
358
358
|
...(base.autonomous ?? {}),
|
|
359
|
-
...withoutUndefined(
|
|
360
|
-
(override.autonomous ?? {}) as Record<string, unknown>,
|
|
361
|
-
),
|
|
359
|
+
...withoutUndefined((override.autonomous ?? {}) as Record<string, unknown>),
|
|
362
360
|
};
|
|
363
361
|
}
|
|
364
362
|
if (base.limits || override.limits) {
|
|
365
363
|
merged.limits = {
|
|
366
364
|
...(base.limits ?? {}),
|
|
367
|
-
...withoutUndefined(
|
|
368
|
-
(override.limits ?? {}) as Record<string, unknown>,
|
|
369
|
-
),
|
|
365
|
+
...withoutUndefined((override.limits ?? {}) as Record<string, unknown>),
|
|
370
366
|
};
|
|
371
367
|
}
|
|
372
368
|
if (base.runtime || override.runtime) {
|
|
373
369
|
merged.runtime = {
|
|
374
370
|
...(base.runtime ?? {}),
|
|
375
|
-
...withoutUndefined(
|
|
376
|
-
(override.runtime ?? {}) as Record<string, unknown>,
|
|
377
|
-
),
|
|
371
|
+
...withoutUndefined((override.runtime ?? {}) as Record<string, unknown>),
|
|
378
372
|
};
|
|
379
373
|
}
|
|
380
374
|
if (base.control || override.control) {
|
|
381
375
|
merged.control = {
|
|
382
376
|
...(base.control ?? {}),
|
|
383
|
-
...withoutUndefined(
|
|
384
|
-
(override.control ?? {}) as Record<string, unknown>,
|
|
385
|
-
),
|
|
377
|
+
...withoutUndefined((override.control ?? {}) as Record<string, unknown>),
|
|
386
378
|
};
|
|
387
379
|
}
|
|
388
380
|
if (base.worktree || override.worktree) {
|
|
389
381
|
merged.worktree = {
|
|
390
382
|
...(base.worktree ?? {}),
|
|
391
|
-
...withoutUndefined(
|
|
392
|
-
(override.worktree ?? {}) as Record<string, unknown>,
|
|
393
|
-
),
|
|
383
|
+
...withoutUndefined((override.worktree ?? {}) as Record<string, unknown>),
|
|
394
384
|
};
|
|
395
385
|
}
|
|
396
386
|
if (base.ui || override.ui) {
|
|
@@ -402,75 +392,52 @@ function mergeConfig(
|
|
|
402
392
|
if (base.agents || override.agents) {
|
|
403
393
|
merged.agents = {
|
|
404
394
|
...(base.agents ?? {}),
|
|
405
|
-
...withoutUndefined(
|
|
406
|
-
(override.agents ?? {}) as Record<string, unknown>,
|
|
407
|
-
),
|
|
395
|
+
...withoutUndefined((override.agents ?? {}) as Record<string, unknown>),
|
|
408
396
|
overrides: {
|
|
409
397
|
...(base.agents?.overrides ?? {}),
|
|
410
|
-
...(withoutUndefined(
|
|
411
|
-
(override.agents?.overrides ?? {}) as Record<
|
|
412
|
-
string,
|
|
413
|
-
unknown
|
|
414
|
-
>,
|
|
415
|
-
) as Record<string, AgentOverrideConfig>),
|
|
398
|
+
...(withoutUndefined((override.agents?.overrides ?? {}) as Record<string, unknown>) as Record<string, AgentOverrideConfig>),
|
|
416
399
|
},
|
|
417
400
|
};
|
|
418
401
|
}
|
|
419
402
|
if (base.tools || override.tools) {
|
|
420
403
|
merged.tools = {
|
|
421
404
|
...(base.tools ?? {}),
|
|
422
|
-
...withoutUndefined(
|
|
423
|
-
(override.tools ?? {}) as Record<string, unknown>,
|
|
424
|
-
),
|
|
405
|
+
...withoutUndefined((override.tools ?? {}) as Record<string, unknown>),
|
|
425
406
|
};
|
|
426
407
|
}
|
|
427
408
|
if (base.telemetry || override.telemetry) {
|
|
428
409
|
merged.telemetry = {
|
|
429
410
|
...(base.telemetry ?? {}),
|
|
430
|
-
...withoutUndefined(
|
|
431
|
-
(override.telemetry ?? {}) as Record<string, unknown>,
|
|
432
|
-
),
|
|
411
|
+
...withoutUndefined((override.telemetry ?? {}) as Record<string, unknown>),
|
|
433
412
|
};
|
|
434
413
|
}
|
|
435
414
|
if (base.policy || override.policy) {
|
|
436
415
|
merged.policy = {
|
|
437
416
|
...(base.policy ?? {}),
|
|
438
|
-
...withoutUndefined(
|
|
439
|
-
(override.policy ?? {}) as Record<string, unknown>,
|
|
440
|
-
),
|
|
417
|
+
...withoutUndefined((override.policy ?? {}) as Record<string, unknown>),
|
|
441
418
|
};
|
|
442
419
|
}
|
|
443
420
|
if (base.notifications || override.notifications) {
|
|
444
421
|
merged.notifications = {
|
|
445
422
|
...(base.notifications ?? {}),
|
|
446
|
-
...withoutUndefined(
|
|
447
|
-
(override.notifications ?? {}) as Record<string, unknown>,
|
|
448
|
-
),
|
|
423
|
+
...withoutUndefined((override.notifications ?? {}) as Record<string, unknown>),
|
|
449
424
|
};
|
|
450
425
|
}
|
|
451
426
|
if (base.observability || override.observability) {
|
|
452
427
|
merged.observability = {
|
|
453
428
|
...(base.observability ?? {}),
|
|
454
|
-
...withoutUndefined(
|
|
455
|
-
(override.observability ?? {}) as Record<string, unknown>,
|
|
456
|
-
),
|
|
429
|
+
...withoutUndefined((override.observability ?? {}) as Record<string, unknown>),
|
|
457
430
|
};
|
|
458
431
|
}
|
|
459
432
|
if (base.reliability || override.reliability) {
|
|
460
433
|
merged.reliability = {
|
|
461
434
|
...(base.reliability ?? {}),
|
|
462
|
-
...withoutUndefined(
|
|
463
|
-
(override.reliability ?? {}) as Record<string, unknown>,
|
|
464
|
-
),
|
|
435
|
+
...withoutUndefined((override.reliability ?? {}) as Record<string, unknown>),
|
|
465
436
|
retryPolicy:
|
|
466
|
-
base.reliability?.retryPolicy ||
|
|
467
|
-
override.reliability?.retryPolicy
|
|
437
|
+
base.reliability?.retryPolicy || override.reliability?.retryPolicy
|
|
468
438
|
? {
|
|
469
439
|
...(base.reliability?.retryPolicy ?? {}),
|
|
470
|
-
...withoutUndefined(
|
|
471
|
-
(override.reliability?.retryPolicy ??
|
|
472
|
-
{}) as Record<string, unknown>,
|
|
473
|
-
),
|
|
440
|
+
...withoutUndefined((override.reliability?.retryPolicy ?? {}) as Record<string, unknown>),
|
|
474
441
|
}
|
|
475
442
|
: undefined,
|
|
476
443
|
};
|
|
@@ -478,16 +445,13 @@ function mergeConfig(
|
|
|
478
445
|
if (base.otlp || override.otlp) {
|
|
479
446
|
merged.otlp = {
|
|
480
447
|
...(base.otlp ?? {}),
|
|
481
|
-
...withoutUndefined(
|
|
482
|
-
(override.otlp ?? {}) as Record<string, unknown>,
|
|
483
|
-
),
|
|
448
|
+
...withoutUndefined((override.otlp ?? {}) as Record<string, unknown>),
|
|
484
449
|
headers: {
|
|
485
450
|
...(base.otlp?.headers ?? {}),
|
|
486
451
|
...(override.otlp?.headers ?? {}),
|
|
487
452
|
},
|
|
488
453
|
};
|
|
489
|
-
if (Object.keys(merged.otlp.headers ?? {}).length === 0)
|
|
490
|
-
delete merged.otlp.headers;
|
|
454
|
+
if (Object.keys(merged.otlp.headers ?? {}).length === 0) delete merged.otlp.headers;
|
|
491
455
|
// Validate OTLP headers for injection attacks:
|
|
492
456
|
// - Check top-level keys for dangerous prototype pollution patterns
|
|
493
457
|
// - Block ALL control characters except tab (0x09) to prevent header
|
|
@@ -506,22 +470,23 @@ function mergeConfig(
|
|
|
506
470
|
if (DANGEROUS_OBJECT_KEYS.has(lowerKey)) return true;
|
|
507
471
|
return false;
|
|
508
472
|
};
|
|
509
|
-
if (checkKey(k)) {
|
|
473
|
+
if (checkKey(k)) {
|
|
474
|
+
invalidHeaders.push(k);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
510
477
|
// Block any control characters except tab (0x09) in values.
|
|
511
478
|
// Round 28 fix: /[\x00-\x08\x0a-\x1f]/ blocks LF (0x0A) and CR (0x0D) too.
|
|
512
479
|
const valStr = String(v);
|
|
513
|
-
if (/[\x00-\x08\x0a-\x1f]/.test(valStr)) {
|
|
480
|
+
if (/[\x00-\x08\x0a-\x1f]/.test(valStr)) {
|
|
481
|
+
invalidHeaders.push(k);
|
|
482
|
+
}
|
|
514
483
|
}
|
|
515
484
|
if (invalidHeaders.length > 0) {
|
|
516
485
|
delete merged.otlp.headers;
|
|
517
486
|
warnings.push(`OTLP headers blocked due to invalid characters: ${invalidHeaders.join(", ")}`);
|
|
518
487
|
}
|
|
519
488
|
}
|
|
520
|
-
if (
|
|
521
|
-
merged.agents?.overrides &&
|
|
522
|
-
Object.keys(merged.agents.overrides).length === 0
|
|
523
|
-
)
|
|
524
|
-
delete merged.agents.overrides;
|
|
489
|
+
if (merged.agents?.overrides && Object.keys(merged.agents.overrides).length === 0) delete merged.agents.overrides;
|
|
525
490
|
return merged;
|
|
526
491
|
}
|
|
527
492
|
|
|
@@ -580,8 +545,7 @@ function sanitizeObject(obj: Record<string, unknown>): Record<string, unknown> {
|
|
|
580
545
|
}
|
|
581
546
|
|
|
582
547
|
export function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
583
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
584
|
-
return undefined;
|
|
548
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
585
549
|
// Defensive: create a sanitized copy to prevent prototype pollution.
|
|
586
550
|
// Uses Object.create(null) so the result has no prototype chain.
|
|
587
551
|
// WARNING: The returned object has no prototype methods (no hasOwnProperty,
|
|
@@ -590,11 +554,7 @@ export function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
590
554
|
return sanitizeObject(value as Record<string, unknown>);
|
|
591
555
|
}
|
|
592
556
|
|
|
593
|
-
function parseWithSchema<T extends TSchema>(
|
|
594
|
-
schema: T,
|
|
595
|
-
value: unknown,
|
|
596
|
-
context?: string,
|
|
597
|
-
): Static<T> | undefined {
|
|
557
|
+
function parseWithSchema<T extends TSchema>(schema: T, value: unknown, context?: string): Static<T> | undefined {
|
|
598
558
|
if (!Value.Check(schema, value)) {
|
|
599
559
|
if (context) {
|
|
600
560
|
logInternalError("config.parseWithSchema", undefined, `${context}: schema validation failed`);
|
|
@@ -604,18 +564,11 @@ function parseWithSchema<T extends TSchema>(
|
|
|
604
564
|
return Value.Decode(schema, value);
|
|
605
565
|
}
|
|
606
566
|
|
|
607
|
-
function parseIntegerInRange(
|
|
608
|
-
value: unknown,
|
|
609
|
-
minimum = 1,
|
|
610
|
-
maximum = Number.MAX_SAFE_INTEGER,
|
|
611
|
-
): number | undefined {
|
|
567
|
+
function parseIntegerInRange(value: unknown, minimum = 1, maximum = Number.MAX_SAFE_INTEGER): number | undefined {
|
|
612
568
|
return parseWithSchema(Type.Integer({ minimum, maximum }), value);
|
|
613
569
|
}
|
|
614
570
|
|
|
615
|
-
function parsePositiveInteger(
|
|
616
|
-
value: unknown,
|
|
617
|
-
max = Number.MAX_SAFE_INTEGER,
|
|
618
|
-
): number | undefined {
|
|
571
|
+
function parsePositiveInteger(value: unknown, max = Number.MAX_SAFE_INTEGER): number | undefined {
|
|
619
572
|
return parseIntegerInRange(value, 1, max);
|
|
620
573
|
}
|
|
621
574
|
|
|
@@ -626,34 +579,21 @@ function parseProfile(value: unknown): PiTeamsAutonomyProfile | undefined {
|
|
|
626
579
|
function parseStringList(value: unknown): string[] | undefined {
|
|
627
580
|
const items = parseWithSchema(Type.Array(Type.String()), value);
|
|
628
581
|
if (!items || items.length === 0) return undefined;
|
|
629
|
-
const normalized = items
|
|
630
|
-
.map((entry) => entry.trim())
|
|
631
|
-
.filter((entry) => entry.length > 0);
|
|
582
|
+
const normalized = items.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
632
583
|
return normalized.length > 0 ? normalized : undefined;
|
|
633
584
|
}
|
|
634
585
|
|
|
635
586
|
function parseStringArrayOrFalse(value: unknown): string[] | false | undefined {
|
|
636
587
|
if (value === false) return false;
|
|
637
|
-
if (typeof value === "string")
|
|
638
|
-
return value.trim() === "" ? [] : parseStringList(value.split(","));
|
|
588
|
+
if (typeof value === "string") return value.trim() === "" ? [] : parseStringList(value.split(","));
|
|
639
589
|
return parseStringList(value);
|
|
640
590
|
}
|
|
641
591
|
|
|
642
592
|
export function effectiveAutonomousConfig(
|
|
643
593
|
config: PiTeamsAutonomousConfig | undefined,
|
|
644
|
-
): Required<
|
|
645
|
-
Pick<
|
|
646
|
-
PiTeamsAutonomousConfig,
|
|
647
|
-
| "profile"
|
|
648
|
-
| "enabled"
|
|
649
|
-
| "injectPolicy"
|
|
650
|
-
| "preferAsyncForLongTasks"
|
|
651
|
-
| "allowWorktreeSuggestion"
|
|
652
|
-
>
|
|
653
|
-
> &
|
|
594
|
+
): Required<Pick<PiTeamsAutonomousConfig, "profile" | "enabled" | "injectPolicy" | "preferAsyncForLongTasks" | "allowWorktreeSuggestion">> &
|
|
654
595
|
Pick<PiTeamsAutonomousConfig, "magicKeywords"> {
|
|
655
|
-
const profile =
|
|
656
|
-
config?.enabled === false ? "manual" : (config?.profile ?? "suggested");
|
|
596
|
+
const profile = config?.enabled === false ? "manual" : (config?.profile ?? "suggested");
|
|
657
597
|
const profileDefaults: Record<
|
|
658
598
|
PiTeamsAutonomyProfile,
|
|
659
599
|
{
|
|
@@ -693,21 +633,14 @@ export function effectiveAutonomousConfig(
|
|
|
693
633
|
profile,
|
|
694
634
|
enabled: config?.enabled ?? defaults.enabled,
|
|
695
635
|
injectPolicy: config?.injectPolicy ?? defaults.injectPolicy,
|
|
696
|
-
preferAsyncForLongTasks:
|
|
697
|
-
|
|
698
|
-
allowWorktreeSuggestion:
|
|
699
|
-
config?.allowWorktreeSuggestion ?? defaults.allowWorktreeSuggestion,
|
|
636
|
+
preferAsyncForLongTasks: config?.preferAsyncForLongTasks ?? defaults.preferAsyncForLongTasks,
|
|
637
|
+
allowWorktreeSuggestion: config?.allowWorktreeSuggestion ?? defaults.allowWorktreeSuggestion,
|
|
700
638
|
magicKeywords: config?.magicKeywords,
|
|
701
639
|
};
|
|
702
640
|
}
|
|
703
641
|
|
|
704
|
-
function parseStringArrayRecord(
|
|
705
|
-
|
|
706
|
-
): Record<string, string[]> | undefined {
|
|
707
|
-
const record = parseWithSchema(
|
|
708
|
-
Type.Record(Type.String({ minLength: 1 }), Type.Array(Type.String())),
|
|
709
|
-
value,
|
|
710
|
-
);
|
|
642
|
+
function parseStringArrayRecord(value: unknown): Record<string, string[]> | undefined {
|
|
643
|
+
const record = parseWithSchema(Type.Record(Type.String({ minLength: 1 }), Type.Array(Type.String())), value);
|
|
711
644
|
if (!record) return undefined;
|
|
712
645
|
const result: Record<string, string[]> = {};
|
|
713
646
|
for (const [key, rawValues] of Object.entries(record)) {
|
|
@@ -717,87 +650,42 @@ function parseStringArrayRecord(
|
|
|
717
650
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
718
651
|
}
|
|
719
652
|
|
|
720
|
-
function parseAutonomousConfig(
|
|
721
|
-
value: unknown,
|
|
722
|
-
): PiTeamsAutonomousConfig | undefined {
|
|
653
|
+
function parseAutonomousConfig(value: unknown): PiTeamsAutonomousConfig | undefined {
|
|
723
654
|
const obj = asRecord(value);
|
|
724
655
|
if (!obj) return undefined;
|
|
725
656
|
const config: PiTeamsAutonomousConfig = {
|
|
726
657
|
profile: parseProfile(obj.profile),
|
|
727
658
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
728
659
|
injectPolicy: parseWithSchema(Type.Boolean(), obj.injectPolicy),
|
|
729
|
-
preferAsyncForLongTasks: parseWithSchema(
|
|
730
|
-
|
|
731
|
-
obj.preferAsyncForLongTasks,
|
|
732
|
-
),
|
|
733
|
-
allowWorktreeSuggestion: parseWithSchema(
|
|
734
|
-
Type.Boolean(),
|
|
735
|
-
obj.allowWorktreeSuggestion,
|
|
736
|
-
),
|
|
660
|
+
preferAsyncForLongTasks: parseWithSchema(Type.Boolean(), obj.preferAsyncForLongTasks),
|
|
661
|
+
allowWorktreeSuggestion: parseWithSchema(Type.Boolean(), obj.allowWorktreeSuggestion),
|
|
737
662
|
magicKeywords: parseStringArrayRecord(obj.magicKeywords),
|
|
738
663
|
};
|
|
739
|
-
return Object.values(config).some((entry) => entry !== undefined)
|
|
740
|
-
? config
|
|
741
|
-
: undefined;
|
|
664
|
+
return Object.values(config).some((entry) => entry !== undefined) ? config : undefined;
|
|
742
665
|
}
|
|
743
666
|
|
|
744
667
|
function parseLimitsConfig(value: unknown): CrewLimitsConfig | undefined {
|
|
745
668
|
const obj = asRecord(value);
|
|
746
669
|
if (!obj) return undefined;
|
|
747
670
|
const limits: CrewLimitsConfig = {
|
|
748
|
-
maxConcurrentWorkers: parsePositiveInteger(
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
),
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
),
|
|
756
|
-
maxTaskDepth: parsePositiveInteger(
|
|
757
|
-
obj.maxTaskDepth,
|
|
758
|
-
LIMIT_CEILINGS.maxTaskDepth,
|
|
759
|
-
),
|
|
760
|
-
maxChildrenPerTask: parsePositiveInteger(
|
|
761
|
-
obj.maxChildrenPerTask,
|
|
762
|
-
LIMIT_CEILINGS.maxChildrenPerTask,
|
|
763
|
-
),
|
|
764
|
-
maxRunMinutes: parsePositiveInteger(
|
|
765
|
-
obj.maxRunMinutes,
|
|
766
|
-
LIMIT_CEILINGS.maxRunMinutes,
|
|
767
|
-
),
|
|
768
|
-
maxRetriesPerTask: parsePositiveInteger(
|
|
769
|
-
obj.maxRetriesPerTask,
|
|
770
|
-
LIMIT_CEILINGS.maxRetriesPerTask,
|
|
771
|
-
),
|
|
772
|
-
maxTasksPerRun: parsePositiveInteger(
|
|
773
|
-
obj.maxTasksPerRun,
|
|
774
|
-
LIMIT_CEILINGS.maxTasksPerRun,
|
|
775
|
-
),
|
|
776
|
-
heartbeatStaleMs: parsePositiveInteger(
|
|
777
|
-
obj.heartbeatStaleMs,
|
|
778
|
-
LIMIT_CEILINGS.heartbeatStaleMs,
|
|
779
|
-
),
|
|
671
|
+
maxConcurrentWorkers: parsePositiveInteger(obj.maxConcurrentWorkers, LIMIT_CEILINGS.maxConcurrentWorkers),
|
|
672
|
+
allowUnboundedConcurrency: parseWithSchema(Type.Boolean(), obj.allowUnboundedConcurrency),
|
|
673
|
+
maxTaskDepth: parsePositiveInteger(obj.maxTaskDepth, LIMIT_CEILINGS.maxTaskDepth),
|
|
674
|
+
maxChildrenPerTask: parsePositiveInteger(obj.maxChildrenPerTask, LIMIT_CEILINGS.maxChildrenPerTask),
|
|
675
|
+
maxRunMinutes: parsePositiveInteger(obj.maxRunMinutes, LIMIT_CEILINGS.maxRunMinutes),
|
|
676
|
+
maxRetriesPerTask: parsePositiveInteger(obj.maxRetriesPerTask, LIMIT_CEILINGS.maxRetriesPerTask),
|
|
677
|
+
maxTasksPerRun: parsePositiveInteger(obj.maxTasksPerRun, LIMIT_CEILINGS.maxTasksPerRun),
|
|
678
|
+
heartbeatStaleMs: parsePositiveInteger(obj.heartbeatStaleMs, LIMIT_CEILINGS.heartbeatStaleMs),
|
|
780
679
|
};
|
|
781
|
-
return Object.values(limits).some((entry) => entry !== undefined)
|
|
782
|
-
? limits
|
|
783
|
-
: undefined;
|
|
680
|
+
return Object.values(limits).some((entry) => entry !== undefined) ? limits : undefined;
|
|
784
681
|
}
|
|
785
682
|
|
|
786
|
-
function parseIsolationPolicy(
|
|
787
|
-
value: unknown,
|
|
788
|
-
): CrewRuntimeConfig["isolationPolicy"] | undefined {
|
|
683
|
+
function parseIsolationPolicy(value: unknown): CrewRuntimeConfig["isolationPolicy"] | undefined {
|
|
789
684
|
const obj = asRecord(value);
|
|
790
685
|
if (!obj) return undefined;
|
|
791
686
|
const isolatedRoles = parseStringList(obj.isolatedRoles);
|
|
792
|
-
const defaultRuntime = parseWithSchema(
|
|
793
|
-
|
|
794
|
-
Type.Literal("live-session"),
|
|
795
|
-
Type.Literal("child-process"),
|
|
796
|
-
]),
|
|
797
|
-
obj.defaultRuntime,
|
|
798
|
-
);
|
|
799
|
-
if (isolatedRoles === undefined && defaultRuntime === undefined)
|
|
800
|
-
return undefined;
|
|
687
|
+
const defaultRuntime = parseWithSchema(Type.Union([Type.Literal("live-session"), Type.Literal("child-process")]), obj.defaultRuntime);
|
|
688
|
+
if (isolatedRoles === undefined && defaultRuntime === undefined) return undefined;
|
|
801
689
|
return {
|
|
802
690
|
...(isolatedRoles !== undefined ? { isolatedRoles } : {}),
|
|
803
691
|
...(defaultRuntime !== undefined ? { defaultRuntime } : {}),
|
|
@@ -809,73 +697,29 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
|
|
|
809
697
|
if (!obj) return undefined;
|
|
810
698
|
const runtime: CrewRuntimeConfig = {
|
|
811
699
|
mode: parseWithSchema(
|
|
812
|
-
Type.Union([
|
|
813
|
-
Type.Literal("auto"),
|
|
814
|
-
Type.Literal("scaffold"),
|
|
815
|
-
Type.Literal("child-process"),
|
|
816
|
-
Type.Literal("live-session"),
|
|
817
|
-
]),
|
|
700
|
+
Type.Union([Type.Literal("auto"), Type.Literal("scaffold"), Type.Literal("child-process"), Type.Literal("live-session")]),
|
|
818
701
|
obj.mode,
|
|
819
702
|
),
|
|
820
|
-
preferLiveSession: parseWithSchema(
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
),
|
|
824
|
-
allowChildProcessFallback: parseWithSchema(
|
|
825
|
-
Type.Boolean(),
|
|
826
|
-
obj.allowChildProcessFallback,
|
|
827
|
-
),
|
|
828
|
-
maxTurns: parsePositiveInteger(
|
|
829
|
-
obj.maxTurns,
|
|
830
|
-
LIMIT_CEILINGS.runtimeMaxTurns,
|
|
831
|
-
),
|
|
832
|
-
graceTurns: parsePositiveInteger(
|
|
833
|
-
obj.graceTurns,
|
|
834
|
-
LIMIT_CEILINGS.runtimeGraceTurns,
|
|
835
|
-
),
|
|
703
|
+
preferLiveSession: parseWithSchema(Type.Boolean(), obj.preferLiveSession),
|
|
704
|
+
allowChildProcessFallback: parseWithSchema(Type.Boolean(), obj.allowChildProcessFallback),
|
|
705
|
+
maxTurns: parsePositiveInteger(obj.maxTurns, LIMIT_CEILINGS.runtimeMaxTurns),
|
|
706
|
+
graceTurns: parsePositiveInteger(obj.graceTurns, LIMIT_CEILINGS.runtimeGraceTurns),
|
|
836
707
|
inheritContext: parseWithSchema(Type.Boolean(), obj.inheritContext),
|
|
837
|
-
promptMode: parseWithSchema(
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
),
|
|
841
|
-
groupJoin: parseWithSchema(
|
|
842
|
-
Type.Union([
|
|
843
|
-
Type.Literal("off"),
|
|
844
|
-
Type.Literal("group"),
|
|
845
|
-
Type.Literal("smart"),
|
|
846
|
-
]),
|
|
847
|
-
obj.groupJoin,
|
|
848
|
-
),
|
|
849
|
-
groupJoinAckTimeoutMs: parsePositiveInteger(
|
|
850
|
-
obj.groupJoinAckTimeoutMs,
|
|
851
|
-
86_400_000,
|
|
852
|
-
),
|
|
853
|
-
requirePlanApproval: parseWithSchema(
|
|
854
|
-
Type.Boolean(),
|
|
855
|
-
obj.requirePlanApproval,
|
|
856
|
-
),
|
|
708
|
+
promptMode: parseWithSchema(Type.Union([Type.Literal("replace"), Type.Literal("append")]), obj.promptMode),
|
|
709
|
+
groupJoin: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("group"), Type.Literal("smart")]), obj.groupJoin),
|
|
710
|
+
groupJoinAckTimeoutMs: parsePositiveInteger(obj.groupJoinAckTimeoutMs, 86_400_000),
|
|
711
|
+
requirePlanApproval: parseWithSchema(Type.Boolean(), obj.requirePlanApproval),
|
|
857
712
|
completionMutationGuard: parseWithSchema(
|
|
858
|
-
Type.Union([
|
|
859
|
-
Type.Literal("off"),
|
|
860
|
-
Type.Literal("warn"),
|
|
861
|
-
Type.Literal("fail"),
|
|
862
|
-
]),
|
|
713
|
+
Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")]),
|
|
863
714
|
obj.completionMutationGuard,
|
|
864
715
|
),
|
|
865
716
|
effectivenessGuard: parseWithSchema(
|
|
866
|
-
Type.Union([
|
|
867
|
-
Type.Literal("off"),
|
|
868
|
-
Type.Literal("warn"),
|
|
869
|
-
Type.Literal("block"),
|
|
870
|
-
Type.Literal("fail"),
|
|
871
|
-
]),
|
|
717
|
+
Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]),
|
|
872
718
|
obj.effectivenessGuard,
|
|
873
719
|
),
|
|
874
720
|
isolationPolicy: parseIsolationPolicy(obj.isolationPolicy),
|
|
875
721
|
};
|
|
876
|
-
return Object.values(runtime).some((entry) => entry !== undefined)
|
|
877
|
-
? runtime
|
|
878
|
-
: undefined;
|
|
722
|
+
return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
|
|
879
723
|
}
|
|
880
724
|
|
|
881
725
|
function parseControlConfig(value: unknown): CrewControlConfig | undefined {
|
|
@@ -885,9 +729,7 @@ function parseControlConfig(value: unknown): CrewControlConfig | undefined {
|
|
|
885
729
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
886
730
|
needsAttentionAfterMs: parsePositiveInteger(obj.needsAttentionAfterMs),
|
|
887
731
|
};
|
|
888
|
-
return Object.values(control).some((entry) => entry !== undefined)
|
|
889
|
-
? control
|
|
890
|
-
: undefined;
|
|
732
|
+
return Object.values(control).some((entry) => entry !== undefined) ? control : undefined;
|
|
891
733
|
}
|
|
892
734
|
|
|
893
735
|
function parseWorktreeConfig(value: unknown): CrewWorktreeConfig | undefined {
|
|
@@ -897,21 +739,14 @@ function parseWorktreeConfig(value: unknown): CrewWorktreeConfig | undefined {
|
|
|
897
739
|
const setupHook = rawSetupHook?.trim();
|
|
898
740
|
const worktree: CrewWorktreeConfig = {
|
|
899
741
|
setupHook: setupHook ? setupHook : undefined,
|
|
900
|
-
setupHookTimeoutMs: parsePositiveInteger(
|
|
901
|
-
obj.setupHookTimeoutMs,
|
|
902
|
-
300_000,
|
|
903
|
-
),
|
|
742
|
+
setupHookTimeoutMs: parsePositiveInteger(obj.setupHookTimeoutMs, 300_000),
|
|
904
743
|
linkNodeModules: parseWithSchema(Type.Boolean(), obj.linkNodeModules),
|
|
905
744
|
};
|
|
906
|
-
return Object.values(worktree).some((entry) => entry !== undefined)
|
|
907
|
-
? worktree
|
|
908
|
-
: undefined;
|
|
745
|
+
return Object.values(worktree).some((entry) => entry !== undefined) ? worktree : undefined;
|
|
909
746
|
}
|
|
910
747
|
|
|
911
748
|
/** Parse goalWrap config (RFC v0.5 vision: apply goal completion-guarantee to builtins). */
|
|
912
|
-
function parseGoalWrapConfig(
|
|
913
|
-
value: unknown,
|
|
914
|
-
): Record<string, GoalWrapWorkflowConfig> | undefined {
|
|
749
|
+
function parseGoalWrapConfig(value: unknown): Record<string, GoalWrapWorkflowConfig> | undefined {
|
|
915
750
|
const obj = asRecord(value);
|
|
916
751
|
if (!obj) return undefined;
|
|
917
752
|
const result: Record<string, GoalWrapWorkflowConfig> = {};
|
|
@@ -932,9 +767,12 @@ function parseGoalWrapConfig(
|
|
|
932
767
|
const commands = Array.isArray(verObj.commands)
|
|
933
768
|
? verObj.commands.filter((c): c is string => typeof c === "string" && c.length > 0)
|
|
934
769
|
: undefined;
|
|
935
|
-
const mode = verObj.mode === "text-only" ? "text-only" as const : undefined;
|
|
770
|
+
const mode = verObj.mode === "text-only" ? ("text-only" as const) : undefined;
|
|
936
771
|
if (commands || mode) {
|
|
937
|
-
parsed.verification = {
|
|
772
|
+
parsed.verification = {
|
|
773
|
+
...(commands ? { commands } : { commands: [] }),
|
|
774
|
+
...(mode ? { mode } : {}),
|
|
775
|
+
};
|
|
938
776
|
}
|
|
939
777
|
}
|
|
940
778
|
if (Object.values(parsed).some((v) => v !== undefined)) {
|
|
@@ -950,65 +788,34 @@ function parseAgentOverride(value: unknown): AgentOverrideConfig | undefined {
|
|
|
950
788
|
if (!obj) return undefined;
|
|
951
789
|
const override: AgentOverrideConfig = {
|
|
952
790
|
disabled: parseWithSchema(Type.Boolean(), obj.disabled),
|
|
953
|
-
model: parseWithSchema(
|
|
954
|
-
Type.Union([Type.String(), Type.Literal(false)]),
|
|
955
|
-
obj.model,
|
|
956
|
-
),
|
|
791
|
+
model: parseWithSchema(Type.Union([Type.String(), Type.Literal(false)]), obj.model),
|
|
957
792
|
fallbackModels: parseStringArrayOrFalse(obj.fallbackModels),
|
|
958
|
-
thinking: parseWithSchema(
|
|
959
|
-
Type.Union([Type.String(), Type.Literal(false)]),
|
|
960
|
-
obj.thinking,
|
|
961
|
-
),
|
|
793
|
+
thinking: parseWithSchema(Type.Union([Type.String(), Type.Literal(false)]), obj.thinking),
|
|
962
794
|
tools: parseStringArrayOrFalse(obj.tools),
|
|
963
795
|
skills: parseStringArrayOrFalse(obj.skills),
|
|
964
796
|
};
|
|
965
|
-
return Object.values(override).some((entry) => entry !== undefined)
|
|
966
|
-
? override
|
|
967
|
-
: undefined;
|
|
797
|
+
return Object.values(override).some((entry) => entry !== undefined) ? override : undefined;
|
|
968
798
|
}
|
|
969
799
|
|
|
970
800
|
function parseUiConfig(value: unknown): CrewUiConfig | undefined {
|
|
971
801
|
const obj = asRecord(value);
|
|
972
802
|
if (!obj) return undefined;
|
|
973
|
-
const rawWidgetPlacement = parseWithSchema(
|
|
974
|
-
|
|
975
|
-
obj.widgetPlacement,
|
|
976
|
-
);
|
|
977
|
-
const rawDashboardPlacement = parseWithSchema(
|
|
978
|
-
Type.Union([Type.Literal("center"), Type.Literal("right")]),
|
|
979
|
-
obj.dashboardPlacement,
|
|
980
|
-
);
|
|
803
|
+
const rawWidgetPlacement = parseWithSchema(Type.Union([Type.Literal("aboveEditor"), Type.Literal("belowEditor")]), obj.widgetPlacement);
|
|
804
|
+
const rawDashboardPlacement = parseWithSchema(Type.Union([Type.Literal("center"), Type.Literal("right")]), obj.dashboardPlacement);
|
|
981
805
|
const ui: CrewUiConfig = {
|
|
982
806
|
widgetPlacement: rawWidgetPlacement,
|
|
983
807
|
widgetMaxLines: parsePositiveInteger(obj.widgetMaxLines, 50),
|
|
984
808
|
powerbar: parseWithSchema(Type.Boolean(), obj.powerbar),
|
|
985
809
|
dashboardPlacement: rawDashboardPlacement,
|
|
986
810
|
dashboardWidth: parseIntegerInRange(obj.dashboardWidth, 32, 120),
|
|
987
|
-
dashboardLiveRefreshMs: parseIntegerInRange(
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
60_000,
|
|
991
|
-
),
|
|
992
|
-
autoOpenDashboard: parseWithSchema(
|
|
993
|
-
Type.Boolean(),
|
|
994
|
-
obj.autoOpenDashboard,
|
|
995
|
-
),
|
|
996
|
-
autoOpenDashboardForForegroundRuns: parseWithSchema(
|
|
997
|
-
Type.Boolean(),
|
|
998
|
-
obj.autoOpenDashboardForForegroundRuns,
|
|
999
|
-
),
|
|
811
|
+
dashboardLiveRefreshMs: parseIntegerInRange(obj.dashboardLiveRefreshMs, 250, 60_000),
|
|
812
|
+
autoOpenDashboard: parseWithSchema(Type.Boolean(), obj.autoOpenDashboard),
|
|
813
|
+
autoOpenDashboardForForegroundRuns: parseWithSchema(Type.Boolean(), obj.autoOpenDashboardForForegroundRuns),
|
|
1000
814
|
showModel: parseWithSchema(Type.Boolean(), obj.showModel),
|
|
1001
815
|
showTokens: parseWithSchema(Type.Boolean(), obj.showTokens),
|
|
1002
816
|
showTools: parseWithSchema(Type.Boolean(), obj.showTools),
|
|
1003
|
-
transcriptTailBytes: parseIntegerInRange(
|
|
1004
|
-
|
|
1005
|
-
1024,
|
|
1006
|
-
50 * 1024 * 1024,
|
|
1007
|
-
),
|
|
1008
|
-
mascotStyle: parseWithSchema(
|
|
1009
|
-
Type.Union([Type.Literal("cat"), Type.Literal("armin")]),
|
|
1010
|
-
obj.mascotStyle,
|
|
1011
|
-
),
|
|
817
|
+
transcriptTailBytes: parseIntegerInRange(obj.transcriptTailBytes, 1024, 50 * 1024 * 1024),
|
|
818
|
+
mascotStyle: parseWithSchema(Type.Union([Type.Literal("cat"), Type.Literal("armin")]), obj.mascotStyle),
|
|
1012
819
|
mascotEffect: parseWithSchema(
|
|
1013
820
|
Type.Union([
|
|
1014
821
|
Type.Literal("random"),
|
|
@@ -1024,23 +831,15 @@ function parseUiConfig(value: unknown): CrewUiConfig | undefined {
|
|
|
1024
831
|
obj.mascotEffect,
|
|
1025
832
|
),
|
|
1026
833
|
};
|
|
1027
|
-
return Object.values(ui).some((entry) => entry !== undefined)
|
|
1028
|
-
? ui
|
|
1029
|
-
: undefined;
|
|
834
|
+
return Object.values(ui).some((entry) => entry !== undefined) ? ui : undefined;
|
|
1030
835
|
}
|
|
1031
836
|
|
|
1032
837
|
function parseAgentsConfig(value: unknown): CrewAgentsConfig | undefined {
|
|
1033
838
|
const obj = asRecord(value);
|
|
1034
839
|
if (!obj) return undefined;
|
|
1035
840
|
const overrides: Record<string, AgentOverrideConfig> = {};
|
|
1036
|
-
if (
|
|
1037
|
-
obj.overrides
|
|
1038
|
-
typeof obj.overrides === "object" &&
|
|
1039
|
-
!Array.isArray(obj.overrides)
|
|
1040
|
-
) {
|
|
1041
|
-
for (const [name, rawOverride] of Object.entries(
|
|
1042
|
-
obj.overrides as Record<string, unknown>,
|
|
1043
|
-
)) {
|
|
841
|
+
if (obj.overrides && typeof obj.overrides === "object" && !Array.isArray(obj.overrides)) {
|
|
842
|
+
for (const [name, rawOverride] of Object.entries(obj.overrides as Record<string, unknown>)) {
|
|
1044
843
|
const parsed = parseAgentOverride(rawOverride);
|
|
1045
844
|
if (parsed && name.trim()) overrides[name.trim()] = parsed;
|
|
1046
845
|
}
|
|
@@ -1049,28 +848,18 @@ function parseAgentsConfig(value: unknown): CrewAgentsConfig | undefined {
|
|
|
1049
848
|
disableBuiltins: parseWithSchema(Type.Boolean(), obj.disableBuiltins),
|
|
1050
849
|
overrides: Object.keys(overrides).length > 0 ? overrides : undefined,
|
|
1051
850
|
};
|
|
1052
|
-
return Object.values(agents).some((entry) => entry !== undefined)
|
|
1053
|
-
? agents
|
|
1054
|
-
: undefined;
|
|
851
|
+
return Object.values(agents).some((entry) => entry !== undefined) ? agents : undefined;
|
|
1055
852
|
}
|
|
1056
853
|
|
|
1057
854
|
function parseToolsConfig(value: unknown): CrewToolsConfig | undefined {
|
|
1058
855
|
const obj = asRecord(value);
|
|
1059
856
|
if (!obj) return undefined;
|
|
1060
857
|
const tools: CrewToolsConfig = {
|
|
1061
|
-
enableClaudeStyleAliases: parseWithSchema(
|
|
1062
|
-
Type.Boolean(),
|
|
1063
|
-
obj.enableClaudeStyleAliases,
|
|
1064
|
-
),
|
|
858
|
+
enableClaudeStyleAliases: parseWithSchema(Type.Boolean(), obj.enableClaudeStyleAliases),
|
|
1065
859
|
enableSteer: parseWithSchema(Type.Boolean(), obj.enableSteer),
|
|
1066
|
-
terminateOnForeground: parseWithSchema(
|
|
1067
|
-
Type.Boolean(),
|
|
1068
|
-
obj.terminateOnForeground,
|
|
1069
|
-
),
|
|
860
|
+
terminateOnForeground: parseWithSchema(Type.Boolean(), obj.terminateOnForeground),
|
|
1070
861
|
};
|
|
1071
|
-
return Object.values(tools).some((entry) => entry !== undefined)
|
|
1072
|
-
? tools
|
|
1073
|
-
: undefined;
|
|
862
|
+
return Object.values(tools).some((entry) => entry !== undefined) ? tools : undefined;
|
|
1074
863
|
}
|
|
1075
864
|
|
|
1076
865
|
function parseTelemetryConfig(value: unknown): CrewTelemetryConfig | undefined {
|
|
@@ -1079,125 +868,68 @@ function parseTelemetryConfig(value: unknown): CrewTelemetryConfig | undefined {
|
|
|
1079
868
|
const telemetry: CrewTelemetryConfig = {
|
|
1080
869
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
1081
870
|
};
|
|
1082
|
-
return Object.values(telemetry).some((entry) => entry !== undefined)
|
|
1083
|
-
? telemetry
|
|
1084
|
-
: undefined;
|
|
871
|
+
return Object.values(telemetry).some((entry) => entry !== undefined) ? telemetry : undefined;
|
|
1085
872
|
}
|
|
1086
873
|
|
|
1087
874
|
function parsePolicyConfig(value: unknown): CrewPolicyConfig | undefined {
|
|
1088
875
|
const obj = asRecord(value);
|
|
1089
876
|
if (!obj) return undefined;
|
|
1090
877
|
const policy: CrewPolicyConfig = {
|
|
1091
|
-
requireIntentForDestructiveActions: parseWithSchema(
|
|
1092
|
-
|
|
1093
|
-
obj.requireIntentForDestructiveActions,
|
|
1094
|
-
),
|
|
1095
|
-
disabledCapabilities: parseWithSchema(
|
|
1096
|
-
Type.Array(Type.String()),
|
|
1097
|
-
obj.disabledCapabilities,
|
|
1098
|
-
),
|
|
878
|
+
requireIntentForDestructiveActions: parseWithSchema(Type.Boolean(), obj.requireIntentForDestructiveActions),
|
|
879
|
+
disabledCapabilities: parseWithSchema(Type.Array(Type.String()), obj.disabledCapabilities),
|
|
1099
880
|
};
|
|
1100
|
-
return Object.values(policy).some((entry) => entry !== undefined)
|
|
1101
|
-
? policy
|
|
1102
|
-
: undefined;
|
|
881
|
+
return Object.values(policy).some((entry) => entry !== undefined) ? policy : undefined;
|
|
1103
882
|
}
|
|
1104
883
|
|
|
1105
|
-
function parseNotificationsConfig(
|
|
1106
|
-
value: unknown,
|
|
1107
|
-
): CrewNotificationsConfig | undefined {
|
|
884
|
+
function parseNotificationsConfig(value: unknown): CrewNotificationsConfig | undefined {
|
|
1108
885
|
const obj = asRecord(value);
|
|
1109
886
|
if (!obj) return undefined;
|
|
1110
887
|
const notifications: CrewNotificationsConfig = {
|
|
1111
888
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
1112
889
|
severityFilter: parseWithSchema(
|
|
1113
|
-
Type.Array(
|
|
1114
|
-
Type.Union([
|
|
1115
|
-
Type.Literal("info"),
|
|
1116
|
-
Type.Literal("warning"),
|
|
1117
|
-
Type.Literal("error"),
|
|
1118
|
-
Type.Literal("critical"),
|
|
1119
|
-
]),
|
|
1120
|
-
),
|
|
890
|
+
Type.Array(Type.Union([Type.Literal("info"), Type.Literal("warning"), Type.Literal("error"), Type.Literal("critical")])),
|
|
1121
891
|
obj.severityFilter,
|
|
1122
892
|
),
|
|
1123
|
-
dedupWindowMs: parsePositiveInteger(
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
),
|
|
1127
|
-
batchWindowMs: parseWithSchema(
|
|
1128
|
-
Type.Integer({ minimum: 0, maximum: 60_000 }),
|
|
1129
|
-
obj.batchWindowMs,
|
|
1130
|
-
),
|
|
1131
|
-
quietHours: parseWithSchema(
|
|
1132
|
-
Type.String({ pattern: "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$" }),
|
|
1133
|
-
obj.quietHours,
|
|
1134
|
-
),
|
|
893
|
+
dedupWindowMs: parsePositiveInteger(obj.dedupWindowMs, 24 * 60 * 60 * 1000),
|
|
894
|
+
batchWindowMs: parseWithSchema(Type.Integer({ minimum: 0, maximum: 60_000 }), obj.batchWindowMs),
|
|
895
|
+
quietHours: parseWithSchema(Type.String({ pattern: "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$" }), obj.quietHours),
|
|
1135
896
|
sinkRetentionDays: parsePositiveInteger(obj.sinkRetentionDays, 90),
|
|
1136
897
|
};
|
|
1137
|
-
return Object.values(notifications).some((entry) => entry !== undefined)
|
|
1138
|
-
? notifications
|
|
1139
|
-
: undefined;
|
|
898
|
+
return Object.values(notifications).some((entry) => entry !== undefined) ? notifications : undefined;
|
|
1140
899
|
}
|
|
1141
900
|
|
|
1142
|
-
function parseObservabilityConfig(
|
|
1143
|
-
value: unknown,
|
|
1144
|
-
): CrewObservabilityConfig | undefined {
|
|
901
|
+
function parseObservabilityConfig(value: unknown): CrewObservabilityConfig | undefined {
|
|
1145
902
|
const obj = asRecord(value);
|
|
1146
903
|
if (!obj) return undefined;
|
|
1147
904
|
const observability: CrewObservabilityConfig = {
|
|
1148
905
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
1149
|
-
pollIntervalMs: parseWithSchema(
|
|
1150
|
-
Type.Integer({ minimum: 1000, maximum: 60_000 }),
|
|
1151
|
-
obj.pollIntervalMs,
|
|
1152
|
-
),
|
|
906
|
+
pollIntervalMs: parseWithSchema(Type.Integer({ minimum: 1000, maximum: 60_000 }), obj.pollIntervalMs),
|
|
1153
907
|
metricRetentionDays: parsePositiveInteger(obj.metricRetentionDays, 365),
|
|
1154
908
|
};
|
|
1155
|
-
return Object.values(observability).some((entry) => entry !== undefined)
|
|
1156
|
-
? observability
|
|
1157
|
-
: undefined;
|
|
909
|
+
return Object.values(observability).some((entry) => entry !== undefined) ? observability : undefined;
|
|
1158
910
|
}
|
|
1159
911
|
|
|
1160
|
-
function parseReliabilityConfig(
|
|
1161
|
-
value: unknown,
|
|
1162
|
-
): CrewReliabilityConfig | undefined {
|
|
912
|
+
function parseReliabilityConfig(value: unknown): CrewReliabilityConfig | undefined {
|
|
1163
913
|
const obj = asRecord(value);
|
|
1164
914
|
if (!obj) return undefined;
|
|
1165
915
|
const retryObj = asRecord(obj.retryPolicy);
|
|
1166
916
|
const retryPolicy: CrewRetryPolicyConfig | undefined = retryObj
|
|
1167
917
|
? {
|
|
1168
918
|
maxAttempts: parsePositiveInteger(retryObj.maxAttempts, 10),
|
|
1169
|
-
backoffMs: parseWithSchema(
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
),
|
|
1173
|
-
jitterRatio: parseWithSchema(
|
|
1174
|
-
Type.Number({ minimum: 0, maximum: 1 }),
|
|
1175
|
-
retryObj.jitterRatio,
|
|
1176
|
-
),
|
|
1177
|
-
exponentialFactor: parseWithSchema(
|
|
1178
|
-
Type.Number({ minimum: 1, maximum: 5 }),
|
|
1179
|
-
retryObj.exponentialFactor,
|
|
1180
|
-
),
|
|
919
|
+
backoffMs: parseWithSchema(Type.Integer({ minimum: 100, maximum: 60_000 }), retryObj.backoffMs),
|
|
920
|
+
jitterRatio: parseWithSchema(Type.Number({ minimum: 0, maximum: 1 }), retryObj.jitterRatio),
|
|
921
|
+
exponentialFactor: parseWithSchema(Type.Number({ minimum: 1, maximum: 5 }), retryObj.exponentialFactor),
|
|
1181
922
|
retryableErrors: parseStringList(retryObj.retryableErrors),
|
|
1182
923
|
}
|
|
1183
924
|
: undefined;
|
|
1184
925
|
const reliability: CrewReliabilityConfig = {
|
|
1185
926
|
autoRetry: parseWithSchema(Type.Boolean(), obj.autoRetry),
|
|
1186
|
-
retryPolicy:
|
|
1187
|
-
retryPolicy &&
|
|
1188
|
-
Object.values(retryPolicy).some((entry) => entry !== undefined)
|
|
1189
|
-
? retryPolicy
|
|
1190
|
-
: undefined,
|
|
927
|
+
retryPolicy: retryPolicy && Object.values(retryPolicy).some((entry) => entry !== undefined) ? retryPolicy : undefined,
|
|
1191
928
|
autoRecover: parseWithSchema(Type.Boolean(), obj.autoRecover),
|
|
1192
929
|
deadletterThreshold: parsePositiveInteger(obj.deadletterThreshold),
|
|
1193
|
-
cleanupOrphanedTempDirs: parseWithSchema(
|
|
1194
|
-
Type.Boolean(),
|
|
1195
|
-
obj.cleanupOrphanedTempDirs,
|
|
1196
|
-
),
|
|
930
|
+
cleanupOrphanedTempDirs: parseWithSchema(Type.Boolean(), obj.cleanupOrphanedTempDirs),
|
|
1197
931
|
};
|
|
1198
|
-
return Object.values(reliability).some((entry) => entry !== undefined)
|
|
1199
|
-
? reliability
|
|
1200
|
-
: undefined;
|
|
932
|
+
return Object.values(reliability).some((entry) => entry !== undefined) ? reliability : undefined;
|
|
1201
933
|
}
|
|
1202
934
|
|
|
1203
935
|
function parseOtlpConfig(value: unknown): CrewOtlpConfig | undefined {
|
|
@@ -1236,14 +968,9 @@ function parseOtlpConfig(value: unknown): CrewOtlpConfig | undefined {
|
|
|
1236
968
|
enabled: parseWithSchema(Type.Boolean(), obj.enabled),
|
|
1237
969
|
endpoint: parseWithSchema(Type.String({ minLength: 1 }), obj.endpoint),
|
|
1238
970
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1239
|
-
intervalMs: parseWithSchema(
|
|
1240
|
-
Type.Integer({ minimum: 5000 }),
|
|
1241
|
-
obj.intervalMs,
|
|
1242
|
-
),
|
|
971
|
+
intervalMs: parseWithSchema(Type.Integer({ minimum: 5000 }), obj.intervalMs),
|
|
1243
972
|
};
|
|
1244
|
-
return Object.values(otlp).some((entry) => entry !== undefined)
|
|
1245
|
-
? otlp
|
|
1246
|
-
: undefined;
|
|
973
|
+
return Object.values(otlp).some((entry) => entry !== undefined) ? otlp : undefined;
|
|
1247
974
|
}
|
|
1248
975
|
|
|
1249
976
|
export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
@@ -1252,14 +979,8 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
|
1252
979
|
return {
|
|
1253
980
|
asyncByDefault: parseWithSchema(Type.Boolean(), obj.asyncByDefault),
|
|
1254
981
|
executeWorkers: parseWithSchema(Type.Boolean(), obj.executeWorkers),
|
|
1255
|
-
notifierIntervalMs: parseWithSchema(
|
|
1256
|
-
|
|
1257
|
-
obj.notifierIntervalMs,
|
|
1258
|
-
),
|
|
1259
|
-
requireCleanWorktreeLeader: parseWithSchema(
|
|
1260
|
-
Type.Boolean(),
|
|
1261
|
-
obj.requireCleanWorktreeLeader,
|
|
1262
|
-
),
|
|
982
|
+
notifierIntervalMs: parseWithSchema(Type.Number({ minimum: 1_000 }), obj.notifierIntervalMs),
|
|
983
|
+
requireCleanWorktreeLeader: parseWithSchema(Type.Boolean(), obj.requireCleanWorktreeLeader),
|
|
1263
984
|
autonomous: parseAutonomousConfig(obj.autonomous),
|
|
1264
985
|
limits: parseLimitsConfig(obj.limits),
|
|
1265
986
|
runtime: parseRuntimeConfig(obj.runtime),
|
|
@@ -1279,8 +1000,7 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
|
|
|
1279
1000
|
}
|
|
1280
1001
|
|
|
1281
1002
|
export function parseConfigWithWarnings(raw: unknown): ConfigValidationResult {
|
|
1282
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1283
|
-
return { config: {}, warnings: [] };
|
|
1003
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { config: {}, warnings: [] };
|
|
1284
1004
|
const parsed = parseConfig(raw);
|
|
1285
1005
|
const warnings = validateConfigWithWarnings(raw as Record<string, unknown>);
|
|
1286
1006
|
return { config: parsed, warnings };
|
|
@@ -1292,8 +1012,7 @@ function unsetPath(record: Record<string, unknown>, dottedPath: string): void {
|
|
|
1292
1012
|
let target: Record<string, unknown> = record;
|
|
1293
1013
|
for (const part of parts.slice(0, -1)) {
|
|
1294
1014
|
const current = target[part];
|
|
1295
|
-
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
1296
|
-
return;
|
|
1015
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return;
|
|
1297
1016
|
target = current as Record<string, unknown>;
|
|
1298
1017
|
}
|
|
1299
1018
|
delete target[parts[parts.length - 1]!];
|
|
@@ -1333,17 +1052,14 @@ function readOptionalConfig(filePath: string): {
|
|
|
1333
1052
|
config: PiTeamsConfig;
|
|
1334
1053
|
warnings: string[];
|
|
1335
1054
|
} {
|
|
1336
|
-
if (!fs.existsSync(filePath))
|
|
1337
|
-
return { exists: false, config: {}, warnings: [] };
|
|
1055
|
+
if (!fs.existsSync(filePath)) return { exists: false, config: {}, warnings: [] };
|
|
1338
1056
|
try {
|
|
1339
1057
|
const raw = readConfigRecord(filePath);
|
|
1340
1058
|
const parsed = parseConfigWithWarnings(raw);
|
|
1341
1059
|
return {
|
|
1342
1060
|
exists: true,
|
|
1343
1061
|
config: parsed.config,
|
|
1344
|
-
warnings: parsed.warnings.map(
|
|
1345
|
-
(warning) => `${filePath}: ${warning}`,
|
|
1346
|
-
),
|
|
1062
|
+
warnings: parsed.warnings.map((warning) => `${filePath}: ${warning}`),
|
|
1347
1063
|
};
|
|
1348
1064
|
} catch (error) {
|
|
1349
1065
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1358,6 +1074,27 @@ function readOptionalConfig(filePath: string): {
|
|
|
1358
1074
|
export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
1359
1075
|
const filePath = configPath();
|
|
1360
1076
|
const legacyPath = legacyConfigPath();
|
|
1077
|
+
|
|
1078
|
+
// (F16) Quick-win cache: skip the expensive full-reparse on hot paths
|
|
1079
|
+
// (preload tick 1 Hz, per-write validation, subagent completion) when the
|
|
1080
|
+
// on-disk config files are unchanged. See CONFIG_CACHE_TTL_MS at the
|
|
1081
|
+
// top of this file for the rationale and the manifestCache pattern in
|
|
1082
|
+
// state-store.ts:75-130 for the canonical implementation we mirror.
|
|
1083
|
+
const cacheParts = readCachedConfigParts(filePath, legacyPath, cwd);
|
|
1084
|
+
const cacheKey = buildConfigCacheKey(cacheParts);
|
|
1085
|
+
const cached = configCache.get(cacheKey);
|
|
1086
|
+
const ttlMs = configCacheTtlMsOverride ?? CONFIG_CACHE_TTL_MS;
|
|
1087
|
+
if (cached && Date.now() - cached.cachedAt <= ttlMs) {
|
|
1088
|
+
const currentMtimes = readCacheMtimes(cacheParts);
|
|
1089
|
+
if (matchesCachedMtimes(cached.mtimes, currentMtimes)) {
|
|
1090
|
+
// Refresh insertion order so a frequently-accessed key isn't
|
|
1091
|
+
// evicted from the Map on the next eviction sweep.
|
|
1092
|
+
configCache.delete(cacheKey);
|
|
1093
|
+
configCache.set(cacheKey, cached);
|
|
1094
|
+
return cached.value;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1361
1098
|
const paths = cwd ? [filePath, projectConfigPath(cwd)] : [filePath];
|
|
1362
1099
|
const warnings: string[] = [];
|
|
1363
1100
|
const legacyConfig = readOptionalConfig(legacyPath);
|
|
@@ -1367,12 +1104,7 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
|
1367
1104
|
}
|
|
1368
1105
|
const userConfig = readOptionalConfig(filePath);
|
|
1369
1106
|
warnings.push(...userConfig.warnings);
|
|
1370
|
-
let config = mergeConfig(
|
|
1371
|
-
legacyConfig.exists && legacyPath !== filePath
|
|
1372
|
-
? legacyConfig.config
|
|
1373
|
-
: {},
|
|
1374
|
-
userConfig.config,
|
|
1375
|
-
);
|
|
1107
|
+
let config = mergeConfig(legacyConfig.exists && legacyPath !== filePath ? legacyConfig.config : {}, userConfig.config);
|
|
1376
1108
|
if (cwd) {
|
|
1377
1109
|
const projectPath = projectConfigPath(cwd);
|
|
1378
1110
|
const projectConfig = readOptionalConfig(projectPath);
|
|
@@ -1381,15 +1113,8 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
|
1381
1113
|
// Sensitive fields have already been sanitized by sanitizeProjectConfig.
|
|
1382
1114
|
let effectiveConfig = {};
|
|
1383
1115
|
if (projectConfig.exists) {
|
|
1384
|
-
const projectSafeConfig = sanitizeProjectConfig(
|
|
1385
|
-
|
|
1386
|
-
config,
|
|
1387
|
-
projectConfig.config,
|
|
1388
|
-
);
|
|
1389
|
-
warnings.push(
|
|
1390
|
-
...projectConfig.warnings,
|
|
1391
|
-
...projectSafeConfig.warnings,
|
|
1392
|
-
);
|
|
1116
|
+
const projectSafeConfig = sanitizeProjectConfig(projectPath, config, projectConfig.config);
|
|
1117
|
+
warnings.push(...projectConfig.warnings, ...projectSafeConfig.warnings);
|
|
1393
1118
|
// merge(base=projectConfig, override=userConfig) → override wins
|
|
1394
1119
|
effectiveConfig = mergeConfig(effectiveConfig, projectSafeConfig.config);
|
|
1395
1120
|
}
|
|
@@ -1397,7 +1122,6 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
|
1397
1122
|
effectiveConfig = mergeConfig(effectiveConfig, config);
|
|
1398
1123
|
config = effectiveConfig;
|
|
1399
1124
|
|
|
1400
|
-
|
|
1401
1125
|
// `.pi/pi-crew.json` is the project-owned config file.
|
|
1402
1126
|
// Merge project config FIRST (base), then user config on top (override).
|
|
1403
1127
|
// This ensures user preferences always take precedence over project settings.
|
|
@@ -1406,11 +1130,7 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
|
1406
1130
|
const piCrewJsonConfig = readOptionalConfig(piCrewJsonPath);
|
|
1407
1131
|
if (piCrewJsonConfig.exists) {
|
|
1408
1132
|
warnings.push(...piCrewJsonConfig.warnings);
|
|
1409
|
-
const projectPart = sanitizeProjectConfig(
|
|
1410
|
-
piCrewJsonPath,
|
|
1411
|
-
config,
|
|
1412
|
-
piCrewJsonConfig.config,
|
|
1413
|
-
);
|
|
1133
|
+
const projectPart = sanitizeProjectConfig(piCrewJsonPath, config, piCrewJsonConfig.config);
|
|
1414
1134
|
warnings.push(...projectPart.warnings);
|
|
1415
1135
|
// base=project config, override=user config → user wins
|
|
1416
1136
|
const mergedProject = mergeConfig(projectPart.config, config);
|
|
@@ -1418,53 +1138,49 @@ export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
|
|
|
1418
1138
|
paths.push(piCrewJsonPath);
|
|
1419
1139
|
}
|
|
1420
1140
|
}
|
|
1421
|
-
|
|
1141
|
+
const result: LoadedPiTeamsConfig = {
|
|
1422
1142
|
path: filePath,
|
|
1423
1143
|
paths,
|
|
1424
1144
|
config,
|
|
1425
1145
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1426
1146
|
};
|
|
1147
|
+
// Only cache when at least one of the watched paths exists — this avoids
|
|
1148
|
+
// pinning stale empty results when a user later creates one of these files
|
|
1149
|
+
// in the cache window. mtime stat below picks up the new file (it appears
|
|
1150
|
+
// in currentMtimes but not in cached.mtimes) and triggers a re-parse.
|
|
1151
|
+
if (Object.keys(readCacheMtimes(cacheParts)).length > 0) {
|
|
1152
|
+
setConfigCache(cacheKey, result, readCacheMtimes(cacheParts));
|
|
1153
|
+
}
|
|
1154
|
+
return result;
|
|
1427
1155
|
}
|
|
1428
1156
|
|
|
1429
|
-
export function updateConfig(
|
|
1430
|
-
|
|
1431
|
-
options: UpdateConfigOptions = {},
|
|
1432
|
-
): SavedPiTeamsConfig {
|
|
1433
|
-
const filePath =
|
|
1434
|
-
options.scope === "project" && options.cwd
|
|
1435
|
-
? projectConfigPath(options.cwd)
|
|
1436
|
-
: configPath();
|
|
1157
|
+
export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions = {}): SavedPiTeamsConfig {
|
|
1158
|
+
const filePath = options.scope === "project" && options.cwd ? projectConfigPath(options.cwd) : configPath();
|
|
1437
1159
|
const lockPath = filePath + ".lock";
|
|
1438
1160
|
return withFileLockSync(lockPath, () => {
|
|
1439
1161
|
let current: Record<string, unknown>;
|
|
1440
1162
|
try {
|
|
1441
1163
|
current = readConfigRecord(filePath);
|
|
1442
1164
|
} catch (error) {
|
|
1443
|
-
const message =
|
|
1444
|
-
error instanceof Error ? error.message : String(error);
|
|
1165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1445
1166
|
throw new Error(`Could not update pi-crew config: ${message}`);
|
|
1446
1167
|
}
|
|
1447
1168
|
let merged = mergeConfig(parseConfig(current), patch);
|
|
1448
1169
|
if (options.unsetPaths?.length) {
|
|
1449
|
-
const raw = JSON.parse(JSON.stringify(merged)) as Record<
|
|
1450
|
-
string,
|
|
1451
|
-
unknown
|
|
1452
|
-
>;
|
|
1170
|
+
const raw = JSON.parse(JSON.stringify(merged)) as Record<string, unknown>;
|
|
1453
1171
|
for (const unset of options.unsetPaths) unsetPath(raw, unset);
|
|
1454
1172
|
merged = parseConfig(raw);
|
|
1455
1173
|
}
|
|
1456
1174
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1457
|
-
atomicWriteFile(
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
);
|
|
1175
|
+
atomicWriteFile(filePath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
1176
|
+
// (F16) Invalidate the loadConfig cache after a write — the next
|
|
1177
|
+
// caller must see the new value, not a 0-2s stale snapshot.
|
|
1178
|
+
invalidateConfigCache();
|
|
1461
1179
|
return { path: filePath, config: merged };
|
|
1462
1180
|
});
|
|
1463
1181
|
}
|
|
1464
1182
|
|
|
1465
|
-
export function updateAutonomousConfig(
|
|
1466
|
-
patch: PiTeamsAutonomousConfig,
|
|
1467
|
-
): SavedPiTeamsConfig {
|
|
1183
|
+
export function updateAutonomousConfig(patch: PiTeamsAutonomousConfig): SavedPiTeamsConfig {
|
|
1468
1184
|
const filePath = configPath();
|
|
1469
1185
|
const lockPath = filePath + ".lock";
|
|
1470
1186
|
return withFileLockSync(lockPath, () => {
|
|
@@ -1472,21 +1188,17 @@ export function updateAutonomousConfig(
|
|
|
1472
1188
|
try {
|
|
1473
1189
|
current = readConfigRecord(filePath);
|
|
1474
1190
|
} catch (error) {
|
|
1475
|
-
const message =
|
|
1476
|
-
error instanceof Error ? error.message : String(error);
|
|
1191
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1477
1192
|
throw new Error(`Could not update pi-crew config: ${message}`);
|
|
1478
1193
|
}
|
|
1479
1194
|
const currentAutonomous =
|
|
1480
|
-
current.autonomous &&
|
|
1481
|
-
typeof current.autonomous === "object" &&
|
|
1482
|
-
!Array.isArray(current.autonomous)
|
|
1195
|
+
current.autonomous && typeof current.autonomous === "object" && !Array.isArray(current.autonomous)
|
|
1483
1196
|
? (current.autonomous as Record<string, unknown>)
|
|
1484
1197
|
: {};
|
|
1485
1198
|
current.autonomous = { ...currentAutonomous, ...patch };
|
|
1486
|
-
atomicWriteFile(
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
);
|
|
1199
|
+
atomicWriteFile(filePath, `${JSON.stringify(current, null, 2)}\n`);
|
|
1200
|
+
// (F16) Invalidate the loadConfig cache after a write — see updateConfig.
|
|
1201
|
+
invalidateConfigCache();
|
|
1490
1202
|
return { path: filePath, config: parseConfig(current) };
|
|
1491
1203
|
});
|
|
1492
1204
|
}
|