mixdog 1.3.5 → 1.4.1
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/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +16 -5
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +37 -37
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +123 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -4
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +20 -2
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/app.mjs +4 -0
- package/src/defaults/shipped-history.json +22 -1
- package/src/defaults/skills/browser-use/SKILL.md +62 -12
- 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 +35 -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 +39 -20
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +11 -10
- package/src/rules/lead/LEAD.md +23 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/rules/shared/70-delivery.md +7 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
- 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/cache-break-trace.mjs +2 -0
- 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 +73 -68
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +57 -2
- 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/mcp/oauth-login.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +192 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- 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-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +159 -69
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +70 -33
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- 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/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +25 -13
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- 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/mimo-oauth-login.mjs +1 -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-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
- 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/openai-ws-stream/event-switch.mjs +20 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
- 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 +38 -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 +49 -16
- 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/orchestration.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +9 -3
- 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/runtime-core/tool-defs.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +798 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +280 -45
- 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/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
- 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 +51 -25
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +37 -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/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +31 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +20 -3
- 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 +5 -4
- 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 +49 -21
- 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-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- 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 +24 -14
- 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 +130 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +81 -8
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -8
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +9 -4
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +24 -9
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -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-delete.mjs +7 -0
- 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 +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +81 -22
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +92 -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 +79 -3
- 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/stall-policy.mjs +23 -0
- 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/invocation-plan.mjs +2 -2
- 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/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +31 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +110 -26
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
- 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/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/whisper-port.mjs +10 -1
- 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 +28 -2
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -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/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- 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/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- 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/bot-owned-work.mjs +56 -0
- 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 -17
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- 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/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- 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 +126 -0
- package/src/runtime/shared/llm/cost.mjs +5 -1
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -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 +48 -1
- package/src/runtime/shared/session-scratch.mjs +37 -0
- 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-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +2 -15
- package/src/runtime/shared/tool-surface.mjs +3 -0
- package/src/runtime/shared/tool-work-units.mjs +2 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +15 -0
- package/src/runtime/shared/worktrees/manager.mjs +87 -38
- package/src/runtime/shared/worktrees/test-support.mjs +14 -13
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +29 -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 +489 -185
- package/src/session-runtime/agent-task-sessions.mjs +467 -229
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- 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 +11 -2
- package/src/session-runtime/bot-status.mjs +12 -6
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.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/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +271 -25
- package/src/session-runtime/conversation-import/service.mjs +26 -5
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +144 -41
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- 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/model-route/set-route.mjs +11 -2
- 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/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- 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/resource-recall-api.mjs +6 -1
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +13 -4
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- 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/provider-admin.mjs +2 -1
- 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 +17 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/session-runtime/workspace/transitions.mjs +25 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
- 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 +142 -190
- 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/agent-tree/agent-turns.mjs +16 -12
- 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 +30 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/app/app-view/floating-panel.jsx +7 -4
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +9 -5
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/dist/index.mjs +133 -92
- 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 +16 -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/ui/statusline-agents.mjs +1 -3
- 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
|
@@ -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;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Bot-owned work: the live work in a session that a bot sent. A bot's accepted
|
|
2
|
+
// `session` send or create (session-runtime/agent-task-sessions.mjs) is the
|
|
3
|
+
// bot's work in that session from its acceptance until it settles or is
|
|
4
|
+
// cancelled — whether or not it reports back (`notify: false`, a report watch
|
|
5
|
+
// that could not be stored). A turn nobody sent (the user typed it in the
|
|
6
|
+
// task session) is owned by no bot, so it never reads as a bot being busy.
|
|
7
|
+
// Process-wide and dependency-free like delegated-approval-host.mjs. The
|
|
8
|
+
// registry is only this process's cache: what keeps the work owned across a
|
|
9
|
+
// restart is stored apart (a reporting send's watch, any other send's record
|
|
10
|
+
// of its work, both in agent-home-delivery.mjs) and re-noted by
|
|
11
|
+
// agent-task-sessions.mjs recover. A change of which bots own a session's work
|
|
12
|
+
// is announced (session-activity-signal.mjs onSessionBotWorkChange), so the
|
|
13
|
+
// session catalog, which stamps them on each row, re-lists.
|
|
14
|
+
import { emitSessionBotWorkChange } from './session-activity-signal.mjs';
|
|
15
|
+
|
|
16
|
+
const KEY = Symbol.for('mixdog.botOwnedWork.v1');
|
|
17
|
+
|
|
18
|
+
/** Map run id → `{ sessionId, agentId }`. */
|
|
19
|
+
function registry() {
|
|
20
|
+
globalThis[KEY] ??= new Map();
|
|
21
|
+
return globalThis[KEY];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** How many owned works of `agentId` run in `sessionId`. */
|
|
25
|
+
function worksOf(sessionId, agentId) {
|
|
26
|
+
let count = 0;
|
|
27
|
+
for (const work of registry().values()) if (work.sessionId === sessionId && work.agentId === agentId) count += 1;
|
|
28
|
+
return count;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Every session with owned work and the bots owning it (each once, in noting
|
|
32
|
+
* order): Map session → agent ids. */
|
|
33
|
+
export function botWorkBySession() {
|
|
34
|
+
const bySession = new Map();
|
|
35
|
+
for (const { sessionId, agentId } of registry().values()) {
|
|
36
|
+
const agents = bySession.get(sessionId) ?? [];
|
|
37
|
+
if (!agents.includes(agentId)) agents.push(agentId);
|
|
38
|
+
bySession.set(sessionId, agents);
|
|
39
|
+
}
|
|
40
|
+
return bySession;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Agent `agentId`'s submission `runId` to `sessionId` was accepted. */
|
|
44
|
+
export function noteBotWork({ runId, sessionId, agentId }) {
|
|
45
|
+
if (!runId || !sessionId || !agentId || registry().has(String(runId))) return;
|
|
46
|
+
registry().set(String(runId), { sessionId: String(sessionId), agentId: String(agentId) });
|
|
47
|
+
if (worksOf(String(sessionId), String(agentId)) === 1) emitSessionBotWorkChange(sessionId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Submission `runId` settled or was cancelled: its work is nobody's now. */
|
|
51
|
+
export function endBotWork(runId) {
|
|
52
|
+
const work = registry().get(String(runId));
|
|
53
|
+
if (!work) return;
|
|
54
|
+
registry().delete(String(runId));
|
|
55
|
+
if (worksOf(work.sessionId, work.agentId) === 0) emitSessionBotWorkChange(work.sessionId);
|
|
56
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -190,6 +190,156 @@ export function connectorMultiAccount(entry) {
|
|
|
190
190
|
return entry?.backend?.kind === 'oauth' && entry.backend.oauthClient === 'google';
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
// ── One server per multi-account connector ──────────────────────────────────
|
|
194
|
+
// A multi-account connector is ONE MCP server (`gmail`, `google-calendar`,
|
|
195
|
+
// `google-drive`) holding its accounts:
|
|
196
|
+
// { type: 'http', url: connectorAccountsUrl(id), connector: id,
|
|
197
|
+
// accounts: [{ name, url, headers?, oauthClientId? }, …] }
|
|
198
|
+
// Each account keeps the transport it had as a server of its own (the Google
|
|
199
|
+
// sign-in URL its tokens are stored under, or the app-password address and
|
|
200
|
+
// account header), so its keychain entries are untouched. Account names share
|
|
201
|
+
// the server-name space (`gmail`, `gmail-2`…): a name-based call (remove, sign
|
|
202
|
+
// in, sign out) that names an account acts on that account. The server is
|
|
203
|
+
// answered in-process (connectors/connector-accounts-mcp.mjs): one tool set
|
|
204
|
+
// with an `account` argument.
|
|
205
|
+
const ACCOUNT_FIELDS = ['url', 'headers', 'oauthClientId'];
|
|
206
|
+
|
|
207
|
+
/** The reserved (never resolvable) address of connector `id`'s accounts server. */
|
|
208
|
+
export function connectorAccountsUrl(id) {
|
|
209
|
+
return `https://connectors.mixdog.invalid/${encodeURIComponent(id)}/mcp`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
213
|
+
|
|
214
|
+
/** The stored accounts of a multi-account connector server, [] for any other
|
|
215
|
+
* config (a legacy one-account server included). */
|
|
216
|
+
export function connectorAccountsOf(cfg) {
|
|
217
|
+
if (!isRecord(cfg) || !Array.isArray(cfg.accounts)) return [];
|
|
218
|
+
if (!connectorMultiAccount(connectorById(connectorOfServer(cfg)))) return [];
|
|
219
|
+
return cfg.accounts.filter(
|
|
220
|
+
(account) =>
|
|
221
|
+
isRecord(account) && typeof account.name === 'string' && account.name && typeof account.url === 'string'
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The one-account server config of `account` of server `cfg` (the shape the
|
|
226
|
+
* account had as a server of its own), named by the account. */
|
|
227
|
+
export function connectorAccountServer(cfg, account) {
|
|
228
|
+
const out = { name: account.name, type: 'http' };
|
|
229
|
+
for (const field of ACCOUNT_FIELDS) if (account[field] !== undefined) out[field] = account[field];
|
|
230
|
+
out.connector = cfg.connector;
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Every account of `cfg` as its one-account server config. */
|
|
235
|
+
export function connectorAccountServers(cfg) {
|
|
236
|
+
return connectorAccountsOf(cfg).map((account) => connectorAccountServer(cfg, account));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Whether stored account `account` is switched on (`enabled: false` is off). */
|
|
240
|
+
export const connectorAccountEnabled = (account) => account.enabled !== false;
|
|
241
|
+
|
|
242
|
+
/** The switched-on accounts of `cfg` as their one-account server configs: the
|
|
243
|
+
* ones the server connects and routes calls to. */
|
|
244
|
+
export function connectorLiveAccountServers(cfg) {
|
|
245
|
+
return connectorAccountsOf(cfg)
|
|
246
|
+
.filter(connectorAccountEnabled)
|
|
247
|
+
.map((account) => connectorAccountServer(cfg, account));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Server `cfg` with its account `name` switched on or off. Off flags the
|
|
252
|
+
* account (`enabled: false`) and switches the server off with its last
|
|
253
|
+
* switched-on account. On drops the flag (a switched-on account carries none)
|
|
254
|
+
* and switches the server on. Every other entry stays as stored.
|
|
255
|
+
*/
|
|
256
|
+
export function withConnectorAccountEnabled(cfg, name, enabled) {
|
|
257
|
+
const accounts = cfg.accounts.map((account) => {
|
|
258
|
+
if (!isRecord(account) || account.name !== name) return account;
|
|
259
|
+
const { enabled: _enabled, ...rest } = account;
|
|
260
|
+
return enabled ? rest : { ...rest, enabled: false };
|
|
261
|
+
});
|
|
262
|
+
const next = { ...cfg, accounts };
|
|
263
|
+
if (enabled) return { ...next, enabled: true };
|
|
264
|
+
return connectorAccountsOf(next).some(connectorAccountEnabled) ? next : { ...next, enabled: false };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** The stored account entry of one-account config `cfg` under `name`. */
|
|
268
|
+
export function connectorAccountEntry(name, cfg) {
|
|
269
|
+
const out = { name };
|
|
270
|
+
for (const field of ACCOUNT_FIELDS) if (cfg?.[field] !== undefined) out[field] = cfg[field];
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const naturalOrder = (a, b) => a.localeCompare(b, 'en', { numeric: true });
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Merges every multi-account connector's servers into one server holding the
|
|
278
|
+
* accounts: legacy one-account servers (`gmail`, `gmail-2`…, Calendar and
|
|
279
|
+
* Drive alike) and any second accounts server. The server keeps the name of
|
|
280
|
+
* an existing accounts server, else the connector id when a server has it,
|
|
281
|
+
* else the first name; its other fields come from that server. Accounts keep
|
|
282
|
+
* their names, transports and order (that server's first); a second entry
|
|
283
|
+
* for the same name or app-password address is dropped. The merged server is
|
|
284
|
+
* off only when every merged one was; otherwise the accounts of a merged
|
|
285
|
+
* server that was off stay off (`enabled: false` on the account), and an
|
|
286
|
+
* account's own `enabled: false` is kept. A server left with no switched-on
|
|
287
|
+
* account is off. Idempotent: `merged` is empty when
|
|
288
|
+
* nothing changed. Returns `{ servers, merged: [{ connector, name, from }] }`.
|
|
289
|
+
*/
|
|
290
|
+
export function mergeConnectorAccountServers(input) {
|
|
291
|
+
const servers = isRecord(input) ? { ...input } : {};
|
|
292
|
+
const merged = [];
|
|
293
|
+
for (const entry of CONNECTOR_CATALOG.filter(connectorMultiAccount)) {
|
|
294
|
+
const names = Object.keys(servers)
|
|
295
|
+
.filter((name) => isRecord(servers[name]) && connectorOfServer(servers[name]) === entry.id)
|
|
296
|
+
.sort(naturalOrder);
|
|
297
|
+
if (!names.length) continue;
|
|
298
|
+
const grouped = names.filter((name) => Array.isArray(servers[name].accounts));
|
|
299
|
+
if (names.length === 1 && grouped.length === 1) continue;
|
|
300
|
+
const target = grouped[0] ?? (names.includes(entry.id) ? entry.id : names[0]);
|
|
301
|
+
const order = [target, ...names.filter((name) => name !== target)];
|
|
302
|
+
const off = order.every((name) => input[name].enabled === false);
|
|
303
|
+
const accounts = [];
|
|
304
|
+
const seenNames = new Set();
|
|
305
|
+
const seenAddresses = new Set();
|
|
306
|
+
for (const name of order) {
|
|
307
|
+
const cfg = servers[name];
|
|
308
|
+
const own = Array.isArray(cfg.accounts) ? connectorAccountsOf(cfg) : [connectorAccountEntry(name, cfg)];
|
|
309
|
+
// A merged server that was off keeps its accounts off, unless the whole
|
|
310
|
+
// connector is off (then re-enabling it brings every account back).
|
|
311
|
+
const serverOff = !off && cfg.enabled === false;
|
|
312
|
+
for (const account of own) {
|
|
313
|
+
const address = gmailAppPasswordAccount(connectorAccountServer(cfg, account));
|
|
314
|
+
if (seenNames.has(account.name) || (address && seenAddresses.has(address))) continue;
|
|
315
|
+
seenNames.add(account.name);
|
|
316
|
+
if (address) seenAddresses.add(address);
|
|
317
|
+
accounts.push(serverOff ? { ...account, enabled: false } : account);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const {
|
|
321
|
+
type: _type,
|
|
322
|
+
url: _url,
|
|
323
|
+
headers: _headers,
|
|
324
|
+
oauthClientId: _client,
|
|
325
|
+
accounts: _accounts,
|
|
326
|
+
enabled: _enabled,
|
|
327
|
+
...rest
|
|
328
|
+
} = servers[target];
|
|
329
|
+
for (const name of order) delete servers[name];
|
|
330
|
+
servers[target] = {
|
|
331
|
+
...rest,
|
|
332
|
+
type: 'http',
|
|
333
|
+
url: connectorAccountsUrl(entry.id),
|
|
334
|
+
connector: entry.id,
|
|
335
|
+
accounts,
|
|
336
|
+
...(off || (accounts.length > 0 && !accounts.some(connectorAccountEnabled)) ? { enabled: false } : {}),
|
|
337
|
+
};
|
|
338
|
+
merged.push({ connector: entry.id, name: target, from: order });
|
|
339
|
+
}
|
|
340
|
+
return { servers, merged };
|
|
341
|
+
}
|
|
342
|
+
|
|
193
343
|
/** Whether a new connection of `entry` goes by app password only (Gmail). */
|
|
194
344
|
export function connectorAppPasswordOnly(entry) {
|
|
195
345
|
return entry?.appPasswordOnly === true;
|