pi-crew 0.9.51 → 0.9.52
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 +35 -0
- package/README.md +216 -20
- package/dist/build-meta.json +2090 -1638
- package/dist/index.mjs +18057 -17119
- package/docs/actions-reference.md +431 -1
- package/package.json +19 -9
- package/schema.json +41 -0
- package/scripts/check-bundle-size.mjs +38 -0
- package/scripts/test-runner.mjs +11 -1
- package/dist/index.mjs.map +0 -7
- package/docs/FEATURE_INTAKE.md +0 -126
- package/docs/HARNESS.md +0 -87
- package/docs/HARNESS_BACKLOG.md +0 -89
- package/docs/PROJECT_REVIEW.md +0 -270
- package/docs/PROJECT_REVIEW_FIXES.md +0 -342
- package/docs/PROJECT_REVIEW_ROUND4.md +0 -156
- package/docs/PROJECT_REVIEW_ROUND5.md +0 -86
- package/docs/REVIEW-FINDINGS-2026-06 +0 -357
- package/docs/REVIEW-FINDINGS-2026-07-CORE.md +0 -139
- package/docs/REVIEW-FINDINGS-2026-07.md +0 -125
- package/docs/TEST_MATRIX.md +0 -51
- package/docs/archive/README.md +0 -91
- package/docs/archive/followup-review-round3-2026-05-12.md +0 -342
- package/docs/archive/followup-review-round4-2026-05-13.md +0 -107
- package/docs/archive/pi-crew-bugs.md +0 -961
- package/docs/archive/pi-crew-test-final.md +0 -120
- package/docs/archive/pi-crew-test-results.md +0 -260
- package/docs/archive/pi-crew-test-round2.md +0 -136
- package/docs/archive/pi-crew-test-round4.md +0 -100
- package/docs/archive/pi-crew-test-round5.md +0 -70
- package/docs/archive/pi-crew-test-round6.md +0 -110
- package/docs/archive/pi-crew-v0.5.10-audit-fix-plan.md +0 -60
- package/docs/archive/pi-crew-v0.5.11-audit-fix-plan.md +0 -92
- package/docs/archive/pi-crew-v0.5.12-audit-fix-plan.md +0 -76
- package/docs/archive/pi-crew-v0.5.13-audit-fix-plan.md +0 -75
- package/docs/archive/pi-crew-v0.5.14-audit-fix-plan.md +0 -75
- package/docs/archive/pi-crew-v0.5.16-audit-fix-plan.md +0 -35
- package/docs/archive/pi-crew-v0.5.17-audit-fix-plan.md +0 -80
- package/docs/archive/pi-crew-v0.5.5-audit-fix-plan.md +0 -133
- package/docs/archive/pi-crew-v0.5.9-audit-fix-plan.md +0 -88
- package/docs/archive/pi-mono-opportunities.md +0 -969
- package/docs/archive/pi-mono-review.md +0 -291
- package/docs/archive/pi-subagent4-comparison.md +0 -261
- package/docs/archive/pi-subagents3-deep-analysis.md +0 -508
- package/docs/bugs/bug-021-notification-badge-counter-misleading.md +0 -293
- package/docs/bugs/bug-023-chain-windows-path-resolution.md +0 -106
- package/docs/bugs/bug-quota-display-truncation.md +0 -223
- package/docs/bugs/cross-session-notification-leakage.md +0 -82
- package/docs/code-review-2026-05-11.md +0 -592
- package/docs/coding-agent-optimization.md +0 -268
- package/docs/comparison-pi-subagents-vs-pi-crew.md +0 -303
- package/docs/decisions/0001-durable-state.md +0 -41
- package/docs/decisions/0002-child-process-for-async.md +0 -42
- package/docs/decisions/0003-depth-guard.md +0 -36
- package/docs/decisions/0004-execfile-over-exec.md +0 -34
- package/docs/decisions/0005-no-parameter-properties.md +0 -49
- package/docs/decisions/0006-publish-bundled-esm.md +0 -63
- package/docs/decisions/0007-active-run-binary-index.md +0 -54
- package/docs/decisions/0008-child-pi-warm-pool.md +0 -61
- package/docs/decisions/2026-07-21-broker-phase4-default-on.md +0 -77
- package/docs/decisions/2026-07-21-broker-windows-perms.md +0 -91
- package/docs/decisions/2026-07-22-broker-phase4-gated-on.md +0 -99
- package/docs/decisions/2026-07-24-oidc-trusted-publishing.md +0 -112
- package/docs/decisions/2026-07-26-c6-mascot-visibility-not-wired.md +0 -84
- package/docs/decisions/README.md +0 -26
- package/docs/deep-review-report.md +0 -384
- package/docs/distillation/cybersecurity-patterns.md +0 -294
- package/docs/feature-analysis-subagent4.md +0 -305
- package/docs/fix-plan-disabletools-exit-null.md +0 -219
- package/docs/fixes/BATCH_A_H1_H2.md +0 -86
- package/docs/fixes/bug-006-foreground-cancel-concurrent.md +0 -78
- package/docs/fixes/bug-007-async-notifier-stale-ctx.md +0 -112
- package/docs/fixes/bug-008-child-process-silent-timeout.md +0 -100
- package/docs/fixes/bug-009-executor-yield-limit-needs-attention.md +0 -75
- package/docs/fixes/bug-010-child-process-api-key-filtered.md +0 -109
- package/docs/fixes/bug-011-spawn-pi-enoent.md +0 -92
- package/docs/fixes/bug-012-essential-env-stripped.md +0 -89
- package/docs/fixes/bug-013-background-runner-death.md +0 -84
- package/docs/fixes/bug-014-infinite-retry-loop-needs-attention.md +0 -82
- package/docs/fixes/bug-015-background-runner-sigterm.md +0 -65
- package/docs/fixes/bug-017-background-runner-session-shutdown.md +0 -66
- package/docs/fixes/bug-017-background-runner-sigkill-double-fork.md +0 -28
- package/docs/fixes/bug-018-child-pi-worker-stdin-hang.md +0 -61
- package/docs/fixes/bug-019-phantom-runs-temp-workspace.md +0 -52
- package/docs/fixes/bug-020-infinite-retry-loop-needs-attention.md +0 -47
- package/docs/fixes/v0.9.10/locks-fix-verify.md +0 -3
- package/docs/fixes/v0.9.10/smoke-test.md +0 -12
- package/docs/followup-plan-2026-05-12.md +0 -463
- package/docs/followup-review-2026-05-12.md +0 -297
- package/docs/goals.md +0 -59
- package/docs/implementation-plan-top3.md +0 -333
- package/docs/issue-29-analysis.md +0 -189
- package/docs/live-mailbox-runtime.md +0 -36
- package/docs/migration/atomic-write-v2-migration.md +0 -299
- package/docs/next-upgrade-roadmap.md +0 -808
- package/docs/oh-my-pi-research.md +0 -509
- package/docs/optimization-plan-2026-07.md +0 -366
- package/docs/optimization-plan.md +0 -644
- package/docs/patterns/command-agent-skill.md +0 -71
- package/docs/perf/baseline-2026-05.md +0 -113
- package/docs/perf/final-report-2026-05.md +0 -206
- package/docs/perf/optimization-plan-2026-07-verified.md +0 -396
- package/docs/perf/performance-audit-report-2026-07.md +0 -948
- package/docs/perf/performance-review-2026-07.md +0 -287
- package/docs/perf/sprint-1-report.md +0 -71
- package/docs/perf/sprint-2-report.md +0 -81
- package/docs/perf/sprint-2.5-report.md +0 -53
- package/docs/perf/sprint-3-report.md +0 -36
- package/docs/perf/sprint-4-report.md +0 -47
- package/docs/perf/sprint-5-report.md +0 -51
- package/docs/perf/sprint-6-report.md +0 -94
- package/docs/perf/sprint-7-report.md +0 -74
- package/docs/perf/upgrade-plan-2026-05.md +0 -147
- package/docs/performance-optimization-execution-plan.md +0 -520
- package/docs/phase4-triage.md +0 -58
- package/docs/pi-crew-investigation-report.md +0 -411
- package/docs/product/README.md +0 -31
- package/docs/product/platform.md +0 -27
- package/docs/product/runtime-safety.md +0 -37
- package/docs/product/team-run.md +0 -39
- package/docs/product/team-tool.md +0 -37
- package/docs/runtime-analysis-child-vs-live.md +0 -171
- package/docs/runtime-migration-in-process-analysis.md +0 -250
- package/docs/skills/REFERENCE.md +0 -138
- package/docs/stories/README.md +0 -32
- package/docs/stories/US-DEPS-major-upgrade.md +0 -62
- package/docs/stories/backlog.md +0 -36
- package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +0 -1270
- package/docs/templates/decision.md +0 -27
- package/docs/templates/story.md +0 -44
- package/docs/templates/validation-report.md +0 -32
- package/docs/ui-optimization-plan.md +0 -447
- package/src/adapters/claude-adapter.ts +0 -23
- package/src/adapters/codex-adapter.ts +0 -21
- package/src/adapters/cursor-adapter.ts +0 -17
- package/src/adapters/export-util.ts +0 -143
- package/src/adapters/index.ts +0 -15
- package/src/adapters/registry.ts +0 -18
- package/src/adapters/types.ts +0 -23
- package/src/agents/agent-config.ts +0 -194
- package/src/agents/agent-search.ts +0 -98
- package/src/agents/agent-serializer.ts +0 -38
- package/src/agents/discover-agents.ts +0 -623
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/config.ts +0 -1269
- package/src/config/defaults.ts +0 -193
- package/src/config/drift-detector.ts +0 -290
- package/src/config/markers.ts +0 -330
- package/src/config/resilient-parser.ts +0 -117
- package/src/config/role-tools.ts +0 -118
- package/src/config/suggestions.ts +0 -75
- package/src/config/types.ts +0 -278
- package/src/errors.ts +0 -194
- package/src/extension/action-suggestions.ts +0 -117
- package/src/extension/async-notifier.ts +0 -179
- package/src/extension/autonomous-policy.ts +0 -209
- package/src/extension/command-completions.ts +0 -120
- package/src/extension/context-status-injection.ts +0 -185
- package/src/extension/crew-autocomplete.ts +0 -131
- package/src/extension/crew-cleanup.ts +0 -168
- package/src/extension/crew-input-router.ts +0 -93
- package/src/extension/crew-shortcuts.ts +0 -87
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/crew-vibes/config.ts +0 -195
- package/src/extension/crew-vibes/figures.ts +0 -101
- package/src/extension/crew-vibes/font-detect.ts +0 -71
- package/src/extension/crew-vibes/footer.ts +0 -292
- package/src/extension/crew-vibes/index.ts +0 -432
- package/src/extension/crew-vibes/provider-usage.ts +0 -396
- package/src/extension/crew-vibes/render.ts +0 -206
- package/src/extension/crew-vibes/speed.ts +0 -286
- package/src/extension/cross-extension-rpc.ts +0 -350
- package/src/extension/help.ts +0 -62
- package/src/extension/import-index.ts +0 -74
- package/src/extension/knowledge-injection.ts +0 -402
- package/src/extension/management.ts +0 -571
- package/src/extension/message-renderers.ts +0 -108
- package/src/extension/notification-router.ts +0 -152
- package/src/extension/notification-sink.ts +0 -54
- package/src/extension/pi-api.ts +0 -56
- package/src/extension/plan-orchestrate.ts +0 -302
- package/src/extension/project-init.ts +0 -170
- package/src/extension/register.ts +0 -113
- package/src/extension/registration/artifact-cleanup.ts +0 -20
- package/src/extension/registration/command-registration.ts +0 -58
- package/src/extension/registration/command-utils.ts +0 -58
- package/src/extension/registration/commands.ts +0 -1228
- package/src/extension/registration/compaction-guard.ts +0 -333
- package/src/extension/registration/context-builder.ts +0 -135
- package/src/extension/registration/crash-recovery-cache.ts +0 -54
- package/src/extension/registration/foreground-run-controller.ts +0 -238
- package/src/extension/registration/hook-registration.ts +0 -119
- package/src/extension/registration/lazy-configurers.ts +0 -124
- package/src/extension/registration/lifecycle-handlers.ts +0 -923
- package/src/extension/registration/lifecycle.ts +0 -259
- package/src/extension/registration/observability.ts +0 -322
- package/src/extension/registration/registration-types.ts +0 -158
- package/src/extension/registration/runtime-cleanup.ts +0 -230
- package/src/extension/registration/subagent-helpers.ts +0 -125
- package/src/extension/registration/subagent-manager-setup.ts +0 -330
- package/src/extension/registration/subagent-tools.ts +0 -456
- package/src/extension/registration/team-tool.ts +0 -220
- package/src/extension/registration/tool-registration.ts +0 -50
- package/src/extension/registration/ui.ts +0 -172
- package/src/extension/registration/viewers.ts +0 -121
- package/src/extension/registration/wire-cross-extension.ts +0 -37
- package/src/extension/result-watcher.ts +0 -139
- package/src/extension/rpc-hmac.ts +0 -256
- package/src/extension/run-bundle-schema.ts +0 -104
- package/src/extension/run-export.ts +0 -97
- package/src/extension/run-import.ts +0 -168
- package/src/extension/run-index.ts +0 -131
- package/src/extension/run-maintenance.ts +0 -190
- package/src/extension/session-summary.ts +0 -18
- package/src/extension/team-manager-command.ts +0 -153
- package/src/extension/team-onboard.ts +0 -177
- package/src/extension/team-recommendation.ts +0 -288
- package/src/extension/team-tool/anchor.ts +0 -172
- package/src/extension/team-tool/api.ts +0 -1244
- package/src/extension/team-tool/auto-summarize.ts +0 -142
- package/src/extension/team-tool/cache-control.ts +0 -19
- package/src/extension/team-tool/cancel.ts +0 -337
- package/src/extension/team-tool/chain-dispatch.ts +0 -95
- package/src/extension/team-tool/chain-executor.ts +0 -379
- package/src/extension/team-tool/config-patch.ts +0 -50
- package/src/extension/team-tool/context.ts +0 -149
- package/src/extension/team-tool/destructive-gate.ts +0 -52
- package/src/extension/team-tool/doctor.ts +0 -431
- package/src/extension/team-tool/explain.ts +0 -282
- package/src/extension/team-tool/failure-patterns.ts +0 -118
- package/src/extension/team-tool/goal-wrap.ts +0 -330
- package/src/extension/team-tool/goal.ts +0 -556
- package/src/extension/team-tool/handle-schedule.ts +0 -332
- package/src/extension/team-tool/handle-settings.ts +0 -520
- package/src/extension/team-tool/health-monitor.ts +0 -481
- package/src/extension/team-tool/inspect.ts +0 -88
- package/src/extension/team-tool/intent-policy.ts +0 -45
- package/src/extension/team-tool/lifecycle-actions.ts +0 -615
- package/src/extension/team-tool/orchestrate.ts +0 -98
- package/src/extension/team-tool/parallel-dispatch.ts +0 -183
- package/src/extension/team-tool/plan.ts +0 -50
- package/src/extension/team-tool/respond.ts +0 -150
- package/src/extension/team-tool/run-not-found.ts +0 -49
- package/src/extension/team-tool/run.ts +0 -1035
- package/src/extension/team-tool/status.ts +0 -260
- package/src/extension/team-tool/workflow-manage.ts +0 -261
- package/src/extension/team-tool-types.ts +0 -26
- package/src/extension/team-tool.ts +0 -1116
- package/src/extension/tool-result.ts +0 -16
- package/src/extension/validate-resources.ts +0 -108
- package/src/hooks/registry.ts +0 -200
- package/src/hooks/types.ts +0 -69
- package/src/i18n.ts +0 -212
- package/src/observability/correlation.ts +0 -50
- package/src/observability/event-bus.ts +0 -86
- package/src/observability/event-to-metric.ts +0 -170
- package/src/observability/exporters/adapter.ts +0 -27
- package/src/observability/exporters/otlp-exporter.ts +0 -356
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-registry.ts +0 -100
- package/src/observability/metric-retention.ts +0 -64
- package/src/observability/metric-sink.ts +0 -99
- package/src/observability/metrics-primitives.ts +0 -219
- package/src/plugins/plugin-define.ts +0 -6
- package/src/plugins/plugin-registry.ts +0 -32
- package/src/plugins/plugins/index.ts +0 -3
- package/src/plugins/plugins/nextjs.ts +0 -19
- package/src/plugins/plugins/vite.ts +0 -10
- package/src/plugins/plugins/vitest.ts +0 -9
- package/src/prompt/prompt-runtime.ts +0 -380
- package/src/runtime/adaptive-plan.ts +0 -563
- package/src/runtime/agent-control.ts +0 -215
- package/src/runtime/agent-memory.ts +0 -81
- package/src/runtime/agent-observability.ts +0 -122
- package/src/runtime/anchor-manager.ts +0 -475
- package/src/runtime/async-marker.ts +0 -32
- package/src/runtime/async-runner.ts +0 -381
- package/src/runtime/attention-events.ts +0 -28
- package/src/runtime/auto-summarize.ts +0 -344
- package/src/runtime/background-runner.ts +0 -837
- package/src/runtime/batch-barrier.ts +0 -147
- package/src/runtime/broker-issuer.ts +0 -37
- package/src/runtime/cancellation-token.ts +0 -99
- package/src/runtime/cancellation.ts +0 -99
- package/src/runtime/capability-inventory.ts +0 -137
- package/src/runtime/chain-parser.ts +0 -237
- package/src/runtime/chain-runner.ts +0 -606
- package/src/runtime/checkpoint.ts +0 -291
- package/src/runtime/child-pi-constants.ts +0 -42
- package/src/runtime/child-pi-kill.ts +0 -180
- package/src/runtime/child-pi-pool.ts +0 -68
- package/src/runtime/child-pi-spawn.ts +0 -287
- package/src/runtime/child-pi-steering.ts +0 -128
- package/src/runtime/child-pi-streams.ts +0 -296
- package/src/runtime/child-pi-transcript.ts +0 -169
- package/src/runtime/child-pi.ts +0 -1105
- package/src/runtime/coalesce-tasks.ts +0 -268
- package/src/runtime/code-summary.ts +0 -292
- package/src/runtime/command-trace.ts +0 -105
- package/src/runtime/compact-pipeline.ts +0 -56
- package/src/runtime/compact-stages/ansi-strip-stage.ts +0 -25
- package/src/runtime/compact-stages/blank-collapse-stage.ts +0 -31
- package/src/runtime/compact-stages/deduplicate-stage.ts +0 -34
- package/src/runtime/compact-stages/head-snap-stage.ts +0 -57
- package/src/runtime/compact-stages/index.ts +0 -23
- package/src/runtime/compact-stages/tail-capture-stage.ts +0 -77
- package/src/runtime/compact-stages/truncation-stage.ts +0 -74
- package/src/runtime/compaction-summary.ts +0 -278
- package/src/runtime/completion-guard.ts +0 -207
- package/src/runtime/concurrency.ts +0 -58
- package/src/runtime/crash-classification.ts +0 -229
- package/src/runtime/crash-recovery.ts +0 -577
- package/src/runtime/crew-agent-records.ts +0 -593
- package/src/runtime/crew-agent-runtime.ts +0 -62
- package/src/runtime/crew-broker-child.ts +0 -88
- package/src/runtime/crew-broker-client.ts +0 -673
- package/src/runtime/crew-broker-tokens.ts +0 -84
- package/src/runtime/crew-broker.ts +0 -1276
- package/src/runtime/crew-hooks.ts +0 -227
- package/src/runtime/cross-extension-rpc.ts +0 -143
- package/src/runtime/custom-tools/irc-tool.ts +0 -282
- package/src/runtime/custom-tools/submit-result-tool.ts +0 -102
- package/src/runtime/deadletter.ts +0 -47
- package/src/runtime/delivery-coordinator.ts +0 -211
- package/src/runtime/delta-conflict.ts +0 -348
- package/src/runtime/deterministic-ast.ts +0 -161
- package/src/runtime/diagnostic-export.ts +0 -171
- package/src/runtime/direct-run.ts +0 -45
- package/src/runtime/dwf-state-store.ts +0 -102
- package/src/runtime/dynamic-workflow-context.ts +0 -1016
- package/src/runtime/dynamic-workflow-runner.ts +0 -310
- package/src/runtime/effectiveness.ts +0 -90
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/event-stream-bridge.ts +0 -98
- package/src/runtime/foreground-control.ts +0 -193
- package/src/runtime/foreground-watchdog.ts +0 -132
- package/src/runtime/global-worker-cap.ts +0 -96
- package/src/runtime/goal-achievement.ts +0 -148
- package/src/runtime/goal-evaluator.ts +0 -361
- package/src/runtime/goal-loop-runner.ts +0 -828
- package/src/runtime/goal-state-store.ts +0 -219
- package/src/runtime/green-contract.ts +0 -55
- package/src/runtime/group-join.ts +0 -267
- package/src/runtime/handoff-manager.ts +0 -598
- package/src/runtime/heartbeat-gradient.ts +0 -36
- package/src/runtime/heartbeat-watcher.ts +0 -239
- package/src/runtime/hidden-handoff.ts +0 -407
- package/src/runtime/important-line-classifier.ts +0 -140
- package/src/runtime/intercom-bridge.ts +0 -187
- package/src/runtime/iteration-hooks.ts +0 -305
- package/src/runtime/live-agent-control.ts +0 -136
- package/src/runtime/live-agent-manager.ts +0 -704
- package/src/runtime/live-control-realtime.ts +0 -56
- package/src/runtime/live-extension-bridge.ts +0 -148
- package/src/runtime/live-irc.ts +0 -97
- package/src/runtime/live-session-health.ts +0 -108
- package/src/runtime/live-session-runtime.ts +0 -1139
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/manifest-cache.ts +0 -375
- package/src/runtime/mcp-proxy.ts +0 -105
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/model-fallback.ts +0 -409
- package/src/runtime/model-resolver.ts +0 -126
- package/src/runtime/model-scope.ts +0 -158
- package/src/runtime/orphan-worker-registry.ts +0 -465
- package/src/runtime/output-validator.ts +0 -202
- package/src/runtime/overflow-recovery.ts +0 -206
- package/src/runtime/parallel-research.ts +0 -68
- package/src/runtime/parallel-utils.ts +0 -160
- package/src/runtime/parent-guard.ts +0 -134
- package/src/runtime/path-overlap.ts +0 -150
- package/src/runtime/peer-dep.ts +0 -292
- package/src/runtime/per-write-validator.ts +0 -181
- package/src/runtime/phase-progress.ts +0 -217
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pi-args.ts +0 -655
- package/src/runtime/pi-json-output.ts +0 -170
- package/src/runtime/pi-spawn.ts +0 -273
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/plan-templates.ts +0 -201
- package/src/runtime/policy-engine.ts +0 -115
- package/src/runtime/post-checks.ts +0 -142
- package/src/runtime/post-exit-stdio-guard.ts +0 -104
- package/src/runtime/process-lifecycle.ts +0 -491
- package/src/runtime/process-status.ts +0 -154
- package/src/runtime/progress-event-coalescer.ts +0 -44
- package/src/runtime/progress-tracker.ts +0 -124
- package/src/runtime/prose-compressor.ts +0 -162
- package/src/runtime/recovery-recipes.ts +0 -203
- package/src/runtime/replace.ts +0 -570
- package/src/runtime/resilient-edit.ts +0 -153
- package/src/runtime/result-extractor.ts +0 -213
- package/src/runtime/retry-executor.ts +0 -100
- package/src/runtime/retry-runner.ts +0 -336
- package/src/runtime/role-permission.ts +0 -59
- package/src/runtime/run-coalesced-task-group.ts +0 -308
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/run-tracker.ts +0 -116
- package/src/runtime/runtime-policy.ts +0 -29
- package/src/runtime/runtime-resolver.ts +0 -172
- package/src/runtime/runtime-warmup.ts +0 -160
- package/src/runtime/scheduler.ts +0 -353
- package/src/runtime/semaphore.ts +0 -141
- package/src/runtime/sensitive-paths.ts +0 -93
- package/src/runtime/session-resources.ts +0 -25
- package/src/runtime/session-snapshot.ts +0 -59
- package/src/runtime/session-usage.ts +0 -79
- package/src/runtime/settings-store.ts +0 -155
- package/src/runtime/sidechain-output.ts +0 -35
- package/src/runtime/single-agent-compose.ts +0 -84
- package/src/runtime/skill-effectiveness.ts +0 -524
- package/src/runtime/skill-instructions.ts +0 -391
- package/src/runtime/stale-reconciler.ts +0 -680
- package/src/runtime/stream-preview.ts +0 -184
- package/src/runtime/streaming-output.ts +0 -50
- package/src/runtime/subagent-manager.ts +0 -561
- package/src/runtime/subprocess-tool-registry.ts +0 -70
- package/src/runtime/supervisor-contact.ts +0 -64
- package/src/runtime/task-display.ts +0 -52
- package/src/runtime/task-graph-scheduler.ts +0 -227
- package/src/runtime/task-graph.ts +0 -290
- package/src/runtime/task-health.ts +0 -83
- package/src/runtime/task-id.ts +0 -161
- package/src/runtime/task-output-context.ts +0 -602
- package/src/runtime/task-packet.ts +0 -268
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/capabilities.ts +0 -78
- package/src/runtime/task-runner/context-retrieval.ts +0 -163
- package/src/runtime/task-runner/live-executor.ts +0 -227
- package/src/runtime/task-runner/output-splitter.ts +0 -152
- package/src/runtime/task-runner/progress.ts +0 -157
- package/src/runtime/task-runner/prompt-builder.ts +0 -278
- package/src/runtime/task-runner/prompt-pipeline.ts +0 -88
- package/src/runtime/task-runner/result-utils.ts +0 -16
- package/src/runtime/task-runner/retrieval-orchestrator.ts +0 -310
- package/src/runtime/task-runner/run-projection.ts +0 -125
- package/src/runtime/task-runner/state-helpers.ts +0 -176
- package/src/runtime/task-runner/tail-read.ts +0 -34
- package/src/runtime/task-runner.ts +0 -1420
- package/src/runtime/team-runner-artifacts.ts +0 -13
- package/src/runtime/team-runner.ts +0 -1993
- package/src/runtime/tool-output-pruner.ts +0 -333
- package/src/runtime/tool-progress.ts +0 -278
- package/src/runtime/usage-tracker.ts +0 -73
- package/src/runtime/verification-gates.ts +0 -571
- package/src/runtime/verification-integrity.ts +0 -105
- package/src/runtime/verification-worktree.ts +0 -186
- package/src/runtime/worker-heartbeat.ts +0 -25
- package/src/runtime/worker-startup.ts +0 -83
- package/src/runtime/workflow-state.ts +0 -195
- package/src/runtime/workspace-lock.ts +0 -443
- package/src/runtime/workspace-tree.ts +0 -312
- package/src/runtime/yield-handler.ts +0 -224
- package/src/runtime/zombie-scanner.ts +0 -298
- package/src/schema/config-schema.ts +0 -327
- package/src/schema/team-tool-schema.ts +0 -415
- package/src/schema/validation-types.ts +0 -159
- package/src/skills/discover-skills.ts +0 -197
- package/src/skills/skill-templates.ts +0 -456
- package/src/skills/validate.ts +0 -285
- package/src/state/active-run-registry.ts +0 -439
- package/src/state/artifact-store.ts +0 -176
- package/src/state/atomic-write.ts +0 -903
- package/src/state/blob-store.ts +0 -308
- package/src/state/contracts.ts +0 -158
- package/src/state/crew-init.ts +0 -269
- package/src/state/decision-ledger.ts +0 -345
- package/src/state/event-log-rotation.ts +0 -342
- package/src/state/event-log.ts +0 -1311
- package/src/state/event-reconstructor.ts +0 -229
- package/src/state/gitignore-manager.ts +0 -46
- package/src/state/health-store.ts +0 -79
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/instinct-store.ts +0 -275
- package/src/state/jsonl-writer.ts +0 -107
- package/src/state/locks.ts +0 -562
- package/src/state/mailbox.ts +0 -916
- package/src/state/observation-store.ts +0 -176
- package/src/state/run-cache.ts +0 -193
- package/src/state/run-graph.ts +0 -183
- package/src/state/run-metrics.ts +0 -165
- package/src/state/schedule.ts +0 -166
- package/src/state/session-state-map.ts +0 -51
- package/src/state/state-store.ts +0 -958
- package/src/state/task-claims.ts +0 -61
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/state/types.ts +0 -440
- package/src/state/usage.ts +0 -138
- package/src/state/worker-atomic-writer.ts +0 -194
- package/src/subagents/async-entry.ts +0 -1
- package/src/subagents/index.ts +0 -3
- package/src/subagents/live/control.ts +0 -1
- package/src/subagents/live/manager.ts +0 -1
- package/src/subagents/live/realtime.ts +0 -1
- package/src/subagents/live/session-runtime.ts +0 -1
- package/src/subagents/manager.ts +0 -1
- package/src/subagents/spawn.ts +0 -1
- package/src/teams/discover-teams.ts +0 -187
- package/src/teams/team-config.ts +0 -27
- package/src/teams/team-serializer.ts +0 -38
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/types/diff.d.ts +0 -18
- package/src/types/new-api-types.ts +0 -35
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/card-colors.ts +0 -139
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -111
- package/src/ui/dashboard-panes/agents-pane.ts +0 -164
- package/src/ui/dashboard-panes/cancellation-pane.ts +0 -66
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/dashboard-panes/health-pane.ts +0 -31
- package/src/ui/dashboard-panes/mailbox-pane.ts +0 -35
- package/src/ui/dashboard-panes/metrics-pane.ts +0 -37
- package/src/ui/dashboard-panes/progress-pane.ts +0 -38
- package/src/ui/dashboard-panes/transcript-pane.ts +0 -10
- package/src/ui/deploy-bundled-themes.ts +0 -71
- package/src/ui/dwf-phase-display.ts +0 -151
- package/src/ui/dynamic-border.ts +0 -35
- package/src/ui/heartbeat-aggregator.ts +0 -74
- package/src/ui/key-utils.ts +0 -42
- package/src/ui/keybinding-map.ts +0 -248
- package/src/ui/layout-primitives.ts +0 -106
- package/src/ui/live-conversation-overlay.ts +0 -183
- package/src/ui/live-duration.ts +0 -55
- package/src/ui/live-run-sidebar.ts +0 -293
- package/src/ui/loaders.ts +0 -6
- package/src/ui/mascot.ts +0 -449
- package/src/ui/overlays/agent-picker-overlay.ts +0 -65
- package/src/ui/overlays/confirm-overlay.ts +0 -60
- package/src/ui/overlays/help-overlay.ts +0 -177
- package/src/ui/overlays/mailbox-compose-overlay.ts +0 -181
- package/src/ui/overlays/mailbox-compose-preview.ts +0 -78
- package/src/ui/overlays/mailbox-detail-overlay.ts +0 -154
- package/src/ui/pi-ui-compat.ts +0 -68
- package/src/ui/powerbar-publisher.ts +0 -459
- package/src/ui/render-coalescer.ts +0 -101
- package/src/ui/render-diff.ts +0 -160
- package/src/ui/render-scheduler.ts +0 -278
- package/src/ui/run-action-dispatcher.ts +0 -182
- package/src/ui/run-dashboard.ts +0 -904
- package/src/ui/run-event-bus.ts +0 -381
- package/src/ui/run-snapshot-cache.ts +0 -1057
- package/src/ui/settings-overlay.ts +0 -1067
- package/src/ui/shared-overlay-scheduler.ts +0 -96
- package/src/ui/snapshot-types.ts +0 -90
- package/src/ui/spinner.ts +0 -17
- package/src/ui/status-colors.ts +0 -143
- package/src/ui/syntax-highlight.ts +0 -136
- package/src/ui/terminal-status.ts +0 -269
- package/src/ui/theme-adapter.ts +0 -271
- package/src/ui/theme-discovery.ts +0 -199
- package/src/ui/tool-progress-formatter.ts +0 -93
- package/src/ui/tool-render.ts +0 -453
- package/src/ui/tool-renderers/brief-mode.ts +0 -225
- package/src/ui/tool-renderers/index.ts +0 -726
- package/src/ui/transcript-cache.ts +0 -128
- package/src/ui/transcript-entries.ts +0 -256
- package/src/ui/transcript-viewer.ts +0 -424
- package/src/ui/widget/index.ts +0 -391
- package/src/ui/widget/widget-formatters.ts +0 -182
- package/src/ui/widget/widget-model.ts +0 -110
- package/src/ui/widget/widget-renderer.ts +0 -182
- package/src/ui/widget/widget-types.ts +0 -36
- package/src/utils/bm25-search.ts +0 -235
- package/src/utils/completion-dedupe.ts +0 -63
- package/src/utils/conflict-detect.ts +0 -668
- package/src/utils/env-allowlist.ts +0 -30
- package/src/utils/env-filter.ts +0 -155
- package/src/utils/file-coalescer.ts +0 -98
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/frontmatter.ts +0 -70
- package/src/utils/fs-watch.ts +0 -37
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/git.ts +0 -260
- package/src/utils/guards.ts +0 -107
- package/src/utils/ids.ts +0 -24
- package/src/utils/incremental-reader.ts +0 -220
- package/src/utils/internal-error.ts +0 -10
- package/src/utils/names.ts +0 -36
- package/src/utils/ndjson.ts +0 -115
- package/src/utils/paths.ts +0 -227
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/redaction.ts +0 -347
- package/src/utils/resolve-shell.ts +0 -36
- package/src/utils/run-watcher-registry.ts +0 -152
- package/src/utils/safe-paths.ts +0 -383
- package/src/utils/scan-cache.ts +0 -144
- package/src/utils/session-utils.ts +0 -110
- package/src/utils/sleep.ts +0 -54
- package/src/utils/socket-path.ts +0 -127
- package/src/utils/sse-parser.ts +0 -129
- package/src/utils/task-name-generator.ts +0 -337
- package/src/utils/timings.ts +0 -33
- package/src/utils/token-counter.ts +0 -200
- package/src/utils/visual.ts +0 -207
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/discover-workflows.ts +0 -290
- package/src/workflows/intermediate-store.ts +0 -166
- package/src/workflows/preflight-validator.ts +0 -168
- package/src/workflows/topology-analyzer.ts +0 -203
- package/src/workflows/validate-workflow.ts +0 -50
- package/src/workflows/workflow-config.ts +0 -70
- package/src/workflows/workflow-serializer.ts +0 -32
- package/src/worktree/branch-freshness.ts +0 -112
- package/src/worktree/cleanup.ts +0 -341
- package/src/worktree/worktree-manager.ts +0 -1200
package/src/state/mailbox.ts
DELETED
|
@@ -1,916 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { DEFAULT_MAILBOX } from "../config/defaults.ts";
|
|
4
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
5
|
-
import { redactSecrets } from "../utils/redaction.ts";
|
|
6
|
-
import { atomicWriteFile } from "./atomic-write.ts";
|
|
7
|
-
import { withEventLogLockSync } from "./event-log.ts";
|
|
8
|
-
import { withFileLockAsync, withFileLockSync } from "./locks.ts";
|
|
9
|
-
import type { TeamRunManifest } from "./types.ts";
|
|
10
|
-
|
|
11
|
-
export type MailboxDirection = "inbox" | "outbox";
|
|
12
|
-
export type MailboxMessageStatus = "queued" | "delivered" | "acknowledged";
|
|
13
|
-
export type MailboxMessageKind = "message" | "steer" | "follow-up" | "response" | "group_join";
|
|
14
|
-
export type MailboxMessagePriority = "urgent" | "normal" | "low";
|
|
15
|
-
export type MailboxDeliveryMode = "interrupt" | "next_turn";
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Phase 1.3: post-append observer (single notification point)
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// A registry of callbacks invoked AFTER a durable mailbox append completes
|
|
21
|
-
// (both sync and async paths). The broker registers here to fan out live
|
|
22
|
-
// notifications to connected recipients. The notifier is non-throwing and
|
|
23
|
-
// never blocks the append — it queues work via queueMicrotask so a slow
|
|
24
|
-
// observer cannot stall the mailbox write path. Registration is idempotent.
|
|
25
|
-
|
|
26
|
-
export type MailboxAppendObserver = (message: MailboxMessage) => void;
|
|
27
|
-
|
|
28
|
-
const mailboxAppendObservers = new Set<MailboxAppendObserver>();
|
|
29
|
-
|
|
30
|
-
/** Register a post-append observer. Returns an unsubscribe function. */
|
|
31
|
-
export function registerMailboxAppendObserver(fn: MailboxAppendObserver): () => void {
|
|
32
|
-
mailboxAppendObservers.add(fn);
|
|
33
|
-
return () => {
|
|
34
|
-
mailboxAppendObservers.delete(fn);
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Internal: invoked by appendMailboxMessage[Async] AFTER the durable write +
|
|
40
|
-
* delivery RMW have completed. Non-throwing; never blocks the caller.
|
|
41
|
-
*/
|
|
42
|
-
function notifyMailboxAppended(message: MailboxMessage): void {
|
|
43
|
-
if (mailboxAppendObservers.size === 0) return;
|
|
44
|
-
// Snapshot the message so a later mutation by the caller cannot affect
|
|
45
|
-
// what the observer sees.
|
|
46
|
-
const snapshot = { ...message };
|
|
47
|
-
queueMicrotask(() => {
|
|
48
|
-
for (const fn of mailboxAppendObservers) {
|
|
49
|
-
try {
|
|
50
|
-
fn(snapshot);
|
|
51
|
-
} catch {
|
|
52
|
-
/* observer must not break the append path */
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface MailboxMessage {
|
|
59
|
-
id: string;
|
|
60
|
-
runId: string;
|
|
61
|
-
direction: MailboxDirection;
|
|
62
|
-
from: string;
|
|
63
|
-
to: string;
|
|
64
|
-
body: string;
|
|
65
|
-
createdAt: string;
|
|
66
|
-
status: MailboxMessageStatus;
|
|
67
|
-
kind?: MailboxMessageKind;
|
|
68
|
-
priority?: MailboxMessagePriority;
|
|
69
|
-
deliveryMode?: MailboxDeliveryMode;
|
|
70
|
-
taskId?: string;
|
|
71
|
-
acknowledgedAt?: string;
|
|
72
|
-
data?: Record<string, unknown>;
|
|
73
|
-
/** ID of the original message this is a reply to. */
|
|
74
|
-
replyTo?: string;
|
|
75
|
-
/** Task ID sending the reply. */
|
|
76
|
-
replyFrom?: string;
|
|
77
|
-
/** Ms epoch deadline for a reply. */
|
|
78
|
-
replyDeadline?: number;
|
|
79
|
-
/** ISO timestamp when a reply was received for this message. */
|
|
80
|
-
repliedAt?: string;
|
|
81
|
-
/** Content of the reply received for this message. */
|
|
82
|
-
replyContent?: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface MailboxDeliveryState {
|
|
86
|
-
messages: Record<string, MailboxMessageStatus>;
|
|
87
|
-
updatedAt: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface MailboxValidationIssue {
|
|
91
|
-
level: "error" | "warning";
|
|
92
|
-
path: string;
|
|
93
|
-
message: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface MailboxValidationReport {
|
|
97
|
-
issues: MailboxValidationIssue[];
|
|
98
|
-
repaired: string[];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export interface MailboxReplayResult {
|
|
102
|
-
messages: MailboxMessage[];
|
|
103
|
-
updatedAt: string;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function mailboxDir(manifest: TeamRunManifest): string {
|
|
107
|
-
return path.join(manifest.stateRoot, "mailbox");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function safeMailboxDir(manifest: TeamRunManifest, create = false): string {
|
|
111
|
-
const dir = mailboxDir(manifest);
|
|
112
|
-
if (create) {
|
|
113
|
-
try {
|
|
114
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
115
|
-
} catch (error) {
|
|
116
|
-
// Windows EPERM: can occur when dir uses long-name form (runneradmin)
|
|
117
|
-
// but filesystem parent only exists in short-name form (RUNNER~1).
|
|
118
|
-
// Retry with realpathSync-resolved form.
|
|
119
|
-
if (process.platform === "win32" && (error as NodeJS.ErrnoException).code === "EPERM") {
|
|
120
|
-
try {
|
|
121
|
-
const realDir = fs.realpathSync(path.dirname(dir));
|
|
122
|
-
const correctedDir = path.join(realDir, path.basename(dir));
|
|
123
|
-
fs.mkdirSync(correctedDir, { recursive: true });
|
|
124
|
-
} catch {
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// SECURITY: When create=true, dir now exists and must be validated via
|
|
133
|
-
// resolveRealContainedPath. When create=false, missing dir must throw —
|
|
134
|
-
// never return an unvalidated bare path (bypasses containment checks).
|
|
135
|
-
if (!fs.existsSync(dir)) {
|
|
136
|
-
if (create) throw new Error(`Mailbox directory creation failed: ${dir}`);
|
|
137
|
-
return path.join(dir); // will throw in callers via resolveRealContainedPath on read
|
|
138
|
-
}
|
|
139
|
-
if (fs.lstatSync(dir).isSymbolicLink()) throw new Error(`Invalid mailbox directory: ${dir}`);
|
|
140
|
-
// Return dir as-is. The containment guarantee is provided by
|
|
141
|
-
// manifest.stateRoot which was validated by createRunPaths. Re-resolving
|
|
142
|
-
// through resolveRealContainedPath can change the path form on Windows
|
|
143
|
-
// (short-name vs long-name), causing subsequent operations to see a
|
|
144
|
-
// different filesystem entry.
|
|
145
|
-
return dir;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function safeTaskId(taskId: string): string {
|
|
149
|
-
if (!/^[\w.-]+$/.test(taskId) || taskId.includes("..") || path.isAbsolute(taskId))
|
|
150
|
-
throw new Error(`Invalid mailbox task id: ${taskId}`);
|
|
151
|
-
return taskId;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function safeMailboxTasksRoot(manifest: TeamRunManifest, create = false): string {
|
|
155
|
-
const root = path.join(safeMailboxDir(manifest, create), "tasks");
|
|
156
|
-
if (create) fs.mkdirSync(root, { recursive: true });
|
|
157
|
-
if (!fs.existsSync(root)) return root;
|
|
158
|
-
if (fs.lstatSync(root).isSymbolicLink()) throw new Error(`Invalid mailbox tasks directory: ${root}`);
|
|
159
|
-
// Return root as-is (see safeMailboxDir comment about path form consistency)
|
|
160
|
-
return root;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function taskMailboxDir(manifest: TeamRunManifest, taskId: string, create = false): string {
|
|
164
|
-
const tasksRoot = safeMailboxTasksRoot(manifest, create);
|
|
165
|
-
const normalizedTaskId = safeTaskId(taskId);
|
|
166
|
-
const resolved = path.resolve(tasksRoot, normalizedTaskId);
|
|
167
|
-
const relative = path.relative(tasksRoot, resolved);
|
|
168
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Invalid mailbox task id: ${taskId}`);
|
|
169
|
-
if (create) fs.mkdirSync(resolved, { recursive: true });
|
|
170
|
-
if (fs.existsSync(resolved) && fs.lstatSync(resolved).isSymbolicLink()) throw new Error(`Invalid mailbox task directory: ${resolved}`);
|
|
171
|
-
// Return resolved as-is (see safeMailboxDir comment about path form consistency)
|
|
172
|
-
return resolved;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function mailboxPath(manifest: TeamRunManifest, direction: MailboxDirection, taskId?: string, create = false): string {
|
|
176
|
-
return taskId
|
|
177
|
-
? path.join(taskMailboxDir(manifest, taskId, create), `${direction}.jsonl`)
|
|
178
|
-
: path.join(safeMailboxDir(manifest, create), `${direction}.jsonl`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function deliveryPath(manifest: TeamRunManifest, create = false): string {
|
|
182
|
-
return path.join(safeMailboxDir(manifest, create), "delivery.json");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function safeMailboxFile(filePath: string, parentDir: string): string {
|
|
186
|
-
if (!fs.existsSync(filePath)) return filePath;
|
|
187
|
-
if (fs.lstatSync(filePath).isSymbolicLink()) throw new Error(`Invalid mailbox file: ${filePath}`);
|
|
188
|
-
// Return filePath as-is instead of calling resolveRealContainedPath().
|
|
189
|
-
// The containment guarantee is already provided by safeMailboxDir/taskMailboxDir
|
|
190
|
-
// when the parent directory is created. Re-resolving can change the path
|
|
191
|
-
// form (e.g. short-name vs long-name on Windows), causing subsequent
|
|
192
|
-
// existsSync/appendFileSync to see a different file.
|
|
193
|
-
return filePath;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function mailboxFile(manifest: TeamRunManifest, direction: MailboxDirection, taskId?: string, create = false): string {
|
|
197
|
-
const parent = taskId ? taskMailboxDir(manifest, taskId, create) : safeMailboxDir(manifest, create);
|
|
198
|
-
return safeMailboxFile(path.join(parent, `${direction}.jsonl`), parent);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function deliveryFile(manifest: TeamRunManifest, create = false): string {
|
|
202
|
-
// Pass create=true to ensure mailbox dir exists before computing delivery.json path.
|
|
203
|
-
// This mirrors ensureRunMailbox() pattern — always create before computing nested paths.
|
|
204
|
-
// When create=false, a missing directory is tolerated (callers like readDeliveryState
|
|
205
|
-
// handle missing file via try/catch; but missing directory must not throw here).
|
|
206
|
-
try {
|
|
207
|
-
const parent = safeMailboxDir(manifest, create);
|
|
208
|
-
return safeMailboxFile(path.join(parent, "delivery.json"), parent);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
211
|
-
// Directory missing and create=false: return unvalidated path so callers
|
|
212
|
-
// (readDeliveryState) that have their own try/catch can handle gracefully.
|
|
213
|
-
return path.join(mailboxDir(manifest), "delivery.json");
|
|
214
|
-
}
|
|
215
|
-
throw err;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function ensureRunMailbox(manifest: TeamRunManifest): void {
|
|
220
|
-
safeMailboxDir(manifest, true);
|
|
221
|
-
for (const direction of ["inbox", "outbox"] as const) {
|
|
222
|
-
const filePath = mailboxFile(manifest, direction, undefined, true);
|
|
223
|
-
if (!fs.existsSync(filePath)) {
|
|
224
|
-
// Ensure parent dir exists (may have been lost due to race or
|
|
225
|
-
// Windows path normalization mismatch)
|
|
226
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
227
|
-
atomicWriteFile(filePath, "");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const delivery = deliveryFile(manifest, true);
|
|
231
|
-
if (!fs.existsSync(delivery)) {
|
|
232
|
-
fs.mkdirSync(path.dirname(delivery), { recursive: true });
|
|
233
|
-
atomicWriteFile(delivery, `${JSON.stringify({ messages: {}, updatedAt: new Date().toISOString() }, null, 2)}\n`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function ensureTaskMailbox(manifest: TeamRunManifest, taskId: string): void {
|
|
238
|
-
ensureRunMailbox(manifest);
|
|
239
|
-
taskMailboxDir(manifest, taskId, true);
|
|
240
|
-
for (const direction of ["inbox", "outbox"] as const) {
|
|
241
|
-
const filePath = mailboxFile(manifest, direction, taskId, true);
|
|
242
|
-
if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, "", "utf-8");
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function isDirection(value: unknown): value is MailboxDirection {
|
|
247
|
-
return value === "inbox" || value === "outbox";
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function isStatus(value: unknown): value is MailboxMessageStatus {
|
|
251
|
-
return value === "queued" || value === "delivered" || value === "acknowledged";
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function isKind(value: unknown): value is MailboxMessageKind {
|
|
255
|
-
return value === "message" || value === "steer" || value === "follow-up" || value === "response" || value === "group_join";
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function isPriority(value: unknown): value is MailboxMessagePriority {
|
|
259
|
-
return value === "urgent" || value === "normal" || value === "low";
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function isDeliveryMode(value: unknown): value is MailboxDeliveryMode {
|
|
263
|
-
return value === "interrupt" || value === "next_turn";
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function parseMailboxMessage(raw: unknown, expectedDirection: MailboxDirection): MailboxMessage | undefined {
|
|
267
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
268
|
-
const obj = raw as Record<string, unknown>;
|
|
269
|
-
if (
|
|
270
|
-
typeof obj.id !== "string" ||
|
|
271
|
-
typeof obj.runId !== "string" ||
|
|
272
|
-
!isDirection(obj.direction) ||
|
|
273
|
-
typeof obj.from !== "string" ||
|
|
274
|
-
typeof obj.to !== "string" ||
|
|
275
|
-
typeof obj.body !== "string" ||
|
|
276
|
-
typeof obj.createdAt !== "string" ||
|
|
277
|
-
!isStatus(obj.status)
|
|
278
|
-
)
|
|
279
|
-
return undefined;
|
|
280
|
-
if (obj.direction !== expectedDirection) return undefined;
|
|
281
|
-
const data = obj.data && typeof obj.data === "object" && !Array.isArray(obj.data) ? (obj.data as Record<string, unknown>) : undefined;
|
|
282
|
-
const dataKind = data?.kind;
|
|
283
|
-
return {
|
|
284
|
-
id: obj.id,
|
|
285
|
-
runId: obj.runId,
|
|
286
|
-
direction: obj.direction,
|
|
287
|
-
from: obj.from,
|
|
288
|
-
to: obj.to,
|
|
289
|
-
body: obj.body,
|
|
290
|
-
createdAt: obj.createdAt,
|
|
291
|
-
status: obj.status,
|
|
292
|
-
kind: isKind(obj.kind) ? obj.kind : isKind(dataKind) ? dataKind : undefined,
|
|
293
|
-
priority: isPriority(obj.priority) ? obj.priority : undefined,
|
|
294
|
-
deliveryMode: isDeliveryMode(obj.deliveryMode) ? obj.deliveryMode : undefined,
|
|
295
|
-
taskId: typeof obj.taskId === "string" ? obj.taskId : undefined,
|
|
296
|
-
acknowledgedAt: typeof obj.acknowledgedAt === "string" ? obj.acknowledgedAt : undefined,
|
|
297
|
-
data,
|
|
298
|
-
replyTo: typeof obj.replyTo === "string" ? obj.replyTo : undefined,
|
|
299
|
-
replyFrom: typeof obj.replyFrom === "string" ? obj.replyFrom : undefined,
|
|
300
|
-
replyDeadline: typeof obj.replyDeadline === "number" ? obj.replyDeadline : undefined,
|
|
301
|
-
repliedAt: typeof obj.repliedAt === "string" ? obj.repliedAt : undefined,
|
|
302
|
-
replyContent: typeof obj.replyContent === "string" ? obj.replyContent : undefined,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function readMailboxFile(filePath: string, direction: MailboxDirection): MailboxMessage[] {
|
|
307
|
-
if (!fs.existsSync(filePath)) return [];
|
|
308
|
-
const messages: MailboxMessage[] = [];
|
|
309
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
310
|
-
for (const line of raw.split(/\r?\n/).filter(Boolean)) {
|
|
311
|
-
try {
|
|
312
|
-
const message = parseMailboxMessage(JSON.parse(line) as unknown, direction);
|
|
313
|
-
if (message) messages.push(message);
|
|
314
|
-
} catch {
|
|
315
|
-
// Invalid mailbox lines are reported by validateMailbox().
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return messages;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function safeReadMailboxFile(filePath: string, direction: MailboxDirection): MailboxMessage[] {
|
|
322
|
-
if (!fs.existsSync(filePath)) return [];
|
|
323
|
-
const messages: MailboxMessage[] = readMailboxFile(filePath, direction);
|
|
324
|
-
// 3.3 — also include any rotated archive files alongside the live file.
|
|
325
|
-
// Archive naming: `<filename>.<isoTimestamp>.archive.jsonl`.
|
|
326
|
-
try {
|
|
327
|
-
const dir = path.dirname(filePath);
|
|
328
|
-
const base = path.basename(filePath);
|
|
329
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
330
|
-
if (!entry.startsWith(`${base}.`) || !entry.endsWith(".archive.jsonl")) continue;
|
|
331
|
-
const archivePath = path.join(dir, entry);
|
|
332
|
-
messages.push(...readMailboxFile(archivePath, direction));
|
|
333
|
-
}
|
|
334
|
-
} catch {
|
|
335
|
-
// Directory missing — nothing to read.
|
|
336
|
-
}
|
|
337
|
-
return messages;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* 3.3 — rotate a mailbox JSONL file when it grows past `thresholdBytes`.
|
|
342
|
-
* Renames it to `<file>.<timestamp>.archive.jsonl` and re-creates an empty
|
|
343
|
-
* primary file. Readers continue to see all messages because
|
|
344
|
-
* `safeReadMailboxFile` walks both the primary file and any archives.
|
|
345
|
-
*/
|
|
346
|
-
const MAILBOX_ARCHIVE_THRESHOLD_BYTES = DEFAULT_MAILBOX.perFileThresholdBytes;
|
|
347
|
-
function rotateMailboxFileIfNeeded(filePath: string, thresholdBytes = MAILBOX_ARCHIVE_THRESHOLD_BYTES): boolean {
|
|
348
|
-
try {
|
|
349
|
-
if (!fs.existsSync(filePath)) return false;
|
|
350
|
-
const stat = fs.statSync(filePath);
|
|
351
|
-
if (stat.size < thresholdBytes) return false;
|
|
352
|
-
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
353
|
-
const archivePath = `${filePath}.${ts}.archive.jsonl`;
|
|
354
|
-
fs.renameSync(filePath, archivePath);
|
|
355
|
-
atomicWriteFile(filePath, "");
|
|
356
|
-
// FIX: Prune old archives so total per-direction count stays bounded.
|
|
357
|
-
pruneOldMailboxArchives(filePath);
|
|
358
|
-
return true;
|
|
359
|
-
} catch (error) {
|
|
360
|
-
logInternalError("mailbox.rotate", error, filePath);
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Keep at most `DEFAULT_MAILBOX.maxArchivesPerDirection` archive files per
|
|
367
|
-
* mailbox. Older archives are deleted. Prevents unbounded growth on long runs.
|
|
368
|
-
*/
|
|
369
|
-
function pruneOldMailboxArchives(mailboxFilePath: string): void {
|
|
370
|
-
try {
|
|
371
|
-
const dir = path.dirname(mailboxFilePath);
|
|
372
|
-
const base = path.basename(mailboxFilePath);
|
|
373
|
-
const archives = fs
|
|
374
|
-
.readdirSync(dir)
|
|
375
|
-
.filter((f) => f.startsWith(base) && f.includes(".archive.jsonl"))
|
|
376
|
-
.sort(); // Chronological (ISO timestamp in filename)
|
|
377
|
-
const excess = archives.length - DEFAULT_MAILBOX.maxArchivesPerDirection;
|
|
378
|
-
for (let i = 0; i < excess; i += 1) {
|
|
379
|
-
fs.rmSync(path.join(dir, archives[i]), { force: true });
|
|
380
|
-
}
|
|
381
|
-
} catch (error) {
|
|
382
|
-
logInternalError("mailbox.prune", error, mailboxFilePath);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export function readMailbox(
|
|
387
|
-
manifest: TeamRunManifest,
|
|
388
|
-
direction?: MailboxDirection,
|
|
389
|
-
taskId?: string,
|
|
390
|
-
kind?: MailboxMessageKind,
|
|
391
|
-
): MailboxMessage[] {
|
|
392
|
-
const directions = direction ? [direction] : (["inbox", "outbox"] as const);
|
|
393
|
-
return directions
|
|
394
|
-
.flatMap((item) => safeReadMailboxFile(mailboxFile(manifest, item, taskId), item))
|
|
395
|
-
.filter((msg) => !kind || msg.kind === kind)
|
|
396
|
-
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
export function readAllMailboxMessages(manifest: TeamRunManifest, direction?: MailboxDirection, signal?: AbortSignal): MailboxMessage[] {
|
|
400
|
-
const directions = direction ? [direction] : (["inbox", "outbox"] as const);
|
|
401
|
-
return directions.flatMap((item) => readAllMessages(manifest, item, signal)).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function readAllMessages(manifest: TeamRunManifest, direction: MailboxDirection, signal?: AbortSignal): MailboxMessage[] {
|
|
405
|
-
const messages = [...safeReadMailboxFile(mailboxFile(manifest, direction), direction)];
|
|
406
|
-
const tasksDir = safeMailboxTasksRoot(manifest);
|
|
407
|
-
if (fs.existsSync(tasksDir)) {
|
|
408
|
-
for (const entry of fs.readdirSync(tasksDir, { withFileTypes: true })) {
|
|
409
|
-
if (signal?.aborted) break;
|
|
410
|
-
if (!entry.isDirectory()) continue;
|
|
411
|
-
messages.push(...safeReadMailboxFile(mailboxFile(manifest, direction, entry.name), direction));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return messages.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function readAllInboxMessages(manifest: TeamRunManifest): MailboxMessage[] {
|
|
418
|
-
return readAllMessages(manifest, "inbox");
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// FIND-01: in-process delivery cache to avoid O(N²) re-reads on every append.
|
|
422
|
-
// Keyed by delivery file path; invalidated by mtime check on read + updated on
|
|
423
|
-
// write. Team-runner is single-process so in-process caching is sufficient.
|
|
424
|
-
const deliveryCache = new Map<string, { mtimeMs: number; state: MailboxDeliveryState }>();
|
|
425
|
-
const MAX_DELIVERY_CACHE_ENTRIES = 256;
|
|
426
|
-
// R1 review fix: setDeliveryCacheEntry stores an immutable snapshot (deep
|
|
427
|
-
// copy of `messages`) so callers mutating the returned state cannot corrupt
|
|
428
|
-
// the cache (TOCTOU race), and bounds the map size with FIFO eviction to
|
|
429
|
-
// prevent unbounded growth across runs.
|
|
430
|
-
function setDeliveryCacheEntry(filePath: string, entry: { mtimeMs: number; state: MailboxDeliveryState }): void {
|
|
431
|
-
if (deliveryCache.size >= MAX_DELIVERY_CACHE_ENTRIES) {
|
|
432
|
-
const oldest = deliveryCache.keys().next().value;
|
|
433
|
-
if (oldest !== undefined) deliveryCache.delete(oldest);
|
|
434
|
-
}
|
|
435
|
-
deliveryCache.set(filePath, {
|
|
436
|
-
mtimeMs: entry.mtimeMs,
|
|
437
|
-
state: { ...entry.state, messages: { ...entry.state.messages } },
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
export function readDeliveryState(manifest: TeamRunManifest): MailboxDeliveryState {
|
|
442
|
-
const filePath = deliveryFile(manifest);
|
|
443
|
-
let stat: fs.Stats;
|
|
444
|
-
try {
|
|
445
|
-
stat = fs.statSync(filePath);
|
|
446
|
-
} catch (e) {
|
|
447
|
-
// R1 review fix: narrow to ENOENT so permission errors aren't silently
|
|
448
|
-
// treated as "missing file" (would wipe a valid cache entry).
|
|
449
|
-
if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
|
|
450
|
-
deliveryCache.delete(filePath);
|
|
451
|
-
return { messages: {}, updatedAt: new Date().toISOString() };
|
|
452
|
-
}
|
|
453
|
-
const cached = deliveryCache.get(filePath);
|
|
454
|
-
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
455
|
-
// R2 review fix: return a copy so callers mutating the result cannot
|
|
456
|
-
// leak into the cached snapshot (residual TOCTOU: the cache holds the
|
|
457
|
-
// snapshot until the next write replaces it; without this copy, a
|
|
458
|
-
// pre-write mutation by one caller would be persisted into the
|
|
459
|
-
// post-write snapshot by the next writer's setDeliveryCacheEntry).
|
|
460
|
-
return { ...cached.state, messages: { ...cached.state.messages } };
|
|
461
|
-
}
|
|
462
|
-
try {
|
|
463
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
|
|
464
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Invalid delivery state.");
|
|
465
|
-
const obj = raw as Record<string, unknown>;
|
|
466
|
-
const messages: Record<string, MailboxMessageStatus> = {};
|
|
467
|
-
if (obj.messages && typeof obj.messages === "object" && !Array.isArray(obj.messages)) {
|
|
468
|
-
for (const [id, status] of Object.entries(obj.messages)) if (isStatus(status)) messages[id] = status;
|
|
469
|
-
}
|
|
470
|
-
const state: MailboxDeliveryState = {
|
|
471
|
-
messages,
|
|
472
|
-
updatedAt: typeof obj.updatedAt === "string" ? obj.updatedAt : new Date().toISOString(),
|
|
473
|
-
};
|
|
474
|
-
setDeliveryCacheEntry(filePath, { mtimeMs: stat.mtimeMs, state });
|
|
475
|
-
return state;
|
|
476
|
-
} catch {
|
|
477
|
-
return { messages: {}, updatedAt: new Date().toISOString() };
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function writeDeliveryState(
|
|
482
|
-
manifest: TeamRunManifest,
|
|
483
|
-
state: MailboxDeliveryState,
|
|
484
|
-
options?: { durability?: "full" | "best-effort" },
|
|
485
|
-
): void {
|
|
486
|
-
ensureRunMailbox(manifest);
|
|
487
|
-
// Prune oldest entries if capped
|
|
488
|
-
const MAX_DELIVERY_MESSAGES = 10000;
|
|
489
|
-
if (Object.keys(state.messages).length > MAX_DELIVERY_MESSAGES) {
|
|
490
|
-
const sorted = Object.entries(state.messages).sort(([, a], [, b]) => {
|
|
491
|
-
const order = { queued: 0, delivered: 1, acknowledged: 2 };
|
|
492
|
-
return (order[a] ?? 3) - (order[b] ?? 3);
|
|
493
|
-
});
|
|
494
|
-
const trimmed = sorted.slice(0, MAX_DELIVERY_MESSAGES);
|
|
495
|
-
state.messages = Object.fromEntries(trimmed);
|
|
496
|
-
}
|
|
497
|
-
// F4: mailbox delivery is informational — accept losing the very last write on
|
|
498
|
-
// a hard crash (the next message will overwrite it on disk). Cheaper fsync on
|
|
499
|
-
// the hot path; terminal/reply paths still pass full durability below.
|
|
500
|
-
const filePath = deliveryFile(manifest, true);
|
|
501
|
-
atomicWriteFile(filePath, `${JSON.stringify(redactSecrets(state), null, 2)}\n`, {
|
|
502
|
-
durability: options?.durability ?? "best-effort",
|
|
503
|
-
});
|
|
504
|
-
// FIND-01: update cache with post-write mtime so subsequent reads get a hit.
|
|
505
|
-
try {
|
|
506
|
-
const postStat = fs.statSync(filePath);
|
|
507
|
-
// setDeliveryCacheEntry stores an immutable snapshot (deep copy of
|
|
508
|
-
// messages) so subsequent read-modify-write callers mutating the
|
|
509
|
-
// returned state cannot corrupt the cache.
|
|
510
|
-
setDeliveryCacheEntry(filePath, { mtimeMs: postStat.mtimeMs, state });
|
|
511
|
-
} catch {
|
|
512
|
-
deliveryCache.delete(filePath);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Append a message to a run's or task's mailbox.
|
|
518
|
-
*
|
|
519
|
-
* SECURITY NOTE: The `from` field is caller-declared — there is no cryptographic
|
|
520
|
-
* sender authentication. This is acceptable because `appendMailboxMessage` is an
|
|
521
|
-
* internal API only callable from within the pi-crew process (no external input).
|
|
522
|
-
* All callers (handleSteer, handleRespond, handleFollowUp) derive `from` from
|
|
523
|
-
* authenticated context (session role, task assignment).
|
|
524
|
-
*
|
|
525
|
-
* If pi-crew ever exposes mailbox writes to external/untrusted input, sender
|
|
526
|
-
* authentication (HMAC or session key) must be added.
|
|
527
|
-
*/
|
|
528
|
-
export function appendMailboxMessage(
|
|
529
|
-
manifest: TeamRunManifest,
|
|
530
|
-
message: Omit<MailboxMessage, "id" | "runId" | "createdAt" | "status"> & {
|
|
531
|
-
id?: string;
|
|
532
|
-
status?: MailboxMessageStatus;
|
|
533
|
-
},
|
|
534
|
-
): MailboxMessage {
|
|
535
|
-
if (message.taskId) ensureTaskMailbox(manifest, message.taskId);
|
|
536
|
-
else ensureRunMailbox(manifest);
|
|
537
|
-
const createdAt = new Date().toISOString();
|
|
538
|
-
const complete: MailboxMessage = {
|
|
539
|
-
id: message.id ?? `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
540
|
-
runId: manifest.runId,
|
|
541
|
-
direction: message.direction,
|
|
542
|
-
from: message.from,
|
|
543
|
-
to: message.to,
|
|
544
|
-
body: message.body,
|
|
545
|
-
createdAt,
|
|
546
|
-
status: message.status ?? "queued",
|
|
547
|
-
kind: message.kind,
|
|
548
|
-
priority: message.priority,
|
|
549
|
-
deliveryMode: message.deliveryMode,
|
|
550
|
-
taskId: message.taskId,
|
|
551
|
-
data: message.data,
|
|
552
|
-
replyTo: message.replyTo,
|
|
553
|
-
replyFrom: message.replyFrom,
|
|
554
|
-
replyDeadline: message.replyDeadline,
|
|
555
|
-
repliedAt: message.repliedAt,
|
|
556
|
-
replyContent: message.replyContent,
|
|
557
|
-
};
|
|
558
|
-
// H2 fix: wrap append in cross-process lock to prevent interleaving on Windows.
|
|
559
|
-
// B8: rotation (rename + recreate) also runs INSIDE the lock so it is serialized
|
|
560
|
-
// with appends — otherwise a concurrent append between rename and recreate can
|
|
561
|
-
// be truncated to an empty file.
|
|
562
|
-
withEventLogLockSync(mailboxFile(manifest, complete.direction, complete.taskId), () => {
|
|
563
|
-
fs.appendFileSync(
|
|
564
|
-
mailboxFile(manifest, complete.direction, complete.taskId),
|
|
565
|
-
`${JSON.stringify(redactSecrets(complete))}\n`,
|
|
566
|
-
"utf-8",
|
|
567
|
-
);
|
|
568
|
-
// 3.3 — rotate mailbox file if it has grown past 10 MB. Cheap stat check;
|
|
569
|
-
// rotates at most once per append.
|
|
570
|
-
rotateMailboxFileIfNeeded(mailboxFile(manifest, complete.direction, complete.taskId));
|
|
571
|
-
});
|
|
572
|
-
// BUGFIX (Round 12 C3): the delivery.json read-modify-write below was
|
|
573
|
-
// UNLOCKED, so concurrent appendMailboxMessage calls could interleave and
|
|
574
|
-
// clobber each other's delivery entries (lost-update race). FIX: wrap the
|
|
575
|
-
// entire read-modify-write in a file lock on the delivery file.
|
|
576
|
-
withFileLockSync(deliveryFile(manifest, true), () => {
|
|
577
|
-
const delivery = readDeliveryState(manifest);
|
|
578
|
-
delivery.messages[complete.id] = complete.status;
|
|
579
|
-
delivery.updatedAt = createdAt;
|
|
580
|
-
// F4: complete transitions are terminal-ish — keep full durability.
|
|
581
|
-
writeDeliveryState(manifest, delivery, { durability: "full" });
|
|
582
|
-
});
|
|
583
|
-
notifyMailboxAppended(complete);
|
|
584
|
-
return complete;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
export function appendSteeringMessage(
|
|
588
|
-
manifest: TeamRunManifest,
|
|
589
|
-
input: {
|
|
590
|
-
taskId: string;
|
|
591
|
-
body: string;
|
|
592
|
-
from?: string;
|
|
593
|
-
to?: string;
|
|
594
|
-
priority?: MailboxMessagePriority;
|
|
595
|
-
status?: MailboxMessageStatus;
|
|
596
|
-
data?: Record<string, unknown>;
|
|
597
|
-
},
|
|
598
|
-
): MailboxMessage {
|
|
599
|
-
return appendMailboxMessage(manifest, {
|
|
600
|
-
direction: "inbox",
|
|
601
|
-
from: input.from ?? "leader",
|
|
602
|
-
to: input.to ?? input.taskId,
|
|
603
|
-
taskId: input.taskId,
|
|
604
|
-
body: input.body,
|
|
605
|
-
kind: "steer",
|
|
606
|
-
priority: input.priority ?? "urgent",
|
|
607
|
-
deliveryMode: "interrupt",
|
|
608
|
-
status: input.status,
|
|
609
|
-
data: { ...(input.data ?? {}), kind: "steer" },
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
export function appendFollowUpMessage(
|
|
614
|
-
manifest: TeamRunManifest,
|
|
615
|
-
input: {
|
|
616
|
-
taskId: string;
|
|
617
|
-
body: string;
|
|
618
|
-
from?: string;
|
|
619
|
-
to?: string;
|
|
620
|
-
priority?: MailboxMessagePriority;
|
|
621
|
-
status?: MailboxMessageStatus;
|
|
622
|
-
data?: Record<string, unknown>;
|
|
623
|
-
},
|
|
624
|
-
): MailboxMessage {
|
|
625
|
-
return appendMailboxMessage(manifest, {
|
|
626
|
-
direction: "inbox",
|
|
627
|
-
from: input.from ?? "leader",
|
|
628
|
-
to: input.to ?? input.taskId,
|
|
629
|
-
taskId: input.taskId,
|
|
630
|
-
body: input.body,
|
|
631
|
-
kind: "follow-up",
|
|
632
|
-
priority: input.priority ?? "normal",
|
|
633
|
-
deliveryMode: "next_turn",
|
|
634
|
-
status: input.status,
|
|
635
|
-
data: { ...(input.data ?? {}), kind: "follow-up" },
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* FIND-02: Async variant of appendMailboxMessage for the live-session path.
|
|
641
|
-
* Uses withFileLockAsync (promise-chain, no sleepSync) instead of
|
|
642
|
-
* withEventLogLockSync/withFileLockSync, preventing event-loop stalls during
|
|
643
|
-
* steering/follow-up delivery. readDeliveryState/writeDeliveryState remain
|
|
644
|
-
* sync but are cheap thanks to the FIND-01 delivery cache.
|
|
645
|
-
*/
|
|
646
|
-
export async function appendMailboxMessageAsync(
|
|
647
|
-
manifest: TeamRunManifest,
|
|
648
|
-
message: Omit<MailboxMessage, "id" | "runId" | "createdAt" | "status"> & {
|
|
649
|
-
id?: string;
|
|
650
|
-
status?: MailboxMessageStatus;
|
|
651
|
-
},
|
|
652
|
-
): Promise<MailboxMessage> {
|
|
653
|
-
if (message.taskId) ensureTaskMailbox(manifest, message.taskId);
|
|
654
|
-
else ensureRunMailbox(manifest);
|
|
655
|
-
const createdAt = new Date().toISOString();
|
|
656
|
-
const complete: MailboxMessage = {
|
|
657
|
-
id: message.id ?? `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
658
|
-
runId: manifest.runId,
|
|
659
|
-
direction: message.direction,
|
|
660
|
-
from: message.from,
|
|
661
|
-
to: message.to,
|
|
662
|
-
body: message.body,
|
|
663
|
-
createdAt,
|
|
664
|
-
status: message.status ?? "queued",
|
|
665
|
-
kind: message.kind,
|
|
666
|
-
priority: message.priority,
|
|
667
|
-
deliveryMode: message.deliveryMode,
|
|
668
|
-
taskId: message.taskId,
|
|
669
|
-
data: message.data,
|
|
670
|
-
replyTo: message.replyTo,
|
|
671
|
-
replyFrom: message.replyFrom,
|
|
672
|
-
replyDeadline: message.replyDeadline,
|
|
673
|
-
repliedAt: message.repliedAt,
|
|
674
|
-
replyContent: message.replyContent,
|
|
675
|
-
};
|
|
676
|
-
const mbFile = mailboxFile(manifest, complete.direction, complete.taskId);
|
|
677
|
-
await withFileLockAsync(mbFile, async () => {
|
|
678
|
-
await fs.promises.appendFile(mbFile, `${JSON.stringify(redactSecrets(complete))}\n`, "utf-8");
|
|
679
|
-
rotateMailboxFileIfNeeded(mbFile);
|
|
680
|
-
});
|
|
681
|
-
// R1 review fix / plan §5 #6: delivery RMW uses the cross-process sync
|
|
682
|
-
// lock (withFileLockSync) so sync callers (acknowledgeMailboxMessage,
|
|
683
|
-
// replayPendingMailboxMessages) serialize against this async path. The
|
|
684
|
-
// body has no await, so sync locking is safe and restores the
|
|
685
|
-
// cross-process safety net that the async lock cannot provide.
|
|
686
|
-
withFileLockSync(deliveryFile(manifest, true), () => {
|
|
687
|
-
const delivery = readDeliveryState(manifest);
|
|
688
|
-
delivery.messages[complete.id] = complete.status;
|
|
689
|
-
delivery.updatedAt = createdAt;
|
|
690
|
-
writeDeliveryState(manifest, delivery, { durability: "full" });
|
|
691
|
-
});
|
|
692
|
-
notifyMailboxAppended(complete);
|
|
693
|
-
return complete;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
export async function appendSteeringMessageAsync(
|
|
697
|
-
manifest: TeamRunManifest,
|
|
698
|
-
input: {
|
|
699
|
-
taskId: string;
|
|
700
|
-
body: string;
|
|
701
|
-
from?: string;
|
|
702
|
-
to?: string;
|
|
703
|
-
priority?: MailboxMessagePriority;
|
|
704
|
-
status?: MailboxMessageStatus;
|
|
705
|
-
data?: Record<string, unknown>;
|
|
706
|
-
},
|
|
707
|
-
): Promise<MailboxMessage> {
|
|
708
|
-
return appendMailboxMessageAsync(manifest, {
|
|
709
|
-
direction: "inbox",
|
|
710
|
-
from: input.from ?? "leader",
|
|
711
|
-
to: input.to ?? input.taskId,
|
|
712
|
-
taskId: input.taskId,
|
|
713
|
-
body: input.body,
|
|
714
|
-
kind: "steer",
|
|
715
|
-
priority: input.priority ?? "urgent",
|
|
716
|
-
deliveryMode: "interrupt",
|
|
717
|
-
status: input.status,
|
|
718
|
-
data: { ...(input.data ?? {}), kind: "steer" },
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
export async function appendFollowUpMessageAsync(
|
|
723
|
-
manifest: TeamRunManifest,
|
|
724
|
-
input: {
|
|
725
|
-
taskId: string;
|
|
726
|
-
body: string;
|
|
727
|
-
from?: string;
|
|
728
|
-
to?: string;
|
|
729
|
-
priority?: MailboxMessagePriority;
|
|
730
|
-
status?: MailboxMessageStatus;
|
|
731
|
-
data?: Record<string, unknown>;
|
|
732
|
-
},
|
|
733
|
-
): Promise<MailboxMessage> {
|
|
734
|
-
return appendMailboxMessageAsync(manifest, {
|
|
735
|
-
direction: "inbox",
|
|
736
|
-
from: input.from ?? "leader",
|
|
737
|
-
to: input.to ?? input.taskId,
|
|
738
|
-
taskId: input.taskId,
|
|
739
|
-
body: input.body,
|
|
740
|
-
kind: "follow-up",
|
|
741
|
-
priority: input.priority ?? "normal",
|
|
742
|
-
deliveryMode: "next_turn",
|
|
743
|
-
status: input.status,
|
|
744
|
-
data: { ...(input.data ?? {}), kind: "follow-up" },
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
export function listMailboxByKind(manifest: TeamRunManifest, kind: MailboxMessageKind, direction?: MailboxDirection): MailboxMessage[] {
|
|
749
|
-
const messages = direction
|
|
750
|
-
? readAllMessages(manifest, direction)
|
|
751
|
-
: [...readAllMessages(manifest, "inbox"), ...readAllMessages(manifest, "outbox")].sort((a, b) =>
|
|
752
|
-
a.createdAt.localeCompare(b.createdAt),
|
|
753
|
-
);
|
|
754
|
-
return messages.filter((message) => message.kind === kind || message.data?.kind === kind);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
export function findMailboxMessageByRequestId(manifest: TeamRunManifest, requestId: string): MailboxMessage | undefined {
|
|
758
|
-
return readMailbox(manifest).find((message) => message.data?.requestId === requestId);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
export function readMailboxMessage(manifest: TeamRunManifest, messageId: string): MailboxMessage | undefined {
|
|
762
|
-
return readMailbox(manifest).find((message) => message.id === messageId);
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
export function acknowledgeMailboxMessage(manifest: TeamRunManifest, messageId: string): MailboxDeliveryState {
|
|
766
|
-
// BUGFIX (Round 12 I6): unlocked read-modify-write on delivery.json could
|
|
767
|
-
// clobber concurrent appends. FIX: wrap in a file lock.
|
|
768
|
-
return withFileLockSync(deliveryFile(manifest, true), () => {
|
|
769
|
-
const delivery = readDeliveryState(manifest);
|
|
770
|
-
if (!delivery.messages[messageId]) throw new Error(`Mailbox message '${messageId}' not found.`);
|
|
771
|
-
delivery.messages[messageId] = "acknowledged";
|
|
772
|
-
delivery.updatedAt = new Date().toISOString();
|
|
773
|
-
// F4: acknowledge is terminal for that message — keep full durability.
|
|
774
|
-
writeDeliveryState(manifest, delivery, { durability: "full" });
|
|
775
|
-
return delivery;
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Update an original mailbox message with reply metadata.
|
|
781
|
-
* Rewrites the mailbox file line containing the original message
|
|
782
|
-
* to include `repliedAt` and `replyContent`.
|
|
783
|
-
*/
|
|
784
|
-
export function updateMailboxMessageReply(manifest: TeamRunManifest, originalMessageId: string, replyContent: string): void {
|
|
785
|
-
const directions: MailboxDirection[] = ["inbox", "outbox"];
|
|
786
|
-
|
|
787
|
-
// Collect all mailbox file paths (global + task-specific)
|
|
788
|
-
const filesToSearch: Array<{
|
|
789
|
-
filePath: string;
|
|
790
|
-
direction: MailboxDirection;
|
|
791
|
-
}> = [];
|
|
792
|
-
for (const direction of directions) {
|
|
793
|
-
filesToSearch.push({
|
|
794
|
-
filePath: mailboxFile(manifest, direction),
|
|
795
|
-
direction,
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
const tasksDir = safeMailboxTasksRoot(manifest);
|
|
799
|
-
if (fs.existsSync(tasksDir)) {
|
|
800
|
-
for (const entry of fs.readdirSync(tasksDir, { withFileTypes: true })) {
|
|
801
|
-
if (!entry.isDirectory()) continue;
|
|
802
|
-
for (const direction of directions) {
|
|
803
|
-
filesToSearch.push({
|
|
804
|
-
filePath: mailboxFile(manifest, direction, entry.name),
|
|
805
|
-
direction,
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
for (const { filePath, direction } of filesToSearch) {
|
|
812
|
-
if (!fs.existsSync(filePath)) continue;
|
|
813
|
-
// FIX: Wrap read-modify-write in withFileLockSync to prevent concurrent
|
|
814
|
-
// updates from clobbering each other (each reply rewrites the whole file).
|
|
815
|
-
const found = withFileLockSync(filePath, () => {
|
|
816
|
-
const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
817
|
-
let localFound = false;
|
|
818
|
-
const updatedLines: string[] = [];
|
|
819
|
-
for (const line of lines) {
|
|
820
|
-
try {
|
|
821
|
-
const parsed = JSON.parse(line) as unknown;
|
|
822
|
-
const msg = parseMailboxMessage(parsed, direction);
|
|
823
|
-
if (msg && msg.id === originalMessageId) {
|
|
824
|
-
msg.repliedAt = new Date().toISOString();
|
|
825
|
-
msg.replyContent = replyContent;
|
|
826
|
-
updatedLines.push(JSON.stringify(redactSecrets(msg)));
|
|
827
|
-
localFound = true;
|
|
828
|
-
} else {
|
|
829
|
-
updatedLines.push(line);
|
|
830
|
-
}
|
|
831
|
-
} catch {
|
|
832
|
-
updatedLines.push(line);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (localFound) {
|
|
836
|
-
atomicWriteFile(filePath, `${updatedLines.join("\n")}\n`);
|
|
837
|
-
}
|
|
838
|
-
return localFound;
|
|
839
|
-
});
|
|
840
|
-
if (found) return;
|
|
841
|
-
}
|
|
842
|
-
// Not finding the original is non-fatal; the reply is still delivered.
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
export function replayPendingMailboxMessages(manifest: TeamRunManifest): MailboxReplayResult {
|
|
846
|
-
// BUGFIX (Round 12 I6): unlocked read-modify-write on delivery.json could
|
|
847
|
-
// clobber concurrent appends/acknowledgments. FIX: wrap in a file lock.
|
|
848
|
-
return withFileLockSync(deliveryFile(manifest, true), () => {
|
|
849
|
-
const delivery = readDeliveryState(manifest);
|
|
850
|
-
const pending = readAllInboxMessages(manifest).filter(
|
|
851
|
-
(message) => message.status !== "acknowledged" && delivery.messages[message.id] !== "acknowledged",
|
|
852
|
-
);
|
|
853
|
-
if (!pending.length) return { messages: [], updatedAt: delivery.updatedAt };
|
|
854
|
-
const updatedAt = new Date().toISOString();
|
|
855
|
-
for (const message of pending) delivery.messages[message.id] = "delivered";
|
|
856
|
-
delivery.updatedAt = updatedAt;
|
|
857
|
-
writeDeliveryState(manifest, delivery);
|
|
858
|
-
return { messages: pending, updatedAt };
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
export function validateMailbox(
|
|
863
|
-
manifest: TeamRunManifest,
|
|
864
|
-
options: { repair?: boolean; signal?: AbortSignal } = {},
|
|
865
|
-
): MailboxValidationReport {
|
|
866
|
-
ensureRunMailbox(manifest);
|
|
867
|
-
const issues: MailboxValidationIssue[] = [];
|
|
868
|
-
const repaired: string[] = [];
|
|
869
|
-
for (const direction of ["inbox", "outbox"] as const) {
|
|
870
|
-
if (options.signal?.aborted) break;
|
|
871
|
-
const filePath = mailboxFile(manifest, direction);
|
|
872
|
-
// FIX: Wrap read + optional repair in withFileLockSync so concurrent appends
|
|
873
|
-
// don't race with the read-modify-write. Mailbox files are capped at 10MB
|
|
874
|
-
// (MAILBOX_ARCHIVE_THRESHOLD_BYTES), so the per-call memory is bounded.
|
|
875
|
-
withFileLockSync(filePath, () => {
|
|
876
|
-
const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
877
|
-
const validLines: string[] = [];
|
|
878
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
879
|
-
if (options.signal?.aborted) break;
|
|
880
|
-
const line = lines[i];
|
|
881
|
-
if (!line) continue;
|
|
882
|
-
try {
|
|
883
|
-
const parsed = JSON.parse(line) as unknown;
|
|
884
|
-
const message = parseMailboxMessage(parsed, direction);
|
|
885
|
-
if (!message) throw new Error("invalid message schema");
|
|
886
|
-
validLines.push(JSON.stringify(redactSecrets(message)));
|
|
887
|
-
} catch (error) {
|
|
888
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
889
|
-
issues.push({ level: "error", path: filePath, message });
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (options.repair && validLines.length !== lines.length) {
|
|
893
|
-
atomicWriteFile(filePath, `${validLines.join("\n")}${validLines.length ? "\n" : ""}`);
|
|
894
|
-
repaired.push(filePath);
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
const delivery = readDeliveryState(manifest);
|
|
899
|
-
const allMessages = readMailbox(manifest);
|
|
900
|
-
for (const message of allMessages) {
|
|
901
|
-
if (options.signal?.aborted) break;
|
|
902
|
-
if (!delivery.messages[message.id])
|
|
903
|
-
issues.push({
|
|
904
|
-
level: "warning",
|
|
905
|
-
path: deliveryFile(manifest),
|
|
906
|
-
message: `Missing delivery entry for ${message.id}.`,
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (options.repair) {
|
|
910
|
-
for (const message of allMessages) delivery.messages[message.id] ??= message.status;
|
|
911
|
-
delivery.updatedAt = new Date().toISOString();
|
|
912
|
-
writeDeliveryState(manifest, delivery);
|
|
913
|
-
repaired.push(deliveryFile(manifest));
|
|
914
|
-
}
|
|
915
|
-
return { issues, repaired };
|
|
916
|
-
}
|