mixdog 1.3.0 → 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/NOTICE.md +52 -9
- package/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +8 -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 +54 -4
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +17 -10
- 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/output-styles/common.md +13 -8
- package/src/rules/agent-mode/AGENT_MODE.md +9 -6
- package/src/rules/agent-mode/MIXY.md +11 -6
- 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 +81 -16
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
- 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-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
- 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 +19 -1
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -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-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
- 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-responses-input.mjs +3 -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/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -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 +5 -5
- 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 +17 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
- 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 +21 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -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/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 +156 -25
- 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/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
- 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/loop/usage.mjs +46 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- 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/provider-replay.mjs +50 -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-lifecycle.mjs +62 -47
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -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-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
- 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 +35 -4
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
- 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 +49 -14
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
- 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 +55 -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 +6 -3
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/effort-judge/model-manifest.json +7 -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/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-kind.mjs +152 -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-route-config.d.mts +1 -0
- package/src/runtime/shared/agent-route-config.mjs +22 -8
- package/src/runtime/shared/agent-store.mjs +149 -0
- 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 +457 -0
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/connector-catalog.d.mts +1 -2
- package/src/runtime/shared/connector-catalog.mjs +2 -4
- package/src/runtime/shared/connector-triggers.mjs +2 -0
- 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 +7 -1
- package/src/runtime/shared/error-presentation.mjs +113 -10
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/plain-preview.mjs +22 -17
- 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/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- 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/usage-readout-totals.mjs +15 -13
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +46 -44
- 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 +859 -136
- package/src/session-runtime/agent-room-delivery.mjs +167 -19
- package/src/session-runtime/agent-task-sessions.mjs +831 -69
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +58 -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 +16 -6
- 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 +78 -32
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- 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 +45 -6
- 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-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +11 -2
- package/src/session-runtime/model-route/set-route.mjs +82 -2
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -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 +21 -19
- 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/helpers.mjs +24 -5
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
- 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 +12 -6
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- 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 +20 -3
- package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
- package/src/session-runtime/setup-tool/settings-contract.mjs +37 -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 -358
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
- 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 +578 -301
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- 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 +60 -11
- package/src/standalone/daemon.mjs +34 -4
- 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/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +19 -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 +599 -342
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -4
- 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 -236
- 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 +4 -0
- package/src/tui/session/turn-live-call.mjs +11 -3
- package/src/tui/session/turn.mjs +36 -17
- 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,25 +17,48 @@
|
|
|
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
|
-
import {
|
|
28
|
+
import { randomUUID } from 'node:crypto';
|
|
29
|
+
import { readFileSync } from 'node:fs';
|
|
22
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';
|
|
23
35
|
import {
|
|
24
36
|
MIXY_AGENT_ID,
|
|
25
37
|
agentHomeSessionId,
|
|
26
38
|
agentIdOfHomeSession,
|
|
39
|
+
isAgentHomeSessionId,
|
|
27
40
|
} from '../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
28
41
|
import {
|
|
42
|
+
isRoomThreadSessionId,
|
|
43
|
+
roomIdOfSession,
|
|
44
|
+
roomMemberOfSession,
|
|
45
|
+
roomSessionId,
|
|
46
|
+
} from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
47
|
+
import {
|
|
48
|
+
TASK_LOOP_CAP,
|
|
29
49
|
agentTaskMessage,
|
|
50
|
+
agentTaskRelayFailedMessage,
|
|
30
51
|
agentTaskWakePrompt,
|
|
52
|
+
boundedReply,
|
|
31
53
|
routineOmittedMessage,
|
|
32
54
|
routineResultMessage,
|
|
33
55
|
routineResultNotice,
|
|
34
56
|
routineRunNotice,
|
|
35
57
|
routineSummary,
|
|
36
58
|
} from '../runtime/agent/orchestrator/session/routine-result.mjs';
|
|
37
|
-
import {
|
|
59
|
+
import { readJsonState, updateJsonAtomic } from '../runtime/shared/atomic-file.mjs';
|
|
38
60
|
import { ROUTINE_LIMITS } from '../runtime/shared/routine-limits.mjs';
|
|
61
|
+
import { submissionIdsOf, submissionOutcome } from './agent-task-sessions.mjs';
|
|
39
62
|
import {
|
|
40
63
|
heldRequestMessage,
|
|
41
64
|
heldRequestNotice,
|
|
@@ -49,14 +72,51 @@ function deliveredRow(item, messageId) {
|
|
|
49
72
|
return { message, notice: heldRequestNotice(message) };
|
|
50
73
|
}
|
|
51
74
|
if (item.kind === 'agent-task') {
|
|
52
|
-
// A turn's end (not its start or a wait for the user) wakes the agent
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
// A turn's end (not its start or a wait for the user) wakes the agent —
|
|
76
|
+
// except past the round-trip cap with another agent's own thread, where
|
|
77
|
+
// each wake could answer the other agent again without end.
|
|
78
|
+
const capped =
|
|
79
|
+
(isAgentHomeSessionId(item.taskSessionId) || isRoomThreadSessionId(item.taskSessionId)) &&
|
|
80
|
+
(item.round || 0) > TASK_LOOP_CAP;
|
|
81
|
+
const message = agentTaskMessage({ ...item, messageId, ...(capped ? { woken: false } : {}) });
|
|
82
|
+
return { message, notice: routineResultNotice(message), wake: item.roundTrip === true && !capped };
|
|
55
83
|
}
|
|
56
84
|
const message = routineResultMessage({ ...item, messageId });
|
|
57
85
|
return { message, notice: routineResultNotice(message) };
|
|
58
86
|
}
|
|
59
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
|
+
|
|
60
120
|
/** The agent a schedule belongs to; ownerless rows belong to Mixy. */
|
|
61
121
|
export function scheduleOwnerAgentId(schedule) {
|
|
62
122
|
const owner = typeof schedule?.ownerAgentId === 'string' ? schedule.ownerAgentId.trim() : '';
|
|
@@ -64,9 +124,11 @@ export function scheduleOwnerAgentId(schedule) {
|
|
|
64
124
|
}
|
|
65
125
|
|
|
66
126
|
let liveSurface = null;
|
|
67
|
-
/** `{ busy(sessionId), notice(sessionId, notice), wake(sessionId, prompt),
|
|
127
|
+
/** `{ busy(sessionId), notice(sessionId, notice), wake(sessionId, prompt, { id }),
|
|
68
128
|
* submit(sessionId, prompt, options), abort(sessionId) }` from the session
|
|
69
|
-
* service; `wake` starts one hidden turn
|
|
129
|
+
* service; `wake` starts one hidden turn with submission `id` (resolves
|
|
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
|
|
70
132
|
* (resolves whether it was accepted), `abort` stops the running turn. */
|
|
71
133
|
export function setAgentHomeLiveSurface(surface) {
|
|
72
134
|
liveSurface = surface || null;
|
|
@@ -80,48 +142,269 @@ export function agentHomeLiveSurface() {
|
|
|
80
142
|
// Run ids already posted, kept per home so a replayed run never posts twice,
|
|
81
143
|
// even into a thread that was reset ("대화 새로 시작") since.
|
|
82
144
|
const DELIVERED_LEDGER = 500;
|
|
145
|
+
// A target's round-trip count left idle this long starts over.
|
|
146
|
+
export const ROUND_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
147
|
+
// Per home: round-trip targets counted (least recently counted drop first),
|
|
148
|
+
// reporting turns watched at once, and posted results owed a wake.
|
|
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;
|
|
156
|
+
|
|
157
|
+
/** `homeId`'s wake obligations in `wakes`: posted task results not in a batch
|
|
158
|
+
* yet (`owed`), those past the cap collapsed into one marker (`overflow`:
|
|
159
|
+
* `{ count, oldest, newest }` run ids) the next batch takes over, and
|
|
160
|
+
* batches (`{ id, runIds, overflow? }`) stored before their wake turn was
|
|
161
|
+
* submitted under that id. */
|
|
162
|
+
function wakeState(wakes, homeId) {
|
|
163
|
+
const raw = wakes[homeId];
|
|
164
|
+
if (Array.isArray(raw)) return { owed: raw, batches: [], overflow: null };
|
|
165
|
+
return {
|
|
166
|
+
owed: Array.isArray(raw?.owed) ? raw.owed : [],
|
|
167
|
+
batches: Array.isArray(raw?.batches)
|
|
168
|
+
? raw.batches.filter((batch) => typeof batch?.id === 'string' && Array.isArray(batch.runIds))
|
|
169
|
+
: [],
|
|
170
|
+
overflow: raw?.overflow?.count > 0 ? raw.overflow : null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const owesWake = (state) => state.owed.length > 0 || state.batches.length > 0 || Boolean(state.overflow);
|
|
175
|
+
|
|
176
|
+
function withWakeState(doc, homeId, { owed, batches, overflow }) {
|
|
177
|
+
const wakes = { ...doc.wakes };
|
|
178
|
+
if (owesWake({ owed, batches, overflow })) wakes[homeId] = { owed, batches, ...(overflow ? { overflow } : {}) };
|
|
179
|
+
else delete wakes[homeId];
|
|
180
|
+
return { ...doc, wakes };
|
|
181
|
+
}
|
|
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
|
+
}
|
|
83
259
|
|
|
84
260
|
/** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
|
|
85
261
|
* omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
|
|
86
262
|
* runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
|
|
87
263
|
* counted under `omitted` so the thread gets one "n omitted" notice. */
|
|
88
|
-
export function createRoutineResultQueue(
|
|
264
|
+
export function createRoutineResultQueue(
|
|
265
|
+
filePath,
|
|
266
|
+
{ limit = ROUTINE_LIMITS.homeQueueLimit, now = Date.now, caps = QUEUE_CAPS } = {}
|
|
267
|
+
) {
|
|
89
268
|
const section = (doc, key) => (doc && typeof doc[key] === 'object' && doc[key]) || {};
|
|
269
|
+
const freshRounds = (own) => freshRoundCounts(own, now(), caps.roundTargets);
|
|
270
|
+
const tidyRounds = (rounds) =>
|
|
271
|
+
Object.fromEntries(
|
|
272
|
+
Object.entries(rounds || {})
|
|
273
|
+
.map(([homeId, own]) => [homeId, freshRounds(own)])
|
|
274
|
+
.filter(([, own]) => Object.keys(own).length)
|
|
275
|
+
);
|
|
90
276
|
const parts = (doc) => ({
|
|
91
277
|
homes: section(doc, 'homes'),
|
|
92
278
|
omitted: section(doc, 'omitted'),
|
|
93
279
|
delivered: section(doc, 'delivered'),
|
|
94
280
|
// Agent-initiated round trips per task session (session create).
|
|
95
281
|
rounds: section(doc, 'rounds'),
|
|
282
|
+
// Reporting turns not yet queued, by submission id (agent-task-sessions.mjs).
|
|
283
|
+
watches: section(doc, 'watches'),
|
|
284
|
+
// Posted task results whose wake turn was not taken yet, per home.
|
|
285
|
+
wakes: section(doc, 'wakes'),
|
|
96
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.
|
|
97
290
|
async function read() {
|
|
98
|
-
|
|
99
|
-
return parts(parseJsonText(await readFile(filePath, 'utf8')));
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (error?.code === 'ENOENT') return parts(null);
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
291
|
+
return parts(await readJsonState(filePath));
|
|
104
292
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
omitted[homeId] = { id: previous?.id || `omit_${next[0].runId}`, count: (previous?.count || 0) + count };
|
|
118
|
-
}
|
|
119
|
-
return { ...doc, homes: { ...doc.homes, [homeId]: next.slice(drop) }, omitted };
|
|
293
|
+
/** Read-modify-write under the file lock (`mutator(doc)` returns the next
|
|
294
|
+
* doc, or nothing to keep the file); every write also retires stale and
|
|
295
|
+
* surplus round counts. */
|
|
296
|
+
function update(mutator) {
|
|
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
|
+
);
|
|
120
305
|
}
|
|
306
|
+
const appended = (doc, homeId, items, omittedCount = 0) => appendedItems(doc, homeId, items, limit, omittedCount);
|
|
121
307
|
return {
|
|
122
308
|
async pendingHomes() {
|
|
123
|
-
const { homes, omitted } = await read();
|
|
124
|
-
return [
|
|
309
|
+
const { homes, omitted, wakes } = await read();
|
|
310
|
+
return [
|
|
311
|
+
...new Set([
|
|
312
|
+
...Object.keys(homes).filter((id) => homes[id]?.length),
|
|
313
|
+
...Object.keys(omitted),
|
|
314
|
+
...Object.keys(wakes).filter((id) => owesWake(wakeState(wakes, id))),
|
|
315
|
+
]),
|
|
316
|
+
];
|
|
317
|
+
},
|
|
318
|
+
/** `homeId`'s wake obligations: `{ owed, batches, overflow }` (wakeState). */
|
|
319
|
+
async wakes(homeId) {
|
|
320
|
+
return wakeState((await read()).wakes, homeId);
|
|
321
|
+
},
|
|
322
|
+
/** Store the owed results (and overflow marker) of `homeId` as one new
|
|
323
|
+
* batch under a fresh wake id, before its wake is submitted; null when
|
|
324
|
+
* none are owed. */
|
|
325
|
+
async batchWake(homeId) {
|
|
326
|
+
let batch = null;
|
|
327
|
+
await update((doc) => {
|
|
328
|
+
batch = null;
|
|
329
|
+
const state = wakeState(doc.wakes, homeId);
|
|
330
|
+
if (!state.owed.length && !state.overflow) return undefined;
|
|
331
|
+
batch = {
|
|
332
|
+
id: `awake_${randomUUID()}`,
|
|
333
|
+
runIds: state.owed,
|
|
334
|
+
...(state.overflow ? { overflow: state.overflow } : {}),
|
|
335
|
+
};
|
|
336
|
+
return withWakeState(doc, homeId, { owed: [], batches: [...state.batches, batch], overflow: null });
|
|
337
|
+
});
|
|
338
|
+
return batch;
|
|
339
|
+
},
|
|
340
|
+
/** The wake of batch `batchId` was taken (or is moot): it is owed no more. */
|
|
341
|
+
async woke(homeId, batchId) {
|
|
342
|
+
await update((doc) => {
|
|
343
|
+
const state = wakeState(doc.wakes, homeId);
|
|
344
|
+
const batches = state.batches.filter((batch) => batch.id !== batchId);
|
|
345
|
+
if (batches.length === state.batches.length) return undefined;
|
|
346
|
+
return withWakeState(doc, homeId, { ...state, batches });
|
|
347
|
+
});
|
|
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
|
+
},
|
|
374
|
+
/** Drop every wake obligation of `homeId` (its owner is gone). */
|
|
375
|
+
async dropWakes(homeId) {
|
|
376
|
+
await update((doc) =>
|
|
377
|
+
doc.wakes[homeId] ? withWakeState(doc, homeId, { owed: [], batches: [], overflow: null }) : undefined
|
|
378
|
+
);
|
|
379
|
+
},
|
|
380
|
+
/** Every home the rounds, wakes, posted ledger or watches name. */
|
|
381
|
+
async bookkeptHomes() {
|
|
382
|
+
const doc = await read();
|
|
383
|
+
const watched = Object.values(doc.watches).map((watch) => watch?.homeSessionId);
|
|
384
|
+
return [
|
|
385
|
+
...new Set(
|
|
386
|
+
[...Object.keys(doc.rounds), ...Object.keys(doc.wakes), ...Object.keys(doc.delivered), ...watched].filter(
|
|
387
|
+
(id) => typeof id === 'string' && id
|
|
388
|
+
)
|
|
389
|
+
),
|
|
390
|
+
];
|
|
391
|
+
},
|
|
392
|
+
/** Retire the rounds, wakes, posted ledger and watches of the `gone`
|
|
393
|
+
* homes (and, as every write does, stale round counts). */
|
|
394
|
+
async prune(gone) {
|
|
395
|
+
await update((doc) => {
|
|
396
|
+
const kept = (part) => Object.fromEntries(Object.entries(part).filter(([homeId]) => !gone.includes(homeId)));
|
|
397
|
+
const next = {
|
|
398
|
+
...doc,
|
|
399
|
+
rounds: tidyRounds(kept(doc.rounds)),
|
|
400
|
+
wakes: kept(doc.wakes),
|
|
401
|
+
delivered: kept(doc.delivered),
|
|
402
|
+
watches: Object.fromEntries(
|
|
403
|
+
Object.entries(doc.watches).filter(([, watch]) => !gone.includes(watch?.homeSessionId))
|
|
404
|
+
),
|
|
405
|
+
};
|
|
406
|
+
return JSON.stringify(next) === JSON.stringify(doc) ? undefined : next;
|
|
407
|
+
});
|
|
125
408
|
},
|
|
126
409
|
async items(homeId) {
|
|
127
410
|
return (await read()).homes[homeId] || [];
|
|
@@ -130,20 +413,74 @@ export function createRoutineResultQueue(filePath, { limit = ROUTINE_LIMITS.home
|
|
|
130
413
|
async omitted(homeId) {
|
|
131
414
|
return (await read()).omitted[homeId] || null;
|
|
132
415
|
},
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
416
|
+
/** Queue `item` for `homeId`; `unwatch` drops that watch in the same write,
|
|
417
|
+
* also when the item was queued or posted before. */
|
|
418
|
+
async add(homeId, item, { unwatch = '' } = {}) {
|
|
419
|
+
await update((doc) => {
|
|
420
|
+
let next = null;
|
|
421
|
+
if (item.roundTrip !== true) next = appended(doc, homeId, [item]);
|
|
422
|
+
else {
|
|
423
|
+
// A task turn's end counts one round trip, once per run id, per home
|
|
424
|
+
// and target: the user's next message in that home starts over
|
|
425
|
+
// (resetRounds), and so does a count idle past ROUND_TTL_MS.
|
|
426
|
+
const own = freshRounds(doc.rounds[homeId]);
|
|
427
|
+
const round = (own[item.taskSessionId]?.n || 0) + 1;
|
|
428
|
+
next = appended(doc, homeId, [{ ...item, round }]);
|
|
429
|
+
if (next) {
|
|
430
|
+
const counted = { ...own, [item.taskSessionId]: { n: round, at: now() } };
|
|
431
|
+
next = { ...next, rounds: { ...doc.rounds, [homeId]: counted } };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (!unwatch || !doc.watches[unwatch]) return next ?? undefined;
|
|
435
|
+
const watches = { ...doc.watches };
|
|
436
|
+
delete watches[unwatch];
|
|
437
|
+
return { ...(next || doc), watches };
|
|
438
|
+
});
|
|
439
|
+
},
|
|
440
|
+
/** Keep `watch` ({ runId, agentId, homeSessionId, taskSessionId, title,
|
|
441
|
+
* target? }) until its report is queued, dropping the watches `drop` in
|
|
442
|
+
* the same write. Throws when its home already keeps `caps.watches`. */
|
|
443
|
+
async watch(watch, { drop = [] } = {}) {
|
|
444
|
+
await update((doc) => {
|
|
445
|
+
const watches = { ...doc.watches };
|
|
446
|
+
for (const runId of drop) delete watches[runId];
|
|
447
|
+
const own = Object.values(watches).filter(
|
|
448
|
+
(kept) => kept?.homeSessionId === watch.homeSessionId && kept.runId !== watch.runId
|
|
449
|
+
);
|
|
450
|
+
if (own.length >= caps.watches) {
|
|
451
|
+
throw Object.assign(new Error(`${watch.homeSessionId} already waits on ${own.length} session reports`), {
|
|
452
|
+
code: 'REPORT_WATCH_CAP',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return { ...doc, watches: { ...watches, [watch.runId]: watch } };
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
watchCap: caps.watches,
|
|
459
|
+
/** Every stored watch, oldest first. */
|
|
460
|
+
async watches() {
|
|
461
|
+
return Object.values((await read()).watches);
|
|
462
|
+
},
|
|
463
|
+
/** Drop the watch of `runId` (its turn was never taken). */
|
|
464
|
+
async unwatch(runId) {
|
|
465
|
+
await update((doc) => {
|
|
466
|
+
if (!doc.watches[runId]) return undefined;
|
|
467
|
+
const watches = { ...doc.watches };
|
|
468
|
+
delete watches[runId];
|
|
469
|
+
return { ...doc, watches };
|
|
470
|
+
});
|
|
471
|
+
},
|
|
472
|
+
/** The user spoke in `homeId`: its round trips count from zero again. */
|
|
473
|
+
async resetRounds(homeId) {
|
|
474
|
+
await update((doc) => {
|
|
475
|
+
if (!doc.rounds[homeId]) return undefined;
|
|
476
|
+
const rounds = { ...doc.rounds };
|
|
477
|
+
delete rounds[homeId];
|
|
478
|
+
return { ...doc, rounds };
|
|
141
479
|
});
|
|
142
480
|
},
|
|
143
481
|
/** Hand every queued item (and omission) of `fromHome` to `toHome`. */
|
|
144
482
|
async move(fromHome, toHome) {
|
|
145
|
-
await
|
|
146
|
-
const doc = parts(raw);
|
|
483
|
+
await update((doc) => {
|
|
147
484
|
const items = doc.homes[fromHome] || [];
|
|
148
485
|
const dropped = doc.omitted[fromHome]?.count || 0;
|
|
149
486
|
const next = appended(doc, toHome, items, dropped) || doc;
|
|
@@ -154,32 +491,300 @@ export function createRoutineResultQueue(filePath, { limit = ROUTINE_LIMITS.home
|
|
|
154
491
|
return { ...next, homes, omitted };
|
|
155
492
|
});
|
|
156
493
|
},
|
|
157
|
-
/** Dequeue the posted `runIds` (and the posted omission notice `omitted`)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const current = omittedNow[homeId];
|
|
169
|
-
if (omitted && current?.id === omitted.id) {
|
|
170
|
-
// More dropped while this pass posted: they get a notice of their own.
|
|
171
|
-
const left = current.count - omitted.count;
|
|
172
|
-
if (left > 0) omittedNow[homeId] = { id: `${current.id}.${omitted.count}`, count: left };
|
|
173
|
-
else delete omittedNow[homeId];
|
|
174
|
-
}
|
|
175
|
-
return { ...doc, homes, omitted: omittedNow, delivered };
|
|
494
|
+
/** Dequeue the posted `runIds` (and the posted omission notice `omitted`);
|
|
495
|
+
* those in `wake` stay owed a wake (`wakes`) in the same write, at most
|
|
496
|
+
* `caps.wakeRunIds` run ids per home: past it the oldest owed collapse
|
|
497
|
+
* into the overflow marker the next batch takes over (`overflowed`
|
|
498
|
+
* counts them). Stored batches never change. */
|
|
499
|
+
async remove(homeId, runIds, omitted = null, { wake = [] } = {}) {
|
|
500
|
+
let overflowed = 0;
|
|
501
|
+
await update((doc) => {
|
|
502
|
+
let wakes;
|
|
503
|
+
({ wakes, overflowed } = owedWakes(doc, homeId, wake, caps.wakeRunIds));
|
|
504
|
+
return { ...withDequeued(doc, homeId, runIds, omitted), wakes };
|
|
176
505
|
});
|
|
506
|
+
return { overflowed };
|
|
177
507
|
},
|
|
178
508
|
};
|
|
179
509
|
}
|
|
180
510
|
|
|
181
511
|
const messageIdOf = (runId) => `routine_${runId}`;
|
|
182
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
|
+
|
|
183
788
|
export function createAgentHomeDelivery({
|
|
184
789
|
loadSession,
|
|
185
790
|
saveSession,
|
|
@@ -191,6 +796,25 @@ export function createAgentHomeDelivery({
|
|
|
191
796
|
missingRetryMs = 60_000,
|
|
192
797
|
// 'present' | 'deleted' | 'unknown' per agent id: a deleted owner's results go to Mixy.
|
|
193
798
|
agentPresence = () => 'unknown',
|
|
799
|
+
// The same per room id. The daemon has no room store (rooms live in the
|
|
800
|
+
// desktop host), so by default no room counts as deleted.
|
|
801
|
+
roomPresence = () => 'unknown',
|
|
802
|
+
// True only on positive evidence that session `id` was deleted; an
|
|
803
|
+
// unreadable or unknown record is not deleted.
|
|
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),
|
|
194
818
|
log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
|
|
195
819
|
}) {
|
|
196
820
|
const busy = (sessionId) => {
|
|
@@ -205,6 +829,20 @@ export function createAgentHomeDelivery({
|
|
|
205
829
|
const again = new Set(); // homes enqueued to while their pass ran
|
|
206
830
|
const waiting = new Set(); // homes with a retry timer armed
|
|
207
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
|
+
});
|
|
208
846
|
|
|
209
847
|
/** A held request waits on the user, possibly raised by the very turn that
|
|
210
848
|
* keeps its thread busy (a routine turn): show it live at once; the stored
|
|
@@ -215,11 +853,7 @@ export function createAgentHomeDelivery({
|
|
|
215
853
|
const messageId = messageIdOf(item.runId);
|
|
216
854
|
if (shownLive.has(messageId)) continue;
|
|
217
855
|
shownLive.add(messageId);
|
|
218
|
-
|
|
219
|
-
live()?.notice?.(homeId, { ...deliveredRow(item, messageId).notice, messageId });
|
|
220
|
-
} catch (error) {
|
|
221
|
-
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
222
|
-
}
|
|
856
|
+
noticeLive(live, log, homeId, { ...deliveredRow(item, messageId).notice, messageId });
|
|
223
857
|
}
|
|
224
858
|
}
|
|
225
859
|
|
|
@@ -232,17 +866,59 @@ export function createAgentHomeDelivery({
|
|
|
232
866
|
}, ms);
|
|
233
867
|
}
|
|
234
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
|
+
|
|
235
908
|
/** Post every queued result of `homeId` if its thread is idle; else retry later. */
|
|
236
909
|
async function drainOnce(homeId) {
|
|
910
|
+
if (roomIdOfSession(homeId)) return drainRoomLog(homeId);
|
|
237
911
|
const items = await queue.items(homeId);
|
|
238
912
|
const omitted = await queue.omitted(homeId);
|
|
239
|
-
|
|
913
|
+
const owed = owesWake(await queue.wakes(homeId));
|
|
914
|
+
if (!items.length && !omitted && !owed) return;
|
|
240
915
|
const agentId = agentIdOfHomeSession(homeId);
|
|
241
916
|
if (agentId && agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted') {
|
|
242
917
|
// The owner is gone (its home archived): Mixy, the default owner, gets them.
|
|
243
918
|
const mixyHome = agentHomeSessionId(MIXY_AGENT_ID);
|
|
244
919
|
log(`${items.length} result(s) of the deleted agent ${agentId} go to ${mixyHome}`);
|
|
245
920
|
await queue.move(homeId, mixyHome);
|
|
921
|
+
if (owed) await queue.dropWakes(homeId);
|
|
246
922
|
void drain(mixyHome);
|
|
247
923
|
return;
|
|
248
924
|
}
|
|
@@ -253,53 +929,35 @@ export function createAgentHomeDelivery({
|
|
|
253
929
|
// From here to the push nothing awaits, so no turn can start in between.
|
|
254
930
|
const session = loadSession(homeId);
|
|
255
931
|
if (!session || session.closed === true) {
|
|
256
|
-
|
|
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
|
+
}
|
|
257
939
|
return later(homeId, missingRetryMs);
|
|
258
940
|
}
|
|
259
941
|
if (!Array.isArray(session.messages)) session.messages = [];
|
|
260
|
-
const
|
|
261
|
-
const fresh = [];
|
|
262
|
-
const omittedId = omitted ? messageIdOf(omitted.id) : '';
|
|
263
|
-
if (omitted && !posted.has(omittedId)) {
|
|
264
|
-
const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
|
|
265
|
-
session.messages.push(message);
|
|
266
|
-
fresh.push(message);
|
|
267
|
-
}
|
|
268
|
-
const woken = [];
|
|
269
|
-
for (const item of items) {
|
|
270
|
-
const row = deliveredRow(item, messageIdOf(item.runId));
|
|
271
|
-
// Every dequeued task result wakes once, posted in this pass or before.
|
|
272
|
-
if (row.wake) woken.push(row.message);
|
|
273
|
-
if (posted.has(messageIdOf(item.runId))) continue;
|
|
274
|
-
session.messages.push(row.message);
|
|
275
|
-
fresh.push(row);
|
|
276
|
-
}
|
|
942
|
+
const { fresh, toWake } = postQueuedRows(session, items, omitted);
|
|
277
943
|
for (const item of items) shownLive.delete(messageIdOf(item.runId));
|
|
278
944
|
if (fresh.length) {
|
|
279
945
|
session.updatedAt = Date.now();
|
|
280
946
|
await saveSession(session);
|
|
281
947
|
for (const { message, notice } of fresh) {
|
|
282
|
-
|
|
283
|
-
live()?.notice?.(homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
284
|
-
} catch (error) {
|
|
285
|
-
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
286
|
-
}
|
|
948
|
+
noticeLive(live, log, homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
287
949
|
}
|
|
288
950
|
}
|
|
289
|
-
|
|
951
|
+
// Dequeuing and owing the wake are one write: no restart between posting
|
|
952
|
+
// and waking loses the wake.
|
|
953
|
+
const { overflowed } = await queue.remove(
|
|
290
954
|
homeId,
|
|
291
955
|
items.map((item) => item.runId),
|
|
292
|
-
omitted
|
|
956
|
+
omitted,
|
|
957
|
+
{ wake: toWake }
|
|
293
958
|
);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (woken.length) {
|
|
297
|
-
try {
|
|
298
|
-
await live()?.wake?.(homeId, agentTaskWakePrompt(woken));
|
|
299
|
-
} catch (error) {
|
|
300
|
-
log(`waking ${homeId} failed: ${error?.message || error}`);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
959
|
+
if (overflowed) log(`${homeId} is at its wake cap: ${overflowed} result(s) ride the next wake as a count`);
|
|
960
|
+
await relay.wakeOwed(homeId, session);
|
|
303
961
|
}
|
|
304
962
|
|
|
305
963
|
/** The single drainer of `homeId`; a call during a pass runs one more pass. */
|
|
@@ -332,17 +990,7 @@ export function createAgentHomeDelivery({
|
|
|
332
990
|
async function enqueue(row, result) {
|
|
333
991
|
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
334
992
|
if (!result?.runId) throw new Error('routine result: runId required');
|
|
335
|
-
await queue.add(homeId,
|
|
336
|
-
runId: String(result.runId),
|
|
337
|
-
kind: result.kind === 'webhook' ? 'webhook' : 'schedule',
|
|
338
|
-
name: row.name,
|
|
339
|
-
ok: result.ok === true,
|
|
340
|
-
text: routineSummary(result.text),
|
|
341
|
-
runSessionId: result.runSessionId || null,
|
|
342
|
-
...(result.reason ? { reason: String(result.reason) } : {}),
|
|
343
|
-
...(result.paused === true ? { paused: true } : {}),
|
|
344
|
-
at: Date.now(),
|
|
345
|
-
});
|
|
993
|
+
await queue.add(homeId, routineResultItem(row, result));
|
|
346
994
|
void drain(homeId);
|
|
347
995
|
return homeId;
|
|
348
996
|
}
|
|
@@ -365,19 +1013,22 @@ export function createAgentHomeDelivery({
|
|
|
365
1013
|
/** Persist one report of a coding task session `agentId` created (session
|
|
366
1014
|
* create) for its home thread, then start draining it. `status` 'started'
|
|
367
1015
|
* and 'blocked' only post a line; a turn's end also wakes the agent. */
|
|
368
|
-
async function enqueueTask(agentId,
|
|
1016
|
+
async function enqueueTask(agentId, report) {
|
|
1017
|
+
const { runId, taskSessionId, status, text = '', watchId = '', from = '' } = report;
|
|
369
1018
|
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
370
1019
|
if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
status
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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 });
|
|
381
1032
|
void drain(homeId);
|
|
382
1033
|
return homeId;
|
|
383
1034
|
}
|
|
@@ -390,35 +1041,74 @@ export function createAgentHomeDelivery({
|
|
|
390
1041
|
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
391
1042
|
if (!run?.runId || busy(homeId)) return false;
|
|
392
1043
|
const notice = routineRunNotice({ ...run, name: row.name, messageId: messageIdOf(run.runId) });
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
1044
|
+
return noticeLive(live, log, homeId, notice) === true;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const homeDeleted = (homeId) => homeProvenDeleted(homeId, agentPresence, roomPresence);
|
|
1048
|
+
|
|
1049
|
+
/** Retire the bookkeeping of deleted homes (and stale round counts). */
|
|
1050
|
+
async function prune() {
|
|
1051
|
+
await queue.prune((await queue.bookkeptHomes()).filter(homeDeleted));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** Store `watch`; a home at its watch cap first gives up its oldest watches
|
|
1055
|
+
* whose target sessions are proven deleted, and refuses the watch (so the
|
|
1056
|
+
* send claims no report) when that frees too few. */
|
|
1057
|
+
async function watchTask(watch) {
|
|
1058
|
+
const drop = watchesToDrop(await queue.watches(), watch, queue.watchCap, sessionDeleted);
|
|
1059
|
+
if (drop.length) {
|
|
1060
|
+
log(`${watch.homeSessionId} is at its watch cap: dropped ${drop.length} watch(es) of deleted sessions`);
|
|
398
1061
|
}
|
|
1062
|
+
await queue.watch(watch, { drop });
|
|
399
1063
|
}
|
|
400
1064
|
|
|
401
|
-
/** Drain every queue left by an earlier process. */
|
|
1065
|
+
/** Drain every queue left by an earlier process, after retiring stale bookkeeping. */
|
|
402
1066
|
async function recover() {
|
|
1067
|
+
try {
|
|
1068
|
+
await prune();
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
log(`pruning the routine results failed: ${error?.message || error}`);
|
|
1071
|
+
}
|
|
403
1072
|
const homes = await queue.pendingHomes();
|
|
404
1073
|
for (const homeId of homes) void drain(homeId);
|
|
405
1074
|
return homes;
|
|
406
1075
|
}
|
|
407
1076
|
|
|
408
|
-
return {
|
|
1077
|
+
return {
|
|
1078
|
+
enqueue,
|
|
1079
|
+
enqueueHeld,
|
|
1080
|
+
enqueueTask,
|
|
1081
|
+
announce,
|
|
1082
|
+
drain,
|
|
1083
|
+
recover,
|
|
1084
|
+
watchTask,
|
|
1085
|
+
unwatchTask: (runId) => queue.unwatch(runId),
|
|
1086
|
+
taskWatches: () => queue.watches(),
|
|
1087
|
+
resetRounds: (homeId) => queue.resetRounds(homeId),
|
|
1088
|
+
};
|
|
409
1089
|
}
|
|
410
1090
|
|
|
411
1091
|
let defaultDelivery = null;
|
|
412
1092
|
/** The process delivery over the session store and data dir; built on first use. */
|
|
413
1093
|
async function processDelivery() {
|
|
414
1094
|
if (!defaultDelivery) {
|
|
415
|
-
const [
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
1095
|
+
const [
|
|
1096
|
+
{ loadSession, saveSessionAsync },
|
|
1097
|
+
{ isSessionTurnInFlight },
|
|
1098
|
+
{ getPluginData },
|
|
1099
|
+
{ createAgentEditorApi },
|
|
1100
|
+
{ probePath, PROBE_ABSENT },
|
|
1101
|
+
{ sessionPath },
|
|
1102
|
+
{ mixdogRoot },
|
|
1103
|
+
] = await Promise.all([
|
|
1104
|
+
import('../runtime/agent/orchestrator/session/store.mjs'),
|
|
1105
|
+
import('../runtime/agent/orchestrator/session/manager/runtime-liveness.mjs'),
|
|
1106
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
1107
|
+
import('./workflow-agents-api/agent-editor.mjs'),
|
|
1108
|
+
import('../runtime/agent/orchestrator/session/store/fs-probe.mjs'),
|
|
1109
|
+
import('../runtime/agent/orchestrator/session/store/session-paths.mjs'),
|
|
1110
|
+
import('../runtime/shared/plugin-paths.mjs'),
|
|
1111
|
+
]);
|
|
422
1112
|
// The editor's per-id definition probe: 'deleted' only when proven gone.
|
|
423
1113
|
const { agentDefinitionPresence } = createAgentEditorApi({ cfgMod: { getPluginData } });
|
|
424
1114
|
defaultDelivery = createAgentHomeDelivery({
|
|
@@ -427,6 +1117,19 @@ async function processDelivery() {
|
|
|
427
1117
|
turnInFlight: isSessionTurnInFlight,
|
|
428
1118
|
queue: createRoutineResultQueue(join(getPluginData(), 'routine-results.json')),
|
|
429
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 }),
|
|
1123
|
+
// Deleting a session unlinks its record (session-delete.mjs) and leaves
|
|
1124
|
+
// no tombstone: only an absent file with no in-memory copy proves it.
|
|
1125
|
+
// An unreadable file probes 'unreadable' and stays.
|
|
1126
|
+
sessionDeleted: (sessionId) => {
|
|
1127
|
+
try {
|
|
1128
|
+
return probePath(sessionPath(sessionId)).state === PROBE_ABSENT && !loadSession(sessionId);
|
|
1129
|
+
} catch {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
430
1133
|
});
|
|
431
1134
|
}
|
|
432
1135
|
return defaultDelivery;
|
|
@@ -453,6 +1156,26 @@ export async function deliverAgentTask(agentId, report) {
|
|
|
453
1156
|
return (await processDelivery()).enqueueTask(agentId, report);
|
|
454
1157
|
}
|
|
455
1158
|
|
|
1159
|
+
/** Keep one reporting turn's watch until its report is queued. */
|
|
1160
|
+
export async function watchAgentTask(watch) {
|
|
1161
|
+
return (await processDelivery()).watchTask(watch);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/** Drop one reporting turn's watch (its turn was never taken). */
|
|
1165
|
+
export async function unwatchAgentTask(runId) {
|
|
1166
|
+
return (await processDelivery()).unwatchTask(runId);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/** A user's own message in `homeId`: its round trips with every target restart. */
|
|
1170
|
+
export async function resetAgentTaskRounds(homeId) {
|
|
1171
|
+
return (await processDelivery()).resetRounds(homeId);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/** The reporting turns whose report was not queued yet. */
|
|
1175
|
+
export async function agentTaskWatches() {
|
|
1176
|
+
return (await processDelivery()).taskWatches();
|
|
1177
|
+
}
|
|
1178
|
+
|
|
456
1179
|
/** Daemon start: post the results an earlier process left queued. */
|
|
457
1180
|
export async function recoverScheduleResults() {
|
|
458
1181
|
return (await processDelivery()).recover();
|