mixdog 1.3.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +4 -2
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +17 -1
- package/src/defaults/skills/browser-use/SKILL.md +59 -11
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +10 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +7 -1
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +10 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +448 -185
- package/src/session-runtime/agent-task-sessions.mjs +329 -180
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +124 -188
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/dist/index.mjs +58 -64
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +17 -1
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +19 -2
- package/src/tui/session/turn.mjs +1 -0
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -158,8 +158,9 @@ function runtimePaths(verDir) {
|
|
|
158
158
|
// Download with retry
|
|
159
159
|
// ---------------------------------------------------------------------------
|
|
160
160
|
|
|
161
|
-
async function downloadWithRetry(url, destPath, expectedBytes) {
|
|
161
|
+
async function downloadWithRetry(url, destPath, expectedBytes, signal) {
|
|
162
162
|
return downloadToFileWithRetry(url, destPath, {
|
|
163
|
+
signal,
|
|
163
164
|
maxBytes: expectedBytes,
|
|
164
165
|
expectedBytes,
|
|
165
166
|
label: 'memory runtime download',
|
|
@@ -532,11 +533,12 @@ function publishRuntimeDir(runtimeBaseDir, stagingDir, key, sha256) {
|
|
|
532
533
|
* staging lock, ending with the stale-version GC. Returns the installed
|
|
533
534
|
* runtime-{version} directory.
|
|
534
535
|
*/
|
|
535
|
-
async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey }) {
|
|
536
|
+
async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey, signal }) {
|
|
536
537
|
// tar is only required for the download/extract path. Probe here (not at
|
|
537
538
|
// ensureRuntime entry) so a machine without tar can still reuse an
|
|
538
539
|
// already-extracted, sha-matching cached runtime via the fast path.
|
|
539
540
|
await probeTar();
|
|
541
|
+
signal?.throwIfAborted();
|
|
540
542
|
|
|
541
543
|
__mixdogMemoryLog(`[runtime-fetcher] downloading runtime ${version} for ${pkey} (~${size} bytes) …\n`);
|
|
542
544
|
|
|
@@ -566,7 +568,7 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
|
|
|
566
568
|
`[runtime-fetcher] retrying runtime download from fallback release repo — ${candidateUrl}\n`
|
|
567
569
|
);
|
|
568
570
|
}
|
|
569
|
-
await downloadWithRetry(candidateUrl, tarPath, size);
|
|
571
|
+
await downloadWithRetry(candidateUrl, tarPath, size, signal);
|
|
570
572
|
await verifySha256(tarPath, sha256);
|
|
571
573
|
downloadOk = true;
|
|
572
574
|
break;
|
|
@@ -575,9 +577,12 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
|
|
|
575
577
|
try {
|
|
576
578
|
rmSync(tarPath, { force: true });
|
|
577
579
|
} catch {}
|
|
580
|
+
// A cancelled install tries no fallback repository.
|
|
581
|
+
signal?.throwIfAborted();
|
|
578
582
|
}
|
|
579
583
|
}
|
|
580
584
|
if (!downloadOk && lastDownloadErr) throw lastDownloadErr;
|
|
585
|
+
signal?.throwIfAborted();
|
|
581
586
|
await _extractRuntimeTarGz(tarPath, stagingDir, stagingDir);
|
|
582
587
|
} finally {
|
|
583
588
|
try {
|
|
@@ -591,6 +596,11 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
|
|
|
591
596
|
} catch {}
|
|
592
597
|
throw new Error(`[runtime-fetcher] download or verify failed for ${version}`);
|
|
593
598
|
}
|
|
599
|
+
// Cancelled after the extract: drop the unpublished tree, publish nothing.
|
|
600
|
+
if (signal?.aborted) {
|
|
601
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
602
|
+
signal.throwIfAborted();
|
|
603
|
+
}
|
|
594
604
|
|
|
595
605
|
// Stamp sha256 inside staging dir.
|
|
596
606
|
writeFileAtomicSync(join(stagingDir, '.version-sha256'), sha256);
|
|
@@ -615,6 +625,11 @@ async function installRuntimeVersion({ runtimeBaseDir, url, sha256, size, versio
|
|
|
615
625
|
gcRuntimeDir(runtimeBaseDir, key);
|
|
616
626
|
} finally {
|
|
617
627
|
releaseStagingLock(stagingLock);
|
|
628
|
+
// Publishing is synchronous past the last cancellation check, so an
|
|
629
|
+
// aborted signal here means nothing was published.
|
|
630
|
+
if (signal?.aborted) {
|
|
631
|
+
__mixdogMemoryLog(`[runtime-fetcher] install of ${version} cancelled; its staging was removed\n`);
|
|
632
|
+
}
|
|
618
633
|
}
|
|
619
634
|
|
|
620
635
|
__mixdogMemoryLog(`[runtime-fetcher] runtime ready at ${verDir}\n`);
|
|
@@ -631,7 +646,10 @@ export const _runtimeLayoutInternals = {
|
|
|
631
646
|
runtimeInstallKey,
|
|
632
647
|
};
|
|
633
648
|
|
|
634
|
-
|
|
649
|
+
/** `signal` cancels a download/install in progress: its retry waits end, its
|
|
650
|
+
* partial files and staging lock are removed and nothing is published, so the
|
|
651
|
+
* next call starts a fresh install. */
|
|
652
|
+
export async function ensureRuntime(dataDir, { signal = null } = {}) {
|
|
635
653
|
const key = resolve(dataDir);
|
|
636
654
|
if (runtimeCache.has(key)) return runtimeCache.get(key);
|
|
637
655
|
|
|
@@ -655,7 +673,7 @@ export async function ensureRuntime(dataDir) {
|
|
|
655
673
|
|
|
656
674
|
const verDir =
|
|
657
675
|
cachedRuntimeDir(runtimeBaseDir, version, sha256) ??
|
|
658
|
-
(await installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey }));
|
|
676
|
+
(await installRuntimeVersion({ runtimeBaseDir, url, sha256, size, version, pkey, signal }));
|
|
659
677
|
|
|
660
678
|
const result = { runtimeDir: verDir, ...runtimePaths(verDir), version };
|
|
661
679
|
runtimeCache.set(key, result);
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Rows are append-only: compaction may drop old messages from a session file,
|
|
8
8
|
* but their search rows stay, so earlier conversation remains findable until
|
|
9
|
-
* the session itself is deleted.
|
|
9
|
+
* the session itself is deleted. The one rewrite: a value marked sensitive
|
|
10
|
+
* replaces the rows that hold it (replaceSessionSearchMessages).
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
import { randomBytes } from 'node:crypto';
|
|
14
|
+
|
|
12
15
|
import { ensurePgInstance, withSchemaBootstrapLock } from '../memory/lib/pg/adapter.mjs';
|
|
13
16
|
import { buildFtsPrefixQuery } from '../memory/lib/memory-text-utils.mjs';
|
|
14
17
|
import {
|
|
@@ -91,13 +94,9 @@ export function sessionSearchRows(sessionId, messages) {
|
|
|
91
94
|
return rows;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
* of newly stored rows. Re-sending an already stored message is a no-op. */
|
|
96
|
-
export async function appendSessionSearchMessages(sessionId, messages, { dataDir } = {}) {
|
|
97
|
-
const rows = sessionSearchRows(sessionId, messages);
|
|
97
|
+
async function insertRows(queryable, rows) {
|
|
98
98
|
if (rows.length === 0) return 0;
|
|
99
|
-
const
|
|
100
|
-
const result = await db.query(
|
|
99
|
+
const result = await queryable.query(
|
|
101
100
|
`INSERT INTO session_search.messages (source_ref, session_id, ordinal, role, ts, content)
|
|
102
101
|
SELECT * FROM unnest($1::text[], $2::text[], $3::int[], $4::text[], $5::bigint[], $6::text[])
|
|
103
102
|
ON CONFLICT (source_ref) DO NOTHING`,
|
|
@@ -113,8 +112,73 @@ export async function appendSessionSearchMessages(sessionId, messages, { dataDir
|
|
|
113
112
|
return Number(result?.rowCount) || 0;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
/** Idempotent append of a transcript's searchable messages; returns the count
|
|
116
|
+
* of newly stored rows. Re-sending an already stored message is a no-op. */
|
|
117
|
+
export async function appendSessionSearchMessages(sessionId, messages, { dataDir, signal } = {}) {
|
|
118
|
+
const rows = sessionSearchRows(sessionId, messages);
|
|
119
|
+
if (rows.length === 0) return 0;
|
|
120
|
+
const db = await getDb(dataDir, { signal });
|
|
121
|
+
return insertRows(db, rows);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** In one transaction: every stored row of `sessionId` whose content
|
|
125
|
+
* `redactText` changes is deleted, the rows of the transcript as stored
|
|
126
|
+
* (`redactedMessages`) are written, and a deleted row that is no message of
|
|
127
|
+
* the transcript is written again with its content redacted.
|
|
128
|
+
*
|
|
129
|
+
* A row is matched to its message by the row's own identity
|
|
130
|
+
* (stableSessionSourceRef: role, original timestamp or else ordinal, tool
|
|
131
|
+
* ids, content): the id `plainMessages` — the same transcript before
|
|
132
|
+
* redaction — gives that message. Every other deleted row (its message was
|
|
133
|
+
* compacted away, or is under an identity the transcript no longer has) has
|
|
134
|
+
* no message to derive an identity from; its replacement gets a fresh random
|
|
135
|
+
* id, so no two rows ever merge and no id is derived from the plaintext.
|
|
136
|
+
* Returns the count of deleted rows.
|
|
137
|
+
*
|
|
138
|
+
* FOR UPDATE locks only the rows that exist: a write of the same session still
|
|
139
|
+
* in flight is not seen, so callers must not overlap it with this one
|
|
140
|
+
* (session-search-ingest.mjs runs a session's writes one at a time). */
|
|
141
|
+
export async function replaceSessionSearchMessages(
|
|
142
|
+
sessionId,
|
|
143
|
+
plainMessages,
|
|
144
|
+
redactedMessages,
|
|
145
|
+
redactText,
|
|
146
|
+
{ dataDir, signal } = {}
|
|
147
|
+
) {
|
|
148
|
+
const rows = sessionSearchRows(sessionId, redactedMessages);
|
|
149
|
+
const previous = new Set(sessionSearchRows(sessionId, plainMessages).map((row) => row.sourceRef));
|
|
150
|
+
const db = await getDb(dataDir, { signal });
|
|
151
|
+
return db.transaction(async (tx) => {
|
|
152
|
+
const { rows: stored } = await tx.query(
|
|
153
|
+
`SELECT source_ref, ordinal, role, ts, content FROM session_search.messages
|
|
154
|
+
WHERE session_id = $1 FOR UPDATE`,
|
|
155
|
+
[sessionId]
|
|
156
|
+
);
|
|
157
|
+
const changed = stored
|
|
158
|
+
.map((row) => ({ row, content: redactText(String(row.content)) }))
|
|
159
|
+
.filter(({ row, content }) => content !== row.content);
|
|
160
|
+
if (changed.length) {
|
|
161
|
+
await tx.query(`DELETE FROM session_search.messages WHERE source_ref = ANY($1::text[])`, [
|
|
162
|
+
changed.map(({ row }) => row.source_ref),
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
const kept = changed
|
|
166
|
+
.filter(({ row }) => !previous.has(row.source_ref))
|
|
167
|
+
.map(({ row, content }) => ({
|
|
168
|
+
sourceRef: `session:${sessionId}:${randomBytes(12).toString('hex')}`,
|
|
169
|
+
sessionId,
|
|
170
|
+
ordinal: Number(row.ordinal),
|
|
171
|
+
role: row.role,
|
|
172
|
+
ts: row.ts == null ? null : Number(row.ts),
|
|
173
|
+
content,
|
|
174
|
+
}));
|
|
175
|
+
await insertRows(tx, [...rows, ...kept]);
|
|
176
|
+
return changed.length;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function deleteSessionSearchMessages(sessionId, { dataDir, signal } = {}) {
|
|
181
|
+
const db = await getDb(dataDir, { signal });
|
|
118
182
|
await db.query(`DELETE FROM session_search.messages WHERE session_id = $1`, [sessionId]);
|
|
119
183
|
await db.query(`DELETE FROM session_search.backfill WHERE session_id = $1`, [sessionId]);
|
|
120
184
|
}
|
|
@@ -2,19 +2,32 @@
|
|
|
2
2
|
* Keeps the PG session-search copy (session-search-db.mjs) in step with the
|
|
3
3
|
* session store: per-turn ingest, a once-per-run backfill of stored sessions,
|
|
4
4
|
* and deletion. Everything is best-effort and never touches the memory runtime.
|
|
5
|
+
*
|
|
6
|
+
* A session's writes run one at a time, in the order they were issued
|
|
7
|
+
* (serialized). That order is what keeps a value marked sensitive out of the
|
|
8
|
+
* index: the replacement that redacts the stored rows
|
|
9
|
+
* (replaceSessionSearchMessages) locks only rows that already exist, so an
|
|
10
|
+
* earlier plaintext append still in flight would otherwise land after it and
|
|
11
|
+
* bring the value back. A write also redacts with the values known when it
|
|
12
|
+
* runs, not when it was issued.
|
|
5
13
|
*/
|
|
6
14
|
import { readdirSync, statSync } from 'node:fs';
|
|
7
15
|
import { join } from 'node:path';
|
|
8
16
|
import { isAgentOnlySession } from '../agent/orchestrator/session/store-summary-visibility.mjs';
|
|
17
|
+
import { redactSensitiveText, sensitiveValuesVersion } from '../agent/orchestrator/session/sensitive-values.mjs';
|
|
18
|
+
import { redactSensitiveMessages } from '../agent/orchestrator/session/sensitive-projection.mjs';
|
|
9
19
|
import {
|
|
10
20
|
appendSessionSearchMessages,
|
|
11
21
|
deleteSessionSearchMessages,
|
|
12
22
|
readSessionSearchBackfillStamps,
|
|
13
23
|
readSessionSearchSessionIds,
|
|
14
24
|
recordSessionSearchBackfillStamp,
|
|
25
|
+
replaceSessionSearchMessages,
|
|
15
26
|
} from './session-search-db.mjs';
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
/** sessionId → { length, version }: the transcript length and the
|
|
29
|
+
* sensitive-values version (sensitive-values.mjs) of the last stored ingest. */
|
|
30
|
+
const lastIngested = new Map();
|
|
18
31
|
const LAST_INGESTED_LIMIT = 500;
|
|
19
32
|
|
|
20
33
|
function logFailure(what, error) {
|
|
@@ -23,6 +36,87 @@ function logFailure(what, error) {
|
|
|
23
36
|
} catch {}
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
/** sessionId → { tail, controller }: the tail of the session's write queue
|
|
40
|
+
* (see serialized) and the lifetime its writes run under (closeSessionSearch
|
|
41
|
+
* aborts it). Present only while a write of the session is queued or running. */
|
|
42
|
+
const writeQueues = new Map();
|
|
43
|
+
|
|
44
|
+
/** The reason a closed session's writes stop with; never reported as a failure. */
|
|
45
|
+
class SessionSearchClosedError extends Error {
|
|
46
|
+
constructor(sessionId) {
|
|
47
|
+
super(`session search writes of ${sessionId} closed`);
|
|
48
|
+
this.name = 'AbortError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const reportUnlessClosed = (what) => (error) => {
|
|
53
|
+
if (!(error instanceof SessionSearchClosedError)) logFailure(what, error);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Runs `task(signal)` once every earlier task of `sessionId` has settled; the
|
|
57
|
+
* task's own promise is returned, so its failure reaches the caller to report.
|
|
58
|
+
* The queue moves on past a failed task: that failure is the caller's, not the
|
|
59
|
+
* next write's. `signal` is the session's write lifetime. */
|
|
60
|
+
function serialized(sessionId, task) {
|
|
61
|
+
const queue = writeQueues.get(sessionId);
|
|
62
|
+
const controller = queue && !queue.controller.signal.aborted ? queue.controller : new AbortController();
|
|
63
|
+
// A write cancelled while queued never starts (rejects with the close reason).
|
|
64
|
+
const run = (queue?.tail ?? Promise.resolve()).then(() => {
|
|
65
|
+
controller.signal.throwIfAborted();
|
|
66
|
+
return task(controller.signal);
|
|
67
|
+
});
|
|
68
|
+
const tail = run.then(
|
|
69
|
+
() => {},
|
|
70
|
+
() => {}
|
|
71
|
+
);
|
|
72
|
+
const entry = { tail, controller };
|
|
73
|
+
writeQueues.set(sessionId, entry);
|
|
74
|
+
tail.then(() => {
|
|
75
|
+
if (writeQueues.get(sessionId) === entry) writeQueues.delete(sessionId);
|
|
76
|
+
});
|
|
77
|
+
return run;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Stops the session's search writes for a runtime close: the queued ones never
|
|
82
|
+
* start and the running one is cancelled — a PostgreSQL runtime install or
|
|
83
|
+
* start it is waiting on included, once no other writer still waits on it
|
|
84
|
+
* (createSharedAbortable). Resolves when the queue has settled, so nothing this
|
|
85
|
+
* session issued writes into the data dir afterwards. The rows are derived:
|
|
86
|
+
* the stored-session backfill restores what a cancelled write left out.
|
|
87
|
+
*/
|
|
88
|
+
export async function closeSessionSearch(sessionId) {
|
|
89
|
+
const queue = writeQueues.get(sessionId);
|
|
90
|
+
if (!queue) return;
|
|
91
|
+
queue.controller.abort(new SessionSearchClosedError(sessionId));
|
|
92
|
+
await queue.tail;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Stores `messages`, the transcript as it was when the turn was ingested, as
|
|
96
|
+
* the session's search rows; runs in the session's write queue. */
|
|
97
|
+
async function storeTranscript(sessionId, messages, signal) {
|
|
98
|
+
// A turn always grows the transcript; an unchanged length means nothing
|
|
99
|
+
// new. This is only a shortcut: the insert itself is idempotent.
|
|
100
|
+
const length = messages.length;
|
|
101
|
+
const version = sensitiveValuesVersion(sessionId);
|
|
102
|
+
const last = lastIngested.get(sessionId);
|
|
103
|
+
if (last?.length === length && last.version === version) return;
|
|
104
|
+
// A value a sensitive fill marked is stored as a marker here too. While
|
|
105
|
+
// the session has values not yet applied to its stored rows (rows made
|
|
106
|
+
// before the mark, compacted ones included), the rows that hold one are
|
|
107
|
+
// replaced in one transaction instead of only appending.
|
|
108
|
+
const redacted = redactSensitiveMessages(sessionId, messages);
|
|
109
|
+
if (version && last?.version !== version) {
|
|
110
|
+
await replaceSessionSearchMessages(sessionId, messages, redacted, (text) => redactSensitiveText(sessionId, text), {
|
|
111
|
+
signal,
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
await appendSessionSearchMessages(sessionId, redacted, { signal });
|
|
115
|
+
}
|
|
116
|
+
if (lastIngested.size >= LAST_INGESTED_LIMIT) lastIngested.clear();
|
|
117
|
+
lastIngested.set(sessionId, { length, version });
|
|
118
|
+
}
|
|
119
|
+
|
|
26
120
|
/** Fire-and-forget: store a completed turn's transcript. Never throws. */
|
|
27
121
|
export function ingestSessionTurn(session) {
|
|
28
122
|
try {
|
|
@@ -30,30 +124,24 @@ export function ingestSessionTurn(session) {
|
|
|
30
124
|
const messages = session?.messages;
|
|
31
125
|
if (!sessionId || !Array.isArray(messages) || messages.length === 0) return;
|
|
32
126
|
if (isAgentOnlySession(session)) return;
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
() => {
|
|
39
|
-
if (lastIngestedLength.size >= LAST_INGESTED_LIMIT) lastIngestedLength.clear();
|
|
40
|
-
lastIngestedLength.set(sessionId, length);
|
|
41
|
-
},
|
|
42
|
-
(error) => logFailure(`ingest ${sessionId}`, error)
|
|
127
|
+
// The write runs after the session's earlier ones, so it keeps the
|
|
128
|
+
// transcript of this call: compaction may rewrite the live array before then.
|
|
129
|
+
const transcript = messages.slice();
|
|
130
|
+
serialized(sessionId, (signal) => storeTranscript(sessionId, transcript, signal)).catch(
|
|
131
|
+
reportUnlessClosed(`ingest ${sessionId}`)
|
|
43
132
|
);
|
|
44
133
|
} catch (error) {
|
|
45
134
|
logFailure('ingest', error);
|
|
46
135
|
}
|
|
47
136
|
}
|
|
48
137
|
|
|
49
|
-
/** Best-effort removal of a deleted session's search rows
|
|
138
|
+
/** Best-effort removal of a deleted session's search rows, after the
|
|
139
|
+
* session's pending writes. Never throws. */
|
|
50
140
|
export async function forgetSessionSearch(sessionId) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
await deleteSessionSearchMessages(sessionId);
|
|
54
|
-
}
|
|
55
|
-
logFailure(`delete ${sessionId}`, error);
|
|
56
|
-
}
|
|
141
|
+
await serialized(sessionId, async (signal) => {
|
|
142
|
+
lastIngested.delete(sessionId);
|
|
143
|
+
await deleteSessionSearchMessages(sessionId, { signal });
|
|
144
|
+
}).catch(reportUnlessClosed(`delete ${sessionId}`));
|
|
57
145
|
}
|
|
58
146
|
|
|
59
147
|
async function loadStoreDeps() {
|
|
@@ -89,8 +177,10 @@ export async function backfillSessionSearch({ deps } = {}) {
|
|
|
89
177
|
if (stamps.get(sessionId) === stamp) continue;
|
|
90
178
|
const session = readSession(dir, file);
|
|
91
179
|
if (session?.id && !isAgentOnlySession(session)) {
|
|
92
|
-
await
|
|
93
|
-
|
|
180
|
+
await serialized(session.id, async () => {
|
|
181
|
+
await appendSessionSearchMessages(session.id, redactSensitiveMessages(session.id, session.messages));
|
|
182
|
+
await recordSessionSearchBackfillStamp(session.id, stamp);
|
|
183
|
+
});
|
|
94
184
|
ingested += 1;
|
|
95
185
|
}
|
|
96
186
|
} catch (error) {
|
|
@@ -50,6 +50,95 @@ export async function runAbortable(signal, task, fallback) {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** Resolves after `ms`, or rejects with the abort reason once `signal` aborts. */
|
|
54
|
+
export function sleepUnlessAborted(ms, signal) {
|
|
55
|
+
if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
if (signal.aborted) return reject(abortReason(signal));
|
|
58
|
+
const onAbort = () => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
reject(abortReason(signal));
|
|
61
|
+
};
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
signal.removeEventListener('abort', onAbort);
|
|
64
|
+
resolve();
|
|
65
|
+
}, ms);
|
|
66
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One operation that several callers await. `start(signal)` runs once, when
|
|
72
|
+
* the first caller that still wants it joins — a live signal, or none — so a
|
|
73
|
+
* caller that is already aborted starts nothing. Its signal aborts only when
|
|
74
|
+
* every caller that joined with a signal has aborted and no caller joined
|
|
75
|
+
* without one (such a caller keeps it running). The caller whose abort cancels
|
|
76
|
+
* the work waits for the work to settle, its cleanup included, so nothing it
|
|
77
|
+
* started outlives it; any other aborted caller stops waiting at once and
|
|
78
|
+
* leaves the work to the callers still on it. `onSettled(failed)` runs once
|
|
79
|
+
* the started work settles; `promise` is that work, null until started.
|
|
80
|
+
*/
|
|
81
|
+
export function createSharedAbortable(start, { onSettled = null } = {}) {
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
let waiting = 0;
|
|
84
|
+
let pinned = false;
|
|
85
|
+
let promise = null;
|
|
86
|
+
const begin = () => {
|
|
87
|
+
if (!promise) {
|
|
88
|
+
promise = Promise.resolve().then(() => start(controller.signal));
|
|
89
|
+
promise.then(
|
|
90
|
+
() => onSettled?.(false),
|
|
91
|
+
() => onSettled?.(true)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return promise;
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
get promise() {
|
|
98
|
+
return promise;
|
|
99
|
+
},
|
|
100
|
+
join(signal) {
|
|
101
|
+
if (!signal) {
|
|
102
|
+
pinned = true;
|
|
103
|
+
return begin();
|
|
104
|
+
}
|
|
105
|
+
if (signal.aborted) return Promise.reject(abortReason(signal));
|
|
106
|
+
waiting += 1;
|
|
107
|
+
const work = begin();
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const onAbort = () => {
|
|
110
|
+
waiting -= 1;
|
|
111
|
+
const reason = abortReason(signal);
|
|
112
|
+
if (waiting > 0 || pinned) {
|
|
113
|
+
reject(reason);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
controller.abort(reason);
|
|
117
|
+
work.then(
|
|
118
|
+
() => reject(reason),
|
|
119
|
+
() => reject(reason)
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
123
|
+
work.then(
|
|
124
|
+
(value) => {
|
|
125
|
+
if (signal.aborted) return;
|
|
126
|
+
signal.removeEventListener('abort', onAbort);
|
|
127
|
+
waiting -= 1;
|
|
128
|
+
resolve(value);
|
|
129
|
+
},
|
|
130
|
+
(error) => {
|
|
131
|
+
if (signal.aborted) return;
|
|
132
|
+
signal.removeEventListener('abort', onAbort);
|
|
133
|
+
waiting -= 1;
|
|
134
|
+
reject(error);
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
53
142
|
/**
|
|
54
143
|
* Bound best-effort cleanup without cancelling the underlying work. The
|
|
55
144
|
* detached promise remains observed and may still finish in the background.
|
|
@@ -42,7 +42,6 @@ export const MASCOT_EXPRESSIONS: readonly string[];
|
|
|
42
42
|
export const MASCOT_INKS: Readonly<Record<string, string>>;
|
|
43
43
|
export const MIXY_AVATAR: Readonly<MascotAvatar>;
|
|
44
44
|
export const AGENT_AVATAR_STATES: readonly AgentAvatarState[];
|
|
45
|
-
export const AGENT_AVATAR_LATER_STATES: readonly string[];
|
|
46
45
|
export const AGENT_AVATAR_IMAGE_FILE: RegExp;
|
|
47
46
|
export const AGENT_AVATAR_STATE_FILE: RegExp;
|
|
48
47
|
export const AGENT_AVATAR_DIR: string;
|
|
@@ -45,8 +45,10 @@ const homeEvidenceCache = new Map();
|
|
|
45
45
|
|
|
46
46
|
/** What `agentId`'s home thread (sessions/sess_home_<id>.json) holds:
|
|
47
47
|
* whether it exists, whether it has the creation greeting, and whether the
|
|
48
|
-
* user
|
|
49
|
-
* or
|
|
48
|
+
* user talked in it: a message the user wrote (a user message that is not a
|
|
49
|
+
* runtime turn or the greeting). The agent's own messages never count: Mixy's
|
|
50
|
+
* heartbeat posts its reports there unasked. A missing or unreadable file
|
|
51
|
+
* holds nothing. */
|
|
50
52
|
export function homeThreadEvidence(dataDir, agentId) {
|
|
51
53
|
const sessionId = agentHomeSessionId(agentId);
|
|
52
54
|
if (!dataDir || !sessionId) return NO_HOME;
|
|
@@ -69,7 +71,6 @@ export function homeThreadEvidence(dataDir, agentId) {
|
|
|
69
71
|
let greeted = false;
|
|
70
72
|
let chatted = false;
|
|
71
73
|
for (const message of messages) {
|
|
72
|
-
if (message?.role === 'assistant') chatted = true;
|
|
73
74
|
if (message?.role !== 'user') continue;
|
|
74
75
|
const text = messageText(message.content);
|
|
75
76
|
if (CREATION_GREETING.test(text)) greeted = true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createWriteStream, rmSync } from 'node:fs';
|
|
2
2
|
import { Transform } from 'node:stream';
|
|
3
3
|
import { pipeline } from 'node:stream/promises';
|
|
4
|
-
import {
|
|
4
|
+
import { sleepUnlessAborted } from './abort-race.mjs';
|
|
5
5
|
|
|
6
6
|
export const MAX_NATIVE_BINARY_DOWNLOAD_BYTES = 256 * 1024 * 1024;
|
|
7
7
|
const DEFAULT_RETRY_DELAYS_MS = [1000, 3000, 9000];
|
|
@@ -118,13 +118,17 @@ export async function downloadToFileWithRetry(
|
|
|
118
118
|
timeoutMs = 180_000,
|
|
119
119
|
retryDelaysMs = DEFAULT_RETRY_DELAYS_MS,
|
|
120
120
|
onRetry = null,
|
|
121
|
+
// Cancels the request, its body and any retry wait; rejects with its reason.
|
|
122
|
+
signal = null,
|
|
121
123
|
} = {}
|
|
122
124
|
) {
|
|
123
125
|
const { maximum, expected } = downloadBounds(maxBytes, expectedBytes, label);
|
|
124
126
|
let lastError;
|
|
125
127
|
for (let attempt = 0; attempt <= retryDelaysMs.length; attempt += 1) {
|
|
128
|
+
signal?.throwIfAborted();
|
|
126
129
|
try {
|
|
127
|
-
const
|
|
130
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
131
|
+
const response = await fetchFn(url, { signal: signal ? AbortSignal.any([signal, timeout]) : timeout });
|
|
128
132
|
try {
|
|
129
133
|
if (!response.ok) throw new DownloadHttpError(response.status, httpLabel, url);
|
|
130
134
|
return await streamResponseToFile(response, destPath, {
|
|
@@ -142,12 +146,13 @@ export async function downloadToFileWithRetry(
|
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
} catch (error) {
|
|
149
|
+
signal?.throwIfAborted();
|
|
145
150
|
lastError = error;
|
|
146
151
|
if (error instanceof DownloadHttpError && error.terminal) throw error;
|
|
147
152
|
const delayMs = retryDelaysMs[attempt];
|
|
148
153
|
if (delayMs === undefined) break;
|
|
149
154
|
onRetry?.({ attempt: attempt + 1, delayMs, error });
|
|
150
|
-
await
|
|
155
|
+
await sleepUnlessAborted(delayMs, signal);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
153
158
|
throw lastError;
|
|
@@ -150,23 +150,12 @@ function readConfigRawCached() {
|
|
|
150
150
|
return raw;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// Canonical on-disk channel shape.
|
|
154
|
-
//
|
|
155
|
-
// so neither belongs in config anymore.
|
|
153
|
+
// Canonical on-disk channel shape. Schedules live in the scheduler DB and
|
|
154
|
+
// prompt injection moved to runtime hooks, so neither belongs in config.
|
|
156
155
|
export function canonicalizeStoredChannelsConfig(value = {}) {
|
|
157
156
|
const next = isPlainObject(value) ? { ...value } : {};
|
|
158
157
|
delete next.promptInjection;
|
|
159
158
|
delete next.schedules;
|
|
160
|
-
delete next.nonInteractive;
|
|
161
|
-
delete next.interactive;
|
|
162
|
-
|
|
163
|
-
// Messaging (Discord/Telegram) is retired: scrub the provider selector and
|
|
164
|
-
// per-provider routing sections from stored config.
|
|
165
|
-
delete next.backend;
|
|
166
|
-
delete next.provider;
|
|
167
|
-
delete next.discord;
|
|
168
|
-
delete next.telegram;
|
|
169
|
-
delete next.channel;
|
|
170
159
|
return next;
|
|
171
160
|
}
|
|
172
161
|
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
// What a multi-account connector server tells the model about itself (MCP
|
|
2
|
-
// `instructions`): the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// `instructions`), in one line: the connected accounts, and that each tool
|
|
3
|
+
// takes `account` (asked of the user when several are connected and none was
|
|
4
|
+
// named). An account that cannot be used right now is listed with why.
|
|
5
5
|
|
|
6
|
-
/** The MCP instructions
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/** The MCP instructions naming `accounts` (`{ email, note? }`, `note` a short
|
|
7
|
+
* reason it is unusable such as "needs sign-in"; one without an address is
|
|
8
|
+
* left out), or undefined when none has one. */
|
|
9
|
+
export function connectorAccountsInstructions(accounts) {
|
|
10
|
+
const named = (Array.isArray(accounts) ? accounts : []).filter((account) => account?.email);
|
|
11
|
+
if (!named.length) return undefined;
|
|
12
|
+
const list = named.map(({ email, note }) => (note ? `${email} (${note})` : email)).join(', ');
|
|
13
|
+
return named.length === 1
|
|
14
|
+
? `Accounts: ${list}. \`account\` may be left out.`
|
|
15
|
+
: `Accounts: ${list}. Pass \`account\`; if the user named none and there are several, ask which.`;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
/** The account
|
|
13
|
-
export function
|
|
14
|
-
|
|
18
|
+
/** The account addresses instructions from connectorAccountsInstructions name. */
|
|
19
|
+
export function accountsOfInstructions(instructions) {
|
|
20
|
+
const list = /^Accounts: (.+?)\. (?:`account`|Pass `account`)/.exec(String(instructions || ''))?.[1] || '';
|
|
21
|
+
return list
|
|
22
|
+
.split(', ')
|
|
23
|
+
.map((part) => part.replace(/ \(.*\)$/, '').trim())
|
|
24
|
+
.filter(Boolean);
|
|
15
25
|
}
|
|
@@ -51,3 +51,32 @@ export function gmailAddress(value: unknown): string;
|
|
|
51
51
|
export function gmailAppPasswordValue(value: unknown): string;
|
|
52
52
|
export function gmailAppPasswordAccount(cfg: unknown): string;
|
|
53
53
|
export function gmailAppPasswordServerInput(name: string, email: string, password: string): Record<string, unknown>;
|
|
54
|
+
|
|
55
|
+
/** One stored account of a multi-account connector server. */
|
|
56
|
+
export interface ConnectorAccountEntry {
|
|
57
|
+
name: string;
|
|
58
|
+
url: string;
|
|
59
|
+
headers?: Record<string, string>;
|
|
60
|
+
oauthClientId?: string;
|
|
61
|
+
/** `false`: the account is kept but neither connected nor called. */
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export function connectorAccountsUrl(id: string): string;
|
|
65
|
+
export function connectorAccountsOf(cfg: unknown): ConnectorAccountEntry[];
|
|
66
|
+
export function connectorAccountServer(
|
|
67
|
+
cfg: Record<string, unknown>,
|
|
68
|
+
account: ConnectorAccountEntry
|
|
69
|
+
): Record<string, unknown>;
|
|
70
|
+
export function connectorAccountServers(cfg: unknown): Record<string, unknown>[];
|
|
71
|
+
export function connectorAccountEnabled(account: ConnectorAccountEntry): boolean;
|
|
72
|
+
export function connectorLiveAccountServers(cfg: unknown): Record<string, unknown>[];
|
|
73
|
+
export function withConnectorAccountEnabled(
|
|
74
|
+
cfg: Record<string, unknown>,
|
|
75
|
+
name: string,
|
|
76
|
+
enabled: boolean
|
|
77
|
+
): Record<string, unknown>;
|
|
78
|
+
export function connectorAccountEntry(name: string, cfg: Record<string, unknown>): ConnectorAccountEntry;
|
|
79
|
+
export function mergeConnectorAccountServers(servers: Record<string, unknown>): {
|
|
80
|
+
servers: Record<string, unknown>;
|
|
81
|
+
merged: { connector: string; name: string; from: string[] }[];
|
|
82
|
+
};
|