pi-crew 0.9.16 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21790 -0
- package/dist/index.mjs +83070 -0
- package/dist/index.mjs.map +7 -0
- package/docs/A +358 -0
- package/docs/M-A +357 -0
- package/docs/REVIEW-FINDINGS-2026-06 +357 -0
- package/docs/Y +357 -0
- package/docs/a +357 -0
- package/docs/aA +358 -0
- package/docs/archive/README.md +91 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/skills/REFERENCE.md +0 -17
- package/docs//303/242mmaAAA/303/242 +357 -0
- package/index.bundle.ts +25 -0
- package/index.ts +95 -4
- package/package.json +15 -4
- package/skills/iterative-audit/SKILL.md +0 -1
- package/skills/widget-rendering/SKILL.md +17 -0
- package/src/adapters/claude-adapter.ts +1 -3
- package/src/adapters/export-util.ts +16 -16
- package/src/adapters/index.ts +5 -5
- package/src/agents/agent-config.ts +20 -14
- package/src/agents/agent-serializer.ts +1 -1
- package/src/agents/discover-agents.ts +83 -39
- package/src/benchmark/benchmark-runner.ts +51 -51
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +166 -590
- package/src/config/defaults.ts +28 -2
- package/src/config/drift-detector.ts +11 -14
- package/src/config/markers.ts +201 -203
- package/src/config/resilient-parser.ts +14 -6
- package/src/config/role-tools.ts +3 -3
- package/src/config/suggestions.ts +5 -12
- package/src/config/types.ts +9 -25
- package/src/errors.ts +151 -157
- package/src/extension/action-suggestions.ts +54 -9
- package/src/extension/async-notifier.ts +43 -13
- package/src/extension/autonomous-policy.ts +77 -37
- package/src/extension/command-completions.ts +9 -8
- package/src/extension/context-status-injection.ts +14 -12
- package/src/extension/crew-autocomplete.ts +8 -16
- package/src/extension/crew-cleanup.ts +2 -1
- package/src/extension/crew-shortcuts.ts +9 -6
- package/src/extension/cross-extension-rpc.ts +136 -42
- package/src/extension/help.ts +1 -1
- package/src/extension/import-index.ts +10 -5
- package/src/extension/knowledge-injection.ts +88 -15
- package/src/extension/management.ts +89 -349
- package/src/extension/message-renderers.ts +3 -4
- package/src/extension/notification-router.ts +22 -5
- package/src/extension/notification-sink.ts +6 -3
- package/src/extension/pi-api.ts +17 -8
- package/src/extension/plan-orchestrate.ts +8 -27
- package/src/extension/project-init.ts +21 -6
- package/src/extension/register.ts +329 -975
- package/src/extension/registration/artifact-cleanup.ts +9 -4
- package/src/extension/registration/command-utils.ts +5 -1
- package/src/extension/registration/commands.ts +806 -314
- package/src/extension/registration/compaction-guard.ts +15 -15
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +293 -0
- package/src/extension/registration/subagent-helpers.ts +29 -7
- package/src/extension/registration/subagent-tools.ts +253 -44
- package/src/extension/registration/team-tool.ts +19 -84
- package/src/extension/registration/ui.ts +168 -0
- package/src/extension/registration/viewers.ts +64 -24
- package/src/extension/result-watcher.ts +18 -7
- package/src/extension/run-bundle-schema.ts +21 -6
- package/src/extension/run-export.ts +15 -6
- package/src/extension/run-import.ts +64 -29
- package/src/extension/run-index.ts +28 -9
- package/src/extension/run-maintenance.ts +32 -12
- package/src/extension/session-summary.ts +1 -1
- package/src/extension/team-manager-command.ts +64 -7
- package/src/extension/team-onboard.ts +151 -150
- package/src/extension/team-recommendation.ts +121 -21
- package/src/extension/team-tool/anchor.ts +31 -64
- package/src/extension/team-tool/api.ts +929 -141
- package/src/extension/team-tool/auto-summarize.ts +14 -26
- package/src/extension/team-tool/cache-control.ts +1 -5
- package/src/extension/team-tool/cancel.ts +138 -38
- package/src/extension/team-tool/chain-dispatch.ts +5 -13
- package/src/extension/team-tool/chain-executor.ts +29 -51
- package/src/extension/team-tool/config-patch.ts +1 -1
- package/src/extension/team-tool/context.ts +40 -20
- package/src/extension/team-tool/destructive-gate.ts +10 -5
- package/src/extension/team-tool/doctor.ts +170 -51
- package/src/extension/team-tool/explain.ts +228 -214
- package/src/extension/team-tool/failure-patterns.ts +3 -9
- package/src/extension/team-tool/goal-wrap.ts +71 -29
- package/src/extension/team-tool/goal.ts +185 -35
- package/src/extension/team-tool/handle-schedule.ts +34 -15
- package/src/extension/team-tool/handle-settings.ts +94 -56
- package/src/extension/team-tool/health-monitor.ts +27 -108
- package/src/extension/team-tool/inspect.ts +42 -9
- package/src/extension/team-tool/intent-policy.ts +4 -1
- package/src/extension/team-tool/lifecycle-actions.ts +244 -48
- package/src/extension/team-tool/orchestrate.ts +7 -18
- package/src/extension/team-tool/parallel-dispatch.ts +51 -29
- package/src/extension/team-tool/plan.ts +29 -5
- package/src/extension/team-tool/respond.ts +49 -16
- package/src/extension/team-tool/run-not-found.ts +3 -8
- package/src/extension/team-tool/run.ts +80 -317
- package/src/extension/team-tool/status.ts +135 -43
- package/src/extension/team-tool/workflow-manage.ts +92 -25
- package/src/extension/team-tool-types.ts +8 -1
- package/src/extension/team-tool.ts +142 -551
- package/src/extension/validate-resources.ts +39 -8
- package/src/hooks/registry.ts +66 -17
- package/src/hooks/types.ts +1 -1
- package/src/i18n.ts +25 -11
- package/src/observability/correlation.ts +17 -3
- package/src/observability/event-bus.ts +51 -56
- package/src/observability/event-to-metric.ts +117 -15
- package/src/observability/exporters/otlp-exporter.ts +56 -17
- package/src/observability/exporters/prometheus-exporter.ts +1 -1
- package/src/observability/metric-registry.ts +16 -4
- package/src/observability/metric-retention.ts +8 -2
- package/src/observability/metric-sink.ts +11 -3
- package/src/observability/metrics-primitives.ts +42 -9
- package/src/plugins/plugin-define.ts +2 -2
- package/src/plugins/plugin-registry.ts +25 -25
- package/src/plugins/plugins/index.ts +1 -1
- package/src/plugins/plugins/nextjs.ts +16 -16
- package/src/plugins/plugins/vite.ts +7 -11
- package/src/plugins/plugins/vitest.ts +6 -11
- package/src/runtime/adaptive-plan.ts +225 -40
- package/src/runtime/agent-control.ts +58 -15
- package/src/runtime/agent-memory.ts +15 -9
- package/src/runtime/agent-observability.ts +10 -2
- package/src/runtime/anchor-manager.ts +27 -25
- package/src/runtime/async-marker.ts +7 -1
- package/src/runtime/async-runner.ts +27 -23
- package/src/runtime/auto-summarize.ts +7 -13
- package/src/runtime/background-runner.ts +189 -251
- package/src/runtime/batch-barrier.ts +18 -16
- package/src/runtime/cancellation-token.ts +16 -6
- package/src/runtime/cancellation.ts +46 -8
- package/src/runtime/capability-inventory.ts +6 -4
- package/src/runtime/chain-parser.ts +44 -11
- package/src/runtime/chain-runner.ts +63 -69
- package/src/runtime/checkpoint.ts +12 -56
- package/src/runtime/child-pi.ts +343 -78
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +71 -26
- package/src/runtime/compact-stages/index.ts +15 -5
- package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
- package/src/runtime/compact-stages/truncation-stage.ts +4 -1
- package/src/runtime/compaction-summary.ts +17 -10
- package/src/runtime/completion-guard.ts +33 -16
- package/src/runtime/crash-classification.ts +28 -7
- package/src/runtime/crash-recovery.ts +177 -37
- package/src/runtime/crew-agent-records.ts +145 -47
- package/src/runtime/crew-hooks.ts +9 -24
- package/src/runtime/cross-extension-rpc.ts +25 -23
- package/src/runtime/custom-tools/irc-tool.ts +43 -15
- package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
- package/src/runtime/delivery-coordinator.ts +34 -7
- package/src/runtime/delta-conflict.ts +9 -21
- package/src/runtime/deterministic-ast.ts +1 -1
- package/src/runtime/diagnostic-export.ts +53 -20
- package/src/runtime/direct-run.ts +12 -2
- package/src/runtime/dwf-state-store.ts +1 -1
- package/src/runtime/dynamic-workflow-context.ts +186 -58
- package/src/runtime/dynamic-workflow-runner.ts +42 -20
- package/src/runtime/effectiveness.ts +16 -8
- package/src/runtime/errors/crew-errors.ts +7 -11
- package/src/runtime/event-stream-bridge.ts +9 -3
- package/src/runtime/foreground-control.ts +54 -14
- package/src/runtime/foreground-watchdog.ts +9 -6
- package/src/runtime/goal-achievement.ts +27 -10
- package/src/runtime/goal-evaluator.ts +54 -19
- package/src/runtime/goal-loop-runner.ts +280 -99
- package/src/runtime/goal-state-store.ts +27 -17
- package/src/runtime/green-contract.ts +11 -2
- package/src/runtime/group-join.ts +64 -31
- package/src/runtime/handoff-manager.ts +37 -42
- package/src/runtime/heartbeat-gradient.ts +10 -2
- package/src/runtime/heartbeat-watcher.ts +32 -6
- package/src/runtime/hidden-handoff.ts +12 -30
- package/src/runtime/important-line-classifier.ts +12 -2
- package/src/runtime/iteration-hooks.ts +25 -15
- package/src/runtime/live-agent-control.ts +55 -7
- package/src/runtime/live-agent-manager.ts +130 -27
- package/src/runtime/live-control-realtime.ts +23 -3
- package/src/runtime/live-irc.ts +4 -1
- package/src/runtime/live-session-health.ts +12 -4
- package/src/runtime/live-session-runtime.ts +379 -117
- package/src/runtime/manifest-cache.ts +28 -12
- package/src/runtime/mcp-proxy.ts +8 -16
- package/src/runtime/metric-parser.ts +1 -5
- package/src/runtime/model-fallback.ts +55 -18
- package/src/runtime/model-resolver.ts +2 -6
- package/src/runtime/model-scope.ts +19 -3
- package/src/runtime/notebook-helpers.ts +60 -62
- package/src/runtime/orphan-worker-registry.ts +37 -26
- package/src/runtime/output-validator.ts +18 -3
- package/src/runtime/overflow-recovery.ts +5 -4
- package/src/runtime/parallel-research.ts +29 -5
- package/src/runtime/parallel-utils.ts +9 -11
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +12 -17
- package/src/runtime/per-write-validator.ts +1 -3
- package/src/runtime/phase-tracker.ts +342 -330
- package/src/runtime/pi-args.ts +26 -8
- package/src/runtime/pi-json-output.ts +1 -1
- package/src/runtime/pi-spawn.ts +30 -19
- package/src/runtime/pipeline-runner.ts +56 -55
- package/src/runtime/plan-templates.ts +5 -6
- package/src/runtime/policy-engine.ts +43 -7
- package/src/runtime/post-checks.ts +12 -4
- package/src/runtime/post-exit-stdio-guard.ts +2 -2
- package/src/runtime/process-lifecycle.ts +20 -10
- package/src/runtime/process-status.ts +16 -5
- package/src/runtime/progress-event-coalescer.ts +2 -1
- package/src/runtime/progress-tracker.ts +103 -103
- package/src/runtime/prose-compressor.ts +9 -11
- package/src/runtime/recovery-recipes.ts +118 -24
- package/src/runtime/replace.ts +25 -10
- package/src/runtime/resilient-edit.ts +10 -25
- package/src/runtime/result-extractor.ts +1 -2
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +26 -50
- package/src/runtime/role-permission.ts +6 -1
- package/src/runtime/run-coalesced-task-group.ts +256 -0
- package/src/runtime/run-drift.ts +14 -15
- package/src/runtime/run-tracker.ts +6 -28
- package/src/runtime/runtime-policy.ts +1 -6
- package/src/runtime/runtime-resolver.ts +80 -15
- package/src/runtime/scheduler.ts +47 -18
- package/src/runtime/semaphore.ts +5 -7
- package/src/runtime/sensitive-paths.ts +2 -1
- package/src/runtime/session-usage.ts +1 -1
- package/src/runtime/settings-store.ts +16 -12
- package/src/runtime/sidechain-output.ts +6 -2
- package/src/runtime/single-agent-compose.ts +1 -4
- package/src/runtime/skill-effectiveness.ts +29 -97
- package/src/runtime/skill-instructions.ts +40 -83
- package/src/runtime/stale-reconciler.ts +73 -197
- package/src/runtime/stream-preview.ts +1 -1
- package/src/runtime/streaming-output.ts +1 -1
- package/src/runtime/subagent-manager.ts +44 -169
- package/src/runtime/subprocess-tool-registry.ts +4 -1
- package/src/runtime/supervisor-contact.ts +10 -5
- package/src/runtime/task-display.ts +19 -5
- package/src/runtime/task-graph-scheduler.ts +95 -21
- package/src/runtime/task-graph.ts +5 -11
- package/src/runtime/task-health.ts +54 -47
- package/src/runtime/task-id.ts +11 -18
- package/src/runtime/task-output-context.ts +265 -81
- package/src/runtime/task-packet.ts +12 -20
- package/src/runtime/task-quality.ts +6 -14
- package/src/runtime/task-runner/context-retrieval.ts +4 -13
- package/src/runtime/task-runner/live-executor.ts +114 -36
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/progress.ts +50 -12
- package/src/runtime/task-runner/prompt-builder.ts +31 -7
- package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
- package/src/runtime/task-runner/result-utils.ts +3 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +27 -8
- package/src/runtime/task-runner/state-helpers.ts +29 -8
- package/src/runtime/task-runner/tail-read.ts +2 -6
- package/src/runtime/task-runner.ts +180 -326
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +456 -974
- package/src/runtime/tool-output-pruner.ts +6 -9
- package/src/runtime/tool-progress.ts +11 -21
- package/src/runtime/verification-gates.ts +33 -24
- package/src/runtime/verification-integrity.ts +1 -4
- package/src/runtime/verification-worktree.ts +62 -12
- package/src/runtime/worker-heartbeat.ts +5 -1
- package/src/runtime/worker-startup.ts +29 -5
- package/src/runtime/workflow-state.ts +17 -9
- package/src/runtime/workspace-lock.ts +30 -35
- package/src/runtime/workspace-tree.ts +20 -32
- package/src/runtime/yield-handler.ts +43 -9
- package/src/runtime/zombie-scanner.ts +5 -4
- package/src/schema/config-schema.ts +266 -172
- package/src/schema/team-tool-schema.ts +29 -74
- package/src/schema/validation-types.ts +25 -17
- package/src/skills/discover-skills.ts +35 -10
- package/src/skills/skill-templates.ts +109 -27
- package/src/skills/validate.ts +26 -8
- package/src/state/active-run-registry.ts +87 -28
- package/src/state/artifact-store.ts +9 -5
- package/src/state/atomic-write-v2.ts +85 -63
- package/src/state/atomic-write.ts +105 -26
- package/src/state/blob-store.ts +47 -20
- package/src/state/contracts.ts +20 -5
- package/src/state/crew-init.ts +5 -22
- package/src/state/decision-ledger.ts +19 -73
- package/src/state/event-log-rotation.ts +41 -15
- package/src/state/event-log.ts +168 -53
- package/src/state/event-reconstructor.ts +11 -1
- package/src/state/gitignore-manager.ts +2 -8
- package/src/state/health-store.ts +57 -57
- package/src/state/hook-instinct-bridge.ts +1 -1
- package/src/state/hook-integrations.ts +1 -1
- package/src/state/instinct-store.ts +17 -5
- package/src/state/jsonl-writer.ts +1 -1
- package/src/state/locks.ts +23 -9
- package/src/state/mailbox.ts +151 -28
- package/src/state/observation-store.ts +20 -14
- package/src/state/run-cache.ts +142 -135
- package/src/state/run-graph.ts +6 -15
- package/src/state/run-metrics.ts +5 -18
- package/src/state/schedule.ts +15 -9
- package/src/state/state-store.ts +129 -45
- package/src/state/task-claims.ts +12 -2
- package/src/state/tiered-eval.ts +52 -43
- package/src/state/types-eval.ts +2 -2
- package/src/state/types.ts +24 -20
- package/src/state/usage.ts +20 -4
- package/src/state/worker-atomic-writer.ts +6 -3
- package/src/subagents/index.ts +2 -2
- package/src/teams/discover-teams.ts +21 -6
- package/src/tools/safe-bash-extension.ts +5 -6
- package/src/tools/safe-bash.ts +10 -11
- package/src/types/new-api-types.ts +6 -10
- package/src/ui/agent-management-overlay.ts +52 -40
- package/src/ui/card-colors.ts +13 -4
- package/src/ui/crew-footer.ts +8 -7
- package/src/ui/crew-select-list.ts +1 -1
- package/src/ui/dashboard-panes/agents-pane.ts +41 -25
- package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
- package/src/ui/dashboard-panes/capability-pane.ts +32 -15
- package/src/ui/dashboard-panes/health-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
- package/src/ui/dashboard-panes/progress-pane.ts +12 -9
- package/src/ui/heartbeat-aggregator.ts +15 -4
- package/src/ui/keybinding-map.ts +40 -7
- package/src/ui/live-conversation-overlay.ts +36 -20
- package/src/ui/live-duration.ts +1 -4
- package/src/ui/live-run-sidebar.ts +88 -25
- package/src/ui/loaders.ts +2 -8
- package/src/ui/mascot.ts +20 -36
- package/src/ui/overlays/agent-picker-overlay.ts +11 -3
- package/src/ui/overlays/confirm-overlay.ts +4 -2
- package/src/ui/overlays/help-overlay.ts +25 -14
- package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
- package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
- package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
- package/src/ui/pi-ui-compat.ts +7 -7
- package/src/ui/powerbar-publisher.ts +120 -42
- package/src/ui/render-diff.ts +10 -3
- package/src/ui/render-scheduler.ts +12 -4
- package/src/ui/run-action-dispatcher.ts +81 -18
- package/src/ui/run-dashboard.ts +172 -76
- package/src/ui/run-event-bus.ts +59 -37
- package/src/ui/run-snapshot-cache.ts +276 -86
- package/src/ui/settings-overlay.ts +361 -74
- package/src/ui/status-colors.ts +12 -1
- package/src/ui/syntax-highlight.ts +1 -1
- package/src/ui/theme-adapter.ts +16 -14
- package/src/ui/theme-discovery.ts +13 -2
- package/src/ui/tool-progress-formatter.ts +7 -7
- package/src/ui/tool-render.ts +128 -56
- package/src/ui/tool-renderers/brief-mode.ts +45 -27
- package/src/ui/tool-renderers/index.ts +109 -43
- package/src/ui/transcript-cache.ts +32 -6
- package/src/ui/transcript-entries.ts +25 -23
- package/src/ui/transcript-viewer.ts +78 -29
- package/src/ui/widget/index.ts +99 -40
- package/src/ui/widget/widget-formatters.ts +13 -6
- package/src/ui/widget/widget-model.ts +19 -8
- package/src/ui/widget/widget-renderer.ts +23 -13
- package/src/ui/widget/widget-types.ts +1 -1
- package/src/utils/bm25-search.ts +199 -199
- package/src/utils/conflict-detect.ts +22 -21
- package/src/utils/env-filter.ts +14 -7
- package/src/utils/file-coalescer.ts +5 -1
- package/src/utils/fingerprint.ts +3 -6
- package/src/utils/frontmatter.ts +3 -1
- package/src/utils/fs-watch.ts +2 -6
- package/src/utils/gh-protocol.ts +119 -44
- package/src/utils/git.ts +13 -15
- package/src/utils/guards.ts +2 -5
- package/src/utils/ids.ts +9 -2
- package/src/utils/incremental-reader.ts +14 -3
- package/src/utils/internal-error.ts +2 -1
- package/src/utils/names.ts +12 -3
- package/src/utils/paths.ts +49 -5
- package/src/utils/project-detector.ts +2 -2
- package/src/utils/redaction.ts +29 -19
- package/src/utils/resolve-shell.ts +9 -7
- package/src/utils/run-watcher-registry.ts +19 -31
- package/src/utils/safe-paths.ts +45 -24
- package/src/utils/scan-cache.ts +9 -2
- package/src/utils/session-utils.ts +2 -4
- package/src/utils/sleep.ts +12 -5
- package/src/utils/sse-parser.ts +5 -17
- package/src/utils/visual.ts +52 -32
- package/src/workflows/discover-workflows.ts +41 -109
- package/src/workflows/intermediate-store.ts +5 -21
- package/src/workflows/preflight-validator.ts +6 -33
- package/src/workflows/topology-analyzer.ts +5 -21
- package/src/workflows/workflow-config.ts +7 -6
- package/src/worktree/branch-freshness.ts +66 -8
- package/src/worktree/cleanup.ts +130 -20
- package/src/worktree/worktree-manager.ts +212 -69
- package/skills/artifact-analysis-loop/SKILL.md +0 -303
- package/skills/detection-pipeline-design/SKILL.md +0 -286
- package/skills/hunting-investigation-loop/SKILL.md +0 -402
- package/skills/incident-playbook-construction/SKILL.md +0 -384
- package/skills/security-review/SKILL.md +0 -561
- package/skills/threat-hypothesis-framework/SKILL.md +0 -176
- package/skills/ui-render-performance/SKILL.md +0 -58
- /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
- /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
- /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
- /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
- /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
- /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
- /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
- /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
- /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
- /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
- /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
- /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
- /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
- /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
|
@@ -2,13 +2,13 @@ import * as crypto from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
|
|
5
|
-
import
|
|
6
|
-
import { atomicWriteJson, renameWithRetry, isSymlinkSafePath } from "./atomic-write.ts";
|
|
5
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
7
6
|
import { userCrewRoot } from "../utils/paths.ts";
|
|
8
7
|
import { isSafePathId } from "../utils/safe-paths.ts";
|
|
9
8
|
import { sharedScanCache } from "../utils/scan-cache.ts";
|
|
10
9
|
import { sleepSync } from "../utils/sleep.ts";
|
|
11
|
-
import {
|
|
10
|
+
import { atomicWriteJson, isSymlinkSafePath, renameWithRetry } from "./atomic-write.ts";
|
|
11
|
+
import type { TeamRunManifest } from "./types.ts";
|
|
12
12
|
|
|
13
13
|
/** Magic bytes prefix for binary registry to prevent deserialization of hostile files. */
|
|
14
14
|
const BINARY_MAGIC = Buffer.from("PICREW2BIN", "utf-8");
|
|
@@ -37,8 +37,6 @@ function registryLockPath(): string {
|
|
|
37
37
|
return `${registryPath()}.lock`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
40
|
function lockCreatedAt(raw: string): number | undefined {
|
|
43
41
|
try {
|
|
44
42
|
const parsed = JSON.parse(raw) as { createdAt?: unknown };
|
|
@@ -73,7 +71,13 @@ function withRegistryLock<T>(fn: () => T): T {
|
|
|
73
71
|
try {
|
|
74
72
|
const fd = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o644);
|
|
75
73
|
try {
|
|
76
|
-
fs.writeSync(
|
|
74
|
+
fs.writeSync(
|
|
75
|
+
fd,
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
pid: process.pid,
|
|
78
|
+
createdAt: new Date().toISOString(),
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
77
81
|
} finally {
|
|
78
82
|
fs.closeSync(fd);
|
|
79
83
|
}
|
|
@@ -81,7 +85,8 @@ function withRegistryLock<T>(fn: () => T): T {
|
|
|
81
85
|
} catch (error) {
|
|
82
86
|
const code = (error as NodeJS.ErrnoException).code;
|
|
83
87
|
if (code !== "EEXIST") throw error;
|
|
84
|
-
if (!removeStaleRegistryLock(filePath, staleMs) && Date.now() > deadline)
|
|
88
|
+
if (!removeStaleRegistryLock(filePath, staleMs) && Date.now() > deadline)
|
|
89
|
+
throw new Error("Active-run registry is locked by another operation.");
|
|
85
90
|
sleepSync(Math.min(250, 25 * 2 ** attempt));
|
|
86
91
|
attempt += 1;
|
|
87
92
|
}
|
|
@@ -139,7 +144,9 @@ export function readActiveRunRegistry(maxEntries = DEFAULT_CACHE.manifestMaxEntr
|
|
|
139
144
|
return [];
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
|
-
const entries = Array.isArray(parsed)
|
|
147
|
+
const entries = Array.isArray(parsed)
|
|
148
|
+
? parsed.map(normalizeEntry).filter((entry): entry is ActiveRunRegistryEntry => entry !== undefined)
|
|
149
|
+
: [];
|
|
143
150
|
const byId = new Map<string, ActiveRunRegistryEntry>();
|
|
144
151
|
for (const entry of entries.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))) {
|
|
145
152
|
if (!byId.has(entry.runId)) byId.set(entry.runId, entry);
|
|
@@ -152,7 +159,8 @@ export function readActiveRunRegistry(maxEntries = DEFAULT_CACHE.manifestMaxEntr
|
|
|
152
159
|
* Writes to temp file first, then renames. Includes version field for forward compatibility.
|
|
153
160
|
*/
|
|
154
161
|
function atomicWriteBinary(filePath: string, entries: ActiveRunRegistryEntry[]): void {
|
|
155
|
-
if (!isSymlinkSafePath(filePath))
|
|
162
|
+
if (!isSymlinkSafePath(filePath))
|
|
163
|
+
throw new Error(`Refusing to write binary registry: target is a symlink or inside untrusted directory: ${filePath}`);
|
|
156
164
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
157
165
|
const tempPath = `${filePath}.${crypto.randomUUID()}.tmp`;
|
|
158
166
|
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
@@ -175,7 +183,11 @@ function atomicWriteBinary(filePath: string, entries: ActiveRunRegistryEntry[]):
|
|
|
175
183
|
fs.closeSync(fd);
|
|
176
184
|
renameWithRetry(tempPath, filePath);
|
|
177
185
|
} catch (error) {
|
|
178
|
-
try {
|
|
186
|
+
try {
|
|
187
|
+
fs.rmSync(tempPath, { force: true });
|
|
188
|
+
} catch {
|
|
189
|
+
/* best-effort */
|
|
190
|
+
}
|
|
179
191
|
throw error;
|
|
180
192
|
}
|
|
181
193
|
}
|
|
@@ -214,26 +226,52 @@ function writeEntries(entries: ActiveRunRegistryEntry[], skipTerminalRunIds?: Se
|
|
|
214
226
|
// instead of deleting both. This preserves whichever registry was successfully written.
|
|
215
227
|
if (jsonRenamed && !binRenamed) {
|
|
216
228
|
// JSON succeeded, binary failed — try to recover binary from temp
|
|
217
|
-
try {
|
|
229
|
+
try {
|
|
230
|
+
renameWithRetry(tempBin, registryBinaryPath());
|
|
231
|
+
binRenamed = true;
|
|
232
|
+
} catch {
|
|
233
|
+
/* recovery failed */
|
|
234
|
+
}
|
|
218
235
|
} else if (binRenamed && !jsonRenamed) {
|
|
219
236
|
// Binary succeeded, JSON failed — try to recover JSON from temp
|
|
220
|
-
try {
|
|
237
|
+
try {
|
|
238
|
+
renameWithRetry(tempJson, registryPath());
|
|
239
|
+
jsonRenamed = true;
|
|
240
|
+
} catch {
|
|
241
|
+
/* recovery failed */
|
|
242
|
+
}
|
|
221
243
|
}
|
|
222
244
|
// FIX Issue 2: If recovery failed, do NOT delete the final files — they may contain valid
|
|
223
245
|
// data from a prior write cycle. Leave temp files for manual recovery and throw error.
|
|
224
246
|
if (!binRenamed || !jsonRenamed) {
|
|
225
247
|
// Attempt to clean up temp files only (not final files)
|
|
226
|
-
try {
|
|
227
|
-
|
|
248
|
+
try {
|
|
249
|
+
fs.rmSync(tempJson, { force: true });
|
|
250
|
+
} catch {
|
|
251
|
+
/* best-effort */
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
fs.rmSync(tempBin, { force: true });
|
|
255
|
+
} catch {
|
|
256
|
+
/* best-effort */
|
|
257
|
+
}
|
|
228
258
|
throw error;
|
|
229
259
|
}
|
|
230
260
|
// FIX Issue 1: Ensure temp file cleanup executes even when recovery rename fails.
|
|
231
261
|
// If recovery throws, the error propagates before reaching the cleanup below.
|
|
232
262
|
// Wrapping in nested try-catch guarantees cleanup runs regardless of recovery outcome.
|
|
233
|
-
try {
|
|
234
|
-
|
|
263
|
+
try {
|
|
264
|
+
fs.rmSync(tempJson, { force: true });
|
|
265
|
+
} catch {
|
|
266
|
+
/* best-effort */
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
fs.rmSync(tempBin, { force: true });
|
|
270
|
+
} catch {
|
|
271
|
+
/* best-effort */
|
|
272
|
+
}
|
|
235
273
|
throw error;
|
|
236
|
-
}
|
|
274
|
+
}
|
|
237
275
|
}
|
|
238
276
|
|
|
239
277
|
const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "blocked"]);
|
|
@@ -246,7 +284,10 @@ const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "blocked"
|
|
|
246
284
|
* Status filtering is best-effort at read time. The terminal runIds set is returned so callers
|
|
247
285
|
* can pass them to writeEntries to skip entries that transitioned to terminal since filtering.
|
|
248
286
|
*/
|
|
249
|
-
function filterAliveEntries(entries: ActiveRunRegistryEntry[]): {
|
|
287
|
+
function filterAliveEntries(entries: ActiveRunRegistryEntry[]): {
|
|
288
|
+
alive: ActiveRunRegistryEntry[];
|
|
289
|
+
terminalRunIds: Set<string>;
|
|
290
|
+
} {
|
|
250
291
|
const terminalRunIds = new Set<string>();
|
|
251
292
|
const alive = entries.filter((entry) => {
|
|
252
293
|
try {
|
|
@@ -259,7 +300,11 @@ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveR
|
|
|
259
300
|
return false;
|
|
260
301
|
}
|
|
261
302
|
try {
|
|
262
|
-
const raw = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8")) as {
|
|
303
|
+
const raw = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8")) as {
|
|
304
|
+
status?: string;
|
|
305
|
+
async?: { pid?: number };
|
|
306
|
+
updatedAt?: string;
|
|
307
|
+
};
|
|
263
308
|
if (TERMINAL_STATUSES.has(raw.status ?? "")) {
|
|
264
309
|
terminalRunIds.add(entry.runId);
|
|
265
310
|
return false;
|
|
@@ -268,7 +313,9 @@ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveR
|
|
|
268
313
|
// EPERM means process exists but we can't signal it (different security context).
|
|
269
314
|
// Also check manifest age to guard against PID reuse.
|
|
270
315
|
if (raw.async?.pid) {
|
|
271
|
-
try {
|
|
316
|
+
try {
|
|
317
|
+
process.kill(raw.async.pid, 0);
|
|
318
|
+
} catch (err) {
|
|
272
319
|
// Only treat "process does not exist" as dead (ESRCH/ENOENT).
|
|
273
320
|
// EPERM means process is alive but protected; treat as alive.
|
|
274
321
|
const code = (err as NodeJS.ErrnoException).code;
|
|
@@ -277,13 +324,13 @@ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveR
|
|
|
277
324
|
}
|
|
278
325
|
// FIX Issue 2: Async runs older than 30 min are stale even if PID is alive.
|
|
279
326
|
// This guards against PID reuse after the original process exits.
|
|
280
|
-
const updatedAt = typeof raw.updatedAt ===
|
|
327
|
+
const updatedAt = typeof raw.updatedAt === "string" ? Date.parse(raw.updatedAt) : NaN;
|
|
281
328
|
if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) return false;
|
|
282
329
|
}
|
|
283
330
|
// 2.19 — Stale non-async run: live-session/scaffold runs older than 30 min
|
|
284
331
|
// Without this, test runs that crash/leak would stay in the registry forever.
|
|
285
332
|
if (!raw.async) {
|
|
286
|
-
const updatedAt = typeof raw.updatedAt ===
|
|
333
|
+
const updatedAt = typeof raw.updatedAt === "string" ? Date.parse(raw.updatedAt) : NaN;
|
|
287
334
|
if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) return false;
|
|
288
335
|
}
|
|
289
336
|
} catch {
|
|
@@ -355,17 +402,29 @@ export function activeRunEntries(): ActiveRunRegistryEntry[] {
|
|
|
355
402
|
if (!isSymlinkSafePath(entry.stateRoot)) continue;
|
|
356
403
|
// FIX Issue 4: Use cached manifest content directly when available.
|
|
357
404
|
// sharedScanCache.readAndCache already read the file; avoid redundant re-read.
|
|
358
|
-
const manifest = (cached?.raw !== undefined
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
405
|
+
const manifest = (cached?.raw !== undefined ? cached.raw : JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"))) as {
|
|
406
|
+
status?: unknown;
|
|
407
|
+
updatedAt?: string;
|
|
408
|
+
async?: { pid?: number };
|
|
409
|
+
};
|
|
410
|
+
if (
|
|
411
|
+
manifest.status !== "queued" &&
|
|
412
|
+
manifest.status !== "planning" &&
|
|
413
|
+
manifest.status !== "running" &&
|
|
414
|
+
manifest.status !== "blocked"
|
|
415
|
+
)
|
|
416
|
+
continue;
|
|
362
417
|
// PID liveness check: async runs with dead PID are stale — don't surface them
|
|
363
418
|
if (manifest.async?.pid) {
|
|
364
|
-
try {
|
|
419
|
+
try {
|
|
420
|
+
process.kill(manifest.async.pid, 0);
|
|
421
|
+
} catch {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
365
424
|
}
|
|
366
425
|
// Stale non-async run: live-session/scaffold runs older than 30 min
|
|
367
426
|
if (!manifest.async) {
|
|
368
|
-
const updatedAt = typeof manifest.updatedAt ===
|
|
427
|
+
const updatedAt = typeof manifest.updatedAt === "string" ? Date.parse(manifest.updatedAt) : NaN;
|
|
369
428
|
if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) continue;
|
|
370
429
|
}
|
|
371
430
|
entries.push(entry);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import type { ArtifactDescriptor } from "./types.ts";
|
|
5
|
-
import { atomicWriteFile } from "./atomic-write.ts";
|
|
6
|
-
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
7
4
|
import { redactSecretString, redactSecrets } from "../utils/redaction.ts";
|
|
5
|
+
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
6
|
+
import { atomicWriteFile } from "./atomic-write.ts";
|
|
7
|
+
import type { ArtifactDescriptor } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
function hashContent(content: string): string {
|
|
10
10
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -107,7 +107,11 @@ function resolveInside(baseDir: string, relativePath: string): string {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
const normalizedRelativePath = relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
110
|
-
if (
|
|
110
|
+
if (
|
|
111
|
+
!normalizedRelativePath ||
|
|
112
|
+
normalizedRelativePath.split("/").some((segment) => segment === "..") ||
|
|
113
|
+
path.isAbsolute(normalizedRelativePath)
|
|
114
|
+
) {
|
|
111
115
|
throw new Error(`Invalid artifact path: ${relativePath}`);
|
|
112
116
|
}
|
|
113
117
|
// Use resolveRealContainedPath to resolve symlinks before checking containment,
|
|
@@ -13,74 +13,96 @@ import * as path from "node:path";
|
|
|
13
13
|
* - UUID-based tmp file to prevent collisions under concurrent writes
|
|
14
14
|
*/
|
|
15
15
|
export class AtomicWriter {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
private initializedDirs = new Set<string>();
|
|
17
|
+
private baseDir: string;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
constructor(baseDir: string) {
|
|
20
|
+
this.baseDir = baseDir;
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
writeSync(targetPath: string, content: string): void {
|
|
24
|
+
this.ensureParentDir(targetPath);
|
|
25
|
+
const tmpPath = this.tmpPath(targetPath);
|
|
26
|
+
const fd = fs.openSync(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
27
|
+
try {
|
|
28
|
+
fs.writeSync(fd, content, undefined, "utf8");
|
|
29
|
+
try {
|
|
30
|
+
fs.fsyncSync(fd);
|
|
31
|
+
} catch {
|
|
32
|
+
/* best-effort */
|
|
33
|
+
}
|
|
34
|
+
} finally {
|
|
35
|
+
fs.closeSync(fd);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
fs.renameSync(tmpPath, targetPath);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
try {
|
|
41
|
+
fs.unlinkSync(tmpPath);
|
|
42
|
+
} catch {
|
|
43
|
+
/* best-effort cleanup */
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
async writeAsync(targetPath: string, content: string): Promise<void> {
|
|
50
|
+
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
51
|
+
const tmpPath = this.tmpPath(targetPath);
|
|
52
|
+
const fd = await fs.promises.open(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
53
|
+
try {
|
|
54
|
+
await fd.writeFile(content, "utf8");
|
|
55
|
+
try {
|
|
56
|
+
await fd.sync();
|
|
57
|
+
} catch {
|
|
58
|
+
/* best-effort */
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
await fd.close();
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
await fs.promises.rename(tmpPath, targetPath);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
try {
|
|
67
|
+
await fs.promises.unlink(tmpPath);
|
|
68
|
+
} catch {
|
|
69
|
+
/* best-effort cleanup */
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
writeJsonSync<T>(targetPath: string, value: T): void {
|
|
76
|
+
this.writeSync(targetPath, JSON.stringify(value, null, 2) + "\n");
|
|
77
|
+
}
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
async writeJsonAsync<T>(targetPath: string, value: T): Promise<void> {
|
|
80
|
+
await this.writeAsync(targetPath, JSON.stringify(value, null, 2) + "\n");
|
|
81
|
+
}
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
private tmpPath(targetPath: string): string {
|
|
84
|
+
const uuid = crypto.randomUUID();
|
|
85
|
+
return `${targetPath}.${uuid}.tmp`;
|
|
86
|
+
}
|
|
71
87
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
private ensureParentDir(targetPath: string): void {
|
|
89
|
+
const dir = path.dirname(targetPath);
|
|
90
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
this.ensureGitignore(dir);
|
|
92
|
+
}
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
private ensureGitignore(dir: string): void {
|
|
95
|
+
if (this.initializedDirs.has(dir)) return;
|
|
96
|
+
this.initializedDirs.add(dir);
|
|
97
|
+
const gitignorePath = path.join(dir, ".gitignore");
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(gitignorePath);
|
|
100
|
+
} catch {
|
|
101
|
+
try {
|
|
102
|
+
fs.writeFileSync(gitignorePath, "*\n", "utf8");
|
|
103
|
+
} catch {
|
|
104
|
+
/* best-effort */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|