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
|
@@ -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;
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
// Curated selection triggers for catalog connectors (MCP servers). A catalog
|
|
2
2
|
// entry carries its line as `trigger`; the deferred-tool manifest lists each
|
|
3
3
|
// connected server once as `- mcp__<server id>: <trigger>`, so one line must
|
|
4
|
-
// say
|
|
5
|
-
//
|
|
4
|
+
// say WHEN to call the server (the user requests it covers, plus a close
|
|
5
|
+
// negative where useful), not list its capabilities; plain ASCII English, at
|
|
6
|
+
// most CONNECTOR_TRIGGER_MAX_CHARS characters.
|
|
6
7
|
export const CONNECTOR_TRIGGER_MAX_CHARS = 100;
|
|
7
8
|
|
|
8
9
|
export const CONNECTOR_TRIGGERS = Object.freeze({
|
|
9
10
|
// Not a catalog connector (Mixdog uses the signed-in GitHub CLI), but a
|
|
10
11
|
// user-added `github` MCP server still gets this line.
|
|
11
|
-
github: 'GitHub
|
|
12
|
-
notion: 'Notion pages, databases and
|
|
13
|
-
linear: '
|
|
14
|
-
sentry: '
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
github: 'GitHub issues, pull requests, Actions runs and repo code search; not local Git history.',
|
|
13
|
+
notion: 'Finding, reading or updating Notion pages, databases and team notes.',
|
|
14
|
+
linear: 'Looking up, creating or triaging Linear issues, projects and cycles.',
|
|
15
|
+
sentry: 'Investigating production errors, stack traces and releases in Sentry.',
|
|
16
|
+
// The line never names an account: that arrives with the server's
|
|
17
|
+
// instructions when its tools are loaded.
|
|
18
|
+
gmail: 'Gmail email: sending, reading, searching or organizing mail.',
|
|
19
|
+
'google-calendar': 'Google Calendar: checking, creating or editing events.',
|
|
20
|
+
'google-drive': 'Google Drive: finding, reading or creating files Mixdog made or was given; not local files.',
|
|
21
|
+
jira: 'Looking up, creating or transitioning Jira tickets, sprints and JQL searches.',
|
|
22
|
+
confluence: 'Searching, reading or editing Confluence wiki pages; not Jira tickets.',
|
|
23
|
+
dropbox: 'Finding, reading, uploading or sharing Dropbox files; not local files.',
|
|
24
|
+
trello: 'Viewing, creating or moving Trello boards, lists and cards.',
|
|
25
|
+
airtable: 'Querying or updating Airtable bases, tables and records.',
|
|
26
|
+
stripe: 'Checking or acting on Stripe payments, invoices, subscriptions and refunds.',
|
|
27
|
+
vercel: 'Checking Vercel deployments, domains, logs and build errors.',
|
|
28
|
+
supabase: 'Working with Supabase Postgres tables, SQL, auth, storage or edge functions.',
|
|
29
|
+
postgres: 'Running SQL or inspecting schemas on a connected PostgreSQL database; not local files.',
|
|
30
|
+
'hugging-face': 'Finding Hugging Face Hub models, datasets, Spaces or papers; not local GGUF setup.',
|
|
28
31
|
});
|
|
29
32
|
|
|
30
33
|
// Server ids differ only in separators and case (google_calendar, huggingface).
|
|
@@ -40,13 +40,12 @@ const AGENT_SECTION_DEFAULTS = Object.freeze({
|
|
|
40
40
|
const MODULE_DEFAULTS = Object.freeze({ enabled: true });
|
|
41
41
|
|
|
42
42
|
export const CHANNELS_DEFAULTS = Object.freeze({
|
|
43
|
-
access: Object.freeze({ dmPolicy: 'allowlist', allowFrom: [], channels: {} }),
|
|
44
43
|
webhook: Object.freeze({ enabled: true, port: 3333 }),
|
|
45
44
|
});
|
|
46
45
|
|
|
47
46
|
// Disabled-agent roster shipped before `advisor` joined the defaults
|
|
48
47
|
// (worker … writer plus the Maintainer). Stored full lists are read against it.
|
|
49
|
-
|
|
48
|
+
const PREVIOUS_DISABLED_AGENT_ROSTER = Object.freeze([
|
|
50
49
|
'front-worker',
|
|
51
50
|
'heavy-worker',
|
|
52
51
|
'maintainer',
|
|
@@ -168,17 +167,6 @@ function removeLegacyKeys(root, dataDir) {
|
|
|
168
167
|
if (isPlainObject(agent.agents)) agent.agents = withoutRetiredAgentRoutes(agent.agents, dataDir);
|
|
169
168
|
next.agent = agent;
|
|
170
169
|
}
|
|
171
|
-
const channelEntries = next.channels?.access?.channels;
|
|
172
|
-
if (isPlainObject(channelEntries)) {
|
|
173
|
-
const channels = Object.fromEntries(
|
|
174
|
-
Object.entries(channelEntries).map(([id, entry]) => {
|
|
175
|
-
if (!isPlainObject(entry)) return [id, entry];
|
|
176
|
-
const { requireMention: _retired, ...rest } = entry;
|
|
177
|
-
return [id, rest];
|
|
178
|
-
})
|
|
179
|
-
);
|
|
180
|
-
next.channels = { ...next.channels, access: { ...next.channels.access, channels } };
|
|
181
|
-
}
|
|
182
170
|
return next;
|
|
183
171
|
}
|
|
184
172
|
|
|
@@ -188,7 +176,7 @@ function removeLegacyKeys(root, dataDir) {
|
|
|
188
176
|
* not set the marker. `stripRouteDefaults` drops shipped presets and
|
|
189
177
|
* maintenance routes (their defaults live with the agent config).
|
|
190
178
|
*/
|
|
191
|
-
|
|
179
|
+
function separateDefaultsInConfig(
|
|
192
180
|
root,
|
|
193
181
|
{ stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}
|
|
194
182
|
) {
|
|
@@ -6,14 +6,86 @@
|
|
|
6
6
|
// owner, and the copy is withdrawn as soon as the owner settles (answered in
|
|
7
7
|
// either place, timed out, its turn ended). Like held-request-host.mjs this
|
|
8
8
|
// registry is process-wide and dependency-free.
|
|
9
|
+
// Hand-offs take the same path: a session chats handed work to (`session`
|
|
10
|
+
// send / create from a bot conversation, agent-task-sessions.mjs) shows its
|
|
11
|
+
// own approvals in EVERY chat whose hand-off to it still owes its report, one
|
|
12
|
+
// copy per chat, all settling the one request. The report watch store
|
|
13
|
+
// (session-runtime/agent-home-delivery.mjs) notes and ends hand-offs as its
|
|
14
|
+
// watches come and go, whatever removes them; a chat
|
|
15
|
+
// whose last hand-off to the session ends loses its copy, the others keep
|
|
16
|
+
// theirs.
|
|
9
17
|
|
|
10
18
|
const KEY = Symbol.for('mixdog.delegatedApprovalHost.v1');
|
|
11
19
|
|
|
12
20
|
function registry() {
|
|
13
|
-
globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null };
|
|
21
|
+
globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null, handoffs: new Map(), ended: new Set() };
|
|
14
22
|
return globalThis[KEY];
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
/** The chats with a live hand-off to `sessionId`, each once (in noting order)
|
|
26
|
+
* with the name its latest hand-off gave the session: Map chat → `{ title }`. */
|
|
27
|
+
export function handoffChatsOf(sessionId) {
|
|
28
|
+
const chats = new Map();
|
|
29
|
+
for (const handoff of registry().handoffs.values()) {
|
|
30
|
+
if (handoff.sessionId !== sessionId) continue;
|
|
31
|
+
chats.delete(handoff.surfaceSessionId);
|
|
32
|
+
chats.set(handoff.surfaceSessionId, { title: handoff.title });
|
|
33
|
+
}
|
|
34
|
+
return chats;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Bring each pending request of `sessionId` that hand-offs reach to exactly
|
|
38
|
+
* the chats now handing it work: a new chat gets its copy, a chat whose
|
|
39
|
+
* hand-offs all ended loses only its own. */
|
|
40
|
+
function syncHandoffCopies(sessionId) {
|
|
41
|
+
const { pending } = registry();
|
|
42
|
+
const chats = handoffChatsOf(sessionId);
|
|
43
|
+
for (const asked of pending.values()) {
|
|
44
|
+
if (!asked.copies || asked.sessionId !== sessionId) continue;
|
|
45
|
+
for (const [chat, copy] of asked.copies) {
|
|
46
|
+
if (chats.has(chat)) continue;
|
|
47
|
+
asked.copies.delete(chat);
|
|
48
|
+
for (const queue of liveQueuesOf(chat)) queue.withdraw(copy.id, null);
|
|
49
|
+
}
|
|
50
|
+
for (const [chat, handoff] of chats) {
|
|
51
|
+
if (asked.copies.has(chat)) continue;
|
|
52
|
+
const copy = { id: asked.id, sessionId: chat, request: asked.copyFor(chat, handoff), answer: asked.answer };
|
|
53
|
+
asked.copies.set(chat, copy);
|
|
54
|
+
deliver(copy, () => pending.get(asked.id) === asked && asked.copies.get(chat) === copy);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Run ids of hand-offs that ended: never noted again (a watch read before it
|
|
60
|
+
// was removed must not bring it back, however many hand-offs ended since).
|
|
61
|
+
// Run ids are never reused, so an ended id is final for the life of the
|
|
62
|
+
// process and there is nothing to age out: no count decides what is
|
|
63
|
+
// forgotten. One id string is kept per ended hand-off.
|
|
64
|
+
|
|
65
|
+
/** Chat `homeSessionId` handed session `taskSessionId` work (`title`: the
|
|
66
|
+
* target's name in that chat) under report watch `runId`. */
|
|
67
|
+
export function noteHandoff({ runId, taskSessionId, homeSessionId, title }) {
|
|
68
|
+
if (!runId || !taskSessionId || !homeSessionId || taskSessionId === homeSessionId) return;
|
|
69
|
+
if (registry().ended.has(String(runId))) return;
|
|
70
|
+
registry().handoffs.set(String(runId), {
|
|
71
|
+
sessionId: String(taskSessionId),
|
|
72
|
+
surfaceSessionId: String(homeSessionId),
|
|
73
|
+
title: String(title || ''),
|
|
74
|
+
});
|
|
75
|
+
syncHandoffCopies(String(taskSessionId));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The report watch `runId` is gone (its work reported back, was never
|
|
79
|
+
* taken, or the watch was retired): its hand-off ends for good. */
|
|
80
|
+
export function endHandoff(runId) {
|
|
81
|
+
const { handoffs, ended } = registry();
|
|
82
|
+
ended.add(String(runId));
|
|
83
|
+
const handoff = handoffs.get(String(runId));
|
|
84
|
+
if (!handoff) return;
|
|
85
|
+
handoffs.delete(String(runId));
|
|
86
|
+
syncHandoffCopies(handoff.sessionId);
|
|
87
|
+
}
|
|
88
|
+
|
|
17
89
|
/** `queue`: `{ sessionId() → string, disposed() → boolean, offer(delegated),
|
|
18
90
|
* withdraw(id, result) }` — one session runtime's approval queue. */
|
|
19
91
|
export function registerApprovalQueue(queue) {
|
|
@@ -46,20 +118,36 @@ function offerTo(sessionId, delegated) {
|
|
|
46
118
|
return live.length > 0;
|
|
47
119
|
}
|
|
48
120
|
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
const {
|
|
53
|
-
|
|
54
|
-
if (offerTo(delegated.sessionId, delegated) || typeof opener !== 'function') return;
|
|
121
|
+
/** Offer `copy` in its chat now, or once that chat loads while `current()`
|
|
122
|
+
* still holds. */
|
|
123
|
+
function deliver(copy, current) {
|
|
124
|
+
const { opener } = registry();
|
|
125
|
+
if (offerTo(copy.sessionId, copy) || typeof opener !== 'function') return;
|
|
55
126
|
void Promise.resolve()
|
|
56
|
-
.then(() => opener(
|
|
127
|
+
.then(() => opener(copy.sessionId))
|
|
57
128
|
.then(() => {
|
|
58
|
-
if (
|
|
129
|
+
if (current()) offerTo(copy.sessionId, copy);
|
|
59
130
|
})
|
|
60
131
|
.catch(() => {});
|
|
61
132
|
}
|
|
62
133
|
|
|
134
|
+
/** Show `delegated` (`{ id, sessionId, request, answer(...finishArgs) → boolean }`,
|
|
135
|
+
* `sessionId` being the top-level conversation) there, now or once it loads. */
|
|
136
|
+
export function delegateApproval(delegated) {
|
|
137
|
+
const { pending } = registry();
|
|
138
|
+
pending.set(delegated.id, delegated);
|
|
139
|
+
deliver(delegated, () => pending.get(delegated.id) === delegated);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A top-level session's request (`{ id, sessionId, request, answer }`,
|
|
143
|
+
* `sessionId` being the asking session) that every chat handing it work
|
|
144
|
+
* shows too, now and while it waits: `copyFor(chat, { title })` is the
|
|
145
|
+
* request as that chat shows it. */
|
|
146
|
+
export function delegateToHandoffs(asked) {
|
|
147
|
+
registry().pending.set(asked.id, { ...asked, copies: new Map() });
|
|
148
|
+
syncHandoffCopies(asked.sessionId);
|
|
149
|
+
}
|
|
150
|
+
|
|
63
151
|
/** The owner settled `id`: every copy goes away. `result` (`{ approved, device }`)
|
|
64
152
|
* lets the copy say who answered. */
|
|
65
153
|
export function withdrawDelegatedApproval(id, result = null) {
|
|
@@ -68,7 +156,11 @@ export function withdrawDelegatedApproval(id, result = null) {
|
|
|
68
156
|
for (const queue of queues) queue.withdraw(id, result);
|
|
69
157
|
}
|
|
70
158
|
|
|
71
|
-
/** The pending delegated approvals a top-level session
|
|
159
|
+
/** The pending delegated approvals (and hand-off copies) a top-level session
|
|
160
|
+
* should show. */
|
|
72
161
|
export function delegatedApprovalsFor(sessionId) {
|
|
73
|
-
return [...registry().pending.values()].
|
|
162
|
+
return [...registry().pending.values()].flatMap((delegated) => {
|
|
163
|
+
if (delegated.copies) return delegated.copies.has(sessionId) ? [delegated.copies.get(sessionId)] : [];
|
|
164
|
+
return delegated.sessionId === sessionId ? [delegated] : [];
|
|
165
|
+
});
|
|
74
166
|
}
|
|
@@ -206,24 +206,25 @@ function hasTypedEvidence(error) {
|
|
|
206
206
|
);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// The window a provider timeout names (providerTimeoutError's message
|
|
210
|
-
|
|
209
|
+
// The window a provider timeout names (providerTimeoutError's message, or a
|
|
210
|
+
// first-byte watchdog's "within Nms"), or 0.
|
|
211
|
+
const timedOutMs = (text) => Number(/(?:timed out after|within) (\d+)ms/i.exec(text)?.[1]) || 0;
|
|
211
212
|
|
|
212
213
|
// A provider timeout before the model produced anything (isFirstByteTimeout):
|
|
213
|
-
// the model is slow, not the connection.
|
|
214
|
-
|
|
214
|
+
// the model is slow, not the connection. The provider name comes from the
|
|
215
|
+
// caller's own provider config, never from the error.
|
|
216
|
+
function stillThinkingText(error, text, providerLabel) {
|
|
215
217
|
const ms = Number(error.waitedMs) || timedOutMs(text);
|
|
216
|
-
const provider =
|
|
217
|
-
typeof error.providerLabel === 'string' && error.providerLabel ? error.providerLabel : 'the provider';
|
|
218
|
+
const provider = typeof providerLabel === 'string' && providerLabel ? providerLabel : 'the provider';
|
|
218
219
|
const attempts = Number(error.attempts) > 0 ? `; attempts: ${Math.floor(Number(error.attempts))}` : '';
|
|
219
220
|
return `Model still thinking: no output in ${formatDurationMs(ms)}; ${provider} sent nothing yet${attempts}.`;
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
// Stamped provider timeout phases and stream cuts. Null when the error names
|
|
223
224
|
// neither, so the text rules below decide.
|
|
224
|
-
function typedStreamFailureText(error, text) {
|
|
225
|
+
function typedStreamFailureText(error, text, providerLabel) {
|
|
225
226
|
if (!error || typeof error !== 'object') return null;
|
|
226
|
-
if (isFirstByteTimeout(error)) return stillThinkingText(error, text);
|
|
227
|
+
if (isFirstByteTimeout(error)) return stillThinkingText(error, text, providerLabel);
|
|
227
228
|
if (error.timeoutPhase === TIMEOUT_PHASE.IDLE) {
|
|
228
229
|
const ms = timedOutMs(text);
|
|
229
230
|
return ms > 0 ? `No progress ${formatDurationMs(ms)}.` : 'No progress.';
|
|
@@ -267,7 +268,7 @@ export function presentErrorText(error, options = {}) {
|
|
|
267
268
|
if (embeddedError) text = embeddedError;
|
|
268
269
|
text = oneLine(stripErrorPrefix(text));
|
|
269
270
|
if (!text) return 'Unknown error';
|
|
270
|
-
const streamFailure = typedStreamFailureText(error, text);
|
|
271
|
+
const streamFailure = typedStreamFailureText(error, text, options.providerLabel);
|
|
271
272
|
if (streamFailure) return streamFailure;
|
|
272
273
|
|
|
273
274
|
if (code === 'PROVIDER_PREFIX_MUTATION' || name === 'ProviderPrefixMutationError') {
|
|
@@ -369,12 +370,20 @@ export function presentErrorText(error, options = {}) {
|
|
|
369
370
|
return capText(safeErrorDetails(text) || 'Unknown error', max);
|
|
370
371
|
}
|
|
371
372
|
|
|
372
|
-
/**
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Our own wording for a typed timeout retry (`first_response_timeout` /
|
|
375
|
+
* `stalled`), built only from the error's stamps and the window in its
|
|
376
|
+
* message; null for any other class. No error text is ever echoed.
|
|
377
|
+
*/
|
|
378
|
+
export function retryTimeoutText(error, reasonClass, providerLabel = null) {
|
|
379
|
+
const text = String(error?.message ?? '');
|
|
380
|
+
const ms = timedOutMs(text);
|
|
381
|
+
if (reasonClass === 'first_response_timeout') {
|
|
382
|
+
if (isFirstByteTimeout(error)) return stillThinkingText(error, text, providerLabel).replace(/\.$/, '');
|
|
383
|
+
return ms > 0 ? `No first response ${formatDurationMs(ms)}` : 'No first response';
|
|
384
|
+
}
|
|
385
|
+
if (reasonClass === 'stalled') return ms > 0 ? `No progress ${formatDurationMs(ms)}` : 'No progress';
|
|
386
|
+
return null;
|
|
378
387
|
}
|
|
379
388
|
|
|
380
389
|
/** The one retry status line every surface shows: `reason · retry n/max in 15s`. */
|
|
@@ -3,14 +3,6 @@ export function enforceOwnerOnlyAclWin32(targetPath: string, options?: { fresh?:
|
|
|
3
3
|
/** Test hook: the live per-process ACL helper, or null. */
|
|
4
4
|
export function ownerOnlyAclHelperForTests(): import('node:child_process').ChildProcess | null;
|
|
5
5
|
|
|
6
|
-
/** Test/diagnostic hook: helper attachment, pending requests, starts in the restart window. */
|
|
7
|
-
export function ownerOnlyAclHelperStats(): {
|
|
8
|
-
alive: boolean;
|
|
9
|
-
pid: number | null;
|
|
10
|
-
pending: number;
|
|
11
|
-
recentStarts: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
6
|
/** Non-fresh calls verify (and tighten) the real DACL on every call through a shared helper. */
|
|
15
7
|
export function enforceOwnerOnlyAclWin32Async(targetPath: string, options?: { fresh?: boolean }): Promise<void>;
|
|
16
8
|
|
|
@@ -267,17 +267,6 @@ export async function prewarmOwnerOnlyAclWin32({ bridge = true } = {}) {
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
/** Test/diagnostic hook: whether a helper is attached, its requests awaiting
|
|
271
|
-
* an answer, and the starts inside the current restart window. */
|
|
272
|
-
export function ownerOnlyAclHelperStats() {
|
|
273
|
-
return {
|
|
274
|
-
alive: aclHelper !== null,
|
|
275
|
-
pid: aclHelper?.child.pid ?? null,
|
|
276
|
-
pending: aclHelper?.pending.size ?? 0,
|
|
277
|
-
recentStarts: aclHelperStartTimes.length,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
270
|
/** Test hook: the live helper process, or null. */
|
|
282
271
|
export function ownerOnlyAclHelperForTests() {
|
|
283
272
|
return aclHelper?.child ?? null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Side-effect import for the git-heavy suites. Import it before anything that
|
|
2
|
+
// spawns git or snapshots process.env.
|
|
3
|
+
//
|
|
4
|
+
// On Windows `git` on PATH is usually Git for Windows' cmd\git.exe launcher,
|
|
5
|
+
// which starts the real git.exe as a second process, so every spawn pays two
|
|
6
|
+
// process creations (~75 ms instead of ~40 ms). These suites spawn hundreds of
|
|
7
|
+
// git processes, test and code under test alike (the git runner worker shares
|
|
8
|
+
// process.env), so the real binary's directory (`git --exec-path`, where git
|
|
9
|
+
// itself resolves its subcommands) goes first on PATH. Same git, same config;
|
|
10
|
+
// only the launcher hop is skipped. Elsewhere `git` already is the binary.
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { delimiter, join, normalize } from 'node:path';
|
|
14
|
+
|
|
15
|
+
if (process.platform === 'win32') {
|
|
16
|
+
const probe = spawnSync('git', ['--exec-path'], { encoding: 'utf8', windowsHide: true });
|
|
17
|
+
const dir = probe.status === 0 ? normalize(probe.stdout.trim()) : '';
|
|
18
|
+
if (dir && existsSync(join(dir, 'git.exe'))) process.env.PATH = `${dir}${delimiter}${process.env.PATH}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Mixy's heartbeat interval setting (`heartbeatIntervalMinutes` in the config):
|
|
2
|
+
// one of a few fixed choices, shared by the runtime, the desktop's IPC check
|
|
3
|
+
// and the Settings picker. "Off" is the separate `heartbeat` switch.
|
|
4
|
+
export const HEARTBEAT_INTERVAL_CHOICES = Object.freeze([30, 60, 120, 240]);
|
|
5
|
+
export const DEFAULT_HEARTBEAT_INTERVAL_MINUTES = 60;
|
|
6
|
+
|
|
7
|
+
/** `value` when it is one of the choices, else the default (a stored value
|
|
8
|
+
* that is no longer, or never was, a choice reads as the default). */
|
|
9
|
+
export function normalizeHeartbeatIntervalMinutes(value) {
|
|
10
|
+
return HEARTBEAT_INTERVAL_CHOICES.includes(value) ? value : DEFAULT_HEARTBEAT_INTERVAL_MINUTES;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** `value` when it is one of the choices; throws otherwise (a write). */
|
|
14
|
+
export function requireHeartbeatIntervalMinutes(value) {
|
|
15
|
+
if (!HEARTBEAT_INTERVAL_CHOICES.includes(value)) {
|
|
16
|
+
throw new Error(`heartbeat interval must be one of ${HEARTBEAT_INTERVAL_CHOICES.join(', ')} minutes`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|