mixdog 1.3.1 → 1.3.3
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 +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- 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 +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- 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 +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- 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 +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- 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-client-version.mjs +1 -1
- 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-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- 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 +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- 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-runtime.mjs +5 -0
- 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/goal-turns.mjs +10 -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 +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- 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 +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- 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 +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- 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/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- 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 +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -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 +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -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 +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- 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 +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -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/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- 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 +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -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/prompt-utils.mjs +36 -0
- 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-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- 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 +52 -11
- 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 +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- 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/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -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/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- 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 +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -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/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- 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 +42 -23
- 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/legacy-office.mjs +110 -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/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- 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 +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- 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/memory-chunk-quality.mjs +14 -2
- 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 +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- 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-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -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 +35 -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/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- 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/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- 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 +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- 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 +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- 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 +23 -3
- 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 +31 -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 +24 -26
- 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-recency.mjs +1 -0
- 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/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- 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-review-api.mjs +4 -1
- 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/job-views/agent-list.mjs +34 -19
- 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 +7 -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 +26 -6
- 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/services/worktree-access.mjs +27 -0
- 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/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -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 +349 -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 +183 -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 +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- 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 +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- 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/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- 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 +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -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 +142 -14
- 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
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -17,10 +17,20 @@
|
|
|
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
|
+
* land in that member session alone, never as rows in the room's log. An
|
|
23
|
+
* ended turn is owed a wake like a home's, but the relay turn runs in the
|
|
24
|
+
* room: the desktop room host (agent-room-host.mjs `relay`) gives the member
|
|
25
|
+
* one turn there to relay the result, with the same batches and retries.
|
|
20
26
|
*/
|
|
21
27
|
import { randomUUID } from 'node:crypto';
|
|
22
|
-
import {
|
|
28
|
+
import { readFileSync } from 'node:fs';
|
|
23
29
|
import { join } from 'node:path';
|
|
30
|
+
import { readAgentDefinitionKind } from '../runtime/shared/agent-kind.mjs';
|
|
31
|
+
import { BOT_AGENT_KIND, WORKFLOW_AGENT_KIND, isContactAgentId } from '../runtime/shared/agent-route-config.mjs';
|
|
32
|
+
import { botStoreActive, isStoreBot } from '../runtime/shared/agent-store.mjs';
|
|
33
|
+
import { readMarkdownDocument } from '../runtime/shared/markdown-frontmatter.mjs';
|
|
24
34
|
import {
|
|
25
35
|
MIXY_AGENT_ID,
|
|
26
36
|
agentHomeSessionId,
|
|
@@ -32,19 +42,23 @@ import {
|
|
|
32
42
|
roomIdOfSession,
|
|
33
43
|
roomMemberOfSession,
|
|
34
44
|
} from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
45
|
+
import { agentRoomHost } from '../runtime/shared/agent-room-host.mjs';
|
|
46
|
+
import { sendDepth } from '../runtime/shared/bot-hops.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,10 @@ 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
|
+
// Waits before the 2nd, 3rd and 4th try of a relay (wake) turn that did not finish.
|
|
151
|
+
export const WAKE_RETRY_DELAYS_MS = Object.freeze([30_000, 120_000, 600_000]);
|
|
152
|
+
// Refused wake submissions of one batch, a minute apart, before it is given up.
|
|
153
|
+
export const WAKE_REFUSAL_LIMIT = 10;
|
|
103
154
|
|
|
104
155
|
/** `homeId`'s wake obligations in `wakes`: posted task results not in a batch
|
|
105
156
|
* yet (`owed`), those past the cap collapsed into one marker (`overflow`:
|
|
@@ -127,6 +178,83 @@ function withWakeState(doc, homeId, { owed, batches, overflow }) {
|
|
|
127
178
|
return { ...doc, wakes };
|
|
128
179
|
}
|
|
129
180
|
|
|
181
|
+
/** `own`'s per-target counts (`{ n, at }`) used within ROUND_TTL_MS of
|
|
182
|
+
* `nowMs`, the `cap` most recently counted. */
|
|
183
|
+
function freshRoundCounts(own, nowMs, cap) {
|
|
184
|
+
return Object.fromEntries(
|
|
185
|
+
Object.entries(own && typeof own === 'object' ? own : {})
|
|
186
|
+
.filter(([, count]) => count && typeof count === 'object' && nowMs - (Number(count.at) || 0) < ROUND_TTL_MS)
|
|
187
|
+
.sort(([, a], [, b]) => (Number(b.at) || 0) - (Number(a.at) || 0))
|
|
188
|
+
.slice(0, cap)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** `doc` with `items` appended to `homeId`'s list, minus run ids it holds or
|
|
193
|
+
* posted; past `limit` the oldest drop, counted under `omitted`. Null when
|
|
194
|
+
* nothing changes. */
|
|
195
|
+
function appendedItems(doc, homeId, items, limit, omittedCount) {
|
|
196
|
+
const list = doc.homes[homeId] || [];
|
|
197
|
+
const seen = new Set([...list.map((row) => row.runId), ...(doc.delivered[homeId] || [])]);
|
|
198
|
+
const fresh = items.filter((item) => !seen.has(item.runId) && seen.add(item.runId));
|
|
199
|
+
if (!fresh.length && !omittedCount) return null;
|
|
200
|
+
const next = [...list, ...fresh];
|
|
201
|
+
const drop = Math.max(0, next.length - limit);
|
|
202
|
+
const omitted = { ...doc.omitted };
|
|
203
|
+
const count = drop + omittedCount;
|
|
204
|
+
if (count) {
|
|
205
|
+
const previous = omitted[homeId];
|
|
206
|
+
omitted[homeId] = { id: previous?.id || `omit_${next[0].runId}`, count: (previous?.count || 0) + count };
|
|
207
|
+
}
|
|
208
|
+
return { ...doc, homes: { ...doc.homes, [homeId]: next.slice(drop) }, omitted };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** `doc.wakes` with the run ids `wake` owed a wake by `homeId`, at most
|
|
212
|
+
* `cap` per home: past it the oldest owed collapse into the overflow marker
|
|
213
|
+
* (`overflowed` counts them). Stored batches never change. */
|
|
214
|
+
function owedWakes(doc, homeId, wake, cap) {
|
|
215
|
+
const state = wakeState(doc.wakes, homeId);
|
|
216
|
+
const batched = state.batches.flatMap((batch) => batch.runIds);
|
|
217
|
+
const known = new Set([
|
|
218
|
+
...state.owed,
|
|
219
|
+
...batched,
|
|
220
|
+
...[state.overflow?.oldest, state.overflow?.newest].filter(Boolean),
|
|
221
|
+
]);
|
|
222
|
+
const added = wake.filter((runId) => !known.has(runId));
|
|
223
|
+
if (!added.length) return { wakes: doc.wakes, overflowed: 0 };
|
|
224
|
+
const owed = [...state.owed, ...added];
|
|
225
|
+
const room = Math.max(0, cap - batched.length);
|
|
226
|
+
const excess = owed.slice(0, Math.max(0, owed.length - room));
|
|
227
|
+
const overflow = excess.length
|
|
228
|
+
? {
|
|
229
|
+
count: (state.overflow?.count || 0) + excess.length,
|
|
230
|
+
oldest: state.overflow?.oldest || excess[0],
|
|
231
|
+
newest: excess.at(-1),
|
|
232
|
+
}
|
|
233
|
+
: state.overflow;
|
|
234
|
+
const { wakes } = withWakeState(doc, homeId, { ...state, owed: owed.slice(excess.length), overflow });
|
|
235
|
+
return { wakes, overflowed: excess.length };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `doc` with the posted `runIds` of `homeId` dequeued into its posted
|
|
239
|
+
* ledger, and the posted omission notice `omitted` retired. */
|
|
240
|
+
function withDequeued(doc, homeId, runIds, omitted) {
|
|
241
|
+
const homes = { ...doc.homes };
|
|
242
|
+
const rest = (homes[homeId] || []).filter((row) => !runIds.includes(row.runId));
|
|
243
|
+
if (rest.length) homes[homeId] = rest;
|
|
244
|
+
else delete homes[homeId];
|
|
245
|
+
const delivered = { ...doc.delivered };
|
|
246
|
+
if (runIds.length) delivered[homeId] = [...(delivered[homeId] || []), ...runIds].slice(-DELIVERED_LEDGER);
|
|
247
|
+
const omittedNow = { ...doc.omitted };
|
|
248
|
+
const current = omittedNow[homeId];
|
|
249
|
+
if (omitted && current?.id === omitted.id) {
|
|
250
|
+
// More dropped while this pass posted: they get a notice of their own.
|
|
251
|
+
const left = current.count - omitted.count;
|
|
252
|
+
if (left > 0) omittedNow[homeId] = { id: `${current.id}.${omitted.count}`, count: left };
|
|
253
|
+
else delete omittedNow[homeId];
|
|
254
|
+
}
|
|
255
|
+
return { ...doc, homes, omitted: omittedNow, delivered };
|
|
256
|
+
}
|
|
257
|
+
|
|
130
258
|
/** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
|
|
131
259
|
* omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
|
|
132
260
|
* runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
|
|
@@ -136,15 +264,7 @@ export function createRoutineResultQueue(
|
|
|
136
264
|
{ limit = ROUTINE_LIMITS.homeQueueLimit, now = Date.now, caps = QUEUE_CAPS } = {}
|
|
137
265
|
) {
|
|
138
266
|
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
|
-
);
|
|
267
|
+
const freshRounds = (own) => freshRoundCounts(own, now(), caps.roundTargets);
|
|
148
268
|
const tidyRounds = (rounds) =>
|
|
149
269
|
Object.fromEntries(
|
|
150
270
|
Object.entries(rounds || {})
|
|
@@ -162,39 +282,26 @@ export function createRoutineResultQueue(
|
|
|
162
282
|
// Posted task results whose wake turn was not taken yet, per home.
|
|
163
283
|
wakes: section(doc, 'wakes'),
|
|
164
284
|
});
|
|
285
|
+
// A damaged queue file (empty, NUL-filled after a crash, malformed) is kept
|
|
286
|
+
// aside and its last good copy restored (readJsonState); it never blocks
|
|
287
|
+
// delivery. I/O errors still throw.
|
|
165
288
|
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
|
-
}
|
|
289
|
+
return parts(await readJsonState(filePath));
|
|
172
290
|
}
|
|
173
291
|
/** Read-modify-write under the file lock (`mutator(doc)` returns the next
|
|
174
292
|
* doc, or nothing to keep the file); every write also retires stale and
|
|
175
293
|
* surplus round counts. */
|
|
176
294
|
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 };
|
|
295
|
+
return updateJsonAtomic(
|
|
296
|
+
filePath,
|
|
297
|
+
(raw) => {
|
|
298
|
+
const next = mutator(parts(raw));
|
|
299
|
+
return next ? { ...next, rounds: tidyRounds(next.rounds) } : undefined;
|
|
300
|
+
},
|
|
301
|
+
{ backup: true }
|
|
302
|
+
);
|
|
197
303
|
}
|
|
304
|
+
const appended = (doc, homeId, items, omittedCount = 0) => appendedItems(doc, homeId, items, limit, omittedCount);
|
|
198
305
|
return {
|
|
199
306
|
async pendingHomes() {
|
|
200
307
|
const { homes, omitted, wakes } = await read();
|
|
@@ -237,6 +344,33 @@ export function createRoutineResultQueue(
|
|
|
237
344
|
return withWakeState(doc, homeId, { ...state, batches });
|
|
238
345
|
});
|
|
239
346
|
},
|
|
347
|
+
/** Batch `batchId`'s relay turn did not finish: it is owed again under a
|
|
348
|
+
* fresh wake id (the old one names the failed turn), as try `tries`, not
|
|
349
|
+
* before `notBefore`. Returns the new batch; null when it is gone. */
|
|
350
|
+
async rewake(homeId, batchId, { tries, notBefore }) {
|
|
351
|
+
let next = null;
|
|
352
|
+
await update((doc) => {
|
|
353
|
+
next = null;
|
|
354
|
+
const state = wakeState(doc.wakes, homeId);
|
|
355
|
+
const index = state.batches.findIndex((batch) => batch.id === batchId);
|
|
356
|
+
if (index < 0) return undefined;
|
|
357
|
+
// The new try's turn is its own: it follows no earlier turn.
|
|
358
|
+
const { follows: _follows, ...batch } = state.batches[index];
|
|
359
|
+
next = { ...batch, 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) =>
|
|
@@ -288,11 +422,14 @@ export function createRoutineResultQueue(
|
|
|
288
422
|
else {
|
|
289
423
|
// A task turn's end counts one round trip, once per run id, per home
|
|
290
424
|
// and target: the user's next message in that home starts over
|
|
291
|
-
// (resetRounds), and so does a count idle past ROUND_TTL_MS.
|
|
425
|
+
// (resetRounds), and so does a count idle past ROUND_TTL_MS. Only a
|
|
426
|
+
// send made in a bot-descended turn counts: the user's or a routine's
|
|
427
|
+
// own delegations never use the budget up.
|
|
292
428
|
const own = freshRounds(doc.rounds[homeId]);
|
|
293
|
-
const
|
|
429
|
+
const counts = sendDepth(item) > 0;
|
|
430
|
+
const round = counts ? (own[item.taskSessionId]?.n || 0) + 1 : 0;
|
|
294
431
|
next = appended(doc, homeId, [{ ...item, round }]);
|
|
295
|
-
if (next) {
|
|
432
|
+
if (next && counts) {
|
|
296
433
|
const counted = { ...own, [item.taskSessionId]: { n: round, at: now() } };
|
|
297
434
|
next = { ...next, rounds: { ...doc.rounds, [homeId]: counted } };
|
|
298
435
|
}
|
|
@@ -314,7 +451,9 @@ export function createRoutineResultQueue(
|
|
|
314
451
|
(kept) => kept?.homeSessionId === watch.homeSessionId && kept.runId !== watch.runId
|
|
315
452
|
);
|
|
316
453
|
if (own.length >= caps.watches) {
|
|
317
|
-
throw new Error(`${watch.homeSessionId} already waits on ${own.length} session reports`)
|
|
454
|
+
throw Object.assign(new Error(`${watch.homeSessionId} already waits on ${own.length} session reports`), {
|
|
455
|
+
code: 'REPORT_WATCH_CAP',
|
|
456
|
+
});
|
|
318
457
|
}
|
|
319
458
|
return { ...doc, watches: { ...watches, [watch.runId]: watch } };
|
|
320
459
|
});
|
|
@@ -324,6 +463,16 @@ export function createRoutineResultQueue(
|
|
|
324
463
|
async watches() {
|
|
325
464
|
return Object.values((await read()).watches);
|
|
326
465
|
},
|
|
466
|
+
/** The watch of `runId` follows the turn submitted as `submissionId`: a
|
|
467
|
+
* room member's one turn answering several sends, or a fresh turn
|
|
468
|
+
* replacing one cut short (recovery reads that turn's end). */
|
|
469
|
+
async followWatch(runId, submissionId) {
|
|
470
|
+
await update((doc) => {
|
|
471
|
+
const watch = doc.watches[runId];
|
|
472
|
+
if (!watch || watch.follows === submissionId) return undefined;
|
|
473
|
+
return { ...doc, watches: { ...doc.watches, [runId]: { ...watch, follows: submissionId } } };
|
|
474
|
+
});
|
|
475
|
+
},
|
|
327
476
|
/** Drop the watch of `runId` (its turn was never taken). */
|
|
328
477
|
async unwatch(runId) {
|
|
329
478
|
await update((doc) => {
|
|
@@ -363,45 +512,9 @@ export function createRoutineResultQueue(
|
|
|
363
512
|
async remove(homeId, runIds, omitted = null, { wake = [] } = {}) {
|
|
364
513
|
let overflowed = 0;
|
|
365
514
|
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 };
|
|
515
|
+
let wakes;
|
|
516
|
+
({ wakes, overflowed } = owedWakes(doc, homeId, wake, caps.wakeRunIds));
|
|
517
|
+
return { ...withDequeued(doc, homeId, runIds, omitted), wakes };
|
|
405
518
|
});
|
|
406
519
|
return { overflowed };
|
|
407
520
|
},
|
|
@@ -410,6 +523,263 @@ export function createRoutineResultQueue(
|
|
|
410
523
|
|
|
411
524
|
const messageIdOf = (runId) => `routine_${runId}`;
|
|
412
525
|
|
|
526
|
+
/** Show `notice` live in `homeId` through the `live()` surface: its answer,
|
|
527
|
+
* or false (logged) when the surface threw. */
|
|
528
|
+
function noticeLive(live, log, homeId, notice) {
|
|
529
|
+
try {
|
|
530
|
+
return live()?.notice?.(homeId, notice);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Dequeue every queued item (and the omission) of `homeId` unposted, and
|
|
538
|
+
* its wake obligations. */
|
|
539
|
+
async function dropQueued(queue, homeId, items, omitted) {
|
|
540
|
+
await queue.remove(
|
|
541
|
+
homeId,
|
|
542
|
+
items.map((item) => item.runId),
|
|
543
|
+
omitted
|
|
544
|
+
);
|
|
545
|
+
await queue.dropWakes(homeId);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** The queued item of `result` ({ runId, kind, ok, text, runSessionId }) of
|
|
549
|
+
* a schedule or webhook `row` (enqueue). */
|
|
550
|
+
function routineResultItem(row, result) {
|
|
551
|
+
return {
|
|
552
|
+
runId: String(result.runId),
|
|
553
|
+
kind: result.kind === 'webhook' ? 'webhook' : 'schedule',
|
|
554
|
+
name: row.name,
|
|
555
|
+
ok: result.ok === true,
|
|
556
|
+
text: routineSummary(result.text),
|
|
557
|
+
runSessionId: result.runSessionId || null,
|
|
558
|
+
...(result.reason ? { reason: String(result.reason) } : {}),
|
|
559
|
+
...(result.paused === true ? { paused: true } : {}),
|
|
560
|
+
at: Date.now(),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** The run ids of `watches` a home at its watch `cap` gives up before
|
|
565
|
+
* storing `watch`: its oldest whose target sessions are proven deleted. */
|
|
566
|
+
function watchesToDrop(watches, watch, cap, sessionDeleted) {
|
|
567
|
+
const own = watches.filter((kept) => kept?.homeSessionId === watch.homeSessionId && kept.runId !== watch.runId);
|
|
568
|
+
const over = own.length + 1 - cap;
|
|
569
|
+
return over > 0
|
|
570
|
+
? own
|
|
571
|
+
.filter((kept) => sessionDeleted(kept.taskSessionId))
|
|
572
|
+
.slice(0, over)
|
|
573
|
+
.map((kept) => kept.runId)
|
|
574
|
+
: [];
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** A bookkept home proven deleted: a bot's home whose definition is gone,
|
|
578
|
+
* or a room thread whose room is. Never Mixy's home, never on a missing
|
|
579
|
+
* thread record, never on an unknown answer. */
|
|
580
|
+
function homeProvenDeleted(homeId, agentPresence, roomPresence) {
|
|
581
|
+
const agentId = agentIdOfHomeSession(homeId);
|
|
582
|
+
if (agentId) return agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted';
|
|
583
|
+
const roomId = roomIdOfSession(homeId) || roomMemberOfSession(homeId)?.roomId || '';
|
|
584
|
+
return Boolean(roomId) && roomPresence(roomId) === 'deleted';
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** The queued item of one task report (enqueueTask). */
|
|
588
|
+
function taskReportItem({ runId, taskSessionId, title, status, text = '', target, files = [], omitted = 0, hop = 0 }) {
|
|
589
|
+
// The model-facing reply keeps its lines up to REPLY_LIMIT; the row's
|
|
590
|
+
// one-line summary is derived from it (agentTaskMessage).
|
|
591
|
+
const reply = boundedReply(text);
|
|
592
|
+
return {
|
|
593
|
+
runId: String(runId),
|
|
594
|
+
kind: 'agent-task',
|
|
595
|
+
taskSessionId,
|
|
596
|
+
title,
|
|
597
|
+
status,
|
|
598
|
+
text: reply.text,
|
|
599
|
+
// The UI row's one line comes from the whole reply, not its bounded part.
|
|
600
|
+
summary: routineSummary(text),
|
|
601
|
+
...(reply.chars > reply.text.length ? { textChars: reply.chars } : {}),
|
|
602
|
+
// Any session other than a task session the agent created.
|
|
603
|
+
...(target === 'session' ? { target } : {}),
|
|
604
|
+
// The files the turn produced (handoff-attachments.mjs collectTurnArtifacts).
|
|
605
|
+
...(Array.isArray(files) && files.length ? { files, ...(omitted > 0 ? { omitted } : {}) } : {}),
|
|
606
|
+
roundTrip: status !== 'started' && status !== 'blocked',
|
|
607
|
+
...(hop > 0 ? { hop } : {}),
|
|
608
|
+
at: Date.now(),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Append to `session.messages` the omission notice and the queued `items`
|
|
613
|
+
* not posted before. `fresh`: the rows newly posted; `toWake`: the run ids
|
|
614
|
+
* owed a wake (every dequeued task result, posted now or before). */
|
|
615
|
+
function postQueuedRows(session, items, omitted) {
|
|
616
|
+
const posted = new Set(session.messages.map((message) => message?.meta?.transcript?.messageId).filter(Boolean));
|
|
617
|
+
const fresh = [];
|
|
618
|
+
const omittedId = omitted ? messageIdOf(omitted.id) : '';
|
|
619
|
+
if (omitted && !posted.has(omittedId)) {
|
|
620
|
+
const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
|
|
621
|
+
session.messages.push(message);
|
|
622
|
+
fresh.push({ message, notice: routineResultNotice(message) });
|
|
623
|
+
}
|
|
624
|
+
const toWake = [];
|
|
625
|
+
for (const item of items) {
|
|
626
|
+
const row = deliveredRow(item, messageIdOf(item.runId));
|
|
627
|
+
if (row.wake) toWake.push(item.runId);
|
|
628
|
+
if (posted.has(messageIdOf(item.runId))) continue;
|
|
629
|
+
session.messages.push(row.message);
|
|
630
|
+
fresh.push(row);
|
|
631
|
+
}
|
|
632
|
+
return { fresh, toWake };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** The relay of posted task results: owed results wake the agent with one
|
|
636
|
+
* hidden turn per batch. A batch is stored with its wake id BEFORE the wake
|
|
637
|
+
* is submitted and dropped once its relay turn settled done, so a restart in
|
|
638
|
+
* between re-checks that exact id; a relay turn that did not finish is owed
|
|
639
|
+
* again under a fresh id (retryRelay). Results owed later form a new batch.
|
|
640
|
+
* A replayed run id is never queued again. */
|
|
641
|
+
function createHomeRelay({
|
|
642
|
+
queue,
|
|
643
|
+
saveSession,
|
|
644
|
+
live,
|
|
645
|
+
roomRelay,
|
|
646
|
+
busy,
|
|
647
|
+
later,
|
|
648
|
+
drain,
|
|
649
|
+
log,
|
|
650
|
+
wakeRetryDelaysMs,
|
|
651
|
+
wakeRefusalLimit,
|
|
652
|
+
busyRetryMs,
|
|
653
|
+
missingRetryMs,
|
|
654
|
+
}) {
|
|
655
|
+
// Wake batch id → 'running' while this process's relay turn runs, then
|
|
656
|
+
// 'done' or 'failed' once it settled (until the batch is retired).
|
|
657
|
+
const relays = new Map();
|
|
658
|
+
|
|
659
|
+
/** How `batch`'s relay turn went: '' when no turn took it, else
|
|
660
|
+
* 'running', 'done' or 'failed'. A turn an earlier process took decides by
|
|
661
|
+
* its recorded end; one with none was cut off by the restart. A room
|
|
662
|
+
* member's batch may be answered by a turn under another submission id
|
|
663
|
+
* (`follows`, recorded before that turn was submitted). */
|
|
664
|
+
function relayOf(batch, messages, taken) {
|
|
665
|
+
const local = relays.get(batch.id);
|
|
666
|
+
if (local) return local;
|
|
667
|
+
const turnId = batch.follows || batch.id;
|
|
668
|
+
if (!taken.has(turnId)) return '';
|
|
669
|
+
const outcome = submissionOutcome(messages, turnId);
|
|
670
|
+
return outcome && outcome !== 'open' && outcome.status === 'completed' ? 'done' : 'failed';
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** The relay turn of `batch` did not finish: owe it again after a backoff,
|
|
674
|
+
* or, past the last try, leave a note in the thread and wake nobody. */
|
|
675
|
+
async function retryRelay(homeId, session, batch) {
|
|
676
|
+
const tries = (Number(batch.tries) || 0) + 1;
|
|
677
|
+
if (tries <= wakeRetryDelaysMs.length) {
|
|
678
|
+
log(`the relay turn in ${homeId} did not finish (try ${tries}); waking it again later`);
|
|
679
|
+
await queue.rewake(homeId, batch.id, { tries, notBefore: Date.now() + wakeRetryDelaysMs[tries - 1] });
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
return abandonRelay(homeId, session, batch, `did not finish after ${tries} tries`, tries);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Give up on `batch`'s relay: a note in the thread (once), nobody woken.
|
|
686
|
+
* False while the thread is busy (try again once it is idle). */
|
|
687
|
+
async function abandonRelay(homeId, session, batch, why, tries) {
|
|
688
|
+
if (busy(homeId)) return false;
|
|
689
|
+
log(`the relay turn in ${homeId} ${why}; its results stay posted unrelayed`);
|
|
690
|
+
const messageId = messageIdOf(`relay_${batch.id}`);
|
|
691
|
+
if (!session.messages.some((message) => message?.meta?.transcript?.messageId === messageId)) {
|
|
692
|
+
const message = agentTaskRelayFailedMessage({
|
|
693
|
+
count: batch.runIds.length + (batch.overflow?.count || 0),
|
|
694
|
+
tries,
|
|
695
|
+
messageId,
|
|
696
|
+
});
|
|
697
|
+
session.messages.push(message);
|
|
698
|
+
session.updatedAt = Date.now();
|
|
699
|
+
await saveSession(session);
|
|
700
|
+
noticeLive(live, log, homeId, { ...routineResultNotice(message), messageId });
|
|
701
|
+
}
|
|
702
|
+
await queue.woke(homeId, batch.id);
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** Wake `homeId` (its idle `session`, just posted to) for its next owed batch. */
|
|
707
|
+
async function wakeOwed(homeId, session) {
|
|
708
|
+
const byId = new Map(session.messages.map((message) => [message?.meta?.transcript?.messageId, message]));
|
|
709
|
+
const taken = new Set(session.messages.flatMap((message) => submissionIdsOf(message)));
|
|
710
|
+
let batch = null;
|
|
711
|
+
let woken = [];
|
|
712
|
+
let more = 0;
|
|
713
|
+
for (;;) {
|
|
714
|
+
batch = (await queue.wakes(homeId)).batches[0] || (await queue.batchWake(homeId));
|
|
715
|
+
if (!batch) return;
|
|
716
|
+
const relay = relayOf(batch, session.messages, taken);
|
|
717
|
+
// Its settle drains again.
|
|
718
|
+
if (relay === 'running') return;
|
|
719
|
+
if (relay) {
|
|
720
|
+
relays.delete(batch.id);
|
|
721
|
+
if (relay === 'done') await queue.woke(homeId, batch.id);
|
|
722
|
+
else if (!(await retryRelay(homeId, session, batch))) return later(homeId, busyRetryMs);
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const wait = (Number(batch.notBefore) || 0) - Date.now();
|
|
726
|
+
if (wait > 0) return later(homeId, wait);
|
|
727
|
+
woken = batch.runIds.map((runId) => byId.get(messageIdOf(runId))).filter(Boolean);
|
|
728
|
+
// Results past the wake cap ride this wake as a count of rows to read.
|
|
729
|
+
more = byId.has(messageIdOf(batch.overflow?.newest)) ? batch.overflow.count : 0;
|
|
730
|
+
if (woken.length || more) break;
|
|
731
|
+
// Its rows are gone with a thread reset.
|
|
732
|
+
await queue.woke(homeId, batch.id);
|
|
733
|
+
}
|
|
734
|
+
// A room member relays in its room (the room host runs that turn), any
|
|
735
|
+
// other thread in a hidden turn of its own.
|
|
736
|
+
const surface = live();
|
|
737
|
+
const wake = roomMemberOfSession(homeId) ? roomRelay : surface?.wake?.bind(surface);
|
|
738
|
+
if (typeof wake !== 'function') return later(homeId, busyRetryMs);
|
|
739
|
+
const batchId = batch.id;
|
|
740
|
+
let turn;
|
|
741
|
+
relays.set(batchId, 'running');
|
|
742
|
+
try {
|
|
743
|
+
// The reports answer sends this home made itself: the wake turn resumes
|
|
744
|
+
// at the depth of the turns that sent them (bot-hops.mjs), 0 for work the
|
|
745
|
+
// user or a routine started, so only bot-descended chains keep counting.
|
|
746
|
+
const hop = Math.max(0, ...woken.map((message) => sendDepth(message?.meta?.routine)));
|
|
747
|
+
// A room relay may be answered under another turn's submission id: the
|
|
748
|
+
// batch records it before that turn is submitted.
|
|
749
|
+
const follow = (submissionId) => queue.markBatch(homeId, batchId, { follows: submissionId });
|
|
750
|
+
turn = await wake(homeId, agentTaskWakePrompt(woken, { more }), { id: batchId, hop, follow });
|
|
751
|
+
if (turn === false) throw new Error('the wake turn was refused');
|
|
752
|
+
} catch (error) {
|
|
753
|
+
relays.delete(batchId);
|
|
754
|
+
// A wake refused (or failing) every time is retried a bounded number of times.
|
|
755
|
+
const refusals = (Number(batch.refusals) || 0) + 1;
|
|
756
|
+
if (refusals >= wakeRefusalLimit) {
|
|
757
|
+
if (await abandonRelay(homeId, session, batch, `could not start (${refusals} refusals)`, refusals)) return;
|
|
758
|
+
return later(homeId, busyRetryMs);
|
|
759
|
+
}
|
|
760
|
+
await queue.markBatch(homeId, batchId, { refusals });
|
|
761
|
+
log(`waking ${homeId} failed (${refusals}/${wakeRefusalLimit}), retrying: ${error?.message || error}`);
|
|
762
|
+
return later(homeId, missingRetryMs);
|
|
763
|
+
}
|
|
764
|
+
// `settled` resolves the relay turn's end status: only 'done' retires the batch.
|
|
765
|
+
if (typeof turn?.settled?.then === 'function') {
|
|
766
|
+
const settled = (status) => {
|
|
767
|
+
relays.set(batchId, status === 'done' ? 'done' : 'failed');
|
|
768
|
+
void drain(homeId);
|
|
769
|
+
};
|
|
770
|
+
turn.settled.then(settled, () => settled('failed'));
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
// A surface that cannot report the turn's end: taking it is relaying it.
|
|
774
|
+
relays.delete(batchId);
|
|
775
|
+
await queue.woke(homeId, batchId);
|
|
776
|
+
// Another batch left by an earlier process wakes once this turn is done.
|
|
777
|
+
if (owesWake(await queue.wakes(homeId))) later(homeId, busyRetryMs);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return { wakeOwed };
|
|
781
|
+
}
|
|
782
|
+
|
|
413
783
|
export function createAgentHomeDelivery({
|
|
414
784
|
loadSession,
|
|
415
785
|
saveSession,
|
|
@@ -427,6 +797,20 @@ export function createAgentHomeDelivery({
|
|
|
427
797
|
// True only on positive evidence that session `id` was deleted; an
|
|
428
798
|
// unreadable or unknown record is not deleted.
|
|
429
799
|
sessionDeleted = () => false,
|
|
800
|
+
// 'bot' | 'none' | 'unknown' per agent id: whether it can have a home chat
|
|
801
|
+
// at all. 'none' (proven: a workflow agent such as Reviewer) drops the rows
|
|
802
|
+
// queued for its missing home instead of waiting on it forever.
|
|
803
|
+
homeAgent = () => 'unknown',
|
|
804
|
+
// Waits before each new try of a relay turn that did not finish; past the
|
|
805
|
+
// last one the results stay posted with a note and nobody is woken.
|
|
806
|
+
wakeRetryDelaysMs = WAKE_RETRY_DELAYS_MS,
|
|
807
|
+
// Refused or failing wake submissions of one batch (missingRetryMs apart)
|
|
808
|
+
// before it is given up the same way.
|
|
809
|
+
wakeRefusalLimit = WAKE_REFUSAL_LIMIT,
|
|
810
|
+
// A room member's relay turn (wake of a member session): its turn in the
|
|
811
|
+
// room, through the desktop room host (agent-room-host.mjs `relay`), with
|
|
812
|
+
// the live surface's `wake` contract.
|
|
813
|
+
roomRelay = (sessionId, prompt, options) => agentRoomHost().relay(sessionId, prompt, options),
|
|
430
814
|
log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
|
|
431
815
|
}) {
|
|
432
816
|
const busy = (sessionId) => {
|
|
@@ -441,6 +825,21 @@ export function createAgentHomeDelivery({
|
|
|
441
825
|
const again = new Set(); // homes enqueued to while their pass ran
|
|
442
826
|
const waiting = new Set(); // homes with a retry timer armed
|
|
443
827
|
const shownLive = new Set(); // held-request message ids shown live in a busy thread
|
|
828
|
+
const waitLogged = new Set(); // missing homes whose wait was logged
|
|
829
|
+
const relay = createHomeRelay({
|
|
830
|
+
queue,
|
|
831
|
+
saveSession,
|
|
832
|
+
live,
|
|
833
|
+
roomRelay,
|
|
834
|
+
busy,
|
|
835
|
+
later,
|
|
836
|
+
drain,
|
|
837
|
+
log,
|
|
838
|
+
wakeRetryDelaysMs,
|
|
839
|
+
wakeRefusalLimit,
|
|
840
|
+
busyRetryMs,
|
|
841
|
+
missingRetryMs,
|
|
842
|
+
});
|
|
444
843
|
|
|
445
844
|
/** A held request waits on the user, possibly raised by the very turn that
|
|
446
845
|
* keeps its thread busy (a routine turn): show it live at once; the stored
|
|
@@ -451,11 +850,7 @@ export function createAgentHomeDelivery({
|
|
|
451
850
|
const messageId = messageIdOf(item.runId);
|
|
452
851
|
if (shownLive.has(messageId)) continue;
|
|
453
852
|
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
|
-
}
|
|
853
|
+
noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId });
|
|
459
854
|
}
|
|
460
855
|
}
|
|
461
856
|
|
|
@@ -468,12 +863,38 @@ export function createAgentHomeDelivery({
|
|
|
468
863
|
}, ms);
|
|
469
864
|
}
|
|
470
865
|
|
|
866
|
+
/** A room thread proven gone: its queued rows are dropped, never retried. */
|
|
867
|
+
async function dropGoneRoomThread(homeId, items, omitted) {
|
|
868
|
+
if (!isRoomThreadSessionId(homeId) || !(homeDeleted(homeId) || sessionDeleted(homeId))) return false;
|
|
869
|
+
log(`${items.length} task report(s) for the deleted ${homeId} are dropped`);
|
|
870
|
+
await dropQueued(queue, homeId, items, omitted);
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/** A room log's queued rows: task reports go to member sessions now, so what
|
|
875
|
+
* an earlier version left under the log's id is dropped, never posted. */
|
|
876
|
+
async function dropRoomLogQueue(logId, items, omitted) {
|
|
877
|
+
log(`${items.length} legacy task report(s) queued for the room log ${logId} are dropped`);
|
|
878
|
+
await dropQueued(queue, logId, items, omitted);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/** A missing home of an agent that can never have one (no bot): its queued
|
|
882
|
+
* rows are dropped with a log line, never retried. */
|
|
883
|
+
async function dropHomeless(homeId, items, omitted) {
|
|
884
|
+
const agentId = agentIdOfHomeSession(homeId);
|
|
885
|
+
if (!agentId || agentId === MIXY_AGENT_ID || homeAgent(agentId) !== 'none') return false;
|
|
886
|
+
log(`${items.length} result(s) for ${homeId} are dropped: ${agentId} is not a bot, so it has no chat`);
|
|
887
|
+
await dropQueued(queue, homeId, items, omitted);
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
|
|
471
891
|
/** Post every queued result of `homeId` if its thread is idle; else retry later. */
|
|
472
892
|
async function drainOnce(homeId) {
|
|
473
893
|
const items = await queue.items(homeId);
|
|
474
894
|
const omitted = await queue.omitted(homeId);
|
|
475
895
|
const owed = owesWake(await queue.wakes(homeId));
|
|
476
896
|
if (!items.length && !omitted && !owed) return;
|
|
897
|
+
if (roomIdOfSession(homeId)) return dropRoomLogQueue(homeId, items, omitted);
|
|
477
898
|
const agentId = agentIdOfHomeSession(homeId);
|
|
478
899
|
if (agentId && agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted') {
|
|
479
900
|
// The owner is gone (its home archived): Mixy, the default owner, gets them.
|
|
@@ -491,37 +912,23 @@ export function createAgentHomeDelivery({
|
|
|
491
912
|
// From here to the push nothing awaits, so no turn can start in between.
|
|
492
913
|
const session = loadSession(homeId);
|
|
493
914
|
if (!session || session.closed === true) {
|
|
494
|
-
|
|
915
|
+
if (await dropGoneRoomThread(homeId, items, omitted)) return;
|
|
916
|
+
if (await dropHomeless(homeId, items, omitted)) return;
|
|
917
|
+
// Kept (a bot, or an agent whose kind cannot be read): logged once per process.
|
|
918
|
+
if (!waitLogged.has(homeId)) {
|
|
919
|
+
waitLogged.add(homeId);
|
|
920
|
+
log(`${items.length} result(s) wait for the home thread ${homeId}`);
|
|
921
|
+
}
|
|
495
922
|
return later(homeId, missingRetryMs);
|
|
496
923
|
}
|
|
497
924
|
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
|
-
}
|
|
925
|
+
const { fresh, toWake } = postQueuedRows(session, items, omitted);
|
|
515
926
|
for (const item of items) shownLive.delete(messageIdOf(item.runId));
|
|
516
927
|
if (fresh.length) {
|
|
517
928
|
session.updatedAt = Date.now();
|
|
518
929
|
await saveSession(session);
|
|
519
930
|
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
|
-
}
|
|
931
|
+
noticeLive(live, log, homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
525
932
|
}
|
|
526
933
|
}
|
|
527
934
|
// Dequeuing and owing the wake are one write: no restart between posting
|
|
@@ -533,39 +940,7 @@ export function createAgentHomeDelivery({
|
|
|
533
940
|
{ wake: toWake }
|
|
534
941
|
);
|
|
535
942
|
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);
|
|
943
|
+
await relay.wakeOwed(homeId, session);
|
|
569
944
|
}
|
|
570
945
|
|
|
571
946
|
/** The single drainer of `homeId`; a call during a pass runs one more pass. */
|
|
@@ -598,17 +973,7 @@ export function createAgentHomeDelivery({
|
|
|
598
973
|
async function enqueue(row, result) {
|
|
599
974
|
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
600
975
|
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
|
-
});
|
|
976
|
+
await queue.add(homeId, routineResultItem(row, result));
|
|
612
977
|
void drain(homeId);
|
|
613
978
|
return homeId;
|
|
614
979
|
}
|
|
@@ -631,32 +996,17 @@ export function createAgentHomeDelivery({
|
|
|
631
996
|
/** Persist one report of a coding task session `agentId` created (session
|
|
632
997
|
* create) for its home thread, then start draining it. `status` 'started'
|
|
633
998
|
* 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
|
-
) {
|
|
999
|
+
async function enqueueTask(agentId, report) {
|
|
1000
|
+
const { runId, taskSessionId, watchId = '', from = '' } = report;
|
|
638
1001
|
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
639
1002
|
if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
status,
|
|
648
|
-
text: routineSummary(text),
|
|
649
|
-
// Any session other than a task session the agent created.
|
|
650
|
-
...(target === 'session' ? { target } : {}),
|
|
651
|
-
// The files the turn produced (handoff-attachments.mjs collectTurnArtifacts).
|
|
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
|
-
);
|
|
658
|
-
void drain(homeId);
|
|
659
|
-
return homeId;
|
|
1003
|
+
const item = taskReportItem(report);
|
|
1004
|
+
const unwatch = watchId ? String(watchId) : '';
|
|
1005
|
+
// A room member's own work reports to its member session, never its home.
|
|
1006
|
+
const target = roomMemberOfSession(from)?.agentId === agentId ? from : homeId;
|
|
1007
|
+
await queue.add(target, item, { unwatch });
|
|
1008
|
+
void drain(target);
|
|
1009
|
+
return target;
|
|
660
1010
|
}
|
|
661
1011
|
|
|
662
1012
|
/** Show `run`'s live-only row (routineRunNotice: `running`, or a result whose
|
|
@@ -667,23 +1017,10 @@ export function createAgentHomeDelivery({
|
|
|
667
1017
|
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
668
1018
|
if (!run?.runId || busy(homeId)) return false;
|
|
669
1019
|
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
|
-
}
|
|
1020
|
+
return noticeLive(live, log, homeId, notice) === true;
|
|
676
1021
|
}
|
|
677
1022
|
|
|
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
|
-
}
|
|
1023
|
+
const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
|
|
687
1024
|
|
|
688
1025
|
/** Retire the bookkeeping of deleted homes (and stale round counts). */
|
|
689
1026
|
async function prune() {
|
|
@@ -694,17 +1031,7 @@ export function createAgentHomeDelivery({
|
|
|
694
1031
|
* whose target sessions are proven deleted, and refuses the watch (so the
|
|
695
1032
|
* send claims no report) when that frees too few. */
|
|
696
1033
|
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
|
-
: [];
|
|
1034
|
+
const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
|
|
708
1035
|
if (drop.length) {
|
|
709
1036
|
log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
|
|
710
1037
|
}
|
|
@@ -732,6 +1059,7 @@ export function createAgentHomeDelivery({
|
|
|
732
1059
|
recover,
|
|
733
1060
|
watchTask,
|
|
734
1061
|
unwatchTask: (runId) => queue.unwatch(runId),
|
|
1062
|
+
followTask: (runId, submissionId) => queue.followWatch(runId, submissionId),
|
|
735
1063
|
taskWatches: () => queue.watches(),
|
|
736
1064
|
resetRounds: (homeId) => queue.resetRounds(homeId),
|
|
737
1065
|
};
|
|
@@ -748,6 +1076,7 @@ async function processDelivery() {
|
|
|
748
1076
|
{ createAgentEditorApi },
|
|
749
1077
|
{ probePath, PROBE_ABSENT },
|
|
750
1078
|
{ sessionPath },
|
|
1079
|
+
{ mixdogRoot },
|
|
751
1080
|
] = await Promise.all([
|
|
752
1081
|
import('../runtime/agent/orchestrator/session/store.mjs'),
|
|
753
1082
|
import('../runtime/agent/orchestrator/session/manager/runtime-liveness.mjs'),
|
|
@@ -755,6 +1084,7 @@ async function processDelivery() {
|
|
|
755
1084
|
import('./workflow-agents-api/agent-editor.mjs'),
|
|
756
1085
|
import('../runtime/agent/orchestrator/session/store/fs-probe.mjs'),
|
|
757
1086
|
import('../runtime/agent/orchestrator/session/store/session-paths.mjs'),
|
|
1087
|
+
import('../runtime/shared/plugin-paths.mjs'),
|
|
758
1088
|
]);
|
|
759
1089
|
// The editor's per-id definition probe: 'deleted' only when proven gone.
|
|
760
1090
|
const { agentDefinitionPresence } = createAgentEditorApi({ cfgMod: { getPluginData } });
|
|
@@ -764,6 +1094,9 @@ async function processDelivery() {
|
|
|
764
1094
|
turnInFlight: isSessionTurnInFlight,
|
|
765
1095
|
queue: createRoutineResultQueue(join(getPluginData(), 'routine-results.json')),
|
|
766
1096
|
agentPresence: (agentId) => agentDefinitionPresence([agentId])[agentId],
|
|
1097
|
+
// A home chat exists only for bots; only a definition read successfully
|
|
1098
|
+
// can prove an agent is none (readHomeAgent).
|
|
1099
|
+
homeAgent: (agentId) => readHomeAgent({ dataDir: getPluginData(), shippedRoot: mixdogRoot(), id: agentId }),
|
|
767
1100
|
// Deleting a session unlinks its record (session-delete.mjs) and leaves
|
|
768
1101
|
// no tombstone: only an absent file with no in-memory copy proves it.
|
|
769
1102
|
// An unreadable file probes 'unreadable' and stays.
|
|
@@ -810,6 +1143,11 @@ export async function unwatchAgentTask(runId) {
|
|
|
810
1143
|
return (await processDelivery()).unwatchTask(runId);
|
|
811
1144
|
}
|
|
812
1145
|
|
|
1146
|
+
/** One reporting turn's watch follows the turn submitted as `submissionId`. */
|
|
1147
|
+
export async function followAgentTask(runId, submissionId) {
|
|
1148
|
+
return (await processDelivery()).followTask(runId, submissionId);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
813
1151
|
/** A user's own message in `homeId`: its round trips with every target restart. */
|
|
814
1152
|
export async function resetAgentTaskRounds(homeId) {
|
|
815
1153
|
return (await processDelivery()).resetRounds(homeId);
|