@excitedjs/dreamux 0.26.0-beta.265 → 0.26.0
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.json +393 -0
- package/CHANGELOG.md +71 -1
- package/README.md +47 -34
- package/dist/admin/client.js +18 -7
- package/dist/admin/client.js.map +1 -1
- package/dist/admin/socket.js +15 -104
- package/dist/admin/socket.js.map +1 -1
- package/dist/agent-runtime/capabilities.js +4 -17
- package/dist/agent-runtime/capabilities.js.map +1 -1
- package/dist/agent-runtime/catalog.js.map +1 -1
- package/dist/agent-runtime/external-provider.js.map +1 -1
- package/dist/agent-runtime/host-context.js +1 -12
- package/dist/agent-runtime/host-context.js.map +1 -1
- package/dist/agent-runtime/host-paths.js +1 -1
- package/dist/agent-runtime/host-paths.js.map +1 -1
- package/dist/agent-runtime/index.js +7 -0
- package/dist/agent-runtime/index.js.map +1 -1
- package/dist/agent-runtime/skill-sources.js +23 -6
- package/dist/agent-runtime/skill-sources.js.map +1 -1
- package/dist/channel/catalog.js.map +1 -1
- package/dist/channel/external-channel-provider.js.map +1 -1
- package/dist/cli/commands/daemon.js +5 -8
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/dispatcher.js +11 -15
- package/dist/cli/commands/dispatcher.js.map +1 -1
- package/dist/cli/commands/index.js +4 -4
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +2 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/onboard.js +0 -4
- package/dist/cli/commands/onboard.js.map +1 -1
- package/dist/cli/commands/parse.js +1 -1
- package/dist/cli/commands/parse.js.map +1 -1
- package/dist/cli/commands/serve.js +3 -2
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/status.js +3 -3
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/types.js +25 -3
- package/dist/cli/commands/types.js.map +1 -1
- package/dist/cli/commands/uninstall.js +2 -8
- package/dist/cli/commands/uninstall.js.map +1 -1
- package/dist/cli/doctor-plugins.js +44 -0
- package/dist/cli/doctor-plugins.js.map +1 -0
- package/dist/cli/doctor-types.js +2 -0
- package/dist/cli/doctor-types.js.map +1 -0
- package/dist/cli/doctor.js +48 -125
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/dreamux.js +2 -30
- package/dist/cli/dreamux.js.map +1 -1
- package/dist/cli/server.js +55 -70
- package/dist/cli/server.js.map +1 -1
- package/dist/command/errors.js +43 -2
- package/dist/command/errors.js.map +1 -1
- package/dist/command/host.js +10 -17
- package/dist/command/host.js.map +1 -1
- package/dist/command/payload.js +0 -9
- package/dist/command/payload.js.map +1 -1
- package/dist/command/port.js +0 -4
- package/dist/command/port.js.map +1 -1
- package/dist/command/registry.js +0 -4
- package/dist/command/registry.js.map +1 -1
- package/dist/command/schema.js.map +1 -1
- package/dist/command/types.js.map +1 -0
- package/dist/command/validate.js +22 -41
- package/dist/command/validate.js.map +1 -1
- package/dist/config/commands.js +46 -0
- package/dist/config/commands.js.map +1 -0
- package/dist/config/config.js +155 -169
- package/dist/config/config.js.map +1 -1
- package/dist/config/load.js +138 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/service.js +196 -0
- package/dist/config/service.js.map +1 -0
- package/dist/daemon/{service-control.js → control.js} +15 -16
- package/dist/daemon/control.js.map +1 -0
- package/dist/daemon/environment.js +353 -0
- package/dist/daemon/environment.js.map +1 -0
- package/dist/daemon/host.js +16 -0
- package/dist/daemon/host.js.map +1 -0
- package/dist/daemon/install.js +167 -69
- package/dist/daemon/install.js.map +1 -1
- package/dist/daemon/status.js +87 -0
- package/dist/daemon/status.js.map +1 -0
- package/dist/{cli/service-status-parse.js → daemon/unit.js} +90 -12
- package/dist/daemon/unit.js.map +1 -0
- package/dist/{service/mcp/descriptor.js → mcp/launch.js} +3 -7
- package/dist/mcp/launch.js.map +1 -0
- package/dist/mcp/server.js +9 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/shim.js +18 -16
- package/dist/mcp/shim.js.map +1 -1
- package/dist/onboard/config-files.js +12 -26
- package/dist/onboard/config-files.js.map +1 -1
- package/dist/onboard/run.js +47 -66
- package/dist/onboard/run.js.map +1 -1
- package/dist/onboard/uninstall.js +59 -42
- package/dist/onboard/uninstall.js.map +1 -1
- package/dist/onboard/wizard.js +33 -18
- package/dist/onboard/wizard.js.map +1 -1
- package/dist/{onboard/commands.js → platform/command-runner.js} +7 -3
- package/dist/platform/command-runner.js.map +1 -0
- package/dist/platform/deduplicate.js +32 -0
- package/dist/platform/deduplicate.js.map +1 -0
- package/dist/{state → platform}/dispatcher-id.js +1 -1
- package/dist/platform/dispatcher-id.js.map +1 -0
- package/dist/platform/errors.js +23 -23
- package/dist/platform/errors.js.map +1 -1
- package/dist/{onboard/ledger.js → platform/file-ledger.js} +13 -38
- package/dist/platform/file-ledger.js.map +1 -0
- package/dist/platform/history-page.js +62 -0
- package/dist/platform/history-page.js.map +1 -0
- package/dist/platform/in-flight-work.js.map +1 -0
- package/dist/platform/instance-lock.js +95 -0
- package/dist/platform/instance-lock.js.map +1 -0
- package/dist/platform/json-value.js +26 -15
- package/dist/platform/json-value.js.map +1 -1
- package/dist/platform/jsonl.js.map +1 -1
- package/dist/platform/logger.js +5 -5
- package/dist/platform/logger.js.map +1 -1
- package/dist/platform/package-bin.js +3 -1
- package/dist/platform/package-bin.js.map +1 -1
- package/dist/platform/paths.js +42 -69
- package/dist/platform/paths.js.map +1 -1
- package/dist/platform/runtime-sockets.js +7 -2
- package/dist/platform/runtime-sockets.js.map +1 -1
- package/dist/platform/serial-queue.js +22 -0
- package/dist/platform/serial-queue.js.map +1 -0
- package/dist/platform/shutdown-errors.js.map +1 -0
- package/dist/platform/work-fence.js +30 -0
- package/dist/platform/work-fence.js.map +1 -0
- package/dist/plugin/hooks.js +407 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.js +97 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/loader.js +250 -0
- package/dist/plugin/loader.js.map +1 -0
- package/dist/provider-diagnostics.js +12 -8
- package/dist/provider-diagnostics.js.map +1 -1
- package/dist/registry/builtins.js +49 -45
- package/dist/registry/builtins.js.map +1 -1
- package/dist/registry/index.js +10 -2
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/provider-loader.js +41 -61
- package/dist/registry/provider-loader.js.map +1 -1
- package/dist/registry/provider-ref.js.map +1 -1
- package/dist/registry/registry.js +14 -22
- package/dist/registry/registry.js.map +1 -1
- package/dist/{command/catalog.js → server/command-catalog.js} +14 -5
- package/dist/server/command-catalog.js.map +1 -0
- package/dist/server/command-host.js +2 -0
- package/dist/server/command-host.js.map +1 -0
- package/dist/server-commands.js +1 -1
- package/dist/server-commands.js.map +1 -1
- package/dist/server.js +70 -129
- package/dist/server.js.map +1 -1
- package/dist/service/{agent-entity/activity-reader.js → agent/activity.js} +86 -34
- package/dist/service/agent/activity.js.map +1 -0
- package/dist/service/{teammate-service/admission-ledger.js → agent/admission.js} +66 -1
- package/dist/service/agent/admission.js.map +1 -0
- package/dist/service/{channel-submission.js → agent/channel-submission.js} +5 -5
- package/dist/service/agent/channel-submission.js.map +1 -0
- package/dist/service/{teammate-collection → agent}/commands.js +38 -50
- package/dist/service/agent/commands.js.map +1 -0
- package/dist/service/{teammate-service → agent}/completion-renderer.js +30 -5
- package/dist/service/agent/completion-renderer.js.map +1 -0
- package/dist/service/agent/errors.js.map +1 -0
- package/dist/service/agent/factory.js +78 -0
- package/dist/service/agent/factory.js.map +1 -0
- package/dist/service/{agent-entity/types.js → agent/identity.js} +28 -34
- package/dist/service/agent/identity.js.map +1 -0
- package/dist/service/agent/index.js +769 -0
- package/dist/service/agent/index.js.map +1 -0
- package/dist/service/agent/mcp.js +420 -0
- package/dist/service/agent/mcp.js.map +1 -0
- package/dist/service/agent/records.js +74 -0
- package/dist/service/agent/records.js.map +1 -0
- package/dist/service/agent/requests.js +172 -0
- package/dist/service/agent/requests.js.map +1 -0
- package/dist/service/{teammate-service/runtime-owner.js → agent/runtime-generation.js} +81 -79
- package/dist/service/agent/runtime-generation.js.map +1 -0
- package/dist/service/agent/runtime-id.js +22 -0
- package/dist/service/agent/runtime-id.js.map +1 -0
- package/dist/service/{agent-entity → agent}/runtime-state.js +33 -40
- package/dist/service/agent/runtime-state.js.map +1 -0
- package/dist/service/agent/service-types.js +28 -0
- package/dist/service/agent/service-types.js.map +1 -0
- package/dist/service/{teammate-service/index.js → agent/service.js} +236 -102
- package/dist/service/agent/service.js.map +1 -0
- package/dist/service/{agent-entity/identity-store.js → agent/store.js} +175 -96
- package/dist/service/agent/store.js.map +1 -0
- package/dist/service/agent/submission.js.map +1 -0
- package/dist/service/{teammate-collection → agent}/system-prompt.js +7 -3
- package/dist/service/agent/system-prompt.js.map +1 -0
- package/dist/service/agent/turn.js +89 -0
- package/dist/service/agent/turn.js.map +1 -0
- package/dist/service/agent/types.js.map +1 -0
- package/dist/service/channel-service/commands.js +7 -3
- package/dist/service/channel-service/commands.js.map +1 -1
- package/dist/{channel → service/channel-service}/core-port.js +6 -5
- package/dist/service/channel-service/core-port.js.map +1 -0
- package/dist/service/channel-service/index.js +211 -93
- package/dist/service/channel-service/index.js.map +1 -1
- package/dist/service/channel-service/mcp-delegate.js +8 -9
- package/dist/service/channel-service/mcp-delegate.js.map +1 -1
- package/dist/service/completion-router/index.js +27 -27
- package/dist/service/completion-router/index.js.map +1 -1
- package/dist/{channel → service/dispatcher-core-events}/conversation-projection.js +9 -7
- package/dist/service/dispatcher-core-events/conversation-projection.js.map +1 -0
- package/dist/service/dispatcher-core-events/index.js +76 -68
- package/dist/service/dispatcher-core-events/index.js.map +1 -1
- package/dist/service/dispatcher-core-events/seal.js +1 -24
- package/dist/service/dispatcher-core-events/seal.js.map +1 -1
- package/dist/service/dispatcher-service/agent.js +267 -45
- package/dist/service/dispatcher-service/agent.js.map +1 -1
- package/dist/service/dispatcher-service/base-prompt.js +2 -2
- package/dist/service/dispatcher-service/base-prompt.js.map +1 -1
- package/dist/service/dispatcher-service/index.js +139 -378
- package/dist/service/dispatcher-service/index.js.map +1 -1
- package/dist/service/dispatcher-service/lifecycle.js +230 -0
- package/dist/service/dispatcher-service/lifecycle.js.map +1 -0
- package/dist/{daemon → service/dispatcher-service}/restart-intent.js +17 -38
- package/dist/service/dispatcher-service/restart-intent.js.map +1 -0
- package/dist/service/dispatcher-workspace.js +10 -23
- package/dist/service/dispatcher-workspace.js.map +1 -1
- package/dist/service/dispatchers/commands.js +30 -31
- package/dist/service/dispatchers/commands.js.map +1 -1
- package/dist/service/dispatchers/index.js +92 -64
- package/dist/service/dispatchers/index.js.map +1 -1
- package/dist/service/index.js +2 -2
- package/dist/service/index.js.map +1 -1
- package/dist/{mcp → service/mcp}/catalog.js +11 -48
- package/dist/service/mcp/catalog.js.map +1 -0
- package/dist/service/mcp/commands.js +3 -3
- package/dist/service/mcp/commands.js.map +1 -1
- package/dist/service/mcp/dispatch-reminders.js +1 -1
- package/dist/service/mcp/dispatch-reminders.js.map +1 -1
- package/dist/service/mcp/identity-version.js +14 -0
- package/dist/service/mcp/identity-version.js.map +1 -1
- package/dist/service/mcp/leases.js +12 -33
- package/dist/service/mcp/leases.js.map +1 -1
- package/dist/service/mcp/tool-metadata.js +15 -76
- package/dist/service/mcp/tool-metadata.js.map +1 -1
- package/dist/service/name-allocator.js +2 -3
- package/dist/service/name-allocator.js.map +1 -1
- package/dist/service/scheduler/commands.js +7 -19
- package/dist/service/scheduler/commands.js.map +1 -1
- package/dist/service/scheduler/errors.js +1 -1
- package/dist/service/scheduler/errors.js.map +1 -1
- package/dist/service/scheduler/{service.js → index.js} +175 -142
- package/dist/service/scheduler/index.js.map +1 -0
- package/dist/service/scheduler/{mcp-delegate.js → mcp.js} +61 -58
- package/dist/service/scheduler/mcp.js.map +1 -0
- package/dist/service/scheduler/requests.js +60 -0
- package/dist/service/scheduler/requests.js.map +1 -0
- package/dist/service/scheduler/store.js +183 -151
- package/dist/service/scheduler/store.js.map +1 -1
- package/dist/service/scheduler/types.js +1 -33
- package/dist/service/scheduler/types.js.map +1 -1
- package/dist/service/{team-collection → team}/commands.js +45 -59
- package/dist/service/team/commands.js.map +1 -0
- package/dist/service/{team-collection → team}/create-request.js +8 -20
- package/dist/service/team/create-request.js.map +1 -0
- package/dist/service/{team-collection → team}/errors.js +7 -10
- package/dist/service/team/errors.js.map +1 -0
- package/dist/service/team/index.js +817 -0
- package/dist/service/team/index.js.map +1 -0
- package/dist/service/team/leader-mcp.js +42 -0
- package/dist/service/team/leader-mcp.js.map +1 -0
- package/dist/service/team/leader.js +157 -0
- package/dist/service/team/leader.js.map +1 -0
- package/dist/service/team/mcp.js +328 -0
- package/dist/service/team/mcp.js.map +1 -0
- package/dist/service/{team-collection/types.js → team/requests.js} +64 -15
- package/dist/service/team/requests.js.map +1 -0
- package/dist/service/team/service.js +903 -0
- package/dist/service/team/service.js.map +1 -0
- package/dist/service/team/store.js +373 -0
- package/dist/service/team/store.js.map +1 -0
- package/dist/service/team/team-summary.js.map +1 -0
- package/dist/service/team/teams-port.js +2 -0
- package/dist/service/team/teams-port.js.map +1 -0
- package/dist/service/team/types.js +22 -0
- package/dist/service/team/types.js.map +1 -0
- package/dist/service/workflow-service/commands.js +5 -6
- package/dist/service/workflow-service/commands.js.map +1 -1
- package/dist/service/workflow-service/errors.js +2 -0
- package/dist/service/workflow-service/errors.js.map +1 -1
- package/dist/service/workflow-service/index.js +80 -112
- package/dist/service/workflow-service/index.js.map +1 -1
- package/dist/service/workflow-service/journal.js +90 -6
- package/dist/service/workflow-service/journal.js.map +1 -1
- package/dist/service/workflow-service/limits.js +20 -9
- package/dist/service/workflow-service/limits.js.map +1 -1
- package/dist/service/workflow-service/mcp.js +171 -0
- package/dist/service/workflow-service/mcp.js.map +1 -0
- package/dist/service/workflow-service/protocol.js +72 -1
- package/dist/service/workflow-service/protocol.js.map +1 -1
- package/dist/service/workflow-service/requests.js +70 -0
- package/dist/service/workflow-service/requests.js.map +1 -0
- package/dist/service/workflow-service/run.js +258 -145
- package/dist/service/workflow-service/run.js.map +1 -1
- package/dist/service/workflow-service/runner-process.js +0 -19
- package/dist/service/workflow-service/runner-process.js.map +1 -1
- package/dist/service/workflow-service/runner.js +6 -30
- package/dist/service/workflow-service/runner.js.map +1 -1
- package/dist/service/workflow-service/script-compiler.js +7 -9
- package/dist/service/workflow-service/script-compiler.js.map +1 -1
- package/dist/service/workflow-service/{run-support.js → semaphore.js} +1 -30
- package/dist/service/workflow-service/semaphore.js.map +1 -0
- package/dist/service/workflow-service/store.js +105 -25
- package/dist/service/workflow-service/store.js.map +1 -1
- package/dist/service/workflow-service/types.js +1 -77
- package/dist/service/workflow-service/types.js.map +1 -1
- package/dist/service/worktree/manager.js +29 -62
- package/dist/service/worktree/manager.js.map +1 -1
- package/dist/service/worktree/paths.js +4 -1
- package/dist/service/worktree/paths.js.map +1 -1
- package/dist/service/worktree/repo-request.js +38 -14
- package/dist/service/worktree/repo-request.js.map +1 -1
- package/dist/service/worktree/types.js +2 -0
- package/dist/service/worktree/types.js.map +1 -0
- package/dist/service/worktree/workspaces.js +27 -34
- package/dist/service/worktree/workspaces.js.map +1 -1
- package/package.json +13 -8
- package/skills/dispatcher/dreamux-maintenance/SKILL.md +5 -4
- package/skills/dispatcher/dreamux-maintenance/references/builtin-bootstrap.md +50 -0
- package/skills/dispatcher/dreamux-maintenance/references/builtin-codex.md +4 -5
- package/skills/dispatcher/dreamux-maintenance/references/builtin-feishu.md +69 -11
- package/skills/dispatcher/dreamux-maintenance/references/config-envelope.md +77 -12
- package/skills/dispatcher/dreamux-maintenance/references/feishu-access-v3.md +39 -24
- package/skills/dispatcher/dreamux-maintenance/references/service-lifecycle.md +105 -37
- package/dist/channel/conversation-projection.js.map +0 -1
- package/dist/channel/core-port.js.map +0 -1
- package/dist/cli/server-ctl.js +0 -163
- package/dist/cli/server-ctl.js.map +0 -1
- package/dist/cli/service-status-parse.js.map +0 -1
- package/dist/command/catalog.js.map +0 -1
- package/dist/config/config-helpers.js +0 -100
- package/dist/config/config-helpers.js.map +0 -1
- package/dist/daemon/restart-intent.js.map +0 -1
- package/dist/daemon/service-control.js.map +0 -1
- package/dist/mcp/catalog.js.map +0 -1
- package/dist/mcp/failure-text.js +0 -46
- package/dist/mcp/failure-text.js.map +0 -1
- package/dist/onboard/commands.js.map +0 -1
- package/dist/onboard/ledger.js.map +0 -1
- package/dist/onboard/service-node.js +0 -183
- package/dist/onboard/service-node.js.map +0 -1
- package/dist/onboard/service.js +0 -310
- package/dist/onboard/service.js.map +0 -1
- package/dist/platform/atomic-write.js +0 -63
- package/dist/platform/atomic-write.js.map +0 -1
- package/dist/platform/error-info.js +0 -11
- package/dist/platform/error-info.js.map +0 -1
- package/dist/platform/json-document-store.js +0 -54
- package/dist/platform/json-document-store.js.map +0 -1
- package/dist/service/agent-entity/activity-errors.js +0 -71
- package/dist/service/agent-entity/activity-errors.js.map +0 -1
- package/dist/service/agent-entity/activity-reader.js.map +0 -1
- package/dist/service/agent-entity/agent-config.js +0 -48
- package/dist/service/agent-entity/agent-config.js.map +0 -1
- package/dist/service/agent-entity/history-query.js +0 -60
- package/dist/service/agent-entity/history-query.js.map +0 -1
- package/dist/service/agent-entity/identity-store.js.map +0 -1
- package/dist/service/agent-entity/read-helpers.js +0 -172
- package/dist/service/agent-entity/read-helpers.js.map +0 -1
- package/dist/service/agent-entity/runtime-profile.js +0 -53
- package/dist/service/agent-entity/runtime-profile.js.map +0 -1
- package/dist/service/agent-entity/runtime-state.js.map +0 -1
- package/dist/service/agent-entity/types.js.map +0 -1
- package/dist/service/channel-service/mcp-delegates.js +0 -21
- package/dist/service/channel-service/mcp-delegates.js.map +0 -1
- package/dist/service/channel-service/types.js.map +0 -1
- package/dist/service/channel-submission.js.map +0 -1
- package/dist/service/closed-fact.js +0 -49
- package/dist/service/closed-fact.js.map +0 -1
- package/dist/service/deduplicate.js +0 -42
- package/dist/service/deduplicate.js.map +0 -1
- package/dist/service/dispatcher-core-events/scoped-source.js +0 -59
- package/dist/service/dispatcher-core-events/scoped-source.js.map +0 -1
- package/dist/service/dispatcher-service/dispatcher-workflows.js +0 -50
- package/dist/service/dispatcher-service/dispatcher-workflows.js.map +0 -1
- package/dist/service/dispatcher-service/identity.js +0 -97
- package/dist/service/dispatcher-service/identity.js.map +0 -1
- package/dist/service/dispatcher-service/inbound-task-drain.js +0 -37
- package/dist/service/dispatcher-service/inbound-task-drain.js.map +0 -1
- package/dist/service/dispatcher-service/input-source-lifecycle.js +0 -312
- package/dist/service/dispatcher-service/input-source-lifecycle.js.map +0 -1
- package/dist/service/dispatcher-service/input-source-start-rollback.js +0 -53
- package/dist/service/dispatcher-service/input-source-start-rollback.js.map +0 -1
- package/dist/service/dispatcher-service/mcp-delegates.js +0 -67
- package/dist/service/dispatcher-service/mcp-delegates.js.map +0 -1
- package/dist/service/dispatcher-service/restart-notice.js +0 -27
- package/dist/service/dispatcher-service/restart-notice.js.map +0 -1
- package/dist/service/dispatcher-service/runnable-channel.js +0 -13
- package/dist/service/dispatcher-service/runnable-channel.js.map +0 -1
- package/dist/service/dispatcher-service/runtime-helpers.js +0 -11
- package/dist/service/dispatcher-service/runtime-helpers.js.map +0 -1
- package/dist/service/dispatcher-service/runtime-status.js +0 -35
- package/dist/service/dispatcher-service/runtime-status.js.map +0 -1
- package/dist/service/dispatcher-service/team-leader-handle.js +0 -29
- package/dist/service/dispatcher-service/team-leader-handle.js.map +0 -1
- package/dist/service/dispatcher-service/team-runtime-stop.js +0 -13
- package/dist/service/dispatcher-service/team-runtime-stop.js.map +0 -1
- package/dist/service/dispatcher-service/teammate-ops.js +0 -14
- package/dist/service/dispatcher-service/teammate-ops.js.map +0 -1
- package/dist/service/frozen-snapshot.js +0 -16
- package/dist/service/frozen-snapshot.js.map +0 -1
- package/dist/service/in-flight-work.js.map +0 -1
- package/dist/service/legacy-state.js +0 -119
- package/dist/service/legacy-state.js.map +0 -1
- package/dist/service/mcp/descriptor.js.map +0 -1
- package/dist/service/scheduler/mcp-delegate.js.map +0 -1
- package/dist/service/scheduler/service.js.map +0 -1
- package/dist/service/serial-queue.js +0 -64
- package/dist/service/serial-queue.js.map +0 -1
- package/dist/service/shutdown-errors.js.map +0 -1
- package/dist/service/team-collection/commands.js.map +0 -1
- package/dist/service/team-collection/create-request.js.map +0 -1
- package/dist/service/team-collection/errors.js.map +0 -1
- package/dist/service/team-collection/index.js +0 -304
- package/dist/service/team-collection/index.js.map +0 -1
- package/dist/service/team-collection/mcp-delegate.js +0 -389
- package/dist/service/team-collection/mcp-delegate.js.map +0 -1
- package/dist/service/team-collection/read-helpers.js +0 -66
- package/dist/service/team-collection/read-helpers.js.map +0 -1
- package/dist/service/team-collection/read-model.js +0 -120
- package/dist/service/team-collection/read-model.js.map +0 -1
- package/dist/service/team-collection/roster-reader.js +0 -52
- package/dist/service/team-collection/roster-reader.js.map +0 -1
- package/dist/service/team-collection/runtime-registry.js +0 -369
- package/dist/service/team-collection/runtime-registry.js.map +0 -1
- package/dist/service/team-collection/store.js +0 -320
- package/dist/service/team-collection/store.js.map +0 -1
- package/dist/service/team-collection/types.js.map +0 -1
- package/dist/service/team-collection/worktree-cleanup.js +0 -45
- package/dist/service/team-collection/worktree-cleanup.js.map +0 -1
- package/dist/service/team-service/closing.js +0 -279
- package/dist/service/team-service/closing.js.map +0 -1
- package/dist/service/team-service/collaborators.js +0 -82
- package/dist/service/team-service/collaborators.js.map +0 -1
- package/dist/service/team-service/completion-targets.js +0 -72
- package/dist/service/team-service/completion-targets.js.map +0 -1
- package/dist/service/team-service/index.js +0 -559
- package/dist/service/team-service/index.js.map +0 -1
- package/dist/service/team-service/leader-agent.js +0 -170
- package/dist/service/team-service/leader-agent.js.map +0 -1
- package/dist/service/team-service/roster-projection.js +0 -69
- package/dist/service/team-service/roster-projection.js.map +0 -1
- package/dist/service/team-service/team-summary.js.map +0 -1
- package/dist/service/team-service/types.js +0 -61
- package/dist/service/team-service/types.js.map +0 -1
- package/dist/service/teammate-collection/commands.js.map +0 -1
- package/dist/service/teammate-collection/dissolve-members.js +0 -44
- package/dist/service/teammate-collection/dissolve-members.js.map +0 -1
- package/dist/service/teammate-collection/errors.js.map +0 -1
- package/dist/service/teammate-collection/index.js +0 -497
- package/dist/service/teammate-collection/index.js.map +0 -1
- package/dist/service/teammate-collection/mcp-delegate.js +0 -210
- package/dist/service/teammate-collection/mcp-delegate.js.map +0 -1
- package/dist/service/teammate-collection/mcp-tool-descriptors.js +0 -421
- package/dist/service/teammate-collection/mcp-tool-descriptors.js.map +0 -1
- package/dist/service/teammate-collection/system-prompt.js.map +0 -1
- package/dist/service/teammate-collection/types.js.map +0 -1
- package/dist/service/teammate-service/admission-ledger.js.map +0 -1
- package/dist/service/teammate-service/completion-renderer.js.map +0 -1
- package/dist/service/teammate-service/factory.js +0 -5
- package/dist/service/teammate-service/factory.js.map +0 -1
- package/dist/service/teammate-service/index.js.map +0 -1
- package/dist/service/teammate-service/runtime-owner.js.map +0 -1
- package/dist/service/teammate-service/submission.js.map +0 -1
- package/dist/service/teammate-service/turn-coordinator.js +0 -93
- package/dist/service/teammate-service/turn-coordinator.js.map +0 -1
- package/dist/service/teammate-service/turn-recording.js +0 -178
- package/dist/service/teammate-service/turn-recording.js.map +0 -1
- package/dist/service/teammate-service/types.js +0 -12
- package/dist/service/teammate-service/types.js.map +0 -1
- package/dist/service/workflow-service/agent-policy.js +0 -6
- package/dist/service/workflow-service/agent-policy.js.map +0 -1
- package/dist/service/workflow-service/json-args.js +0 -72
- package/dist/service/workflow-service/json-args.js.map +0 -1
- package/dist/service/workflow-service/run-support.js.map +0 -1
- package/dist/service/workflow-service/run-terminal.js +0 -111
- package/dist/service/workflow-service/run-terminal.js.map +0 -1
- package/dist/state/dispatcher-id.js.map +0 -1
- package/dist/state/dispatcher-store.js +0 -73
- package/dist/state/dispatcher-store.js.map +0 -1
- /package/dist/{service/channel-service → command}/types.js +0 -0
- /package/dist/{service → platform}/in-flight-work.js +0 -0
- /package/dist/{service → platform}/shutdown-errors.js +0 -0
- /package/dist/service/{teammate-collection → agent}/errors.js +0 -0
- /package/dist/service/{teammate-service → agent}/submission.js +0 -0
- /package/dist/service/{teammate-collection → agent}/types.js +0 -0
- /package/dist/service/{team-service → team}/team-summary.js +0 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import { normalizeSkillSources, parseAgentRuntimeSkillSources, } from '../../agent-runtime/skill-sources.js';
|
|
2
|
+
import { defaultWorkspaceEnabled } from '../../config/config.js';
|
|
3
|
+
import { ServerShuttingDownError } from '../../platform/errors.js';
|
|
4
|
+
import { clampHistoryLimit, decodeCursor, encodeCursor, matchesGrepText, previewText, } from '../../platform/history-page.js';
|
|
5
|
+
import { teamMateCollectionDir } from '../../platform/paths.js';
|
|
6
|
+
import { KeyedAsyncQueue } from '../../platform/serial-queue.js';
|
|
7
|
+
import { throwSettledFailures } from '../../platform/shutdown-errors.js';
|
|
8
|
+
import { requireLifecycleText, } from '../agent/identity.js';
|
|
9
|
+
import { toStatus } from '../agent/records.js';
|
|
10
|
+
import { agentCollectionMemberCount, readAgentIdentity, } from '../agent/store.js';
|
|
11
|
+
import { ensureDispatcherWorkspace } from '../dispatcher-workspace.js';
|
|
12
|
+
import { allocateConcreteNameAsync } from '../name-allocator.js';
|
|
13
|
+
import { repoWorktree } from '../worktree/repo-request.js';
|
|
14
|
+
import { IdempotencyConflictError, TeamClosedError, teamErrorInfo, TeamNotFoundError, } from './errors.js';
|
|
15
|
+
import { TEAM_LEADER_REQUIRED_SKILL_SOURCES } from './leader.js';
|
|
16
|
+
import { settleTeamWorktreeCleanup, TeamService, } from './service.js';
|
|
17
|
+
import { TeamStore } from './store.js';
|
|
18
|
+
import { teamSummary } from './team-summary.js';
|
|
19
|
+
import { validateTeamId, } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* The dispatcher's team collection (issue #233): one per dispatcher, owned by
|
|
22
|
+
* `DispatcherService`. It stores, creates, finds, lists, materializes, and
|
|
23
|
+
* evicts Teams, and nothing else — a Team's own lifecycle, including dissolve,
|
|
24
|
+
* belongs to the {@link TeamService} that is that Team.
|
|
25
|
+
*
|
|
26
|
+
* `get` (private) is a get-or-rebuild factory (like `Dispatchers.get` /
|
|
27
|
+
* `TeammateCollection.entityFor`): cached live service if any, else rebuilt
|
|
28
|
+
* from the persisted {@link TeamRecord} and cached. Each `TeamService` OWNS its
|
|
29
|
+
* per-team `TeammateCollection`, bound to the Team ID and workspace at
|
|
30
|
+
* construction from the shared dependencies forwarded here.
|
|
31
|
+
*
|
|
32
|
+
* Implements {@link TeamsPort} directly: every dispatcher-facing per-Team
|
|
33
|
+
* operation gates itself on the injected the dispatcher fence internally, so a
|
|
34
|
+
* caller reaches a Team through exactly one surface instead of an
|
|
35
|
+
* open/admit/read triple it must fence itself.
|
|
36
|
+
*/
|
|
37
|
+
export class TeamCollection {
|
|
38
|
+
opts;
|
|
39
|
+
dispatcherId;
|
|
40
|
+
store;
|
|
41
|
+
worktrees;
|
|
42
|
+
/**
|
|
43
|
+
* One materialized Team per id: the whole materialization cache.
|
|
44
|
+
* {@link track} sets an entry only once it has also arranged eviction
|
|
45
|
+
* (`service.closed.then(...)`), so an entry here is by definition a Team
|
|
46
|
+
* this collection is both caching and watching for its close.
|
|
47
|
+
*/
|
|
48
|
+
live = new Map();
|
|
49
|
+
constructing = new Map();
|
|
50
|
+
/**
|
|
51
|
+
* Created Teams whose initial turn is still being admitted. Not in `live`,
|
|
52
|
+
* because a Team that is not yet running must not be served; held here,
|
|
53
|
+
* because that turn starts the leader's runtime and a host stop has to reach
|
|
54
|
+
* it.
|
|
55
|
+
*/
|
|
56
|
+
starting = new Set();
|
|
57
|
+
/** Serializes the whole lookup/create sequence per request id. */
|
|
58
|
+
createRequestLifecycle = new KeyedAsyncQueue();
|
|
59
|
+
constructor(opts) {
|
|
60
|
+
this.opts = opts;
|
|
61
|
+
this.dispatcherId = opts.dispatcherId;
|
|
62
|
+
this.worktrees = opts.worktrees;
|
|
63
|
+
this.store = new TeamStore({
|
|
64
|
+
root: opts.root,
|
|
65
|
+
dispatcherId: this.dispatcherId,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create one Team under a `team.create` request identity.
|
|
70
|
+
*
|
|
71
|
+
* The Team record is the whole protocol. Publishing through its canonical
|
|
72
|
+
* serialized owner is the acceptance point: it makes the Team exist, takes the
|
|
73
|
+
* concrete name, and durably records the request id and canonical
|
|
74
|
+
* payload hash a later replay is decided against.
|
|
75
|
+
*
|
|
76
|
+
* Before publication no Team or accepted request exists on disk, so a lost
|
|
77
|
+
* process, a rejected candidate, or a plain retry may pick another name.
|
|
78
|
+
* While a valid Team record carries that acceptance, the same id with the
|
|
79
|
+
* same payload resolves back to that Team, including once it is closed;
|
|
80
|
+
* the same id with a different payload is an idempotency conflict.
|
|
81
|
+
*
|
|
82
|
+
* Gated by the dispatcher fence like every other `TeamsPort` method: a create
|
|
83
|
+
* admitted right as the dispatcher starts closing still crosses the fence
|
|
84
|
+
* before doing any work, rather than building a workspace and a leader only
|
|
85
|
+
* to self-close in `track()` the moment it registers.
|
|
86
|
+
*
|
|
87
|
+
* The caller's `command` is the wire-shaped request, untranslated: only a
|
|
88
|
+
* request that is not a replay of an already-accepted one runs through the
|
|
89
|
+
* owning Dispatcher's `createTeam` hook (replay identity is decided above,
|
|
90
|
+
* against `payloadHash` alone, so a plugin never sees a replayed request),
|
|
91
|
+
* and only after that hook does this method do the repo/skill-source
|
|
92
|
+
* translation `commands.ts` and `mcp.ts` used to each do for themselves —
|
|
93
|
+
* so a plugin's own `leader.skill_sources`/`repo` value gets the identical
|
|
94
|
+
* mandatory-root injection and repo→worktree mapping an admin-supplied one
|
|
95
|
+
* gets, rather than skipping it by running before the hook.
|
|
96
|
+
*/
|
|
97
|
+
async createFromRequest(input) {
|
|
98
|
+
return this.opts.fence.admit(() => this.createRequestLifecycle.run(input.requestId, async () => {
|
|
99
|
+
const accepted = await this.acceptedRequest(input.requestId);
|
|
100
|
+
if (accepted !== null) {
|
|
101
|
+
if (accepted.create_payload_hash !== input.payloadHash) {
|
|
102
|
+
throw new IdempotencyConflictError(`request_id ${JSON.stringify(input.requestId)} was already accepted with a ` +
|
|
103
|
+
'different team.create payload; use a new request_id for a new Team');
|
|
104
|
+
}
|
|
105
|
+
// Read the accepted Team without materializing it or resubmitting work.
|
|
106
|
+
return this.summaryFromRecord(accepted);
|
|
107
|
+
}
|
|
108
|
+
// `request_id` is excluded from what a tap sees: replay identity is
|
|
109
|
+
// decided above, against `payloadHash` alone, before this hook ever
|
|
110
|
+
// runs.
|
|
111
|
+
const { name_prefix, intent, leader, repo: requestedRepo, } = input.command;
|
|
112
|
+
const resolved = await this.opts.dispatcherHooks.createTeam.promise({
|
|
113
|
+
name_prefix,
|
|
114
|
+
intent,
|
|
115
|
+
leader,
|
|
116
|
+
...(requestedRepo !== undefined ? { repo: requestedRepo } : {}),
|
|
117
|
+
});
|
|
118
|
+
// A `createTeam` tap can return any `leader.skill_sources` value the
|
|
119
|
+
// type system cannot check at runtime (a plugin need not even be
|
|
120
|
+
// written in TypeScript). The pre-hook value already passed this same
|
|
121
|
+
// structural check once (`optionalParsedSkillSources`, at parse time);
|
|
122
|
+
// after the hook, and only after it, repeat it so a malformed value
|
|
123
|
+
// fails as a clean RuleViolation naming the field instead of a raw
|
|
124
|
+
// filesystem error deep inside skill-root canonicalization.
|
|
125
|
+
const parsedSkillSources = resolved.leader.skill_sources !== undefined
|
|
126
|
+
? parseAgentRuntimeSkillSources(resolved.leader.skill_sources, 'plugin "createTeam" hook output\'s leader.skill_sources')
|
|
127
|
+
: null;
|
|
128
|
+
const skillSources = await normalizeSkillSources(parsedSkillSources, {
|
|
129
|
+
requiredSources: TEAM_LEADER_REQUIRED_SKILL_SOURCES,
|
|
130
|
+
});
|
|
131
|
+
const repo = repoWorktree(resolved.repo ?? null);
|
|
132
|
+
// A named repository request with no explicit path passes no
|
|
133
|
+
// `repoCwd` at all: `prepareWorkspace()` already falls back to the
|
|
134
|
+
// dispatcher's own default workspace when it sees none.
|
|
135
|
+
const repoCwd = repo?.cwd ?? null;
|
|
136
|
+
const options = {
|
|
137
|
+
intent: resolved.intent,
|
|
138
|
+
leaderAgentRuntime: resolved.leader.agent_runtime,
|
|
139
|
+
...(repoCwd !== null ? { repoCwd } : {}),
|
|
140
|
+
...(repo !== null ? { worktree: repo.worktree } : {}),
|
|
141
|
+
prompt: resolved.leader.prompt,
|
|
142
|
+
identity: resolved.leader.identity,
|
|
143
|
+
...(skillSources !== null ? { skillSources } : {}),
|
|
144
|
+
};
|
|
145
|
+
const outcome = { created: null };
|
|
146
|
+
const teamName = await allocateConcreteNameAsync({
|
|
147
|
+
kind: 'team',
|
|
148
|
+
base: resolved.name_prefix,
|
|
149
|
+
accept: async (candidate) => {
|
|
150
|
+
// No probe here: `create`'s own early-out against the same
|
|
151
|
+
// record is the authoritative answer, so a candidate already
|
|
152
|
+
// owned by another Team is refused there instead of twice.
|
|
153
|
+
outcome.created = await this.createAtCandidate({
|
|
154
|
+
...options,
|
|
155
|
+
name: candidate,
|
|
156
|
+
createRequest: {
|
|
157
|
+
requestId: input.requestId,
|
|
158
|
+
payloadHash: input.payloadHash,
|
|
159
|
+
},
|
|
160
|
+
deliverCompletionToDispatcher: input.deliverCompletionToDispatcher,
|
|
161
|
+
});
|
|
162
|
+
return outcome.created !== null;
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
const created = outcome.created;
|
|
166
|
+
if (created === null) {
|
|
167
|
+
throw new Error(`team.create request ${JSON.stringify(input.requestId)} accepted the name ` +
|
|
168
|
+
`${JSON.stringify(teamName)} without publishing a Team record`);
|
|
169
|
+
}
|
|
170
|
+
return created.status();
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The Team this request id already produced, read from the records.
|
|
175
|
+
*
|
|
176
|
+
* The records are the only ledger: a creation that failed after publishing
|
|
177
|
+
* one still accepted the request, and a process that died before returning
|
|
178
|
+
* left the same proof behind. While that valid record remains, scanning it
|
|
179
|
+
* makes a replay answer the same way across restarts.
|
|
180
|
+
*/
|
|
181
|
+
async acceptedRequest(requestId) {
|
|
182
|
+
for (const team of await this.store.list()) {
|
|
183
|
+
if (team.create_request_id === requestId)
|
|
184
|
+
return team;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Create one Team at a candidate name, or report the candidate as taken.
|
|
190
|
+
*
|
|
191
|
+
* `null` means a valid Team record occupies that name — the candidate is
|
|
192
|
+
* unavailable and the allocator should offer another.
|
|
193
|
+
*/
|
|
194
|
+
async createAtCandidate(input) {
|
|
195
|
+
return this.create(input, validateTeamId(input.name));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Compact rows from records alone; a list never consults a live runtime.
|
|
199
|
+
*
|
|
200
|
+
* Gated like every other per-Team operational read: an inventory read
|
|
201
|
+
* this narrow still names a Team, so it is fenced the same as `history` and
|
|
202
|
+
* `summary` rather than left observable through a dispatcher stop.
|
|
203
|
+
*/
|
|
204
|
+
async list() {
|
|
205
|
+
return this.opts.fence.admit(() => this.listRows());
|
|
206
|
+
}
|
|
207
|
+
/** Gated because a read is still per-Team operational access. */
|
|
208
|
+
async history(input) {
|
|
209
|
+
return this.opts.fence.admit(() => this.historyResult(input));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get-or-rebuild the team's service; a cold-cache miss is deduped (#233).
|
|
213
|
+
*
|
|
214
|
+
* Live Teams only: a closed record has no service, so this reports it as
|
|
215
|
+
* closed rather than building one. Callers outside reach a Team through
|
|
216
|
+
* {@link open} or a lease, which say the same thing in their own words.
|
|
217
|
+
*
|
|
218
|
+
* Creation publishes a Team's record before its object graph is finished, so
|
|
219
|
+
* a read that saw only the record would build a second, competing owner of
|
|
220
|
+
* the same Team. Every path that can produce the object registers through
|
|
221
|
+
* `constructing` instead. `null` from a joined construction means that
|
|
222
|
+
* construction did not produce this Team — a create whose candidate turned
|
|
223
|
+
* out to be taken — so the decision is made again against what is now
|
|
224
|
+
* durable rather than reported as this caller's answer.
|
|
225
|
+
*/
|
|
226
|
+
async get(teamId) {
|
|
227
|
+
const id = validateTeamId(teamId);
|
|
228
|
+
for (;;) {
|
|
229
|
+
const cached = this.live.get(id);
|
|
230
|
+
if (cached !== undefined)
|
|
231
|
+
return cached;
|
|
232
|
+
const joined = this.constructing.get(id);
|
|
233
|
+
if (joined === undefined) {
|
|
234
|
+
const construction = this.rebuild(id);
|
|
235
|
+
this.publishConstruction(id, construction);
|
|
236
|
+
return construction;
|
|
237
|
+
}
|
|
238
|
+
const service = await joined;
|
|
239
|
+
if (service !== null)
|
|
240
|
+
return service;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* One Team's status.
|
|
245
|
+
*
|
|
246
|
+
* An open Team this process already holds answers for itself, because its
|
|
247
|
+
* live state is the more current version of the same shape. Any other Team is
|
|
248
|
+
* read from its records: whether it is closed or simply not materialized
|
|
249
|
+
* here, a read must not build an entity — and a Team with no runtime in this
|
|
250
|
+
* process has no runtime state for a projection to be missing.
|
|
251
|
+
*
|
|
252
|
+
* Gated the same as every other per-Team operational read; the wrap
|
|
253
|
+
* used to sit on the caller (`DispatcherService.getTeamStatus()`) and now
|
|
254
|
+
* sits here instead, on the method itself.
|
|
255
|
+
*/
|
|
256
|
+
async summary(teamId) {
|
|
257
|
+
return this.opts.fence.admit(async () => {
|
|
258
|
+
const record = await this.mustTeam(validateTeamId(teamId));
|
|
259
|
+
return this.summaryFromRecord(record);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* One source selection for status and accepted-request replay.
|
|
264
|
+
*
|
|
265
|
+
* The record just read from the store is authoritative for lifecycle, and it
|
|
266
|
+
* decides whether there is an entity to ask at all: a closed Team is a
|
|
267
|
+
* record, so it answers from that record and never from a cached service.
|
|
268
|
+
* Only an open Team this process holds answers for itself.
|
|
269
|
+
*/
|
|
270
|
+
async summaryFromRecord(record) {
|
|
271
|
+
if (record.status === 'closed')
|
|
272
|
+
return this.recordSummary(record);
|
|
273
|
+
const live = this.live.get(record.team_id) ?? null;
|
|
274
|
+
return live === null ? this.recordSummary(record) : live.status();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Finish the physical reclamation a previous run left pending.
|
|
278
|
+
*
|
|
279
|
+
* The Team's own worktree cleanup fact is the entire recovery authority: a
|
|
280
|
+
* `closed` Team still marked `cleanup-pending` has a managed checkout the
|
|
281
|
+
* operator authorized destroying, and nothing else about a dissolve outlives
|
|
282
|
+
* the process that ran it. Nothing is materialized to do it — a closed Team
|
|
283
|
+
* is a record, and the record is all this work reads and writes.
|
|
284
|
+
*
|
|
285
|
+
* Each reclaim is launched, not awaited: it is the same background work that
|
|
286
|
+
* ran behind the durable close, so a slow Git must not hold up dispatcher
|
|
287
|
+
* start. A failure leaves the pending fact standing for the next start.
|
|
288
|
+
*/
|
|
289
|
+
async recoverWorktreeCleanup() {
|
|
290
|
+
for (const record of await this.store.list()) {
|
|
291
|
+
if (record.status !== 'closed')
|
|
292
|
+
continue;
|
|
293
|
+
if (record.worktree.cleanup_state !== 'cleanup-pending')
|
|
294
|
+
continue;
|
|
295
|
+
void this.reclaimTeamWorktree(record.team_id);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async reclaimTeamWorktree(teamId) {
|
|
299
|
+
try {
|
|
300
|
+
const record = await this.store.acquire(teamId);
|
|
301
|
+
try {
|
|
302
|
+
await settleTeamWorktreeCleanup(record, this.worktrees);
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
await record.release();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.opts.log.error({
|
|
310
|
+
dispatcher_id: this.dispatcherId,
|
|
311
|
+
team_id: teamId,
|
|
312
|
+
err: teamErrorInfo(error),
|
|
313
|
+
}, 'Team managed worktree cleanup recovery failed');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get one Team that can take new work.
|
|
318
|
+
*
|
|
319
|
+
* The collection's whole part in a per-Team operation: find the entity, or
|
|
320
|
+
* say why there is none to reach — {@link TeamNotFoundError} for a Team that
|
|
321
|
+
* does not exist, {@link TeamClosedError} for one that is over. What happens
|
|
322
|
+
* next is the Team's own.
|
|
323
|
+
*/
|
|
324
|
+
async open(teamId) {
|
|
325
|
+
const id = validateTeamId(teamId);
|
|
326
|
+
const record = await this.mustTeam(id);
|
|
327
|
+
if (record.status === 'closed') {
|
|
328
|
+
throw new TeamClosedError(`Team ${JSON.stringify(id)} is closed`);
|
|
329
|
+
}
|
|
330
|
+
return this.get(id);
|
|
331
|
+
}
|
|
332
|
+
async mustTeam(teamId) {
|
|
333
|
+
const team = await this.store.get(validateTeamId(teamId));
|
|
334
|
+
if (team === null) {
|
|
335
|
+
throw new TeamNotFoundError(`Team ${JSON.stringify(teamId)} does not exist`);
|
|
336
|
+
}
|
|
337
|
+
return team;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Submit one turn to a Team's TeamLeader.
|
|
341
|
+
*
|
|
342
|
+
* `deliverCompletionToDispatcher` is resolved to a `completionRecipient` here, inside
|
|
343
|
+
* the fence, rather than accepted as one directly: a caller outside `team/`
|
|
344
|
+
* knows only whether a Core-side initiator is waiting for the leader's
|
|
345
|
+
* completion, never the dispatcher Agent itself. A Channel-originated turn
|
|
346
|
+
* passes `false`, because the leader answers on its own Channel.
|
|
347
|
+
*/
|
|
348
|
+
submitToLeader(teamId, input) {
|
|
349
|
+
return this.opts.fence.admit(async () => {
|
|
350
|
+
const { deliverCompletionToDispatcher, ...submission } = input;
|
|
351
|
+
const completionRecipient = deliverCompletionToDispatcher
|
|
352
|
+
? this.opts.completionOwner.completionRecipient()
|
|
353
|
+
: null;
|
|
354
|
+
return (await this.open(teamId)).submitInput({
|
|
355
|
+
...submission,
|
|
356
|
+
...(completionRecipient !== null ? { completionRecipient } : {}),
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/** Interrupt one Team's leader. */
|
|
361
|
+
interruptLeader(teamId) {
|
|
362
|
+
return this.opts.fence.admit(async () => (await this.open(teamId)).interruptLeader());
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Submit one Team's dissolve.
|
|
366
|
+
*
|
|
367
|
+
* Never waits for the outcome: once the Team owns the operation the caller
|
|
368
|
+
* has its receipt, and a second submission joins the first instead of
|
|
369
|
+
* dismantling the Team twice.
|
|
370
|
+
*/
|
|
371
|
+
dissolve(teamId, input) {
|
|
372
|
+
return this.opts.fence.admit(async () => (await this.open(teamId)).dissolve(input));
|
|
373
|
+
}
|
|
374
|
+
scheduler(teamId) {
|
|
375
|
+
return this.opts.fence.admit(async () => (await this.open(teamId)).scheduler);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Materialize every non-closed Team. Rebuilding a Team reconciles the
|
|
379
|
+
* Workflow records its previous process left running, so a failure here
|
|
380
|
+
* fails the dispatcher's start before any channel opens.
|
|
381
|
+
*/
|
|
382
|
+
async recover() {
|
|
383
|
+
for (const team of await this.store.list()) {
|
|
384
|
+
if (team.status === 'closed')
|
|
385
|
+
continue;
|
|
386
|
+
await this.get(team.team_id);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Open every non-closed Team's admissions (Workflow admission and its
|
|
391
|
+
* scheduler) through {@link TeamService.startAdmissions}. One Team's failure
|
|
392
|
+
* is logged and does not keep the others closed.
|
|
393
|
+
*/
|
|
394
|
+
async startAdmissions() {
|
|
395
|
+
for (const team of await this.store.list()) {
|
|
396
|
+
if (team.status === 'closed')
|
|
397
|
+
continue;
|
|
398
|
+
try {
|
|
399
|
+
const service = await this.get(team.team_id);
|
|
400
|
+
await service.startAdmissions();
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
this.opts.log.error({
|
|
404
|
+
dispatcher_id: this.dispatcherId,
|
|
405
|
+
team_id: team.team_id,
|
|
406
|
+
err: teamErrorInfo(error),
|
|
407
|
+
}, 'Team admissions start failed');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** Close every live Team's admissions (Workflow stop-all and its scheduler). */
|
|
412
|
+
stopAdmissions() {
|
|
413
|
+
for (const service of this.live.values())
|
|
414
|
+
service.stopAdmissions();
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Release the runtime authority this process took over its Teams.
|
|
418
|
+
*
|
|
419
|
+
* Only Teams this process materialized are swept, because only they hold a
|
|
420
|
+
* runtime to release. The discovery pass this replaced listed every durable
|
|
421
|
+
* non-closed record and materialized it first, which on a `starting` record
|
|
422
|
+
* creates that Team's TeamLeader identity — durable work on the stop path,
|
|
423
|
+
* done to entities the run never started.
|
|
424
|
+
*/
|
|
425
|
+
async stopForHost() {
|
|
426
|
+
const services = [...this.live.values(), ...this.starting];
|
|
427
|
+
// Nothing is evicted: a stopped Team is still this collection's until it
|
|
428
|
+
// closes, so the dispatcher's second sweep reaches it again if a pre-fence
|
|
429
|
+
// use restarted its leader in between.
|
|
430
|
+
const results = await Promise.allSettled(
|
|
431
|
+
// The containment root publishes its aggregate admission fence before
|
|
432
|
+
// this sweep. Do not hold a Team route lock while releasing members:
|
|
433
|
+
// their captured completion delivery may resolve the same TeamLeader
|
|
434
|
+
// through that route before the leader itself is released.
|
|
435
|
+
services.map((service) => service.stopForHost()));
|
|
436
|
+
throwSettledFailures(results, 'multiple Team runtimes failed to stop');
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Create one Team at this candidate name, or report the name as taken.
|
|
440
|
+
*
|
|
441
|
+
* `null` means the candidate is not this creation's to use: a valid Team
|
|
442
|
+
* record already occupies it, or a construction already owns it. The caller
|
|
443
|
+
* allocates another one, and nothing this attempt made survives it.
|
|
444
|
+
*/
|
|
445
|
+
async create(input, teamId) {
|
|
446
|
+
requireLifecycleText(input.intent, 'Team create intent');
|
|
447
|
+
// Whoever registers first owns this id synchronously, so a second
|
|
448
|
+
// create at the same candidate steps aside rather than racing it.
|
|
449
|
+
if (this.constructing.has(teamId))
|
|
450
|
+
return null;
|
|
451
|
+
const construction = this.createTeam(input, teamId);
|
|
452
|
+
this.publishConstruction(teamId, construction);
|
|
453
|
+
return construction;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Prepare the workspace, publish the record, and take ownership of the
|
|
457
|
+
* result — the whole of what creating a Team means here.
|
|
458
|
+
*
|
|
459
|
+
* It is one operation because it has one side effect to answer for. The
|
|
460
|
+
* checkout is prepared before any record exists, so an ending that never
|
|
461
|
+
* publishes one has to undo it; after publication the record owns the
|
|
462
|
+
* checkout, and undoing it here would reach into a Team that exists.
|
|
463
|
+
*/
|
|
464
|
+
async createTeam(input, teamId) {
|
|
465
|
+
const record = await this.store.acquire(teamId);
|
|
466
|
+
let transferred = false;
|
|
467
|
+
try {
|
|
468
|
+
// Keep the candidate's captured owner from this cheap early-out through
|
|
469
|
+
// workspace preparation and publication in the same serialized queue.
|
|
470
|
+
if (record.current !== null)
|
|
471
|
+
return null;
|
|
472
|
+
const workspace = await this.prepareWorkspace(input, teamId);
|
|
473
|
+
const serviceInput = {
|
|
474
|
+
teamId,
|
|
475
|
+
name: input.name,
|
|
476
|
+
createRequest: input.createRequest,
|
|
477
|
+
prompt: input.prompt,
|
|
478
|
+
deliverCompletionToDispatcher: input.deliverCompletionToDispatcher,
|
|
479
|
+
leaderAgentRuntime: input.leaderAgentRuntime,
|
|
480
|
+
intent: input.intent,
|
|
481
|
+
identity: input.identity,
|
|
482
|
+
skillSources: input.skillSources,
|
|
483
|
+
workspace,
|
|
484
|
+
};
|
|
485
|
+
let created;
|
|
486
|
+
try {
|
|
487
|
+
created = await TeamService.createNew(this.depsBase(teamId, record), serviceInput);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
await this.discardUnclaimedCheckout(teamId, workspace);
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
if (created === null) {
|
|
494
|
+
await this.discardUnclaimedCheckout(teamId, workspace);
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
this.starting.add(created);
|
|
498
|
+
try {
|
|
499
|
+
this.refuseIfClosing(created);
|
|
500
|
+
await created.startCreated(serviceInput);
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
this.starting.delete(created);
|
|
504
|
+
}
|
|
505
|
+
transferred = true;
|
|
506
|
+
this.track(created, record);
|
|
507
|
+
return created;
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
if (!transferred)
|
|
511
|
+
await record.release();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async prepareWorkspace(input, teamId) {
|
|
515
|
+
const workspaceRoot = await ensureDispatcherWorkspace(this.opts.config.current(), this.dispatcherId);
|
|
516
|
+
return input.worktree === undefined && input.repoCwd === undefined
|
|
517
|
+
? this.worktrees.prepareDefaultWorkspace({
|
|
518
|
+
dispatcherWorkspace: workspaceRoot,
|
|
519
|
+
slug: teamId,
|
|
520
|
+
workspaceEnabled: defaultWorkspaceEnabled(this.opts.config.current(), this.dispatcherId),
|
|
521
|
+
})
|
|
522
|
+
: this.worktrees.prepare({
|
|
523
|
+
dispatcherId: this.dispatcherId,
|
|
524
|
+
teammateName: `team-${teamId}`,
|
|
525
|
+
cwd: input.repoCwd ?? workspaceRoot,
|
|
526
|
+
dispatcherWorkspace: workspaceRoot,
|
|
527
|
+
request: input.worktree,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Undo the checkout this failed attempt made, while it is still nobody's.
|
|
532
|
+
*
|
|
533
|
+
* A Team record is the only owner a managed checkout can have, so its absence
|
|
534
|
+
* is the proof that this preparation is unclaimed. Once a record exists — the
|
|
535
|
+
* closed one an abandoned creation leaves, or the Team that took the name —
|
|
536
|
+
* that record carries the cleanup and this must not touch the directory.
|
|
537
|
+
* Removal honors the requested policy, so a `keep` checkout is kept exactly
|
|
538
|
+
* as a dissolve would keep it, and a reused directory is never reached at all.
|
|
539
|
+
*
|
|
540
|
+
* Nothing here can be retried later: without a record there is no owner to
|
|
541
|
+
* carry a pending reclaim, so a removal that reports a refusal rather than
|
|
542
|
+
* raising one is stated here or nowhere.
|
|
543
|
+
*/
|
|
544
|
+
async discardUnclaimedCheckout(teamId, workspace) {
|
|
545
|
+
if (!workspace.createdCheckout)
|
|
546
|
+
return;
|
|
547
|
+
try {
|
|
548
|
+
if ((await this.store.get(teamId)) !== null)
|
|
549
|
+
return;
|
|
550
|
+
const cleaned = await this.worktrees.cleanup({
|
|
551
|
+
source_cwd: workspace.sourceCwd,
|
|
552
|
+
source_repo: workspace.sourceRepo,
|
|
553
|
+
worktree: workspace.worktree,
|
|
554
|
+
});
|
|
555
|
+
// Removed or deliberately kept is the end of it. Anything else is a
|
|
556
|
+
// directory this attempt made and nobody now owns, so it is reported the
|
|
557
|
+
// same way a raised failure is — once, with where it is and why it stayed.
|
|
558
|
+
if (cleaned.cleanup_state !== 'deleted' &&
|
|
559
|
+
cleaned.cleanup_state !== 'kept') {
|
|
560
|
+
this.opts.log.warn({
|
|
561
|
+
dispatcher_id: this.dispatcherId,
|
|
562
|
+
team_id: teamId,
|
|
563
|
+
path: cleaned.path,
|
|
564
|
+
cleanup_state: cleaned.cleanup_state,
|
|
565
|
+
cleanup_error: cleaned.cleanup_error,
|
|
566
|
+
}, 'prepared Team worktree was left behind');
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
this.opts.log.warn({
|
|
571
|
+
dispatcher_id: this.dispatcherId,
|
|
572
|
+
team_id: teamId,
|
|
573
|
+
path: workspace.worktree.path,
|
|
574
|
+
err: teamErrorInfo(error),
|
|
575
|
+
}, 'prepared Team worktree was left behind');
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Rebuild one Team from its record.
|
|
580
|
+
*
|
|
581
|
+
* A closed record is not a Team, it is that Team's history: nothing here
|
|
582
|
+
* constructs one, so a status read, a startup sweep, or a leftover physical
|
|
583
|
+
* cleanup answers from the record instead. That is what keeps this
|
|
584
|
+
* collection bounded by the Teams that are alive rather than by every Team
|
|
585
|
+
* that ever existed.
|
|
586
|
+
*/
|
|
587
|
+
async rebuild(teamId) {
|
|
588
|
+
const handle = await this.store.acquire(teamId);
|
|
589
|
+
let transferred = false;
|
|
590
|
+
try {
|
|
591
|
+
const record = handle.current;
|
|
592
|
+
if (record === null) {
|
|
593
|
+
throw new TeamNotFoundError(`Team ${JSON.stringify(teamId)} not found`);
|
|
594
|
+
}
|
|
595
|
+
if (record.status === 'closed') {
|
|
596
|
+
throw new TeamClosedError(`Team ${JSON.stringify(teamId)} is closed`);
|
|
597
|
+
}
|
|
598
|
+
const service = await TeamService.rebuild(this.depsBase(teamId, handle), record);
|
|
599
|
+
transferred = true;
|
|
600
|
+
this.track(service, handle);
|
|
601
|
+
return service;
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
if (!transferred)
|
|
605
|
+
await handle.release();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Register the one construction of this Team while it runs.
|
|
610
|
+
*
|
|
611
|
+
* It is removed as soon as it settles: the cache holds what it produced, and
|
|
612
|
+
* a construction that produced nothing leaves no trace to join.
|
|
613
|
+
*/
|
|
614
|
+
publishConstruction(teamId, construction) {
|
|
615
|
+
const tracked = construction.finally(() => {
|
|
616
|
+
this.constructing.delete(teamId);
|
|
617
|
+
});
|
|
618
|
+
this.constructing.set(teamId, tracked);
|
|
619
|
+
// Whoever started it reports its failure; a construction nobody joined must
|
|
620
|
+
// not also surface as an unhandled rejection.
|
|
621
|
+
void tracked.catch(() => undefined);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Take ownership of one materialized Team: cache it and listen for its end.
|
|
625
|
+
*
|
|
626
|
+
* This collection is what holds a Team, so this is where it starts holding
|
|
627
|
+
* one: the factory hands back a finished service and the owner tracks it,
|
|
628
|
+
* rather than being called back into from inside the construction it asked
|
|
629
|
+
* for.
|
|
630
|
+
*/
|
|
631
|
+
track(service, record) {
|
|
632
|
+
this.live.set(service.id, service);
|
|
633
|
+
// The exact instance that ended is the exact instance dropped; a Team
|
|
634
|
+
// rebuilt at the same id afterwards is a different object and stays.
|
|
635
|
+
void service.closed.then(async () => {
|
|
636
|
+
this.evict(service.id, service);
|
|
637
|
+
await record.release();
|
|
638
|
+
});
|
|
639
|
+
this.refuseIfClosing(service);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Stop a just-held Team and refuse to go on, when the dispatcher is already
|
|
643
|
+
* closing.
|
|
644
|
+
*
|
|
645
|
+
* `create`/`rebuild` cross the dispatcher fence before `close()` publishes its
|
|
646
|
+
* fence but finish constructing afterward, after the dispatcher's first
|
|
647
|
+
* sweep may already have passed. `stopForHost()` gives back runtime
|
|
648
|
+
* authority without closing anything, so a caller that went on would simply
|
|
649
|
+
* start the runtime again; throwing is what stops the continuation — for a
|
|
650
|
+
* created Team, before its initial turn starts the leader's runtime.
|
|
651
|
+
*/
|
|
652
|
+
refuseIfClosing(service) {
|
|
653
|
+
if (!this.opts.fence.isClosing())
|
|
654
|
+
return;
|
|
655
|
+
service.stopForHost().catch(() => undefined);
|
|
656
|
+
throw new ServerShuttingDownError(`dispatcher '${this.dispatcherId}' is shutting down`);
|
|
657
|
+
}
|
|
658
|
+
evict(teamId, expectedService) {
|
|
659
|
+
if (this.live.get(teamId) !== expectedService)
|
|
660
|
+
return;
|
|
661
|
+
this.live.delete(teamId);
|
|
662
|
+
}
|
|
663
|
+
depsBase(teamId, record) {
|
|
664
|
+
return {
|
|
665
|
+
...this.opts,
|
|
666
|
+
// Each Team gets its own already-resolved root; nothing below rebuilds it.
|
|
667
|
+
teamRoot: this.store.teamRoot(teamId),
|
|
668
|
+
// Bound to this Team's own id: the service can publish and merge its
|
|
669
|
+
// own record but can never address another Team's by id.
|
|
670
|
+
record,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
// Store-only Team list/summary/history projection; never materializes a
|
|
674
|
+
// runtime.
|
|
675
|
+
async listRows() {
|
|
676
|
+
const out = [];
|
|
677
|
+
for (const team of await this.store.list()) {
|
|
678
|
+
out.push(await this.listRow(team));
|
|
679
|
+
}
|
|
680
|
+
return out;
|
|
681
|
+
}
|
|
682
|
+
async historyResult(input) {
|
|
683
|
+
const rows = [];
|
|
684
|
+
for (const team of await this.store.list()) {
|
|
685
|
+
const row = await this.historyRow(team);
|
|
686
|
+
if (matchesTeamHistoryQuery(row, input))
|
|
687
|
+
rows.push(row);
|
|
688
|
+
}
|
|
689
|
+
rows.sort((a, b) => b.updated_at - a.updated_at ||
|
|
690
|
+
b.created_at - a.created_at ||
|
|
691
|
+
a.team_name.localeCompare(b.team_name));
|
|
692
|
+
const start = input.cursor !== undefined ? decodeCursor(input.cursor) : 0;
|
|
693
|
+
const limit = clampHistoryLimit(input.limit);
|
|
694
|
+
const items = rows.slice(start, start + limit);
|
|
695
|
+
const next = start + items.length;
|
|
696
|
+
return {
|
|
697
|
+
items,
|
|
698
|
+
next_cursor: next < rows.length ? encodeCursor(next) : null,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* One Team's status, read from its records alone.
|
|
703
|
+
*
|
|
704
|
+
* How a closed Team is reported: it has no runtime left to ask, and
|
|
705
|
+
* constructing one to answer a read would resurrect an entity that is over.
|
|
706
|
+
* The leader's runtime state is `null` because nothing is running, not
|
|
707
|
+
* because nothing is known.
|
|
708
|
+
*/
|
|
709
|
+
async recordSummary(team) {
|
|
710
|
+
const leader = await this.leaderIdentity(team);
|
|
711
|
+
return teamSummary(team, leader === null ? null : toStatus(leader, null), await this.memberCount(team));
|
|
712
|
+
}
|
|
713
|
+
async listRow(team) {
|
|
714
|
+
return {
|
|
715
|
+
team_name: team.team_id,
|
|
716
|
+
status: team.status,
|
|
717
|
+
intent: team.intent,
|
|
718
|
+
source_repo: team.source_repo,
|
|
719
|
+
leader_name: team.leader_name,
|
|
720
|
+
leader_agent_runtime: team.leader_agent_runtime,
|
|
721
|
+
leader_state: await this.leaderState(team),
|
|
722
|
+
member_count: await this.memberCount(team),
|
|
723
|
+
created_at: team.created_at,
|
|
724
|
+
updated_at: team.updated_at,
|
|
725
|
+
closed_at: team.closed_at,
|
|
726
|
+
worktree_cleanup: team.worktree.cleanup_state,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
async historyRow(team) {
|
|
730
|
+
return {
|
|
731
|
+
team_name: team.team_id,
|
|
732
|
+
status: team.status,
|
|
733
|
+
intent: team.intent,
|
|
734
|
+
source_repo: team.source_repo,
|
|
735
|
+
leader_name: team.leader_name,
|
|
736
|
+
leader_agent_runtime: team.leader_agent_runtime,
|
|
737
|
+
leader_state: await this.leaderState(team),
|
|
738
|
+
member_count: await this.memberCount(team),
|
|
739
|
+
created_at: team.created_at,
|
|
740
|
+
updated_at: team.updated_at,
|
|
741
|
+
closed_at: team.closed_at,
|
|
742
|
+
close_note: team.close_note,
|
|
743
|
+
close_note_preview: team.close_note === null ? null : previewText(team.close_note),
|
|
744
|
+
worktree_cleanup: team.worktree.cleanup_state,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* The leader's durable status, read from this Team's root and accepted only
|
|
749
|
+
* when the record names the leader the Team record names. No probing: the
|
|
750
|
+
* leader has exactly one location and this is it.
|
|
751
|
+
*
|
|
752
|
+
* A Team this process already holds live answers from that live entity's
|
|
753
|
+
* own in-memory identity — the more current copy, and one that needs no
|
|
754
|
+
* file read — rather than from a second, independently-cached read over
|
|
755
|
+
* the same `identity.json` a live owner already committed through.
|
|
756
|
+
*/
|
|
757
|
+
async leaderState(team) {
|
|
758
|
+
const live = this.live.get(team.team_id) ?? null;
|
|
759
|
+
if (live !== null)
|
|
760
|
+
return live.leaderIdentityStatus();
|
|
761
|
+
return (await this.leaderIdentity(team))?.status ?? null;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* The store decides what an unreadable leader means, and this read accepts
|
|
765
|
+
* that decision unchanged: a missing or corrupt record is the `null` the
|
|
766
|
+
* store already logged, while a record this version refuses to interpret —
|
|
767
|
+
* old state — is raised. Catching here would turn "this file says something
|
|
768
|
+
* Dreamux no longer accepts" into "there is no leader", which is the one
|
|
769
|
+
* answer that is never true.
|
|
770
|
+
*/
|
|
771
|
+
async leaderIdentity(team) {
|
|
772
|
+
const leader = await readAgentIdentity({
|
|
773
|
+
dir: this.store.teamRoot(team.team_id),
|
|
774
|
+
dispatcherId: this.dispatcherId,
|
|
775
|
+
expectedName: null,
|
|
776
|
+
log: this.opts.log,
|
|
777
|
+
});
|
|
778
|
+
return leader !== null && leader.name === team.leader_name ? leader : null;
|
|
779
|
+
}
|
|
780
|
+
/** Directory occupancy is the roster fact; an unreadable member still counts. */
|
|
781
|
+
async memberCount(team) {
|
|
782
|
+
return agentCollectionMemberCount(teamMateCollectionDir(this.store.teamRoot(team.team_id)));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function matchesTeamHistoryQuery(row, input) {
|
|
786
|
+
if (input.name !== undefined &&
|
|
787
|
+
row.team_name !== validateTeamId(input.name)) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
if (input.status !== undefined && row.status !== input.status)
|
|
791
|
+
return false;
|
|
792
|
+
if (input.repo !== undefined) {
|
|
793
|
+
const needle = input.repo.toLowerCase();
|
|
794
|
+
const hit = row.source_repo !== null &&
|
|
795
|
+
row.source_repo.toLowerCase().includes(needle);
|
|
796
|
+
if (!hit)
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
if (input.grep !== undefined && !teamRowMatchesText(row, input.grep)) {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
if (input.since !== undefined && row.updated_at < input.since)
|
|
803
|
+
return false;
|
|
804
|
+
if (input.until !== undefined && row.updated_at > input.until)
|
|
805
|
+
return false;
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
function teamRowMatchesText(row, grep) {
|
|
809
|
+
return matchesGrepText([
|
|
810
|
+
row.team_name,
|
|
811
|
+
row.intent,
|
|
812
|
+
row.source_repo,
|
|
813
|
+
row.leader_name,
|
|
814
|
+
row.close_note,
|
|
815
|
+
], grep);
|
|
816
|
+
}
|
|
817
|
+
//# sourceMappingURL=index.js.map
|