mixdog 1.3.1 → 1.3.2
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/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +5 -1
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/store.mjs +49 -30
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +5 -2
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +4 -0
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +6 -0
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +44 -2
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +41 -42
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +555 -199
- package/src/session-runtime/agent-room-delivery.mjs +143 -36
- package/src/session-runtime/agent-task-sessions.mjs +667 -165
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +45 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +2 -2
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +36 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +14 -4
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +8 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +47 -369
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +51 -10
- package/src/standalone/daemon.mjs +28 -10
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +529 -333
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
|
@@ -17,10 +17,21 @@
|
|
|
17
17
|
* re-drains every queue on start, and
|
|
18
18
|
* registers the live surface so a thread open in a window shows the notice at
|
|
19
19
|
* once; a cold thread picks it up from its stored record.
|
|
20
|
+
*
|
|
21
|
+
* A group-room member's task reports (enqueueTask `from` its member session)
|
|
22
|
+
* go to the room instead: each line lands in the member's own session (never
|
|
23
|
+
* waking it: the room host drives its turns), and a blocked or ended turn
|
|
24
|
+
* also lands in the room's log as that member's speech row
|
|
25
|
+
* (agent-room-delivery.mjs appendTaskReports), where the room's next exchange
|
|
26
|
+
* sees it. Both ride this same durable queue, keyed by those sessions.
|
|
20
27
|
*/
|
|
21
28
|
import { randomUUID } from 'node:crypto';
|
|
22
|
-
import {
|
|
29
|
+
import { readFileSync } from 'node:fs';
|
|
23
30
|
import { join } from 'node:path';
|
|
31
|
+
import { readAgentDefinitionKind } from '../runtime/shared/agent-kind.mjs';
|
|
32
|
+
import { BOT_AGENT_KIND, WORKFLOW_AGENT_KIND, isContactAgentId } from '../runtime/shared/agent-route-config.mjs';
|
|
33
|
+
import { botStoreActive, isStoreBot } from '../runtime/shared/agent-store.mjs';
|
|
34
|
+
import { readMarkdownDocument } from '../runtime/shared/markdown-frontmatter.mjs';
|
|
24
35
|
import {
|
|
25
36
|
MIXY_AGENT_ID,
|
|
26
37
|
agentHomeSessionId,
|
|
@@ -31,20 +42,23 @@ import {
|
|
|
31
42
|
isRoomThreadSessionId,
|
|
32
43
|
roomIdOfSession,
|
|
33
44
|
roomMemberOfSession,
|
|
45
|
+
roomSessionId,
|
|
34
46
|
} from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
35
47
|
import {
|
|
36
48
|
TASK_LOOP_CAP,
|
|
37
49
|
agentTaskMessage,
|
|
50
|
+
agentTaskRelayFailedMessage,
|
|
38
51
|
agentTaskWakePrompt,
|
|
52
|
+
boundedReply,
|
|
39
53
|
routineOmittedMessage,
|
|
40
54
|
routineResultMessage,
|
|
41
55
|
routineResultNotice,
|
|
42
56
|
routineRunNotice,
|
|
43
57
|
routineSummary,
|
|
44
58
|
} from '../runtime/agent/orchestrator/session/routine-result.mjs';
|
|
45
|
-
import {
|
|
59
|
+
import { readJsonState, updateJsonAtomic } from '../runtime/shared/atomic-file.mjs';
|
|
46
60
|
import { ROUTINE_LIMITS } from '../runtime/shared/routine-limits.mjs';
|
|
47
|
-
import { submissionIdsOf } from './agent-task-sessions.mjs';
|
|
61
|
+
import { submissionIdsOf, submissionOutcome } from './agent-task-sessions.mjs';
|
|
48
62
|
import {
|
|
49
63
|
heldRequestMessage,
|
|
50
64
|
heldRequestNotice,
|
|
@@ -71,6 +85,38 @@ function deliveredRow(item, messageId) {
|
|
|
71
85
|
return { message, notice: routineResultNotice(message) };
|
|
72
86
|
}
|
|
73
87
|
|
|
88
|
+
/** Whether agent `id` can have a home chat, decided only from a definition
|
|
89
|
+
* read successfully: 'bot' (a store bot, or one the kind rule calls a bot),
|
|
90
|
+
* 'none' (read, and a workflow agent: an explicit non-bot `kind`, a shipped
|
|
91
|
+
* role, or an unmarked agents/ copy once bots have their own store), else
|
|
92
|
+
* 'unknown' — an unreadable file, an unparsable one, or an unmarked user
|
|
93
|
+
* copy without bot evidence never proves the agent is no bot. */
|
|
94
|
+
export function readHomeAgent({ dataDir, shippedRoot, id, read = (path) => readFileSync(path, 'utf8') }) {
|
|
95
|
+
if (!dataDir || !id) return 'unknown';
|
|
96
|
+
if (isStoreBot(dataDir, id)) return 'bot';
|
|
97
|
+
const readOrAbsent = (path) => {
|
|
98
|
+
try {
|
|
99
|
+
return { text: read(path) };
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR' ? { absent: true } : { failed: true };
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const user = readOrAbsent(join(dataDir, 'agents', id, 'AGENT.md'));
|
|
105
|
+
if (user.failed) return 'unknown';
|
|
106
|
+
const shipped = shippedRoot ? readOrAbsent(join(shippedRoot, 'agents', id, 'AGENT.md')) : { absent: true };
|
|
107
|
+
const own = user.text ?? (shipped.failed ? null : shipped.text);
|
|
108
|
+
if (typeof own !== 'string') return 'unknown';
|
|
109
|
+
const kind = String(readMarkdownDocument(own).frontmatter?.kind || '')
|
|
110
|
+
.trim()
|
|
111
|
+
.toLowerCase();
|
|
112
|
+
if (kind) {
|
|
113
|
+
if (isContactAgentId(id, kind)) return 'bot';
|
|
114
|
+
return kind === WORKFLOW_AGENT_KIND ? 'none' : 'unknown';
|
|
115
|
+
}
|
|
116
|
+
if (user.text === undefined || typeof shipped.text === 'string' || botStoreActive(dataDir)) return 'none';
|
|
117
|
+
return readAgentDefinitionKind({ dataDir, shippedRoot, id }) === BOT_AGENT_KIND ? 'bot' : 'unknown';
|
|
118
|
+
}
|
|
119
|
+
|
|
74
120
|
/** The agent a schedule belongs to; ownerless rows belong to Mixy. */
|
|
75
121
|
export function scheduleOwnerAgentId(schedule) {
|
|
76
122
|
const owner = typeof schedule?.ownerAgentId === 'string' ? schedule.ownerAgentId.trim() : '';
|
|
@@ -81,7 +127,8 @@ let liveSurface = null;
|
|
|
81
127
|
/** `{ busy(sessionId), notice(sessionId, notice), wake(sessionId, prompt, { id }),
|
|
82
128
|
* submit(sessionId, prompt, options), abort(sessionId) }` from the session
|
|
83
129
|
* service; `wake` starts one hidden turn with submission `id` (resolves
|
|
84
|
-
* false when it was refused
|
|
130
|
+
* false when it was refused, else `{ settled }`: a promise of the turn's end
|
|
131
|
+
* status, 'done' when it finished), `submit` queues a routine turn
|
|
85
132
|
* (resolves whether it was accepted), `abort` stops the running turn. */
|
|
86
133
|
export function setAgentHomeLiveSurface(surface) {
|
|
87
134
|
liveSurface = surface || null;
|
|
@@ -100,6 +147,12 @@ export const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
|
100
147
|
// Per home: round-trip targets counted (least recently counted drop first),
|
|
101
148
|
// reporting turns watched at once, and posted results owed a wake.
|
|
102
149
|
export const QUEUE_CAPS = Object.freeze({ roundTargets: 100, watches: 200, wakeRunIds: 100 });
|
|
150
|
+
// A task report's output kept for its room row (newlines kept).
|
|
151
|
+
const ROOM_REPORT_TEXT_LIMIT = 4000;
|
|
152
|
+
// Waits before the 2nd, 3rd and 4th try of a relay (wake) turn that did not finish.
|
|
153
|
+
export const WAKE_RETRY_DELAYS_MS = Object.freeze([30_000, 120_000, 600_000]);
|
|
154
|
+
// Refused wake submissions of one batch, a minute apart, before it is given up.
|
|
155
|
+
export const WAKE_REFUSAL_LIMIT = 10;
|
|
103
156
|
|
|
104
157
|
/** `homeId`'s wake obligations in `wakes`: posted task results not in a batch
|
|
105
158
|
* yet (`owed`), those past the cap collapsed into one marker (`overflow`:
|
|
@@ -127,6 +180,83 @@ function withWakeState(doc, homeId, { owed, batches, overflow }) {
|
|
|
127
180
|
return { ...doc, wakes };
|
|
128
181
|
}
|
|
129
182
|
|
|
183
|
+
/** `own`'s per-target counts (`{ n, at }`) used within ROUND_TTL_MS of
|
|
184
|
+
* `nowMs`, the `cap` most recently counted. */
|
|
185
|
+
function freshRoundCounts(own, nowMs, cap) {
|
|
186
|
+
return Object.fromEntries(
|
|
187
|
+
Object.entries(own && typeof own === 'object' ? own : {})
|
|
188
|
+
.filter(([, count]) => count && typeof count === 'object' && nowMs - (Number(count.at) || 0) < ROUND_TTL_MS)
|
|
189
|
+
.sort(([, a], [, b]) => (Number(b.at) || 0) - (Number(a.at) || 0))
|
|
190
|
+
.slice(0, cap)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** `doc` with `items` appended to `homeId`'s list, minus run ids it holds or
|
|
195
|
+
* posted; past `limit` the oldest drop, counted under `omitted`. Null when
|
|
196
|
+
* nothing changes. */
|
|
197
|
+
function appendedItems(doc, homeId, items, limit, omittedCount) {
|
|
198
|
+
const list = doc.homes[homeId] || [];
|
|
199
|
+
const seen = new Set([...list.map((row) => row.runId), ...(doc.delivered[homeId] || [])]);
|
|
200
|
+
const fresh = items.filter((item) => !seen.has(item.runId) && seen.add(item.runId));
|
|
201
|
+
if (!fresh.length && !omittedCount) return null;
|
|
202
|
+
const next = [...list, ...fresh];
|
|
203
|
+
const drop = Math.max(0, next.length - limit);
|
|
204
|
+
const omitted = { ...doc.omitted };
|
|
205
|
+
const count = drop + omittedCount;
|
|
206
|
+
if (count) {
|
|
207
|
+
const previous = omitted[homeId];
|
|
208
|
+
omitted[homeId] = { id: previous?.id || `omit_${next[0].runId}`, count: (previous?.count || 0) + count };
|
|
209
|
+
}
|
|
210
|
+
return { ...doc, homes: { ...doc.homes, [homeId]: next.slice(drop) }, omitted };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** `doc.wakes` with the run ids `wake` owed a wake by `homeId`, at most
|
|
214
|
+
* `cap` per home: past it the oldest owed collapse into the overflow marker
|
|
215
|
+
* (`overflowed` counts them). Stored batches never change. */
|
|
216
|
+
function owedWakes(doc, homeId, wake, cap) {
|
|
217
|
+
const state = wakeState(doc.wakes, homeId);
|
|
218
|
+
const batched = state.batches.flatMap((batch) => batch.runIds);
|
|
219
|
+
const known = new Set([
|
|
220
|
+
...state.owed,
|
|
221
|
+
...batched,
|
|
222
|
+
...[state.overflow?.oldest, state.overflow?.newest].filter(Boolean),
|
|
223
|
+
]);
|
|
224
|
+
const added = wake.filter((runId) => !known.has(runId));
|
|
225
|
+
if (!added.length) return { wakes: doc.wakes, overflowed: 0 };
|
|
226
|
+
const owed = [...state.owed, ...added];
|
|
227
|
+
const room = Math.max(0, cap - batched.length);
|
|
228
|
+
const excess = owed.slice(0, Math.max(0, owed.length - room));
|
|
229
|
+
const overflow = excess.length
|
|
230
|
+
? {
|
|
231
|
+
count: (state.overflow?.count || 0) + excess.length,
|
|
232
|
+
oldest: state.overflow?.oldest || excess[0],
|
|
233
|
+
newest: excess.at(-1),
|
|
234
|
+
}
|
|
235
|
+
: state.overflow;
|
|
236
|
+
const { wakes } = withWakeState(doc, homeId, { ...state, owed: owed.slice(excess.length), overflow });
|
|
237
|
+
return { wakes, overflowed: excess.length };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** `doc` with the posted `runIds` of `homeId` dequeued into its posted
|
|
241
|
+
* ledger, and the posted omission notice `omitted` retired. */
|
|
242
|
+
function withDequeued(doc, homeId, runIds, omitted) {
|
|
243
|
+
const homes = { ...doc.homes };
|
|
244
|
+
const rest = (homes[homeId] || []).filter((row) => !runIds.includes(row.runId));
|
|
245
|
+
if (rest.length) homes[homeId] = rest;
|
|
246
|
+
else delete homes[homeId];
|
|
247
|
+
const delivered = { ...doc.delivered };
|
|
248
|
+
if (runIds.length) delivered[homeId] = [...(delivered[homeId] || []), ...runIds].slice(-DELIVERED_LEDGER);
|
|
249
|
+
const omittedNow = { ...doc.omitted };
|
|
250
|
+
const current = omittedNow[homeId];
|
|
251
|
+
if (omitted && current?.id === omitted.id) {
|
|
252
|
+
// More dropped while this pass posted: they get a notice of their own.
|
|
253
|
+
const left = current.count - omitted.count;
|
|
254
|
+
if (left > 0) omittedNow[homeId] = { id: `${current.id}.${omitted.count}`, count: left };
|
|
255
|
+
else delete omittedNow[homeId];
|
|
256
|
+
}
|
|
257
|
+
return { ...doc, homes, omitted: omittedNow, delivered };
|
|
258
|
+
}
|
|
259
|
+
|
|
130
260
|
/** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
|
|
131
261
|
* omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
|
|
132
262
|
* runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
|
|
@@ -136,15 +266,7 @@ export function createRoutineResultQueue(
|
|
|
136
266
|
{ limit = ROUTINE_LIMITS.homeQueueLimit, now = Date.now, caps = QUEUE_CAPS } = {}
|
|
137
267
|
) {
|
|
138
268
|
const section = (doc, key) => (doc && typeof doc[key] === 'object' && doc[key]) || {};
|
|
139
|
-
|
|
140
|
-
* `caps.roundTargets` most recently counted. */
|
|
141
|
-
const freshRounds = (own) =>
|
|
142
|
-
Object.fromEntries(
|
|
143
|
-
Object.entries(own && typeof own === 'object' ? own : {})
|
|
144
|
-
.filter(([, count]) => count && typeof count === 'object' && now() - (Number(count.at) || 0) < ROUND_TTL_MS)
|
|
145
|
-
.sort(([, a], [, b]) => (Number(b.at) || 0) - (Number(a.at) || 0))
|
|
146
|
-
.slice(0, caps.roundTargets)
|
|
147
|
-
);
|
|
269
|
+
const freshRounds = (own) => freshRoundCounts(own, now(), caps.roundTargets);
|
|
148
270
|
const tidyRounds = (rounds) =>
|
|
149
271
|
Object.fromEntries(
|
|
150
272
|
Object.entries(rounds || {})
|
|
@@ -162,39 +284,26 @@ export function createRoutineResultQueue(
|
|
|
162
284
|
// Posted task results whose wake turn was not taken yet, per home.
|
|
163
285
|
wakes: section(doc, 'wakes'),
|
|
164
286
|
});
|
|
287
|
+
// A damaged queue file (empty, NUL-filled after a crash, malformed) is kept
|
|
288
|
+
// aside and its last good copy restored (readJsonState); it never blocks
|
|
289
|
+
// delivery. I/O errors still throw.
|
|
165
290
|
async function read() {
|
|
166
|
-
|
|
167
|
-
return parts(parseJsonText(await readFile(filePath, 'utf8')));
|
|
168
|
-
} catch (error) {
|
|
169
|
-
if (error?.code === 'ENOENT') return parts(null);
|
|
170
|
-
throw error;
|
|
171
|
-
}
|
|
291
|
+
return parts(await readJsonState(filePath));
|
|
172
292
|
}
|
|
173
293
|
/** Read-modify-write under the file lock (`mutator(doc)` returns the next
|
|
174
294
|
* doc, or nothing to keep the file); every write also retires stale and
|
|
175
295
|
* surplus round counts. */
|
|
176
296
|
function update(mutator) {
|
|
177
|
-
return updateJsonAtomic(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const seen = new Set([...list.map((row) => row.runId), ...(doc.delivered[homeId] || [])]);
|
|
186
|
-
const fresh = items.filter((item) => !seen.has(item.runId) && seen.add(item.runId));
|
|
187
|
-
if (!fresh.length && !omittedCount) return null;
|
|
188
|
-
const next = [...list, ...fresh];
|
|
189
|
-
const drop = Math.max(0, next.length - limit);
|
|
190
|
-
const omitted = { ...doc.omitted };
|
|
191
|
-
const count = drop + omittedCount;
|
|
192
|
-
if (count) {
|
|
193
|
-
const previous = omitted[homeId];
|
|
194
|
-
omitted[homeId] = { id: previous?.id || `omit_${next[0].runId}`, count: (previous?.count || 0) + count };
|
|
195
|
-
}
|
|
196
|
-
return { ...doc, homes: { ...doc.homes, [homeId]: next.slice(drop) }, omitted };
|
|
297
|
+
return updateJsonAtomic(
|
|
298
|
+
filePath,
|
|
299
|
+
(raw) => {
|
|
300
|
+
const next = mutator(parts(raw));
|
|
301
|
+
return next ? { ...next, rounds: tidyRounds(next.rounds) } : undefined;
|
|
302
|
+
},
|
|
303
|
+
{ backup: true }
|
|
304
|
+
);
|
|
197
305
|
}
|
|
306
|
+
const appended = (doc, homeId, items, omittedCount = 0) => appendedItems(doc, homeId, items, limit, omittedCount);
|
|
198
307
|
return {
|
|
199
308
|
async pendingHomes() {
|
|
200
309
|
const { homes, omitted, wakes } = await read();
|
|
@@ -237,6 +346,31 @@ export function createRoutineResultQueue(
|
|
|
237
346
|
return withWakeState(doc, homeId, { ...state, batches });
|
|
238
347
|
});
|
|
239
348
|
},
|
|
349
|
+
/** Batch `batchId`'s relay turn did not finish: it is owed again under a
|
|
350
|
+
* fresh wake id (the old one names the failed turn), as try `tries`, not
|
|
351
|
+
* before `notBefore`. Returns the new batch; null when it is gone. */
|
|
352
|
+
async rewake(homeId, batchId, { tries, notBefore }) {
|
|
353
|
+
let next = null;
|
|
354
|
+
await update((doc) => {
|
|
355
|
+
next = null;
|
|
356
|
+
const state = wakeState(doc.wakes, homeId);
|
|
357
|
+
const index = state.batches.findIndex((batch) => batch.id === batchId);
|
|
358
|
+
if (index < 0) return undefined;
|
|
359
|
+
next = { ...state.batches[index], id: `awake_${randomUUID()}`, tries, notBefore };
|
|
360
|
+
const batches = state.batches.map((batch, i) => (i === index ? next : batch));
|
|
361
|
+
return withWakeState(doc, homeId, { ...state, batches });
|
|
362
|
+
});
|
|
363
|
+
return next;
|
|
364
|
+
},
|
|
365
|
+
/** Store `fields` on batch `batchId` (same wake id). */
|
|
366
|
+
async markBatch(homeId, batchId, fields) {
|
|
367
|
+
await update((doc) => {
|
|
368
|
+
const state = wakeState(doc.wakes, homeId);
|
|
369
|
+
if (!state.batches.some((batch) => batch.id === batchId)) return undefined;
|
|
370
|
+
const batches = state.batches.map((batch) => (batch.id === batchId ? { ...batch, ...fields } : batch));
|
|
371
|
+
return withWakeState(doc, homeId, { ...state, batches });
|
|
372
|
+
});
|
|
373
|
+
},
|
|
240
374
|
/** Drop every wake obligation of `homeId` (its owner is gone). */
|
|
241
375
|
async dropWakes(homeId) {
|
|
242
376
|
await update((doc) =>
|
|
@@ -314,7 +448,9 @@ export function createRoutineResultQueue(
|
|
|
314
448
|
(kept) => kept?.homeSessionId === watch.homeSessionId && kept.runId !== watch.runId
|
|
315
449
|
);
|
|
316
450
|
if (own.length >= caps.watches) {
|
|
317
|
-
throw new Error(`${watch.homeSessionId} already waits on ${own.length} session reports`)
|
|
451
|
+
throw Object.assign(new Error(`${watch.homeSessionId} already waits on ${own.length} session reports`), {
|
|
452
|
+
code: 'REPORT_WATCH_CAP',
|
|
453
|
+
});
|
|
318
454
|
}
|
|
319
455
|
return { ...doc, watches: { ...watches, [watch.runId]: watch } };
|
|
320
456
|
});
|
|
@@ -363,45 +499,9 @@ export function createRoutineResultQueue(
|
|
|
363
499
|
async remove(homeId, runIds, omitted = null, { wake = [] } = {}) {
|
|
364
500
|
let overflowed = 0;
|
|
365
501
|
await update((doc) => {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const known = new Set([
|
|
370
|
-
...state.owed,
|
|
371
|
-
...batched,
|
|
372
|
-
...[state.overflow?.oldest, state.overflow?.newest].filter(Boolean),
|
|
373
|
-
]);
|
|
374
|
-
const added = wake.filter((runId) => !known.has(runId));
|
|
375
|
-
let wakes = doc.wakes;
|
|
376
|
-
if (added.length) {
|
|
377
|
-
const owed = [...state.owed, ...added];
|
|
378
|
-
const room = Math.max(0, caps.wakeRunIds - batched.length);
|
|
379
|
-
const excess = owed.slice(0, Math.max(0, owed.length - room));
|
|
380
|
-
overflowed = excess.length;
|
|
381
|
-
const overflow = excess.length
|
|
382
|
-
? {
|
|
383
|
-
count: (state.overflow?.count || 0) + excess.length,
|
|
384
|
-
oldest: state.overflow?.oldest || excess[0],
|
|
385
|
-
newest: excess.at(-1),
|
|
386
|
-
}
|
|
387
|
-
: state.overflow;
|
|
388
|
-
({ wakes } = withWakeState(doc, homeId, { ...state, owed: owed.slice(excess.length), overflow }));
|
|
389
|
-
}
|
|
390
|
-
const homes = { ...doc.homes };
|
|
391
|
-
const rest = (homes[homeId] || []).filter((row) => !runIds.includes(row.runId));
|
|
392
|
-
if (rest.length) homes[homeId] = rest;
|
|
393
|
-
else delete homes[homeId];
|
|
394
|
-
const delivered = { ...doc.delivered };
|
|
395
|
-
if (runIds.length) delivered[homeId] = [...(delivered[homeId] || []), ...runIds].slice(-DELIVERED_LEDGER);
|
|
396
|
-
const omittedNow = { ...doc.omitted };
|
|
397
|
-
const current = omittedNow[homeId];
|
|
398
|
-
if (omitted && current?.id === omitted.id) {
|
|
399
|
-
// More dropped while this pass posted: they get a notice of their own.
|
|
400
|
-
const left = current.count - omitted.count;
|
|
401
|
-
if (left > 0) omittedNow[homeId] = { id: `${current.id}.${omitted.count}`, count: left };
|
|
402
|
-
else delete omittedNow[homeId];
|
|
403
|
-
}
|
|
404
|
-
return { ...doc, homes, omitted: omittedNow, delivered, wakes };
|
|
502
|
+
let wakes;
|
|
503
|
+
({ wakes, overflowed } = owedWakes(doc, homeId, wake, caps.wakeRunIds));
|
|
504
|
+
return { ...withDequeued(doc, homeId, runIds, omitted), wakes };
|
|
405
505
|
});
|
|
406
506
|
return { overflowed };
|
|
407
507
|
},
|
|
@@ -410,6 +510,281 @@ export function createRoutineResultQueue(
|
|
|
410
510
|
|
|
411
511
|
const messageIdOf = (runId) => `routine_${runId}`;
|
|
412
512
|
|
|
513
|
+
/** Show `notice` live in `homeId` through the `live()` surface: its answer,
|
|
514
|
+
* or false (logged) when the surface threw. */
|
|
515
|
+
function noticeLive(live, log, homeId, notice) {
|
|
516
|
+
try {
|
|
517
|
+
return live()?.notice?.(homeId, notice);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Dequeue every queued item (and the omission) of `homeId` unposted, and
|
|
525
|
+
* its wake obligations. */
|
|
526
|
+
async function dropQueued(queue, homeId, items, omitted) {
|
|
527
|
+
await queue.remove(
|
|
528
|
+
homeId,
|
|
529
|
+
items.map((item) => item.runId),
|
|
530
|
+
omitted
|
|
531
|
+
);
|
|
532
|
+
await queue.dropWakes(homeId);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** The queued item of `result` ({ runId, kind, ok, text, runSessionId }) of
|
|
536
|
+
* a schedule or webhook `row` (enqueue). */
|
|
537
|
+
function routineResultItem(row, result) {
|
|
538
|
+
return {
|
|
539
|
+
runId: String(result.runId),
|
|
540
|
+
kind: result.kind === 'webhook' ? 'webhook' : 'schedule',
|
|
541
|
+
name: row.name,
|
|
542
|
+
ok: result.ok === true,
|
|
543
|
+
text: routineSummary(result.text),
|
|
544
|
+
runSessionId: result.runSessionId || null,
|
|
545
|
+
...(result.reason ? { reason: String(result.reason) } : {}),
|
|
546
|
+
...(result.paused === true ? { paused: true } : {}),
|
|
547
|
+
at: Date.now(),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** The run ids of `watches` a home at its watch `cap` gives up before
|
|
552
|
+
* storing `watch`: its oldest whose target sessions are proven deleted. */
|
|
553
|
+
function watchesToDrop(watches, watch, cap, sessionDeleted) {
|
|
554
|
+
const own = watches.filter((kept) => kept?.homeSessionId === watch.homeSessionId && kept.runId !== watch.runId);
|
|
555
|
+
const over = own.length + 1 - cap;
|
|
556
|
+
return over > 0
|
|
557
|
+
? own
|
|
558
|
+
.filter((kept) => sessionDeleted(kept.taskSessionId))
|
|
559
|
+
.slice(0, over)
|
|
560
|
+
.map((kept) => kept.runId)
|
|
561
|
+
: [];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** A bookkept home proven deleted: a bot's home whose definition is gone,
|
|
565
|
+
* or a room thread whose room is. Never Mixy's home, never on a missing
|
|
566
|
+
* thread record, never on an unknown answer. */
|
|
567
|
+
function homeProvenDeleted(homeId, agentPresence, roomPresence) {
|
|
568
|
+
const agentId = agentIdOfHomeSession(homeId);
|
|
569
|
+
if (agentId) return agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted';
|
|
570
|
+
const roomId = roomIdOfSession(homeId) || roomMemberOfSession(homeId)?.roomId || '';
|
|
571
|
+
return Boolean(roomId) && roomPresence(roomId) === 'deleted';
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** The queued item of one task report (enqueueTask). */
|
|
575
|
+
function taskReportItem({ runId, taskSessionId, title, status, text = '', target, files = [], omitted = 0, hop = 0 }) {
|
|
576
|
+
// The model-facing reply keeps its lines up to REPLY_LIMIT; the row's
|
|
577
|
+
// one-line summary is derived from it (agentTaskMessage).
|
|
578
|
+
const reply = boundedReply(text);
|
|
579
|
+
return {
|
|
580
|
+
runId: String(runId),
|
|
581
|
+
kind: 'agent-task',
|
|
582
|
+
taskSessionId,
|
|
583
|
+
title,
|
|
584
|
+
status,
|
|
585
|
+
text: reply.text,
|
|
586
|
+
// The UI row's one line comes from the whole reply, not its bounded part.
|
|
587
|
+
summary: routineSummary(text),
|
|
588
|
+
...(reply.chars > reply.text.length ? { textChars: reply.chars } : {}),
|
|
589
|
+
// Any session other than a task session the agent created.
|
|
590
|
+
...(target === 'session' ? { target } : {}),
|
|
591
|
+
// The files the turn produced (handoff-attachments.mjs collectTurnArtifacts).
|
|
592
|
+
...(Array.isArray(files) && files.length ? { files, ...(omitted > 0 ? { omitted } : {}) } : {}),
|
|
593
|
+
roundTrip: status !== 'started' && status !== 'blocked',
|
|
594
|
+
...(hop > 0 ? { hop } : {}),
|
|
595
|
+
at: Date.now(),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** The room-log item of task report `item` by member `agentId` (`text`: the
|
|
600
|
+
* report's whole output, newlines kept). */
|
|
601
|
+
function roomReportItem(item, agentId, text) {
|
|
602
|
+
return {
|
|
603
|
+
runId: item.runId,
|
|
604
|
+
kind: 'room-report',
|
|
605
|
+
speaker: agentId,
|
|
606
|
+
taskSessionId: item.taskSessionId,
|
|
607
|
+
title: item.title,
|
|
608
|
+
status: item.status,
|
|
609
|
+
text: String(text ?? '')
|
|
610
|
+
.trim()
|
|
611
|
+
.slice(0, ROOM_REPORT_TEXT_LIMIT),
|
|
612
|
+
...(item.target ? { target: item.target } : {}),
|
|
613
|
+
...(item.files ? { files: item.files } : {}),
|
|
614
|
+
at: item.at,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Queue a room member's task report: the member's own session (`from`) gets
|
|
619
|
+
* the line without a wake; a blocked or ended turn (`toRoom`) is the
|
|
620
|
+
* member's row in the room log too. The watch drops with the last write, so
|
|
621
|
+
* a restart in between re-queues both (deduplicated). */
|
|
622
|
+
async function queueRoomTaskReport(queue, { from, roomLogId, toRoom }, item, agentId, text, unwatch) {
|
|
623
|
+
await queue.add(from, { ...item, roundTrip: false }, { unwatch: toRoom ? '' : unwatch });
|
|
624
|
+
if (toRoom) {
|
|
625
|
+
await queue.add(roomLogId, roomReportItem(item, agentId, text), { unwatch });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** Append to `session.messages` the omission notice and the queued `items`
|
|
630
|
+
* not posted before. `fresh`: the rows newly posted; `toWake`: the run ids
|
|
631
|
+
* owed a wake (every dequeued task result, posted now or before). */
|
|
632
|
+
function postQueuedRows(session, items, omitted) {
|
|
633
|
+
const posted = new Set(session.messages.map((message) => message?.meta?.transcript?.messageId).filter(Boolean));
|
|
634
|
+
const fresh = [];
|
|
635
|
+
const omittedId = omitted ? messageIdOf(omitted.id) : '';
|
|
636
|
+
if (omitted && !posted.has(omittedId)) {
|
|
637
|
+
const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
|
|
638
|
+
session.messages.push(message);
|
|
639
|
+
fresh.push({ message, notice: routineResultNotice(message) });
|
|
640
|
+
}
|
|
641
|
+
const toWake = [];
|
|
642
|
+
for (const item of items) {
|
|
643
|
+
const row = deliveredRow(item, messageIdOf(item.runId));
|
|
644
|
+
if (row.wake) toWake.push(item.runId);
|
|
645
|
+
if (posted.has(messageIdOf(item.runId))) continue;
|
|
646
|
+
session.messages.push(row.message);
|
|
647
|
+
fresh.push(row);
|
|
648
|
+
}
|
|
649
|
+
return { fresh, toWake };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** The relay of posted task results: owed results wake the agent with one
|
|
653
|
+
* hidden turn per batch. A batch is stored with its wake id BEFORE the wake
|
|
654
|
+
* is submitted and dropped once its relay turn settled done, so a restart in
|
|
655
|
+
* between re-checks that exact id; a relay turn that did not finish is owed
|
|
656
|
+
* again under a fresh id (retryRelay). Results owed later form a new batch.
|
|
657
|
+
* A replayed run id is never queued again. */
|
|
658
|
+
function createHomeRelay({
|
|
659
|
+
queue,
|
|
660
|
+
saveSession,
|
|
661
|
+
live,
|
|
662
|
+
busy,
|
|
663
|
+
later,
|
|
664
|
+
drain,
|
|
665
|
+
log,
|
|
666
|
+
wakeRetryDelaysMs,
|
|
667
|
+
wakeRefusalLimit,
|
|
668
|
+
busyRetryMs,
|
|
669
|
+
missingRetryMs,
|
|
670
|
+
}) {
|
|
671
|
+
// Wake batch id → 'running' while this process's relay turn runs, then
|
|
672
|
+
// 'done' or 'failed' once it settled (until the batch is retired).
|
|
673
|
+
const relays = new Map();
|
|
674
|
+
|
|
675
|
+
/** How batch `batchId`'s relay turn went: '' when no turn took it, else
|
|
676
|
+
* 'running', 'done' or 'failed'. A turn an earlier process took decides by
|
|
677
|
+
* its recorded end; one with none was cut off by the restart. */
|
|
678
|
+
function relayOf(batchId, messages, taken) {
|
|
679
|
+
const local = relays.get(batchId);
|
|
680
|
+
if (local) return local;
|
|
681
|
+
if (!taken.has(batchId)) return '';
|
|
682
|
+
const outcome = submissionOutcome(messages, batchId);
|
|
683
|
+
return outcome && outcome !== 'open' && outcome.status === 'completed' ? 'done' : 'failed';
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/** The relay turn of `batch` did not finish: owe it again after a backoff,
|
|
687
|
+
* or, past the last try, leave a note in the thread and wake nobody. */
|
|
688
|
+
async function retryRelay(homeId, session, batch) {
|
|
689
|
+
const tries = (Number(batch.tries) || 0) + 1;
|
|
690
|
+
if (tries <= wakeRetryDelaysMs.length) {
|
|
691
|
+
log(`the relay turn in ${homeId} did not finish (try ${tries}); waking it again later`);
|
|
692
|
+
await queue.rewake(homeId, batch.id, { tries, notBefore: Date.now() + wakeRetryDelaysMs[tries - 1] });
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
return abandonRelay(homeId, session, batch, `did not finish after ${tries} tries`, tries);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** Give up on `batch`'s relay: a note in the thread (once), nobody woken.
|
|
699
|
+
* False while the thread is busy (try again once it is idle). */
|
|
700
|
+
async function abandonRelay(homeId, session, batch, why, tries) {
|
|
701
|
+
if (busy(homeId)) return false;
|
|
702
|
+
log(`the relay turn in ${homeId} ${why}; its results stay posted unrelayed`);
|
|
703
|
+
const messageId = messageIdOf(`relay_${batch.id}`);
|
|
704
|
+
if (!session.messages.some((message) => message?.meta?.transcript?.messageId === messageId)) {
|
|
705
|
+
const message = agentTaskRelayFailedMessage({
|
|
706
|
+
count: batch.runIds.length + (batch.overflow?.count || 0),
|
|
707
|
+
tries,
|
|
708
|
+
messageId,
|
|
709
|
+
});
|
|
710
|
+
session.messages.push(message);
|
|
711
|
+
session.updatedAt = Date.now();
|
|
712
|
+
await saveSession(session);
|
|
713
|
+
noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId });
|
|
714
|
+
}
|
|
715
|
+
await queue.woke(homeId, batch.id);
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** Wake `homeId` (its idle `session`, just posted to) for its next owed batch. */
|
|
720
|
+
async function wakeOwed(homeId, session) {
|
|
721
|
+
const byId = new Map(session.messages.map((message) => [message?.meta?.transcript?.messageId, message]));
|
|
722
|
+
const taken = new Set(session.messages.flatMap((message) => submissionIdsOf(message)));
|
|
723
|
+
let batch = null;
|
|
724
|
+
let woken = [];
|
|
725
|
+
let more = 0;
|
|
726
|
+
for (;;) {
|
|
727
|
+
batch = (await queue.wakes(homeId)).batches[0] || (await queue.batchWake(homeId));
|
|
728
|
+
if (!batch) return;
|
|
729
|
+
const relay = relayOf(batch.id, session.messages, taken);
|
|
730
|
+
// Its settle drains again.
|
|
731
|
+
if (relay === 'running') return;
|
|
732
|
+
if (relay) {
|
|
733
|
+
relays.delete(batch.id);
|
|
734
|
+
if (relay === 'done') await queue.woke(homeId, batch.id);
|
|
735
|
+
else if (!(await retryRelay(homeId, session, batch))) return later(homeId, busyRetryMs);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const wait = (Number(batch.notBefore) || 0) - Date.now();
|
|
739
|
+
if (wait > 0) return later(homeId, wait);
|
|
740
|
+
woken = batch.runIds.map((runId) => byId.get(messageIdOf(runId))).filter(Boolean);
|
|
741
|
+
// Results past the wake cap ride this wake as a count of rows to read.
|
|
742
|
+
more = byId.has(messageIdOf(batch.overflow?.newest)) ? batch.overflow.count : 0;
|
|
743
|
+
if (woken.length || more) break;
|
|
744
|
+
// Its rows are gone with a thread reset.
|
|
745
|
+
await queue.woke(homeId, batch.id);
|
|
746
|
+
}
|
|
747
|
+
const surface = live();
|
|
748
|
+
if (typeof surface?.wake !== 'function') return later(homeId, busyRetryMs);
|
|
749
|
+
const batchId = batch.id;
|
|
750
|
+
let turn;
|
|
751
|
+
relays.set(batchId, 'running');
|
|
752
|
+
try {
|
|
753
|
+
// The turn descends from the bot-to-bot sends it reports: it carries their highest hop.
|
|
754
|
+
const hop = Math.max(0, ...woken.map((message) => Number(message?.meta?.routine?.hop) || 0));
|
|
755
|
+
turn = await surface.wake(homeId, agentTaskWakePrompt(woken, { more }), { id: batchId, hop });
|
|
756
|
+
if (turn === false) throw new Error('the wake turn was refused');
|
|
757
|
+
} catch (error) {
|
|
758
|
+
relays.delete(batchId);
|
|
759
|
+
// A wake refused (or failing) every time is retried a bounded number of times.
|
|
760
|
+
const refusals = (Number(batch.refusals) || 0) + 1;
|
|
761
|
+
if (refusals >= wakeRefusalLimit) {
|
|
762
|
+
if (await abandonRelay(homeId, session, batch, `could not start (${refusals} refusals)`, refusals)) return;
|
|
763
|
+
return later(homeId, busyRetryMs);
|
|
764
|
+
}
|
|
765
|
+
await queue.markBatch(homeId, batchId, { refusals });
|
|
766
|
+
log(`waking ${homeId} failed (${refusals}/${wakeRefusalLimit}), retrying: ${error?.message || error}`);
|
|
767
|
+
return later(homeId, missingRetryMs);
|
|
768
|
+
}
|
|
769
|
+
// `settled` resolves the relay turn's end status: only 'done' retires the batch.
|
|
770
|
+
if (typeof turn?.settled?.then === 'function') {
|
|
771
|
+
const settled = (status) => {
|
|
772
|
+
relays.set(batchId, status === 'done' ? 'done' : 'failed');
|
|
773
|
+
void drain(homeId);
|
|
774
|
+
};
|
|
775
|
+
turn.settled.then(settled, () => settled('failed'));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
// A surface that cannot report the turn's end: taking it is relaying it.
|
|
779
|
+
relays.delete(batchId);
|
|
780
|
+
await queue.woke(homeId, batchId);
|
|
781
|
+
// Another batch left by an earlier process wakes once this turn is done.
|
|
782
|
+
if (owesWake(await queue.wakes(homeId))) later(homeId, busyRetryMs);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return { wakeOwed };
|
|
786
|
+
}
|
|
787
|
+
|
|
413
788
|
export function createAgentHomeDelivery({
|
|
414
789
|
loadSession,
|
|
415
790
|
saveSession,
|
|
@@ -427,6 +802,19 @@ export function createAgentHomeDelivery({
|
|
|
427
802
|
// True only on positive evidence that session `id` was deleted; an
|
|
428
803
|
// unreadable or unknown record is not deleted.
|
|
429
804
|
sessionDeleted = () => false,
|
|
805
|
+
// 'bot' | 'none' | 'unknown' per agent id: whether it can have a home chat
|
|
806
|
+
// at all. 'none' (proven: a workflow agent such as Reviewer) drops the rows
|
|
807
|
+
// queued for its missing home instead of waiting on it forever.
|
|
808
|
+
homeAgent = () => 'unknown',
|
|
809
|
+
// Waits before each new try of a relay turn that did not finish; past the
|
|
810
|
+
// last one the results stay posted with a note and nobody is woken.
|
|
811
|
+
wakeRetryDelaysMs = WAKE_RETRY_DELAYS_MS,
|
|
812
|
+
// Refused or failing wake submissions of one batch (missingRetryMs apart)
|
|
813
|
+
// before it is given up the same way.
|
|
814
|
+
wakeRefusalLimit = WAKE_REFUSAL_LIMIT,
|
|
815
|
+
// Post queued room task reports into a room log (agent-room-delivery.mjs).
|
|
816
|
+
postRoomReports = async (roomLogId, items) =>
|
|
817
|
+
(await import('./agent-room-delivery.mjs')).appendRoomTaskReports(roomLogId, items),
|
|
430
818
|
log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
|
|
431
819
|
}) {
|
|
432
820
|
const busy = (sessionId) => {
|
|
@@ -441,6 +829,20 @@ export function createAgentHomeDelivery({
|
|
|
441
829
|
const again = new Set(); // homes enqueued to while their pass ran
|
|
442
830
|
const waiting = new Set(); // homes with a retry timer armed
|
|
443
831
|
const shownLive = new Set(); // held-request message ids shown live in a busy thread
|
|
832
|
+
const waitLogged = new Set(); // missing homes whose wait was logged
|
|
833
|
+
const relay = createHomeRelay({
|
|
834
|
+
queue,
|
|
835
|
+
saveSession,
|
|
836
|
+
live,
|
|
837
|
+
busy,
|
|
838
|
+
later,
|
|
839
|
+
drain,
|
|
840
|
+
log,
|
|
841
|
+
wakeRetryDelaysMs,
|
|
842
|
+
wakeRefusalLimit,
|
|
843
|
+
busyRetryMs,
|
|
844
|
+
missingRetryMs,
|
|
845
|
+
});
|
|
444
846
|
|
|
445
847
|
/** A held request waits on the user, possibly raised by the very turn that
|
|
446
848
|
* keeps its thread busy (a routine turn): show it live at once; the stored
|
|
@@ -451,11 +853,7 @@ export function createAgentHomeDelivery({
|
|
|
451
853
|
const messageId = messageIdOf(item.runId);
|
|
452
854
|
if (shownLive.has(messageId)) continue;
|
|
453
855
|
shownLive.add(messageId);
|
|
454
|
-
|
|
455
|
-
live()?.notice?.(homeId, { ...deliveredRow(item, messageId).notice, messageId });
|
|
456
|
-
} catch (error) {
|
|
457
|
-
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
458
|
-
}
|
|
856
|
+
noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId });
|
|
459
857
|
}
|
|
460
858
|
}
|
|
461
859
|
|
|
@@ -468,8 +866,48 @@ export function createAgentHomeDelivery({
|
|
|
468
866
|
}, ms);
|
|
469
867
|
}
|
|
470
868
|
|
|
869
|
+
/** A room thread proven gone: its queued rows are dropped, never retried. */
|
|
870
|
+
async function dropGoneRoomThread(homeId, items, omitted) {
|
|
871
|
+
if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
|
|
872
|
+
log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
|
|
873
|
+
await dropQueued(queue, homeId, items, omitted);
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/** A missing home of an agent that can never have one (no bot): its queued
|
|
878
|
+
* rows are dropped with a log line, never retried. */
|
|
879
|
+
async function dropHomeless(homeId, items, omitted) {
|
|
880
|
+
const agentId = agentIdOfHomeSession(homeId);
|
|
881
|
+
if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
|
|
882
|
+
log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
|
|
883
|
+
await dropQueued(queue, homeId, items, omitted);
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** Post every queued task report of the room log `roomLogId` as member rows. */
|
|
888
|
+
async function drainRoomLog(roomLogId) {
|
|
889
|
+
const items = await queue.items(roomLogId);
|
|
890
|
+
const omitted = await queue.omitted(roomLogId);
|
|
891
|
+
if (!items.length && !omitted) return;
|
|
892
|
+
const session = loadSession(roomLogId);
|
|
893
|
+
if (!session || session.closed === true) {
|
|
894
|
+
if (await dropGoneRoomThread(roomLogId, items, omitted)) return;
|
|
895
|
+
log(`${items.length} task report(s) wait for the room log ${roomLogId}`);
|
|
896
|
+
return later(roomLogId, missingRetryMs);
|
|
897
|
+
}
|
|
898
|
+
// A room log has no notice row: the dropped count is only logged.
|
|
899
|
+
if (omitted) log(`${omitted.count} older task report(s) for ${roomLogId} were dropped: its queue was full`);
|
|
900
|
+
if (items.length) await postRoomReports(roomLogId, items);
|
|
901
|
+
await queue.remove(
|
|
902
|
+
roomLogId,
|
|
903
|
+
items.map((item) => item.runId),
|
|
904
|
+
omitted
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
|
|
471
908
|
/** Post every queued result of `homeId` if its thread is idle; else retry later. */
|
|
472
909
|
async function drainOnce(homeId) {
|
|
910
|
+
if (roomIdOfSession(homeId)) return drainRoomLog(homeId);
|
|
473
911
|
const items = await queue.items(homeId);
|
|
474
912
|
const omitted = await queue.omitted(homeId);
|
|
475
913
|
const owed = owesWake(await queue.wakes(homeId));
|
|
@@ -491,37 +929,23 @@ export function createAgentHomeDelivery({
|
|
|
491
929
|
// From here to the push nothing awaits, so no turn can start in between.
|
|
492
930
|
const session = loadSession(homeId);
|
|
493
931
|
if (!session || session.closed === true) {
|
|
494
|
-
|
|
932
|
+
if (await dropGoneRoomThread(homeId, items, omitted)) return;
|
|
933
|
+
if (await dropHomeless(homeId, items, omitted)) return;
|
|
934
|
+
// Kept (a bot, or an agent whose kind cannot be read): logged once per process.
|
|
935
|
+
if (!waitLogged.has(homeId)) {
|
|
936
|
+
waitLogged.add(homeId);
|
|
937
|
+
log(`${items.length} result(s) wait for the home thread ${homeId}`);
|
|
938
|
+
}
|
|
495
939
|
return later(homeId, missingRetryMs);
|
|
496
940
|
}
|
|
497
941
|
if (!Array.isArray(session.messages)) session.messages = [];
|
|
498
|
-
const
|
|
499
|
-
const fresh = [];
|
|
500
|
-
const omittedId = omitted ? messageIdOf(omitted.id) : '';
|
|
501
|
-
if (omitted && !posted.has(omittedId)) {
|
|
502
|
-
const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
|
|
503
|
-
session.messages.push(message);
|
|
504
|
-
fresh.push(message);
|
|
505
|
-
}
|
|
506
|
-
const toWake = [];
|
|
507
|
-
for (const item of items) {
|
|
508
|
-
const row = deliveredRow(item, messageIdOf(item.runId));
|
|
509
|
-
// Every dequeued task result is owed one wake, posted in this pass or before.
|
|
510
|
-
if (row.wake) toWake.push(item.runId);
|
|
511
|
-
if (posted.has(messageIdOf(item.runId))) continue;
|
|
512
|
-
session.messages.push(row.message);
|
|
513
|
-
fresh.push(row);
|
|
514
|
-
}
|
|
942
|
+
const { fresh, toWake } = postQueuedRows(session, items, omitted);
|
|
515
943
|
for (const item of items) shownLive.delete(messageIdOf(item.runId));
|
|
516
944
|
if (fresh.length) {
|
|
517
945
|
session.updatedAt = Date.now();
|
|
518
946
|
await saveSession(session);
|
|
519
947
|
for (const { message, notice } of fresh) {
|
|
520
|
-
|
|
521
|
-
live()?.notice?.(homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
522
|
-
} catch (error) {
|
|
523
|
-
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
524
|
-
}
|
|
948
|
+
noticeLive(live, log, homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
525
949
|
}
|
|
526
950
|
}
|
|
527
951
|
// Dequeuing and owing the wake are one write: no restart between posting
|
|
@@ -533,39 +957,7 @@ export function createAgentHomeDelivery({
|
|
|
533
957
|
{ wake: toWake }
|
|
534
958
|
);
|
|
535
959
|
if (overflowed) log(`${homeId} is at its wake cap: ${overflowed} result(s) ride the next wake as a count`);
|
|
536
|
-
|
|
537
|
-
// is stored with its wake id BEFORE the wake is submitted and dropped once
|
|
538
|
-
// it is taken, so a restart in between re-checks that exact id; results
|
|
539
|
-
// owed later form a new batch. A replayed run id is never queued again.
|
|
540
|
-
const byId = new Map(session.messages.map((message) => [message?.meta?.transcript?.messageId, message]));
|
|
541
|
-
const taken = new Set(session.messages.flatMap((message) => submissionIdsOf(message)));
|
|
542
|
-
let batch = null;
|
|
543
|
-
let woken = [];
|
|
544
|
-
let more = 0;
|
|
545
|
-
for (;;) {
|
|
546
|
-
batch = (await queue.wakes(homeId)).batches[0] || (await queue.batchWake(homeId));
|
|
547
|
-
if (!batch) return;
|
|
548
|
-
const stale = taken.has(batch.id);
|
|
549
|
-
woken = stale ? [] : batch.runIds.map((runId) => byId.get(messageIdOf(runId))).filter(Boolean);
|
|
550
|
-
// Results past the wake cap ride this wake as a count of rows to read.
|
|
551
|
-
more = !stale && byId.has(messageIdOf(batch.overflow?.newest)) ? batch.overflow.count : 0;
|
|
552
|
-
if (woken.length || more) break;
|
|
553
|
-
// Taken before a restart, or its rows gone with a thread reset.
|
|
554
|
-
await queue.woke(homeId, batch.id);
|
|
555
|
-
}
|
|
556
|
-
const surface = live();
|
|
557
|
-
if (typeof surface?.wake !== 'function') return later(homeId, busyRetryMs);
|
|
558
|
-
try {
|
|
559
|
-
if ((await surface.wake(homeId, agentTaskWakePrompt(woken, { more }), { id: batch.id })) === false) {
|
|
560
|
-
throw new Error('the wake turn was refused');
|
|
561
|
-
}
|
|
562
|
-
} catch (error) {
|
|
563
|
-
log(`waking ${homeId} failed, retrying: ${error?.message || error}`);
|
|
564
|
-
return later(homeId, missingRetryMs);
|
|
565
|
-
}
|
|
566
|
-
await queue.woke(homeId, batch.id);
|
|
567
|
-
// Another batch left by an earlier process wakes once this turn is done.
|
|
568
|
-
if (owesWake(await queue.wakes(homeId))) later(homeId, busyRetryMs);
|
|
960
|
+
await relay.wakeOwed(homeId, session);
|
|
569
961
|
}
|
|
570
962
|
|
|
571
963
|
/** The single drainer of `homeId`; a call during a pass runs one more pass. */
|
|
@@ -598,17 +990,7 @@ export function createAgentHomeDelivery({
|
|
|
598
990
|
async function enqueue(row, result) {
|
|
599
991
|
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
600
992
|
if (!result?.runId) throw new Error('routine result: runId required');
|
|
601
|
-
await queue.add(homeId,
|
|
602
|
-
runId: String(result.runId),
|
|
603
|
-
kind: result.kind === 'webhook' ? 'webhook' : 'schedule',
|
|
604
|
-
name: row.name,
|
|
605
|
-
ok: result.ok === true,
|
|
606
|
-
text: routineSummary(result.text),
|
|
607
|
-
runSessionId: result.runSessionId || null,
|
|
608
|
-
...(result.reason ? { reason: String(result.reason) } : {}),
|
|
609
|
-
...(result.paused === true ? { paused: true } : {}),
|
|
610
|
-
at: Date.now(),
|
|
611
|
-
});
|
|
993
|
+
await queue.add(homeId, routineResultItem(row, result));
|
|
612
994
|
void drain(homeId);
|
|
613
995
|
return homeId;
|
|
614
996
|
}
|
|
@@ -631,30 +1013,22 @@ export function createAgentHomeDelivery({
|
|
|
631
1013
|
/** Persist one report of a coding task session `agentId` created (session
|
|
632
1014
|
* create) for its home thread, then start draining it. `status` 'started'
|
|
633
1015
|
* and 'blocked' only post a line; a turn's end also wakes the agent. */
|
|
634
|
-
async function enqueueTask(
|
|
635
|
-
|
|
636
|
-
{ runId, taskSessionId, title, status, text = '', target, watchId = '', files = [], omitted = 0 }
|
|
637
|
-
) {
|
|
1016
|
+
async function enqueueTask(agentId, report) {
|
|
1017
|
+
const { runId, taskSessionId, status, text = '', watchId = '', from = '' } = report;
|
|
638
1018
|
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
639
1019
|
if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
...(Array.isArray(files) && files.length ? { files, ...(omitted > 0 ? { omitted } : {}) } : {}),
|
|
653
|
-
roundTrip: status !== 'started' && status !== 'blocked',
|
|
654
|
-
at: Date.now(),
|
|
655
|
-
},
|
|
656
|
-
{ unwatch: watchId ? String(watchId) : '' }
|
|
657
|
-
);
|
|
1020
|
+
const item = taskReportItem(report);
|
|
1021
|
+
const unwatch = watchId ? String(watchId) : '';
|
|
1022
|
+
const member = roomMemberOfSession(from);
|
|
1023
|
+
if (member?.agentId === agentId) {
|
|
1024
|
+
const roomLogId = roomSessionId(member.roomId);
|
|
1025
|
+
const toRoom = status !== 'started';
|
|
1026
|
+
await queueRoomTaskReport(queue, { from, roomLogId, toRoom }, item, agentId, text, unwatch);
|
|
1027
|
+
void drain(from);
|
|
1028
|
+
if (toRoom) void drain(roomLogId);
|
|
1029
|
+
return toRoom ? roomLogId : from;
|
|
1030
|
+
}
|
|
1031
|
+
await queue.add(homeId, item, { unwatch });
|
|
658
1032
|
void drain(homeId);
|
|
659
1033
|
return homeId;
|
|
660
1034
|
}
|
|
@@ -667,23 +1041,10 @@ export function createAgentHomeDelivery({
|
|
|
667
1041
|
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
668
1042
|
if (!run?.runId || busy(homeId)) return false;
|
|
669
1043
|
const notice = routineRunNotice({ ...run, name: row.name, messageId: messageIdOf(run.runId) });
|
|
670
|
-
|
|
671
|
-
return live()?.notice?.(homeId, notice) === true;
|
|
672
|
-
} catch (error) {
|
|
673
|
-
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
1044
|
+
return noticeLive(live, log, homeId, notice) === true;
|
|
676
1045
|
}
|
|
677
1046
|
|
|
678
|
-
|
|
679
|
-
* or a room thread whose room is. Never Mixy's home, never on a missing
|
|
680
|
-
* thread record, never on an unknown answer. */
|
|
681
|
-
function homeDeleted(homeId) {
|
|
682
|
-
const agentId = agentIdOfHomeSession(homeId);
|
|
683
|
-
if (agentId) return agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted';
|
|
684
|
-
const roomId = roomIdOfSession(homeId) || roomMemberOfSession(homeId)?.roomId || '';
|
|
685
|
-
return Boolean(roomId) && roomPresence(roomId) === 'deleted';
|
|
686
|
-
}
|
|
1047
|
+
const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
|
|
687
1048
|
|
|
688
1049
|
/** Retire the bookkeeping of deleted homes (and stale round counts). */
|
|
689
1050
|
async function prune() {
|
|
@@ -694,17 +1055,7 @@ export function createAgentHomeDelivery({
|
|
|
694
1055
|
* whose target sessions are proven deleted, and refuses the watch (so the
|
|
695
1056
|
* send claims no report) when that frees too few. */
|
|
696
1057
|
async function watchTask(watch) {
|
|
697
|
-
const
|
|
698
|
-
(kept) => kept?.homeSessionId === watch.homeSessionId && kept.runId !== watch.runId
|
|
699
|
-
);
|
|
700
|
-
const over = own.length + 1 - queue.watchCap;
|
|
701
|
-
const drop =
|
|
702
|
-
over > 0
|
|
703
|
-
? own
|
|
704
|
-
.filter((kept) => sessionDeleted(kept.taskSessionId))
|
|
705
|
-
.slice(0, over)
|
|
706
|
-
.map((kept) => kept.runId)
|
|
707
|
-
: [];
|
|
1058
|
+
const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
|
|
708
1059
|
if (drop.length) {
|
|
709
1060
|
log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
|
|
710
1061
|
}
|
|
@@ -748,6 +1099,7 @@ async function processDelivery() {
|
|
|
748
1099
|
{ createAgentEditorApi },
|
|
749
1100
|
{ probePath, PROBE_ABSENT },
|
|
750
1101
|
{ sessionPath },
|
|
1102
|
+
{ mixdogRoot },
|
|
751
1103
|
] = await Promise.all([
|
|
752
1104
|
import('../runtime/agent/orchestrator/session/store.mjs'),
|
|
753
1105
|
import('../runtime/agent/orchestrator/session/manager/runtime-liveness.mjs'),
|
|
@@ -755,6 +1107,7 @@ async function processDelivery() {
|
|
|
755
1107
|
import('./workflow-agents-api/agent-editor.mjs'),
|
|
756
1108
|
import('../runtime/agent/orchestrator/session/store/fs-probe.mjs'),
|
|
757
1109
|
import('../runtime/agent/orchestrator/session/store/session-paths.mjs'),
|
|
1110
|
+
import('../runtime/shared/plugin-paths.mjs'),
|
|
758
1111
|
]);
|
|
759
1112
|
// The editor's per-id definition probe: 'deleted' only when proven gone.
|
|
760
1113
|
const { agentDefinitionPresence } = createAgentEditorApi({ cfgMod: { getPluginData } });
|
|
@@ -764,6 +1117,9 @@ async function processDelivery() {
|
|
|
764
1117
|
turnInFlight: isSessionTurnInFlight,
|
|
765
1118
|
queue: createRoutineResultQueue(join(getPluginData(), 'routine-results.json')),
|
|
766
1119
|
agentPresence: (agentId) => agentDefinitionPresence([agentId])[agentId],
|
|
1120
|
+
// A home chat exists only for bots; only a definition read successfully
|
|
1121
|
+
// can prove an agent is none (readHomeAgent).
|
|
1122
|
+
homeAgent: (agentId) => readHomeAgent({ dataDir: getPluginData(), shippedRoot: mixdogRoot(), id: agentId }),
|
|
767
1123
|
// Deleting a session unlinks its record (session-delete.mjs) and leaves
|
|
768
1124
|
// no tombstone: only an absent file with no in-memory copy proves it.
|
|
769
1125
|
// An unreadable file probes 'unreadable' and stays.
|