mixdog 1.3.5 → 1.4.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/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +4 -2
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +17 -1
- package/src/defaults/skills/browser-use/SKILL.md +59 -11
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +10 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +7 -1
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +10 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +448 -185
- package/src/session-runtime/agent-task-sessions.mjs +329 -180
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +124 -188
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/dist/index.mjs +58 -64
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +17 -1
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +19 -2
- package/src/tui/session/turn.mjs +1 -0
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* enqueue adds nothing and a crash between posting and dequeuing never posts
|
|
11
11
|
* twice (the posted message id is derived from the run id). One drainer per
|
|
12
12
|
* home appends every queued result FIFO once the thread is idle; posting never
|
|
13
|
-
* starts a turn.
|
|
13
|
+
* starts a turn. Rows that wake nobody (a task session's progress updates,
|
|
14
|
+
* among others) also post right before any turn of the thread starts
|
|
15
|
+
* (postBeforeTurn), so that turn reads them ahead of its prompt. A home thread that does not exist yet keeps its results
|
|
14
16
|
* queued until it does; a deleted owner's results move to Mixy's home. Each
|
|
15
17
|
* home keeps at most ROUTINE_LIMITS.homeQueueLimit results (the oldest drop
|
|
16
18
|
* behind one "n omitted" notice), and posted run ids stay in a ledger so a
|
|
@@ -58,6 +60,7 @@ import {
|
|
|
58
60
|
routineSummary,
|
|
59
61
|
} from '../runtime/agent/orchestrator/session/routine-result.mjs';
|
|
60
62
|
import { readJsonState, updateJsonAtomic } from '../runtime/shared/atomic-file.mjs';
|
|
63
|
+
import { endHandoff, noteHandoff } from '../runtime/shared/delegated-approval-host.mjs';
|
|
61
64
|
import { ROUTINE_LIMITS } from '../runtime/shared/routine-limits.mjs';
|
|
62
65
|
import { submissionIdsOf, submissionOutcome } from './agent-task-sessions.mjs';
|
|
63
66
|
import {
|
|
@@ -65,9 +68,36 @@ import {
|
|
|
65
68
|
heldRequestNotice,
|
|
66
69
|
} from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
|
|
67
70
|
|
|
71
|
+
/** `message` marked as posted outside any turn (`meta.transcript.posted`):
|
|
72
|
+
* its row is a turn of its own, never part of the rows around it
|
|
73
|
+
* (transcript-turn-keys.mjs). */
|
|
74
|
+
function postedMessage(message) {
|
|
75
|
+
return { ...message, meta: { ...message.meta, transcript: { ...message.meta?.transcript, posted: true } } };
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
/** A queued item's stored message and live notice: a routine result, or a
|
|
69
79
|
* held request of an unattended run (session-runtime/held-requests.mjs). */
|
|
70
80
|
function deliveredRow(item, messageId) {
|
|
81
|
+
const row = queuedRow(item, messageId);
|
|
82
|
+
return { ...row, message: postedMessage(row.message) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A task report past the round-trip cap with another agent's own thread:
|
|
86
|
+
* each wake could answer the other agent again without end. */
|
|
87
|
+
const taskWakeCapped = (item) =>
|
|
88
|
+
(isAgentHomeSessionId(item.taskSessionId) || isRoomThreadSessionId(item.taskSessionId)) &&
|
|
89
|
+
(item.round || 0) > TASK_LOOP_CAP;
|
|
90
|
+
|
|
91
|
+
/** Whether queued `item` wakes the agent once posted: a task turn's end (not
|
|
92
|
+
* its start, a wait for the user or a progress update), unless capped. */
|
|
93
|
+
const wakesAgent = (item) => item.kind === 'agent-task' && item.roundTrip === true && !taskWakeCapped(item);
|
|
94
|
+
|
|
95
|
+
const MIXY_HOME = agentHomeSessionId(MIXY_AGENT_ID);
|
|
96
|
+
/** A heartbeat report: Mixy's own reply from a check turn (mixy-heartbeat.mjs
|
|
97
|
+
* wakeMixy, its only sender of agent messages). */
|
|
98
|
+
const isHeartbeatReport = (item) => item?.kind === 'agent-message';
|
|
99
|
+
|
|
100
|
+
function queuedRow(item, messageId) {
|
|
71
101
|
// The agent's own words from a turn it ran elsewhere (mixy-heartbeat.mjs):
|
|
72
102
|
// one ordinary reply of the agent in its thread.
|
|
73
103
|
if (item.kind === 'agent-message') {
|
|
@@ -79,14 +109,9 @@ function deliveredRow(item, messageId) {
|
|
|
79
109
|
return { message, notice: heldRequestNotice(message) };
|
|
80
110
|
}
|
|
81
111
|
if (item.kind === 'agent-task') {
|
|
82
|
-
|
|
83
|
-
// except past the round-trip cap with another agent's own thread, where
|
|
84
|
-
// each wake could answer the other agent again without end.
|
|
85
|
-
const capped =
|
|
86
|
-
(isAgentHomeSessionId(item.taskSessionId) || isRoomThreadSessionId(item.taskSessionId)) &&
|
|
87
|
-
(item.round || 0) > TASK_LOOP_CAP;
|
|
112
|
+
const capped = taskWakeCapped(item);
|
|
88
113
|
const message = agentTaskMessage({ ...item, messageId, ...(capped ? { woken: false } : {}) });
|
|
89
|
-
return { message, notice: routineResultNotice(message), wake: item
|
|
114
|
+
return { message, notice: routineResultNotice(message), wake: wakesAgent(item) };
|
|
90
115
|
}
|
|
91
116
|
const message = routineResultMessage({ ...item, messageId });
|
|
92
117
|
return { message, notice: routineResultNotice(message) };
|
|
@@ -152,14 +177,14 @@ export function agentHomeLiveSurface() {
|
|
|
152
177
|
// even into a thread that was reset ("대화 새로 시작") since.
|
|
153
178
|
const DELIVERED_LEDGER = 500;
|
|
154
179
|
// A target's round-trip count left idle this long starts over.
|
|
155
|
-
|
|
180
|
+
const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
156
181
|
// Per home: round-trip targets counted (least recently counted drop first),
|
|
157
182
|
// reporting turns watched at once, and posted results owed a wake.
|
|
158
183
|
export const QUEUE_CAPS = Object.freeze({ roundTargets: 100, watches: 200, wakeRunIds: 100 });
|
|
159
184
|
// Waits before the 2nd, 3rd and 4th try of a relay (wake) turn that did not finish.
|
|
160
|
-
|
|
185
|
+
const WAKE_RETRY_DELAYS_MS = Object.freeze([30_000, 120_000, 600_000]);
|
|
161
186
|
// Refused wake submissions of one batch, a minute apart, before it is given up.
|
|
162
|
-
|
|
187
|
+
const WAKE_REFUSAL_LIMIT = 10;
|
|
163
188
|
|
|
164
189
|
/** `homeId`'s wake obligations in `wakes`: posted task results not in a batch
|
|
165
190
|
* yet (`owed`), those past the cap collapsed into one marker (`overflow`:
|
|
@@ -264,6 +289,25 @@ function withDequeued(doc, homeId, runIds, omitted) {
|
|
|
264
289
|
return { ...doc, homes, omitted: omittedNow, delivered };
|
|
265
290
|
}
|
|
266
291
|
|
|
292
|
+
/** `{ doc, item }`: task report `item` for `homeId` and the `doc` it joins,
|
|
293
|
+
* so that a final report never repeats progress update `progressRunId` that
|
|
294
|
+
* carried the same answer. Posted (`posted`: the thread holds it, or the
|
|
295
|
+
* ledger says so), the final report points to it instead of repeating its
|
|
296
|
+
* text (the row's one-line summary stays); only queued, that update is
|
|
297
|
+
* dropped and the final report carries the answer in its place. */
|
|
298
|
+
function withoutRepeatedProgress(doc, homeId, item, posted) {
|
|
299
|
+
const { progressRunId, ...report } = item;
|
|
300
|
+
if (!progressRunId) return { doc, item };
|
|
301
|
+
if (posted || (doc.delivered[homeId] || []).includes(progressRunId)) {
|
|
302
|
+
const { text: _text, textChars: _textChars, ...rest } = report;
|
|
303
|
+
return { doc, item: { ...rest, text: '', answerAbove: true } };
|
|
304
|
+
}
|
|
305
|
+
const queued = doc.homes[homeId] || [];
|
|
306
|
+
if (!queued.some((row) => row.runId === progressRunId)) return { doc, item: report };
|
|
307
|
+
const homes = { ...doc.homes, [homeId]: queued.filter((row) => row.runId !== progressRunId) };
|
|
308
|
+
return { doc: { ...doc, homes }, item: report };
|
|
309
|
+
}
|
|
310
|
+
|
|
267
311
|
/** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
|
|
268
312
|
* omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
|
|
269
313
|
* runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
|
|
@@ -299,16 +343,23 @@ export function createRoutineResultQueue(
|
|
|
299
343
|
}
|
|
300
344
|
/** Read-modify-write under the file lock (`mutator(doc)` returns the next
|
|
301
345
|
* doc, or nothing to keep the file); every write also retires stale and
|
|
302
|
-
* surplus round counts.
|
|
303
|
-
|
|
304
|
-
|
|
346
|
+
* surplus round counts. The watches a write added or removed, by any path
|
|
347
|
+
* (a report, a turn never taken, a cap eviction, a pruned home), start or
|
|
348
|
+
* end their hand-offs (delegated-approval-host.mjs). */
|
|
349
|
+
async function update(mutator) {
|
|
350
|
+
let watches = null;
|
|
351
|
+
const result = await updateJsonAtomic(
|
|
305
352
|
filePath,
|
|
306
353
|
(raw) => {
|
|
307
|
-
const
|
|
354
|
+
const doc = parts(raw);
|
|
355
|
+
const next = mutator(doc);
|
|
356
|
+
watches = { before: doc.watches, after: next ? section(next, 'watches') : doc.watches };
|
|
308
357
|
return next ? { ...next, rounds: tidyRounds(next.rounds) } : undefined;
|
|
309
358
|
},
|
|
310
359
|
{ backup: true }
|
|
311
360
|
);
|
|
361
|
+
if (watches) syncHandoffs(watches.before, watches.after);
|
|
362
|
+
return result;
|
|
312
363
|
}
|
|
313
364
|
const appended = (doc, homeId, items, omittedCount = 0) => appendedItems(doc, homeId, items, limit, omittedCount);
|
|
314
365
|
return {
|
|
@@ -423,9 +474,14 @@ export function createRoutineResultQueue(
|
|
|
423
474
|
return (await read()).omitted[homeId] || null;
|
|
424
475
|
},
|
|
425
476
|
/** Queue `item` for `homeId`; `unwatch` (run ids) drops those watches in the
|
|
426
|
-
* same write, also when the item was queued or posted before.
|
|
427
|
-
|
|
428
|
-
|
|
477
|
+
* same write, also when the item was queued or posted before.
|
|
478
|
+
* `progressPosted`: the thread holds the progress update a final report
|
|
479
|
+
* names (withoutRepeatedProgress). `progressed` (`{ runId, turnId }`):
|
|
480
|
+
* watch `runId` reported turn `turnId` as progress — its durable mark,
|
|
481
|
+
* stored in the same write (agent-task-sessions.mjs progressedThrough). */
|
|
482
|
+
async add(homeId, queued, { unwatch = [], progressPosted = false, progressed = null } = {}) {
|
|
483
|
+
await update((stored) => {
|
|
484
|
+
const { doc, item } = withoutRepeatedProgress(stored, homeId, queued, progressPosted);
|
|
429
485
|
let next = null;
|
|
430
486
|
if (item.roundTrip !== true) next = appended(doc, homeId, [item]);
|
|
431
487
|
else {
|
|
@@ -443,11 +499,19 @@ export function createRoutineResultQueue(
|
|
|
443
499
|
next = { ...next, rounds: { ...doc.rounds, [homeId]: counted } };
|
|
444
500
|
}
|
|
445
501
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
502
|
+
let watches = doc.watches;
|
|
503
|
+
const marked = progressed?.turnId && watches[progressed.runId];
|
|
504
|
+
if (marked && marked.progressed !== progressed.turnId) {
|
|
505
|
+
watches = { ...watches, [progressed.runId]: { ...marked, progressed: progressed.turnId } };
|
|
506
|
+
}
|
|
507
|
+
const dropped = unwatch.filter((runId) => watches[runId]);
|
|
508
|
+
if (dropped.length) {
|
|
509
|
+
watches = { ...watches };
|
|
510
|
+
for (const runId of dropped) delete watches[runId];
|
|
511
|
+
}
|
|
512
|
+
const base = next || doc;
|
|
513
|
+
if (watches === doc.watches) return base === stored ? undefined : base;
|
|
514
|
+
return { ...base, watches };
|
|
451
515
|
});
|
|
452
516
|
},
|
|
453
517
|
/** Keep `watch` ({ runId, agentId, homeSessionId, taskSessionId, title,
|
|
@@ -469,9 +533,12 @@ export function createRoutineResultQueue(
|
|
|
469
533
|
});
|
|
470
534
|
},
|
|
471
535
|
watchCap: caps.watches,
|
|
472
|
-
/** Every stored watch, oldest first
|
|
536
|
+
/** Every stored watch, oldest first; each is a live hand-off (after a
|
|
537
|
+
* restart, this read brings them back). */
|
|
473
538
|
async watches() {
|
|
474
|
-
|
|
539
|
+
const watches = Object.values((await read()).watches);
|
|
540
|
+
for (const watch of watches) noteHandoff(watch ?? {});
|
|
541
|
+
return watches;
|
|
475
542
|
},
|
|
476
543
|
/** The watch of `runId` follows the turn submitted as `submissionId`: a
|
|
477
544
|
* room member's one turn answering several sends, or a fresh turn
|
|
@@ -537,7 +604,7 @@ const messageIdOf = (runId) => `routine_${runId}`;
|
|
|
537
604
|
* or false (logged) when the surface threw. */
|
|
538
605
|
function noticeLive(live, log, homeId, notice) {
|
|
539
606
|
try {
|
|
540
|
-
return live()?.notice?.(homeId, notice);
|
|
607
|
+
return live()?.notice?.(homeId, { ...notice, posted: true });
|
|
541
608
|
} catch (error) {
|
|
542
609
|
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
543
610
|
return false;
|
|
@@ -571,6 +638,13 @@ function routineResultItem(row, result) {
|
|
|
571
638
|
};
|
|
572
639
|
}
|
|
573
640
|
|
|
641
|
+
/** End the hand-off of each watch in `before` and not `after`, and start
|
|
642
|
+
* each one `after` added (both keyed by run id). */
|
|
643
|
+
function syncHandoffs(before, after) {
|
|
644
|
+
for (const runId of Object.keys(before)) if (!after[runId]) endHandoff(runId);
|
|
645
|
+
for (const [runId, watch] of Object.entries(after)) if (!before[runId]) noteHandoff(watch ?? {});
|
|
646
|
+
}
|
|
647
|
+
|
|
574
648
|
/** The run ids of `watches` a home at its watch `cap` gives up before
|
|
575
649
|
* storing `watch`: its oldest whose target sessions are proven deleted. */
|
|
576
650
|
function watchesToDrop(watches, watch, cap, sessionDeleted) {
|
|
@@ -594,6 +668,9 @@ function homeProvenDeleted(homeId, agentPresence, roomPresence) {
|
|
|
594
668
|
return Boolean(roomId) && roomPresence(roomId) === 'deleted';
|
|
595
669
|
}
|
|
596
670
|
|
|
671
|
+
// Task report statuses that post a row and wake nobody.
|
|
672
|
+
const QUIET_TASK_STATUSES = new Set(['started', 'blocked', 'working']);
|
|
673
|
+
|
|
597
674
|
/** The queued item of one task report (enqueueTask). */
|
|
598
675
|
function taskReportItem({
|
|
599
676
|
runId,
|
|
@@ -607,6 +684,7 @@ function taskReportItem({
|
|
|
607
684
|
hop = 0,
|
|
608
685
|
watchId = '',
|
|
609
686
|
watchIds = [],
|
|
687
|
+
progressRunId = '',
|
|
610
688
|
}) {
|
|
611
689
|
// The model-facing reply keeps its lines up to REPLY_LIMIT; the row's
|
|
612
690
|
// one-line summary is derived from it (agentTaskMessage).
|
|
@@ -630,7 +708,12 @@ function taskReportItem({
|
|
|
630
708
|
...(watchIds.length > 1 ? { sendIds: watchIds.map(String) } : {}),
|
|
631
709
|
// The files the turn produced (handoff-attachments.mjs collectTurnArtifacts).
|
|
632
710
|
...(Array.isArray(files) && files.length ? { files, ...(omitted > 0 ? { omitted } : {}) } : {}),
|
|
633
|
-
|
|
711
|
+
// The progress update that carried this final report's answer, if one
|
|
712
|
+
// did: the queue keeps the answer from being repeated (withoutRepeatedProgress).
|
|
713
|
+
...(progressRunId ? { progressRunId: String(progressRunId) } : {}),
|
|
714
|
+
// Only a turn's end wakes the agent and counts a round trip: not its
|
|
715
|
+
// start, a wait for the user, or a progress update (status 'working').
|
|
716
|
+
roundTrip: !QUIET_TASK_STATUSES.has(status),
|
|
634
717
|
...(hop > 0 ? { hop } : {}),
|
|
635
718
|
at: Date.now(),
|
|
636
719
|
};
|
|
@@ -644,7 +727,7 @@ function postQueuedRows(session, items, omitted) {
|
|
|
644
727
|
const fresh = [];
|
|
645
728
|
const omittedId = omitted ? messageIdOf(omitted.id) : '';
|
|
646
729
|
if (omitted && !posted.has(omittedId)) {
|
|
647
|
-
const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
|
|
730
|
+
const message = postedMessage(routineOmittedMessage({ count: omitted.count, messageId: omittedId }));
|
|
648
731
|
session.messages.push(message);
|
|
649
732
|
fresh.push({ message, notice: routineResultNotice(message) });
|
|
650
733
|
}
|
|
@@ -716,15 +799,17 @@ function createHomeRelay({
|
|
|
716
799
|
log(`the relay turn in ${homeId} ${why}; its results stay posted unrelayed`);
|
|
717
800
|
const messageId = messageIdOf(`relay_${batch.id}`);
|
|
718
801
|
if (!session.messages.some((message) => message?.meta?.transcript?.messageId === messageId)) {
|
|
719
|
-
const message =
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
802
|
+
const message = postedMessage(
|
|
803
|
+
agentTaskRelayFailedMessage({
|
|
804
|
+
count: batch.runIds.length + (batch.overflow?.count || 0),
|
|
805
|
+
tries,
|
|
806
|
+
messageId,
|
|
807
|
+
})
|
|
808
|
+
);
|
|
724
809
|
session.messages.push(message);
|
|
725
810
|
session.updatedAt = Date.now();
|
|
726
811
|
await saveSession(session);
|
|
727
|
-
noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId });
|
|
812
|
+
noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId, at: message.meta.transcript.at });
|
|
728
813
|
}
|
|
729
814
|
await queue.woke(homeId, batch.id);
|
|
730
815
|
return true;
|
|
@@ -807,6 +892,200 @@ function createHomeRelay({
|
|
|
807
892
|
return { wakeOwed };
|
|
808
893
|
}
|
|
809
894
|
|
|
895
|
+
/** Whether home thread `sessionId` is busy: a turn in flight in this process,
|
|
896
|
+
* or the `live()` surface says so (false when it threw). */
|
|
897
|
+
const homeBusy = (turnInFlight, live) => (sessionId) => {
|
|
898
|
+
if (turnInFlight(sessionId)) return true;
|
|
899
|
+
try {
|
|
900
|
+
return live()?.busy?.(sessionId) === true;
|
|
901
|
+
} catch {
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
/** `later(homeId, ms)`: drain `homeId` again after `ms`, one retry timer per
|
|
907
|
+
* home armed at a time. */
|
|
908
|
+
function createRetryTimer(retryLater, drain) {
|
|
909
|
+
const waiting = new Set(); // homes with a retry timer armed
|
|
910
|
+
return function later(homeId, ms) {
|
|
911
|
+
if (waiting.has(homeId)) return;
|
|
912
|
+
waiting.add(homeId);
|
|
913
|
+
retryLater(() => {
|
|
914
|
+
waiting.delete(homeId);
|
|
915
|
+
void drain(homeId);
|
|
916
|
+
}, ms);
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/** The held requests shown live in a busy thread. A held request waits on the
|
|
921
|
+
* user, possibly raised by the very turn that keeps its thread busy (a
|
|
922
|
+
* routine turn): `show` shows it live at once; the stored row (same
|
|
923
|
+
* messageId) replaces it once the thread is idle, and `forget` lets a later
|
|
924
|
+
* one show again. */
|
|
925
|
+
function createHeldLiveDisplay(live, log) {
|
|
926
|
+
const shownLive = new Set(); // held-request message ids shown live in a busy thread
|
|
927
|
+
return {
|
|
928
|
+
show(homeId, items) {
|
|
929
|
+
for (const item of items) {
|
|
930
|
+
if (item.kind !== 'held-request') continue;
|
|
931
|
+
const messageId = messageIdOf(item.runId);
|
|
932
|
+
if (shownLive.has(messageId)) continue;
|
|
933
|
+
shownLive.add(messageId);
|
|
934
|
+
noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId, at: item.at });
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
forget(items) {
|
|
938
|
+
for (const item of items) shownLive.delete(messageIdOf(item.runId));
|
|
939
|
+
},
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/** The queues a drain drops unposted instead of retrying them. */
|
|
944
|
+
function createQueueDrops({ queue, log, homeAgent, homeDeleted, sessionDeleted }) {
|
|
945
|
+
/** A room thread proven gone: its queued rows are dropped, never retried. */
|
|
946
|
+
async function dropGoneRoomThread(homeId, items, omitted) {
|
|
947
|
+
if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
|
|
948
|
+
log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
|
|
949
|
+
await dropQueued(queue, homeId, items, omitted);
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** A room log's queued rows: task reports go to member sessions now, so what
|
|
954
|
+
* an earlier version left under the log's id is dropped, never posted. */
|
|
955
|
+
async function dropRoomLogQueue(logId, items, omitted) {
|
|
956
|
+
log(`${items.length} legacy task report(s) queued for the room log ${logId} are dropped`);
|
|
957
|
+
await dropQueued(queue, logId, items, omitted);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** A missing home of an agent that can never have one (no bot): its queued
|
|
961
|
+
* rows are dropped with a log line, never retried. */
|
|
962
|
+
async function dropHomeless(homeId, items, omitted) {
|
|
963
|
+
const agentId = agentIdOfHomeSession(homeId);
|
|
964
|
+
if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
|
|
965
|
+
log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
|
|
966
|
+
await dropQueued(queue, homeId, items, omitted);
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
return { dropGoneRoomThread, dropRoomLogQueue, dropHomeless };
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** The durable intake of a home: each enqueue persists its row for the home
|
|
974
|
+
* thread, then starts draining it, and resolves once it is queued. */
|
|
975
|
+
function createHomeEnqueue(queue, drain, { postingFor, postedIn }) {
|
|
976
|
+
/** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
|
|
977
|
+
* or webhook `row` for its owner's home, then start draining it. Resolves
|
|
978
|
+
* once it is queued. */
|
|
979
|
+
async function enqueue(row, result) {
|
|
980
|
+
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
981
|
+
if (!result?.runId) throw new Error('routine result: runId required');
|
|
982
|
+
await queue.add(homeId, routineResultItem(row, result));
|
|
983
|
+
void drain(homeId);
|
|
984
|
+
return homeId;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** Persist one held-request row (`held`: normalizeHeldRequest fields) for
|
|
988
|
+
* `agentId`'s home thread, then start draining it. Each status posts once. */
|
|
989
|
+
async function enqueueHeld(agentId, held) {
|
|
990
|
+
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
991
|
+
if (!held?.id) throw new Error('held request: id required');
|
|
992
|
+
await queue.add(homeId, {
|
|
993
|
+
runId: `${held.id}_${held.status || 'pending'}`,
|
|
994
|
+
kind: 'held-request',
|
|
995
|
+
held,
|
|
996
|
+
at: Date.now(),
|
|
997
|
+
});
|
|
998
|
+
void drain(homeId);
|
|
999
|
+
return homeId;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/** Persist one report of a coding task session `agentId` created (session
|
|
1003
|
+
* create) for its home thread, then start draining it. `status` 'started'
|
|
1004
|
+
* and 'blocked' only post a line; a turn's end also wakes the agent. */
|
|
1005
|
+
async function enqueueTask(agentId, report) {
|
|
1006
|
+
const { runId, taskSessionId, watchId = '', watchIds = [], from = '' } = report;
|
|
1007
|
+
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
1008
|
+
if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
|
|
1009
|
+
const item = taskReportItem(report);
|
|
1010
|
+
const unwatch = [...new Set([watchId, ...watchIds].filter(Boolean).map(String))];
|
|
1011
|
+
// A room member's own work reports to its member session, never its home.
|
|
1012
|
+
const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
|
|
1013
|
+
if (item.progressRunId) {
|
|
1014
|
+
// Whether its answer already went out as that progress update is
|
|
1015
|
+
// decided in the thread's posting turn, against the thread itself: a
|
|
1016
|
+
// row being posted (or posted before a crash, not dequeued yet) counts
|
|
1017
|
+
// as posted, never as still queued.
|
|
1018
|
+
await postingFor(target, async () =>
|
|
1019
|
+
queue.add(target, item, { unwatch, progressPosted: postedIn(target, item.progressRunId) })
|
|
1020
|
+
);
|
|
1021
|
+
} else {
|
|
1022
|
+
// A progress update moves its send's watch mark (`progressed`) to the
|
|
1023
|
+
// turn it carries, in the same write: a restart never reports it again.
|
|
1024
|
+
const progressed = report.progressWatchId
|
|
1025
|
+
? { runId: String(report.progressWatchId), turnId: String(report.answerId || '') }
|
|
1026
|
+
: null;
|
|
1027
|
+
await queue.add(target, item, { unwatch, progressed });
|
|
1028
|
+
}
|
|
1029
|
+
void drain(target);
|
|
1030
|
+
return target;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/** Persist `text`, the agent's own reply from turn `runId` it ran outside
|
|
1034
|
+
* its thread, for `agentId`'s home thread, then start draining it. It posts
|
|
1035
|
+
* once, as the agent's message; no turn follows. */
|
|
1036
|
+
async function enqueueMessage(agentId, { runId, text }) {
|
|
1037
|
+
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
1038
|
+
if (!runId || !String(text || '').trim()) throw new Error('agent message: runId and text required');
|
|
1039
|
+
await queue.add(homeId, { runId: String(runId), kind: 'agent-message', text: String(text).trim(), at: Date.now() });
|
|
1040
|
+
void drain(homeId);
|
|
1041
|
+
return homeId;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
return { enqueue, enqueueHeld, enqueueTask, enqueueMessage };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/** The queue's bookkeeping across processes: report watches and the restart
|
|
1048
|
+
* recovery that retires deleted homes and drains what was left queued. */
|
|
1049
|
+
function createHomeBookkeeping({ queue, log, homeDeleted, sessionDeleted, drain }) {
|
|
1050
|
+
/** Retire the bookkeeping of deleted homes (and stale round counts). */
|
|
1051
|
+
async function prune() {
|
|
1052
|
+
await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/** Store `watch`; a home at its watch cap first gives up its oldest watches
|
|
1056
|
+
* whose target sessions are proven deleted, and refuses the watch (so the
|
|
1057
|
+
* send claims no report) when that frees too few. */
|
|
1058
|
+
async function watchTask(watch) {
|
|
1059
|
+
const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
|
|
1060
|
+
if (drop.length) {
|
|
1061
|
+
log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
|
|
1062
|
+
}
|
|
1063
|
+
await queue.watch(watch, { drop });
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
|
|
1067
|
+
async function recover() {
|
|
1068
|
+
try {
|
|
1069
|
+
await prune();
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
log(`pruning the routine results failed: ${error?.message || error}`);
|
|
1072
|
+
}
|
|
1073
|
+
// The stored watches name the hand-offs an earlier process left open:
|
|
1074
|
+
// reading them notes each one now, so a target that asks again after the
|
|
1075
|
+
// restart reaches the chat that handed it the work.
|
|
1076
|
+
try {
|
|
1077
|
+
await queue.watches();
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
log(`reading the report watches failed: ${error?.message || error}`);
|
|
1080
|
+
}
|
|
1081
|
+
const homes = await queue.pendingHomes();
|
|
1082
|
+
for (const homeId of homes) void drain(homeId);
|
|
1083
|
+
return homes;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return { watchTask, recover };
|
|
1087
|
+
}
|
|
1088
|
+
|
|
810
1089
|
export function createAgentHomeDelivery({
|
|
811
1090
|
loadSession,
|
|
812
1091
|
saveSession,
|
|
@@ -828,6 +1107,9 @@ export function createAgentHomeDelivery({
|
|
|
828
1107
|
// at all. 'none' (proven: a workflow agent such as Reviewer) drops the rows
|
|
829
1108
|
// queued for its missing home instead of waiting on it forever.
|
|
830
1109
|
homeAgent = () => 'unknown',
|
|
1110
|
+
// Whether the representative bot exists (mixy-presence.mjs): while it does
|
|
1111
|
+
// not, heartbeat reports queued for its home are dropped.
|
|
1112
|
+
mixyExists = async () => true,
|
|
831
1113
|
// Waits before each new try of a relay turn that did not finish; past the
|
|
832
1114
|
// last one the results stay posted with a note and nobody is woken.
|
|
833
1115
|
wakeRetryDelaysMs = WAKE_RETRY_DELAYS_MS,
|
|
@@ -840,19 +1122,20 @@ export function createAgentHomeDelivery({
|
|
|
840
1122
|
roomRelay = (sessionId, prompt, options) => agentRoomHost().relay(sessionId, prompt, options),
|
|
841
1123
|
log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
|
|
842
1124
|
}) {
|
|
843
|
-
const busy = (
|
|
844
|
-
if (turnInFlight(sessionId)) return true;
|
|
845
|
-
try {
|
|
846
|
-
return live()?.busy?.(sessionId) === true;
|
|
847
|
-
} catch {
|
|
848
|
-
return false;
|
|
849
|
-
}
|
|
850
|
-
};
|
|
1125
|
+
const busy = homeBusy(turnInFlight, live);
|
|
851
1126
|
const draining = new Map(); // home → the running pass
|
|
852
1127
|
const again = new Set(); // homes enqueued to while their pass ran
|
|
853
|
-
const waiting = new Set(); // homes with a retry timer armed
|
|
854
|
-
const shownLive = new Set(); // held-request message ids shown live in a busy thread
|
|
855
1128
|
const waitLogged = new Set(); // missing homes whose wait was logged
|
|
1129
|
+
const later = createRetryTimer(retryLater, drain);
|
|
1130
|
+
const heldLive = createHeldLiveDisplay(live, log);
|
|
1131
|
+
const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
|
|
1132
|
+
const { dropGoneRoomThread, dropRoomLogQueue, dropHomeless } = createQueueDrops({
|
|
1133
|
+
queue,
|
|
1134
|
+
log,
|
|
1135
|
+
homeAgent,
|
|
1136
|
+
homeDeleted,
|
|
1137
|
+
sessionDeleted,
|
|
1138
|
+
});
|
|
856
1139
|
const relay = createHomeRelay({
|
|
857
1140
|
queue,
|
|
858
1141
|
saveSession,
|
|
@@ -867,61 +1150,123 @@ export function createAgentHomeDelivery({
|
|
|
867
1150
|
busyRetryMs,
|
|
868
1151
|
missingRetryMs,
|
|
869
1152
|
});
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1153
|
+
// Per thread, one posting turn at a time: push queued rows, save the
|
|
1154
|
+
// thread, dequeue them (the drain, the turn prelude), and a final report's
|
|
1155
|
+
// check whether its answer already went out as a progress update. No pass
|
|
1156
|
+
// ever dequeues rows another pushed but has not saved yet, and a row being
|
|
1157
|
+
// posted is never mistaken for one still queued.
|
|
1158
|
+
const postingChains = new Map();
|
|
1159
|
+
function postingFor(homeId, fn) {
|
|
1160
|
+
const run = (postingChains.get(homeId) || Promise.resolve()).then(fn);
|
|
1161
|
+
const tail = run.then(
|
|
1162
|
+
() => {},
|
|
1163
|
+
() => {}
|
|
1164
|
+
);
|
|
1165
|
+
postingChains.set(homeId, tail);
|
|
1166
|
+
void tail.then(() => {
|
|
1167
|
+
if (postingChains.get(homeId) === tail) postingChains.delete(homeId);
|
|
1168
|
+
});
|
|
1169
|
+
return run;
|
|
882
1170
|
}
|
|
1171
|
+
/** Whether queued row `runId` is in thread `homeId` (posted, saved or not). */
|
|
1172
|
+
const postedIn = (homeId, runId) => {
|
|
1173
|
+
const messages = loadSession(homeId)?.messages;
|
|
1174
|
+
const messageId = messageIdOf(runId);
|
|
1175
|
+
return Array.isArray(messages) && messages.some((message) => message?.meta?.transcript?.messageId === messageId);
|
|
1176
|
+
};
|
|
1177
|
+
const intake = createHomeEnqueue(queue, drain, { postingFor, postedIn });
|
|
1178
|
+
const { watchTask, recover } = createHomeBookkeeping({ queue, log, homeDeleted, sessionDeleted, drain });
|
|
883
1179
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
},
|
|
1180
|
+
/** Append `items` (and the omission notice `omitted`) not posted before to
|
|
1181
|
+
* `homeId`'s thread `session`, store it and show the new rows live.
|
|
1182
|
+
* Returns the run ids owed a wake (postQueuedRows). Nothing awaits before
|
|
1183
|
+
* the push, so no turn can start in between. */
|
|
1184
|
+
async function postRows(homeId, session, items, omitted) {
|
|
1185
|
+
if (!Array.isArray(session.messages)) session.messages = [];
|
|
1186
|
+
const { fresh, toWake } = postQueuedRows(session, items, omitted);
|
|
1187
|
+
heldLive.forget(items);
|
|
1188
|
+
// The caller dequeues `items` (and `omitted`) next: the thread holding
|
|
1189
|
+
// them is saved first, also when an earlier pass pushed them but its save
|
|
1190
|
+
// failed (they are in the thread in memory, not on disk).
|
|
1191
|
+
if (items.length || omitted) {
|
|
1192
|
+
if (fresh.length) session.updatedAt = Date.now();
|
|
1193
|
+
await saveSession(session);
|
|
1194
|
+
}
|
|
1195
|
+
for (const { message, notice } of fresh) {
|
|
1196
|
+
const { messageId, at } = message.meta.transcript;
|
|
1197
|
+
noticeLive(live, log, homeId, { ...notice, messageId, at });
|
|
1198
|
+
}
|
|
1199
|
+
return toWake;
|
|
891
1200
|
}
|
|
892
1201
|
|
|
893
|
-
/**
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1202
|
+
/** Right before a turn of thread `homeId` takes its prompt (whatever started
|
|
1203
|
+
* it: the user, a routine, a wake): post its queued rows that wake nobody —
|
|
1204
|
+
* progress updates of the sessions its agent handed work to, start and wait
|
|
1205
|
+
* lines, held requests, routine results — in queue order, so the turn reads
|
|
1206
|
+
* them before its prompt. Rows that wake the agent stay for the drain after
|
|
1207
|
+
* the turn, which posts them and wakes it as before. The caller starts the
|
|
1208
|
+
* turn, so the thread's busy state is its own. Rows the queue dropped past
|
|
1209
|
+
* its limit are announced first by the "n omitted" notice, retired in the
|
|
1210
|
+
* same write that dequeues the posted rows (as the drain does). It runs
|
|
1211
|
+
* in the thread's posting turn (postingFor), never beside the drain's. A
|
|
1212
|
+
* failure is logged; the turn goes on and the drain posts the rows later. */
|
|
1213
|
+
async function postBeforeTurn(homeId) {
|
|
1214
|
+
try {
|
|
1215
|
+
await postingFor(homeId, async () => {
|
|
1216
|
+
const items = (await queue.items(homeId)).filter((item) => !wakesAgent(item));
|
|
1217
|
+
const omitted = await queue.omitted(homeId);
|
|
1218
|
+
if (!items.length && !omitted) return;
|
|
1219
|
+
const session = loadSession(homeId);
|
|
1220
|
+
if (!session || session.closed === true) return;
|
|
1221
|
+
await postRows(homeId, session, items, omitted);
|
|
1222
|
+
await queue.remove(
|
|
1223
|
+
homeId,
|
|
1224
|
+
items.map((item) => item.runId),
|
|
1225
|
+
omitted
|
|
1226
|
+
);
|
|
1227
|
+
});
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
log(`posting the rows owed before a turn of ${homeId} failed: ${error?.message || error}`);
|
|
1230
|
+
}
|
|
899
1231
|
}
|
|
900
1232
|
|
|
901
|
-
/**
|
|
902
|
-
*
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1233
|
+
/** Mixy's heartbeat reports queued for her home while she does not exist
|
|
1234
|
+
* are dropped, never posted once she is created (issue #243); so is the
|
|
1235
|
+
* omission notice when nothing else waits (the rows it counts were these
|
|
1236
|
+
* reports too). Returns what is left to post. */
|
|
1237
|
+
async function dropHeartbeatReports(homeId, items, omitted) {
|
|
1238
|
+
const kept = items.filter((item) => !isHeartbeatReport(item));
|
|
1239
|
+
const retired = kept.length ? null : omitted;
|
|
1240
|
+
log(`${items.length - kept.length} heartbeat report(s) for ${homeId} are dropped: Mixy was never created`);
|
|
1241
|
+
await queue.remove(
|
|
1242
|
+
homeId,
|
|
1243
|
+
items.filter(isHeartbeatReport).map((item) => item.runId),
|
|
1244
|
+
retired
|
|
1245
|
+
);
|
|
1246
|
+
return { items: kept, omitted: retired ? null : omitted };
|
|
906
1247
|
}
|
|
907
1248
|
|
|
908
|
-
/**
|
|
909
|
-
*
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
await
|
|
915
|
-
return true;
|
|
1249
|
+
/** Post every queued result of `homeId` if its thread is idle, in its
|
|
1250
|
+
* posting turn, then wake the agent for what it owes; else retry later.
|
|
1251
|
+
* The wake runs outside the posting turn: the turn it starts posts first
|
|
1252
|
+
* (postBeforeTurn). */
|
|
1253
|
+
async function drainOnce(homeId) {
|
|
1254
|
+
const session = await postingFor(homeId, () => postIdle(homeId));
|
|
1255
|
+
if (session) await relay.wakeOwed(homeId, session);
|
|
916
1256
|
}
|
|
917
1257
|
|
|
918
|
-
/**
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1258
|
+
/** drainOnce's posting: the thread it posted to; nothing when nothing is
|
|
1259
|
+
* owed or the pass ends here (moved, busy, missing, dropped). */
|
|
1260
|
+
async function postIdle(homeId) {
|
|
1261
|
+
let items = await queue.items(homeId);
|
|
1262
|
+
let omitted = await queue.omitted(homeId);
|
|
922
1263
|
const owed = owesWake(await queue.wakes(homeId));
|
|
923
1264
|
if (!items.length && !omitted && !owed) return;
|
|
924
1265
|
if (roomIdOfSession(homeId)) return dropRoomLogQueue(homeId, items, omitted);
|
|
1266
|
+
if (homeId === MIXY_HOME && items.some(isHeartbeatReport) && !(await mixyExists())) {
|
|
1267
|
+
({ items, omitted } = await dropHeartbeatReports(homeId, items, omitted));
|
|
1268
|
+
if (!items.length && !omitted && !owed) return;
|
|
1269
|
+
}
|
|
925
1270
|
const agentId = agentIdOfHomeSession(homeId);
|
|
926
1271
|
if (agentId && agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted') {
|
|
927
1272
|
// The owner is gone (its home archived): Mixy, the default owner, gets them.
|
|
@@ -933,7 +1278,7 @@ export function createAgentHomeDelivery({
|
|
|
933
1278
|
return;
|
|
934
1279
|
}
|
|
935
1280
|
if (busy(homeId)) {
|
|
936
|
-
|
|
1281
|
+
heldLive.show(homeId, items);
|
|
937
1282
|
return later(homeId, busyRetryMs);
|
|
938
1283
|
}
|
|
939
1284
|
// From here to the push nothing awaits, so no turn can start in between.
|
|
@@ -948,16 +1293,7 @@ export function createAgentHomeDelivery({
|
|
|
948
1293
|
}
|
|
949
1294
|
return later(homeId, missingRetryMs);
|
|
950
1295
|
}
|
|
951
|
-
|
|
952
|
-
const { fresh, toWake } = postQueuedRows(session, items, omitted);
|
|
953
|
-
for (const item of items) shownLive.delete(messageIdOf(item.runId));
|
|
954
|
-
if (fresh.length) {
|
|
955
|
-
session.updatedAt = Date.now();
|
|
956
|
-
await saveSession(session);
|
|
957
|
-
for (const { message, notice } of fresh) {
|
|
958
|
-
noticeLive(live, log, homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
959
|
-
}
|
|
960
|
-
}
|
|
1296
|
+
const toWake = await postRows(homeId, session, items, omitted);
|
|
961
1297
|
// Dequeuing and owing the wake are one write: no restart between posting
|
|
962
1298
|
// and waking loses the wake.
|
|
963
1299
|
const { overflowed } = await queue.remove(
|
|
@@ -967,7 +1303,7 @@ export function createAgentHomeDelivery({
|
|
|
967
1303
|
{ wake: toWake }
|
|
968
1304
|
);
|
|
969
1305
|
if (overflowed) log(`${homeId} is at its wake cap: ${overflowed} result(s) ride the next wake as a count`);
|
|
970
|
-
|
|
1306
|
+
return session;
|
|
971
1307
|
}
|
|
972
1308
|
|
|
973
1309
|
/** The single drainer of `homeId`; a call during a pass runs one more pass. */
|
|
@@ -994,59 +1330,6 @@ export function createAgentHomeDelivery({
|
|
|
994
1330
|
return pass;
|
|
995
1331
|
}
|
|
996
1332
|
|
|
997
|
-
/** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
|
|
998
|
-
* or webhook `row` for its owner's home, then start draining it. Resolves
|
|
999
|
-
* once it is queued. */
|
|
1000
|
-
async function enqueue(row, result) {
|
|
1001
|
-
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
1002
|
-
if (!result?.runId) throw new Error('routine result: runId required');
|
|
1003
|
-
await queue.add(homeId, routineResultItem(row, result));
|
|
1004
|
-
void drain(homeId);
|
|
1005
|
-
return homeId;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
/** Persist one held-request row (`held`: normalizeHeldRequest fields) for
|
|
1009
|
-
* `agentId`'s home thread, then start draining it. Each status posts once. */
|
|
1010
|
-
async function enqueueHeld(agentId, held) {
|
|
1011
|
-
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
1012
|
-
if (!held?.id) throw new Error('held request: id required');
|
|
1013
|
-
await queue.add(homeId, {
|
|
1014
|
-
runId: `${held.id}_${held.status || 'pending'}`,
|
|
1015
|
-
kind: 'held-request',
|
|
1016
|
-
held,
|
|
1017
|
-
at: Date.now(),
|
|
1018
|
-
});
|
|
1019
|
-
void drain(homeId);
|
|
1020
|
-
return homeId;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/** Persist one report of a coding task session `agentId` created (session
|
|
1024
|
-
* create) for its home thread, then start draining it. `status` 'started'
|
|
1025
|
-
* and 'blocked' only post a line; a turn's end also wakes the agent. */
|
|
1026
|
-
async function enqueueTask(agentId, report) {
|
|
1027
|
-
const { runId, taskSessionId, watchId = '', watchIds = [], from = '' } = report;
|
|
1028
|
-
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
1029
|
-
if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
|
|
1030
|
-
const item = taskReportItem(report);
|
|
1031
|
-
const unwatch = [...new Set([watchId, ...watchIds].filter(Boolean).map(String))];
|
|
1032
|
-
// A room member's own work reports to its member session, never its home.
|
|
1033
|
-
const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
|
|
1034
|
-
await queue.add(target, item, { unwatch });
|
|
1035
|
-
void drain(target);
|
|
1036
|
-
return target;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
/** Persist `text`, the agent's own reply from turn `runId` it ran outside
|
|
1040
|
-
* its thread, for `agentId`'s home thread, then start draining it. It posts
|
|
1041
|
-
* once, as the agent's message; no turn follows. */
|
|
1042
|
-
async function enqueueMessage(agentId, { runId, text }) {
|
|
1043
|
-
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
1044
|
-
if (!runId || !String(text || '').trim()) throw new Error('agent message: runId and text required');
|
|
1045
|
-
await queue.add(homeId, { runId: String(runId), kind: 'agent-message', text: String(text).trim(), at: Date.now() });
|
|
1046
|
-
void drain(homeId);
|
|
1047
|
-
return homeId;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
1333
|
/** Show `run`'s live-only row (routineRunNotice: `running`, or a result whose
|
|
1051
1334
|
* delivery failed) in `row`'s owner home when it is open and idle. Nothing
|
|
1052
1335
|
* is stored and no turn or unread mark follows; the posted result (same
|
|
@@ -1058,43 +1341,11 @@ export function createAgentHomeDelivery({
|
|
|
1058
1341
|
return noticeLive(live, log, homeId, notice) === true;
|
|
1059
1342
|
}
|
|
1060
1343
|
|
|
1061
|
-
const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
|
|
1062
|
-
|
|
1063
|
-
/** Retire the bookkeeping of deleted homes (and stale round counts). */
|
|
1064
|
-
async function prune() {
|
|
1065
|
-
await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
/** Store `watch`; a home at its watch cap first gives up its oldest watches
|
|
1069
|
-
* whose target sessions are proven deleted, and refuses the watch (so the
|
|
1070
|
-
* send claims no report) when that frees too few. */
|
|
1071
|
-
async function watchTask(watch) {
|
|
1072
|
-
const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
|
|
1073
|
-
if (drop.length) {
|
|
1074
|
-
log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
|
|
1075
|
-
}
|
|
1076
|
-
await queue.watch(watch, { drop });
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
/** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
|
|
1080
|
-
async function recover() {
|
|
1081
|
-
try {
|
|
1082
|
-
await prune();
|
|
1083
|
-
} catch (error) {
|
|
1084
|
-
log(`pruning the routine results failed: ${error?.message || error}`);
|
|
1085
|
-
}
|
|
1086
|
-
const homes = await queue.pendingHomes();
|
|
1087
|
-
for (const homeId of homes) void drain(homeId);
|
|
1088
|
-
return homes;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
1344
|
return {
|
|
1092
|
-
|
|
1093
|
-
enqueueHeld,
|
|
1094
|
-
enqueueTask,
|
|
1095
|
-
enqueueMessage,
|
|
1345
|
+
...intake,
|
|
1096
1346
|
announce,
|
|
1097
1347
|
drain,
|
|
1348
|
+
postBeforeTurn,
|
|
1098
1349
|
recover,
|
|
1099
1350
|
watchTask,
|
|
1100
1351
|
unwatchTask: (runId) => queue.unwatch(runId),
|
|
@@ -1136,6 +1387,9 @@ async function processDelivery() {
|
|
|
1136
1387
|
// A home chat exists only for bots; only a definition read successfully
|
|
1137
1388
|
// can prove an agent is none (readHomeAgent).
|
|
1138
1389
|
homeAgent: (agentId) => readHomeAgent({ dataDir: getPluginData(), shippedRoot: mixdogRoot(), id: agentId }),
|
|
1390
|
+
// The one rule (mixy-presence.mjs; routine-owner.mjs imports this
|
|
1391
|
+
// module, hence late).
|
|
1392
|
+
mixyExists: async () => (await import('./routine-owner.mjs')).readMixyExists(),
|
|
1139
1393
|
// Deleting a session unlinks its record (session-delete.mjs) and leaves
|
|
1140
1394
|
// no tombstone: only an absent file with no in-memory copy proves it.
|
|
1141
1395
|
// An unreadable file probes 'unreadable' and stays.
|
|
@@ -1202,6 +1456,15 @@ export async function agentTaskWatches() {
|
|
|
1202
1456
|
return (await processDelivery()).taskWatches();
|
|
1203
1457
|
}
|
|
1204
1458
|
|
|
1459
|
+
/** The session turn prelude (session-turn-prelude.mjs) of the daemon: before a
|
|
1460
|
+
* turn of an agent's home thread or a room member session starts, its queued
|
|
1461
|
+
* rows that wake nobody post first (postBeforeTurn). Null for any other
|
|
1462
|
+
* session: nothing to wait for. */
|
|
1463
|
+
export function postBeforeAgentTurn(sessionId) {
|
|
1464
|
+
if (!isAgentHomeSessionId(sessionId) && !roomMemberOfSession(sessionId)) return null;
|
|
1465
|
+
return processDelivery().then((delivery) => delivery.postBeforeTurn(sessionId));
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1205
1468
|
/** Daemon start: post the results an earlier process left queued. */
|
|
1206
1469
|
export async function recoverScheduleResults() {
|
|
1207
1470
|
return (await processDelivery()).recover();
|