mixdog 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/build-shipped-history.mjs +15 -5
- package/scripts/gate-local.mjs +65 -19
- package/scripts/graph-parity.mjs +8 -3
- package/scripts/lib/gate-scheduler.mjs +2 -1
- package/scripts/lib/run-node-tests.mjs +17 -5
- package/scripts/lib/test-failure-summary.mjs +1 -8
- package/scripts/lib/test-spawn-ledger.mjs +33 -2
- package/scripts/post-release-check.mjs +26 -16
- package/scripts/provider-pingpong-live.mjs +1 -1
- package/scripts/prune-embedding-runtime.mjs +3 -1
- package/scripts/release-paths.mjs +7 -1
- package/src/defaults/shipped-history.json +20 -0
- package/src/defaults/skills/setup/SKILL.md +2 -2
- package/src/defaults/skills/setup/references/actions.md +16 -3
- package/src/defaults/skills/setup/references/surfaces.md +3 -0
- package/src/defaults/skills/setup-guide/SKILL.md +6 -1
- package/src/lib/instruction-text.cjs +36 -0
- package/src/lib/rules-builder.cjs +34 -20
- package/src/orchestration/balanced.md +23 -0
- package/src/orchestration/focused.md +22 -0
- package/src/orchestration/none.md +5 -0
- package/src/orchestration/swarm.md +24 -0
- package/src/rules/agent-mode/AGENT_MODE.md +3 -1
- package/src/rules/agent-mode/MIXY.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +3 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -2
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +0 -9
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +67 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -4
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -1
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +199 -46
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +3 -5
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +9 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +3 -5
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +8 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +14 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-stop.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +4 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2784 -2872
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +5 -8
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +288 -300
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +10 -30
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +30 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +0 -11
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +8 -0
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +17 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +47 -27
- package/src/runtime/agent/orchestrator/runtime-core/goal-deadlines.mjs +141 -10
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +26 -63
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +28 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +36 -11
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +88 -56
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +51 -32
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +45 -145
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -98
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +32 -99
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +23 -78
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +69 -48
- package/src/runtime/agent/orchestrator/runtime-core/todo-list.mjs +367 -0
- package/src/runtime/agent/orchestrator/runtime-core/todo-tool-defs.mjs +47 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-availability.mjs +155 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +9 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +119 -35
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -22
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +30 -0
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -12
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +1 -2
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/todo-check.mjs +196 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +116 -50
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +5 -6
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/turn-report.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +57 -26
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +27 -11
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-open.mjs +37 -34
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +25 -15
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +79 -24
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-path.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/prompt-settings.mjs +598 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +19 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +19 -10
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +31 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +31 -0
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +42 -6
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +8 -9
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +339 -88
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +135 -11
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +377 -104
- package/src/runtime/agent/orchestrator/session/manager/turn-durability.mjs +144 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +23 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/store/transcript-durability.mjs +88 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-log.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +14 -5
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/worktree-add-guard.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -10
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -24
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary/child-kill.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/modes/calls.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/modes/symbols.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +4 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +3 -4
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4 -22
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +0 -1
- package/src/runtime/attachments/attachment-reference.mjs +1 -1
- package/src/runtime/attachments/pdf-extract.mjs +1 -1
- package/src/runtime/attachments/store.d.mts +1 -0
- package/src/runtime/attachments/store.mjs +2 -2
- package/src/runtime/channels/lib/config-reload-watch.mjs +42 -0
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +29 -9
- package/src/runtime/channels/lib/scheduler.mjs +33 -0
- package/src/runtime/channels/lib/schedules-db.mjs +11 -4
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +4 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +9 -3
- package/src/runtime/channels/lib/whisper-server.mjs +12 -12
- package/src/runtime/channels/lib/worker-main.mjs +9 -31
- package/src/runtime/computer-bridge/linux-readiness.mjs +10 -14
- package/src/runtime/connectors/connector-accounts-mcp.mjs +169 -33
- package/src/runtime/connectors/gmail-app-password.mjs +2 -2
- package/src/runtime/connectors/google-mcp.mjs +1 -1
- package/src/runtime/effort-judge/judge-client.mjs +2 -2
- package/src/runtime/effort-judge/judge-input.mjs +4 -4
- package/src/runtime/explore/explorer.mjs +0 -5
- package/src/runtime/explore/tool.mjs +3 -3
- package/src/runtime/local-provider/hardware.mjs +1 -1
- package/src/runtime/media/adapters/codex-image.mjs +3 -3
- package/src/runtime/media/adapters/gemini-video.mjs +1 -1
- package/src/runtime/media/media-usage.mjs +1 -1
- package/src/runtime/media/store.mjs +1 -1
- package/src/runtime/memory/index.mjs +11 -1
- package/src/runtime/memory/lib/embedding-reindex.mjs +27 -0
- package/src/runtime/memory/lib/memory-embed.mjs +83 -14
- package/src/runtime/memory/lib/memory-process-lock.mjs +159 -36
- package/src/runtime/memory/lib/memory-text-utils.mjs +6 -1
- package/src/runtime/memory/lib/memory.mjs +17 -0
- package/src/runtime/memory/lib/pg/process.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -96
- package/src/runtime/memory/lib/recall-hybrid/hybrid-sql.mjs +12 -3
- package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/pptx-author-action.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-build.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
- package/src/runtime/office/capabilities.mjs +1 -1
- package/src/runtime/office/core/office-core.mjs +1 -1
- package/src/runtime/office/design/content-model.mjs +3 -3
- package/src/runtime/office/design/docx/design-docx/section.mjs +6 -6
- package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +1 -1
- package/src/runtime/office/design/xlsx/design-xlsx.mjs +1 -1
- package/src/runtime/office/pdf/ocr-engine.mjs +1 -1
- package/src/runtime/office/pdf/pdf-writer.mjs +4 -4
- package/src/runtime/office/portable/portable-docx-operations.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +8 -6
- package/src/runtime/office/portable/xlsx-contract.mjs +1 -1
- package/src/runtime/shared/atomic-file.d.mts +8 -0
- package/src/runtime/shared/background-tasks.mjs +22 -4
- package/src/runtime/shared/background-wait.mjs +66 -0
- package/src/runtime/shared/child-guardian.mjs +17 -7
- package/src/runtime/shared/config.mjs +313 -16
- package/src/runtime/shared/connector-catalog.d.mts +4 -5
- package/src/runtime/shared/connector-catalog.mjs +11 -1
- package/src/runtime/shared/file-lock.mjs +8 -3
- package/src/runtime/shared/git-runner.mjs +7 -2
- package/src/runtime/shared/idle-gc.mjs +30 -7
- package/src/runtime/shared/llm/http-agent.mjs +8 -5
- package/src/runtime/shared/llm/system-proxy.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger-quota.mjs +31 -12
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +84 -8
- package/src/runtime/shared/llm/usage-ledger.mjs +3 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +48 -0
- package/src/runtime/shared/maintenance-schedule.mjs +109 -0
- package/src/runtime/shared/process-runner.mjs +8 -4
- package/src/runtime/shared/routine-trigger.d.mts +13 -0
- package/src/runtime/shared/routine-trigger.mjs +34 -0
- package/src/runtime/shared/run-child.d.mts +2 -1
- package/src/runtime/shared/run-child.mjs +70 -18
- package/src/runtime/shared/session-scratch.mjs +14 -2
- package/src/runtime/shared/shipped-definitions.mjs +7 -1
- package/src/runtime/shared/tool-card-model.golden.json +3 -12
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/tool-work-units.mjs +31 -6
- package/src/runtime/shared/turn-snapshot.mjs +26 -4
- package/src/runtime/shared/turn-worktree-snapshot.mjs +6 -11
- package/src/runtime/shared/worker-requests.mjs +17 -3
- package/src/runtime/shared/worktrees/branch-management.mjs +1 -5
- package/src/runtime/shared/worktrees/contract.mjs +16 -6
- package/src/runtime/shared/worktrees/dependencies.mjs +127 -89
- package/src/runtime/shared/worktrees/disk.mjs +79 -2
- package/src/runtime/shared/worktrees/git.mjs +44 -11
- package/src/runtime/shared/worktrees/include.mjs +6 -4
- package/src/runtime/shared/worktrees/maintenance.mjs +422 -30
- package/src/runtime/shared/worktrees/manager.mjs +772 -75
- package/src/runtime/shared/worktrees/settings.mjs +33 -7
- package/src/runtime/shared/worktrees/test-support.mjs +5 -1
- package/src/runtime/shared/worktrees/worker-snapshots.mjs +82 -0
- package/src/runtime/tidy/process.mjs +2 -1
- package/src/runtime/web-search/index.mjs +3 -22
- package/src/runtime/web-search/lib/web-tools.mjs +2 -2
- package/src/runtime/web-search/tool-defs.mjs +17 -26
- package/src/session-runtime/agent-profile-changes.mjs +55 -4
- package/src/session-runtime/boot/apis.mjs +27 -4
- package/src/session-runtime/boot/begin.mjs +2 -0
- package/src/session-runtime/boot/config.mjs +10 -1
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/providers.mjs +70 -3
- package/src/session-runtime/boot/session.mjs +2 -0
- package/src/session-runtime/boot/tools.mjs +69 -25
- package/src/session-runtime/channel-config-api.mjs +1 -0
- package/src/session-runtime/config-change-effects.mjs +179 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +1 -0
- package/src/session-runtime/config-lifecycle.mjs +160 -24
- package/src/session-runtime/conversation-import/discovery.mjs +86 -7
- package/src/session-runtime/conversation-import/service.mjs +3 -2
- package/src/session-runtime/goal-facade-api.mjs +49 -3
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +3 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -2
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +3 -11
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +18 -4
- package/src/session-runtime/internal-tool-executor.mjs +12 -19
- package/src/session-runtime/lifecycle/cold-heir.mjs +7 -0
- package/src/session-runtime/lifecycle/memory-ingest.mjs +3 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +53 -5
- package/src/session-runtime/lifecycle/session-switch.mjs +13 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +4 -4
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +14 -3
- package/src/session-runtime/mcp-glue/server-connection.mjs +34 -8
- package/src/session-runtime/model-route/set-route.mjs +40 -7
- package/src/session-runtime/native-web-search/routes.mjs +3 -3
- package/src/session-runtime/provider-auth/refresh.mjs +6 -3
- package/src/session-runtime/provider-auth/setup.mjs +1 -1
- package/src/session-runtime/provider-readiness.mjs +53 -4
- package/src/session-runtime/resource-api.mjs +1 -1
- package/src/session-runtime/resource-extension-sync.mjs +4 -8
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +5 -1
- package/src/session-runtime/resource-mcp-secrets.mjs +3 -3
- package/src/session-runtime/resource-plugins-api.mjs +4 -0
- package/src/session-runtime/routine-executor.mjs +152 -0
- package/src/session-runtime/routine-home-turn.mjs +46 -5
- package/src/session-runtime/runtime-facade.mjs +5 -0
- package/src/session-runtime/runtime-feature-gates.mjs +2 -11
- package/src/session-runtime/schedule-session-run.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +7 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +110 -6
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +12 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +5 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +24 -6
- package/src/session-runtime/services/agent-tool.mjs +51 -1
- package/src/session-runtime/services/channel-admin.mjs +18 -0
- package/src/session-runtime/services/hook-bus/command-handler.mjs +7 -11
- package/src/session-runtime/services/plugin-admin.mjs +4 -0
- package/src/session-runtime/services/seeds.mjs +7 -0
- package/src/session-runtime/services/usage-stats-model.mjs +32 -11
- package/src/session-runtime/session-create/materialize.mjs +4 -2
- package/src/session-runtime/session-create/session-options.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +1 -9
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +1 -1
- package/src/session-runtime/setup-tool/executor.mjs +8 -1
- package/src/session-runtime/setup-tool/extended-actions.mjs +2 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +28 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +26 -1
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +40 -43
- package/src/session-runtime/tool-policy/mode-switch.mjs +1 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +106 -8
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +2 -1
- package/src/session-runtime/tool-policy-refresh.mjs +182 -5
- package/src/session-runtime/tool-surface.mjs +38 -1
- package/src/session-runtime/turn/agent-ops.mjs +10 -2
- package/src/session-runtime/turn/session-ops.mjs +2 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +5 -0
- package/src/session-runtime/turn/turn-run.mjs +1 -1
- package/src/session-runtime/webhook-session-run.mjs +2 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +1 -4
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +3 -10
- package/src/session-runtime/workflow-agents-api/instruction-files.mjs +165 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +5 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +2 -3
- package/src/session-runtime/workflow-agents-api.mjs +2 -0
- package/src/session-runtime/workspace/project-branches.mjs +1 -1
- package/src/session-runtime/workspace/session-view.mjs +1 -1
- package/src/session-runtime/workspace/transitions.mjs +20 -4
- package/src/session-runtime/workspace/worktree-maintenance.mjs +126 -6
- package/src/session-runtime/workspace/worktree-setup.mjs +13 -1
- package/src/session-runtime/workspace/worktree-tool.mjs +16 -0
- package/src/standalone/daemon-agent-control.mjs +25 -8
- package/src/standalone/daemon-desktop-runtime.mjs +6 -2
- package/src/standalone/daemon-lag-profiler.mjs +7 -7
- package/src/standalone/daemon.mjs +54 -2
- package/src/standalone/local-session-runtime.mjs +16 -0
- package/src/standalone/session-client/daemon-unavailable.mjs +1 -1
- package/src/standalone/session-protocol.mjs +11 -0
- package/src/standalone/session-service/agent-route-follow.mjs +23 -20
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +18 -6
- package/src/standalone/session-service/quota-resume.mjs +9 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +51 -1
- package/src/standalone/session-service/session-calls/session-turns.mjs +37 -18
- package/src/standalone/session-service/session-calls/turn-gate.mjs +34 -0
- package/src/standalone/session-service/session-calls.mjs +2 -1
- package/src/tui/App.jsx +8 -0
- package/src/tui/app/clipboard.mjs +1 -1
- package/src/tui/app/create-app-pickers.mjs +2 -0
- package/src/tui/app/doctor.mjs +2 -2
- package/src/tui/app/panel-signature.mjs +1 -1
- package/src/tui/app/settings-picker.mjs +53 -8
- package/src/tui/app/shell-layout/row-budgets.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +1 -1
- package/src/tui/app/slash-dispatch/session-commands.mjs +1 -1
- package/src/tui/app/use-prompt-queue-history.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +1 -1
- package/src/tui/app/use-transcript-window/geometry.mjs +1 -1
- package/src/tui/components/prompt-input/escape-policy.d.mts +0 -1
- package/src/tui/components/prompt-input/escape-policy.mjs +1 -1
- package/src/tui/components/question-panel-model.mjs +4 -4
- package/src/tui/components/tool-execution/standard-card.jsx +3 -4
- package/src/tui/components/user-message-skills.mjs +1 -1
- package/src/tui/dist/index.mjs +309 -30
- package/src/tui/markdown/render-ansi.mjs +1 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +10 -0
- package/src/tui/session/agent-job-feed.mjs +2 -0
- package/src/tui/session/context-state.mjs +3 -0
- package/src/tui/session/quota-resume.mjs +1 -1
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +7 -0
- package/src/tui/session/session-api/routes-agent-definitions.mjs +5 -0
- package/src/tui/session/session-api/worktree.mjs +13 -1
- package/src/tui/session/session-flow/drain.mjs +11 -14
- package/src/tui/session/tool-card-results/grouped-result.mjs +2 -2
- package/src/tui/session/turn-quiet.mjs +1 -1
- package/src/tui/session/turn.mjs +3 -0
- package/src/tui/session/worktree-setup-notice.mjs +1 -1
- package/src/tui/spinner-meta.mjs +3 -3
- package/src/tui/spinner-verbs.mjs +2 -2
- package/src/defaults/skills/goal-management/SKILL.md +0 -169
- package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +0 -156
- package/src/session-runtime/goal-tasks.mjs +0 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { readFileSync, readdirSync, statSync, mkdirSync, existsSync, watch as fsWatch } from 'node:fs';
|
|
6
6
|
import { basename, join, dirname } from 'node:path';
|
|
7
7
|
import { createRequire } from 'node:module';
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
8
9
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
9
10
|
import { hasOwn, isPlainObject } from './object.mjs';
|
|
10
11
|
import {
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
renameWithRetrySync,
|
|
14
15
|
writeJsonAtomicSync,
|
|
15
16
|
writeJsonAtomicAsync,
|
|
17
|
+
updateJsonAtomicSync,
|
|
18
|
+
updateJsonAtomic,
|
|
16
19
|
withFileLockSync,
|
|
17
20
|
withFileLock,
|
|
18
21
|
} from './atomic-file.mjs';
|
|
@@ -30,6 +33,7 @@ import { AGENT_PROVIDER_ENV, AGENT_PROVIDER_ENV_ALIASES, getAgentApiKey } from '
|
|
|
30
33
|
export { AGENT_PROVIDER_ENV, AGENT_PROVIDER_ENV_ALIASES, getAgentApiKey };
|
|
31
34
|
|
|
32
35
|
const _require = createRequire(import.meta.url);
|
|
36
|
+
const _keychain = _require('../../lib/keychain-cjs.cjs');
|
|
33
37
|
const {
|
|
34
38
|
getSecret: _getSecret,
|
|
35
39
|
setSecret: _setSecret,
|
|
@@ -42,7 +46,8 @@ const {
|
|
|
42
46
|
retryKeychain: _retryKeychain,
|
|
43
47
|
refreshKeychainStatus: _refreshKeychainStatus,
|
|
44
48
|
onKeychainRecovered: _onKeychainRecovered,
|
|
45
|
-
|
|
49
|
+
prewarmSecrets: _prewarmSecrets,
|
|
50
|
+
} = _keychain;
|
|
46
51
|
const _setSecretAsync = _setSecretAsyncImpl ?? (async (account, value) => _setSecret(account, value));
|
|
47
52
|
const _deleteSecretAsync = _deleteSecretAsyncImpl ?? (async (account) => _deleteSecret(account));
|
|
48
53
|
|
|
@@ -80,7 +85,9 @@ function withFreshDefaultsVersion(fresh, root) {
|
|
|
80
85
|
// isolated, freely-mutable object (no shared-reference poisoning). Only the
|
|
81
86
|
// non-RMW readAll() consults this; the RMW path (readAllForRmW) always reads
|
|
82
87
|
// disk under the lock. Read errors and malformed JSON are never cached.
|
|
83
|
-
let _configReadCache = null; // { raw,
|
|
88
|
+
let _configReadCache = null; // { raw, path, stat, atMs } | null
|
|
89
|
+
// While a change notification runs its listeners (runConfigChangeListeners).
|
|
90
|
+
let _inNotificationPass = false;
|
|
84
91
|
// Optional coarse fast-path window (ms). Default 0 => the cached raw string is
|
|
85
92
|
// validated against the file's mtime+size on EVERY read via a cheap statSync
|
|
86
93
|
// (no content read, no whole-file JSON.parse), so a cross-process edit is
|
|
@@ -97,6 +104,12 @@ export function invalidateConfigReadCache() {
|
|
|
97
104
|
_configReadCache = null;
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
// A replacement of the same size within the mtime's resolution still moves the
|
|
108
|
+
// inode (atomic rename) or the change time.
|
|
109
|
+
function statKey(st) {
|
|
110
|
+
return `${st.mtimeMs}:${st.ctimeMs}:${st.size}:${st.ino}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
100
113
|
function readConfigRawCached() {
|
|
101
114
|
const now = Date.now();
|
|
102
115
|
if (_configReadCache && _configReadCache.path !== configPath()) _configReadCache = null;
|
|
@@ -106,12 +119,12 @@ function readConfigRawCached() {
|
|
|
106
119
|
if (CONFIG_READ_TTL_MS > 0 && now - _configReadCache.atMs < CONFIG_READ_TTL_MS) {
|
|
107
120
|
return _configReadCache.raw;
|
|
108
121
|
}
|
|
109
|
-
// Freshness gate: metadata-only stat. Unchanged mtime+size => the cached
|
|
122
|
+
// Freshness gate: metadata-only stat. Unchanged mtime+ctime+size+inode => the cached
|
|
110
123
|
// raw is still current, cross-process writes included — atomic writers land
|
|
111
124
|
// a new inode with a fresh mtime, so this reliably detects them.
|
|
112
125
|
try {
|
|
113
126
|
const st = statSync(configPath());
|
|
114
|
-
if (st
|
|
127
|
+
if (statKey(st) === _configReadCache.stat) {
|
|
115
128
|
_configReadCache.atMs = now;
|
|
116
129
|
return _configReadCache.raw;
|
|
117
130
|
}
|
|
@@ -146,7 +159,7 @@ function readConfigRawCached() {
|
|
|
146
159
|
process.stderr.write(`[config] readJsonFile: unexpected read error for ${configPath()}: ${err.message}\n`);
|
|
147
160
|
throw err;
|
|
148
161
|
}
|
|
149
|
-
_configReadCache = { raw, path: configPath(),
|
|
162
|
+
_configReadCache = { raw, path: configPath(), stat: statKey(st), atMs: now };
|
|
150
163
|
return raw;
|
|
151
164
|
}
|
|
152
165
|
|
|
@@ -225,6 +238,7 @@ function readConfigFile() {
|
|
|
225
238
|
// parse must not be re-served; drop it so the post-quarantine/restore read
|
|
226
239
|
// hits disk fresh (malformed = never cached).
|
|
227
240
|
invalidateConfigReadCache();
|
|
241
|
+
if (_inNotificationPass) throw configMidWriteError(err);
|
|
228
242
|
quarantineMalformedConfig(err);
|
|
229
243
|
return null;
|
|
230
244
|
}
|
|
@@ -422,19 +436,164 @@ function readAllForRmW() {
|
|
|
422
436
|
if (!isPlainObject(parsed)) throw new SyntaxError('config root must be a JSON object');
|
|
423
437
|
return parsed;
|
|
424
438
|
} catch (parseErr) {
|
|
439
|
+
if (_inNotificationPass) throw configMidWriteError(parseErr);
|
|
425
440
|
return restoreAllForRmWOrThrow(parseErr.message, quarantineMalformedConfig(parseErr));
|
|
426
441
|
}
|
|
427
442
|
}
|
|
428
443
|
|
|
444
|
+
// Secret change marker. Keychain writes never touch mixdog-config.json, so they
|
|
445
|
+
// publish through this file beside it (account -> token of its last change):
|
|
446
|
+
// it rides the same directory watch and notification as a config write. Each
|
|
447
|
+
// process remembers the tokens it has applied and drops exactly the changed
|
|
448
|
+
// accounts from its keychain read cache before listeners reload.
|
|
449
|
+
// A deletion removes the account's entry (the marker holds live accounts only)
|
|
450
|
+
// and moves this token: a process that never saw the account's entry (a secret
|
|
451
|
+
// stored before the marker existed, or set and deleted between two of its
|
|
452
|
+
// syncs) may still cache it, so it drops its whole keychain read cache.
|
|
453
|
+
const SECRETS_MARKER_FILE = 'mixdog-secrets-changed.json';
|
|
454
|
+
const SECRET_DELETED_KEY = '#deleted';
|
|
455
|
+
let _secretsSeen = null;
|
|
456
|
+
let _secretsSeenPath = '';
|
|
457
|
+
let _secretsMarkerStat = '';
|
|
458
|
+
|
|
459
|
+
function secretsMarkerPath() {
|
|
460
|
+
return join(dirname(configPath()), SECRETS_MARKER_FILE);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function secretsMarkerStat(path) {
|
|
464
|
+
try {
|
|
465
|
+
const stat = statSync(path);
|
|
466
|
+
return `${stat.mtimeMs}:${stat.size}:${stat.ino}`;
|
|
467
|
+
} catch {
|
|
468
|
+
return 'missing';
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function readSecretsMarker() {
|
|
473
|
+
try {
|
|
474
|
+
const parsed = parseJsonText(readFileSync(secretsMarkerPath(), 'utf8'));
|
|
475
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
476
|
+
} catch {
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Apply marker entries this process has not seen; answers the changed accounts. */
|
|
482
|
+
function syncSecretsMarker() {
|
|
483
|
+
const path = secretsMarkerPath();
|
|
484
|
+
if (_secretsSeenPath !== path) {
|
|
485
|
+
_secretsSeen = null;
|
|
486
|
+
_secretsSeenPath = path;
|
|
487
|
+
}
|
|
488
|
+
_secretsMarkerStat = secretsMarkerStat(path);
|
|
489
|
+
const marker = readSecretsMarker();
|
|
490
|
+
if (_secretsSeen === null) {
|
|
491
|
+
_secretsSeen = marker;
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
const seen = _secretsSeen;
|
|
495
|
+
const changed = [...new Set([...Object.keys(seen), ...Object.keys(marker)])].filter(
|
|
496
|
+
(account) => seen[account] !== marker[account]
|
|
497
|
+
);
|
|
498
|
+
_secretsSeen = marker;
|
|
499
|
+
if (changed.includes(SECRET_DELETED_KEY)) _keychain.invalidateSecretCache?.();
|
|
500
|
+
else for (const account of changed) if (!account.startsWith('#')) _keychain.invalidateSecretCache?.(account);
|
|
501
|
+
return changed;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// The read path of a process that may have no config-change subscriber (an
|
|
505
|
+
// injected config, a helper process): one stat, and a re-read only when the
|
|
506
|
+
// marker moved, so a secret changed or forgotten elsewhere is never served
|
|
507
|
+
// from this process's cache.
|
|
508
|
+
function syncSecretsMarkerIfMoved() {
|
|
509
|
+
if (_secretsSeen !== null && _secretsSeenPath === secretsMarkerPath()) {
|
|
510
|
+
if (secretsMarkerStat(_secretsSeenPath) === _secretsMarkerStat) return;
|
|
511
|
+
}
|
|
512
|
+
syncSecretsMarker();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** account -> token of the last change of that secret this process has
|
|
516
|
+
* applied (its own writes and other processes'). Values are never included. */
|
|
517
|
+
export function secretChangeTokens() {
|
|
518
|
+
if (_secretsSeen === null) syncSecretsMarker();
|
|
519
|
+
return { ..._secretsSeen };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const secretChangeToken = () => `${process.pid}:${Date.now()}:${randomUUID()}`;
|
|
523
|
+
function withSecretChange(marker, account, token, deleted) {
|
|
524
|
+
const next = { ...(isPlainObject(marker) ? marker : {}) };
|
|
525
|
+
if (deleted) {
|
|
526
|
+
delete next[account];
|
|
527
|
+
next[SECRET_DELETED_KEY] = token;
|
|
528
|
+
} else {
|
|
529
|
+
next[account] = token;
|
|
530
|
+
}
|
|
531
|
+
return next;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function beginSecretChange() {
|
|
535
|
+
// Other processes' changes are applied first, so recording our own token
|
|
536
|
+
// never absorbs theirs unapplied.
|
|
537
|
+
syncSecretsMarker();
|
|
538
|
+
return secretChangeToken();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function finishSecretChange(account, token, deleted) {
|
|
542
|
+
// This process already holds the new value (the keychain module updated its
|
|
543
|
+
// cache), so its own change is recorded as applied.
|
|
544
|
+
_secretsSeen = withSecretChange(_secretsSeen, account, token, deleted);
|
|
545
|
+
_secretsMarkerStat = secretsMarkerStat(secretsMarkerPath());
|
|
546
|
+
notifyConfigChanged();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const secretMarkerFailure = (err) =>
|
|
550
|
+
process.stderr.write(`[config] secret change marker write failed: ${err?.message || err}\n`);
|
|
551
|
+
|
|
552
|
+
// Never throws: the secret itself is already stored; a marker that could not
|
|
553
|
+
// be written is reported and only delays what other processes see.
|
|
554
|
+
function publishSecretChange(account, deleted = false) {
|
|
555
|
+
const token = beginSecretChange();
|
|
556
|
+
try {
|
|
557
|
+
mkdirSync(dirname(secretsMarkerPath()), { recursive: true, mode: 0o700 });
|
|
558
|
+
updateJsonAtomicSync(secretsMarkerPath(), (marker) => withSecretChange(marker, account, token, deleted), {
|
|
559
|
+
mode: 0o600,
|
|
560
|
+
});
|
|
561
|
+
} catch (err) {
|
|
562
|
+
secretMarkerFailure(err);
|
|
563
|
+
}
|
|
564
|
+
finishSecretChange(account, token, deleted);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function publishSecretChangeAsync(account, deleted = false) {
|
|
568
|
+
const token = beginSecretChange();
|
|
569
|
+
try {
|
|
570
|
+
mkdirSync(dirname(secretsMarkerPath()), { recursive: true, mode: 0o700 });
|
|
571
|
+
await updateJsonAtomic(secretsMarkerPath(), (marker) => withSecretChange(marker, account, token, deleted), {
|
|
572
|
+
mode: 0o600,
|
|
573
|
+
});
|
|
574
|
+
} catch (err) {
|
|
575
|
+
secretMarkerFailure(err);
|
|
576
|
+
}
|
|
577
|
+
finishSecretChange(account, token, deleted);
|
|
578
|
+
}
|
|
579
|
+
|
|
429
580
|
// Config-change notification. In-process writes notify directly after the file
|
|
430
581
|
// is persisted; writes from other processes arrive through one lazily started
|
|
431
582
|
// directory watch (the atomic rename replaces the file, which would orphan a
|
|
432
583
|
// file-level watch on POSIX). Bursts coalesce into one listener pass.
|
|
433
584
|
const CONFIG_CHANGE_DEBOUNCE_MS = 100;
|
|
585
|
+
// A watch that failed (or could not start) is re-established on this delay
|
|
586
|
+
// while anyone listens; changes missed meanwhile are announced once it is back.
|
|
587
|
+
const CONFIG_WATCH_RETRY_MS = 1000;
|
|
588
|
+
const WATCHED_FILES = new Set(['mixdog-config.json', SECRETS_MARKER_FILE]);
|
|
434
589
|
const _configChangeListeners = new Set();
|
|
435
590
|
let _configChangeTimer = null;
|
|
436
591
|
let _configWatcher = null;
|
|
437
592
|
let _configWatchDir = '';
|
|
593
|
+
let _configWatchRetryTimer = null;
|
|
594
|
+
|
|
595
|
+
/** The directory watch implementation; tests substitute a failing watcher. */
|
|
596
|
+
export const configWatchDeps = { watch: fsWatch };
|
|
438
597
|
|
|
439
598
|
function stopConfigWatcher() {
|
|
440
599
|
try {
|
|
@@ -444,46 +603,142 @@ function stopConfigWatcher() {
|
|
|
444
603
|
_configWatchDir = '';
|
|
445
604
|
}
|
|
446
605
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
606
|
+
// The watched files' bytes at the last listener pass (or the first subscribe).
|
|
607
|
+
// A pass caused only by the watch finds nothing to do when they are unchanged,
|
|
608
|
+
// e.g. another process rewriting a marker with identical content.
|
|
609
|
+
let _configChangeForced = false;
|
|
610
|
+
let _watchedContent = null;
|
|
611
|
+
|
|
612
|
+
function watchedContent() {
|
|
613
|
+
const dir = dirname(configPath());
|
|
614
|
+
return [...WATCHED_FILES].map((file) => {
|
|
615
|
+
try {
|
|
616
|
+
return readFileSync(join(dir, file), 'latin1');
|
|
617
|
+
} catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// A config that does not parse while a notification pass runs its listeners
|
|
624
|
+
// is usually a write in progress (a hand edit saved half-typed or torn, maybe
|
|
625
|
+
// landing after the pass began): every read of the pass (readConfigFile,
|
|
626
|
+
// readAllForRmW) then throws instead of quarantining it, so the listeners keep
|
|
627
|
+
// their current config, and the pass runs again once shortly. Only reads
|
|
628
|
+
// outside a pass (boot, explicit reads, writes) quarantine it.
|
|
629
|
+
const CONFIG_SETTLE_RETRY_MS = 300;
|
|
630
|
+
let _configSettleTimer = null;
|
|
631
|
+
let _configSettleRetried = false;
|
|
632
|
+
|
|
633
|
+
function configMidWriteError(parseErr) {
|
|
634
|
+
// The retry runs the listeners even when the bytes then equal the ones this
|
|
635
|
+
// pass saw (the save completed back to them).
|
|
636
|
+
_configChangeForced = true;
|
|
637
|
+
if (!_configSettleRetried && !_configSettleTimer && _configChangeListeners.size) {
|
|
638
|
+
_configSettleTimer = setTimeout(() => {
|
|
639
|
+
_configSettleTimer = null;
|
|
640
|
+
_configSettleRetried = true;
|
|
641
|
+
void runConfigChangeListeners();
|
|
642
|
+
}, CONFIG_SETTLE_RETRY_MS);
|
|
643
|
+
_configSettleTimer.unref?.();
|
|
644
|
+
}
|
|
645
|
+
const error = new Error(
|
|
646
|
+
`[config] mixdog-config.json does not parse yet (${parseErr.message}); keeping the current config`
|
|
647
|
+
);
|
|
648
|
+
error.code = 'ECONFIG_MID_WRITE';
|
|
649
|
+
return error;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function runConfigChangeListeners() {
|
|
653
|
+
const forced = _configChangeForced;
|
|
654
|
+
_configChangeForced = false;
|
|
655
|
+
const content = watchedContent();
|
|
656
|
+
const last = _watchedContent;
|
|
657
|
+
if (!forced && Array.isArray(last) && last.every((value, index) => value === content[index])) return;
|
|
658
|
+
_watchedContent = content;
|
|
659
|
+
invalidateConfigReadCache();
|
|
660
|
+
// A Linux keyring is read only through a snapshot, which the invalidation
|
|
661
|
+
// dropped: re-take it so listeners reload the changed keys.
|
|
662
|
+
if (syncSecretsMarker().length && process.platform === 'linux') {
|
|
663
|
+
try {
|
|
664
|
+
await _prewarmSecrets?.();
|
|
665
|
+
} catch {}
|
|
666
|
+
}
|
|
667
|
+
_inNotificationPass = true;
|
|
668
|
+
try {
|
|
453
669
|
for (const listener of [..._configChangeListeners]) {
|
|
454
670
|
try {
|
|
455
671
|
listener();
|
|
456
672
|
} catch {}
|
|
457
673
|
}
|
|
674
|
+
} finally {
|
|
675
|
+
_inNotificationPass = false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// In-process writes and a re-established watch always run the listeners (a
|
|
680
|
+
// secret stored while its marker write failed changes no watched byte).
|
|
681
|
+
function notifyConfigChanged({ fromWatch = false } = {}) {
|
|
682
|
+
if (!_configChangeListeners.size) return;
|
|
683
|
+
if (!fromWatch) _configChangeForced = true;
|
|
684
|
+
_configSettleRetried = false;
|
|
685
|
+
if (_configChangeTimer) clearTimeout(_configChangeTimer);
|
|
686
|
+
_configChangeTimer = setTimeout(() => {
|
|
687
|
+
_configChangeTimer = null;
|
|
688
|
+
void runConfigChangeListeners();
|
|
458
689
|
}, CONFIG_CHANGE_DEBOUNCE_MS);
|
|
459
690
|
_configChangeTimer.unref?.();
|
|
460
691
|
}
|
|
461
692
|
|
|
693
|
+
function scheduleConfigWatchRetry() {
|
|
694
|
+
if (_configWatchRetryTimer || !_configChangeListeners.size) return;
|
|
695
|
+
_configWatchRetryTimer = setTimeout(() => {
|
|
696
|
+
_configWatchRetryTimer = null;
|
|
697
|
+
if (!_configChangeListeners.size) return;
|
|
698
|
+
ensureConfigWatcher();
|
|
699
|
+
if (_configWatcher) notifyConfigChanged();
|
|
700
|
+
}, CONFIG_WATCH_RETRY_MS);
|
|
701
|
+
_configWatchRetryTimer.unref?.();
|
|
702
|
+
}
|
|
703
|
+
|
|
462
704
|
function ensureConfigWatcher() {
|
|
463
705
|
const dir = dirname(configPath());
|
|
464
706
|
if (_configWatcher && _configWatchDir === dir) return;
|
|
465
707
|
stopConfigWatcher();
|
|
466
708
|
try {
|
|
467
|
-
|
|
468
|
-
if (!filename || String(filename)
|
|
709
|
+
const watcher = configWatchDeps.watch(dir, (_event, filename) => {
|
|
710
|
+
if (!filename || WATCHED_FILES.has(String(filename))) notifyConfigChanged({ fromWatch: true });
|
|
469
711
|
});
|
|
470
|
-
|
|
471
|
-
|
|
712
|
+
watcher.unref?.();
|
|
713
|
+
watcher.on?.('error', () => {
|
|
714
|
+
if (_configWatcher !== watcher) return;
|
|
715
|
+
stopConfigWatcher();
|
|
716
|
+
scheduleConfigWatchRetry();
|
|
717
|
+
});
|
|
718
|
+
_configWatcher = watcher;
|
|
472
719
|
_configWatchDir = dir;
|
|
473
720
|
} catch {
|
|
474
721
|
_configWatcher = null;
|
|
722
|
+
scheduleConfigWatchRetry();
|
|
475
723
|
}
|
|
476
724
|
}
|
|
477
725
|
|
|
478
|
-
/** Subscribe to persisted config changes (this process or another). Returns unsubscribe. */
|
|
726
|
+
/** Subscribe to persisted config and secret changes (this process or another). Returns unsubscribe. */
|
|
479
727
|
export function subscribeConfigChange(listener) {
|
|
728
|
+
if (!_configChangeListeners.size) _watchedContent = watchedContent();
|
|
480
729
|
_configChangeListeners.add(listener);
|
|
730
|
+
// The first subscriber fixes the secret baseline this process applies from.
|
|
731
|
+
if (_secretsSeen === null) syncSecretsMarker();
|
|
481
732
|
ensureConfigWatcher();
|
|
482
733
|
return () => {
|
|
483
734
|
_configChangeListeners.delete(listener);
|
|
484
735
|
if (!_configChangeListeners.size) {
|
|
485
736
|
if (_configChangeTimer) clearTimeout(_configChangeTimer);
|
|
486
737
|
_configChangeTimer = null;
|
|
738
|
+
clearTimeout(_configSettleTimer);
|
|
739
|
+
_configSettleTimer = null;
|
|
740
|
+
if (_configWatchRetryTimer) clearTimeout(_configWatchRetryTimer);
|
|
741
|
+
_configWatchRetryTimer = null;
|
|
487
742
|
stopConfigWatcher();
|
|
488
743
|
}
|
|
489
744
|
};
|
|
@@ -664,6 +919,7 @@ function _readSecret(account) {
|
|
|
664
919
|
const envVal = process.env[_envKey(account)];
|
|
665
920
|
if (envVal) return envVal;
|
|
666
921
|
try {
|
|
922
|
+
syncSecretsMarkerIfMoved();
|
|
667
923
|
return _getSecret(account);
|
|
668
924
|
} catch {
|
|
669
925
|
return null;
|
|
@@ -692,6 +948,7 @@ export function saveSecret(account, value) {
|
|
|
692
948
|
} catch (err) {
|
|
693
949
|
throw saveSecretError(account, err);
|
|
694
950
|
}
|
|
951
|
+
publishSecretChange(account);
|
|
695
952
|
}
|
|
696
953
|
|
|
697
954
|
/** Non-blocking saveSecret: Linux secret-tool runs off the event loop. */
|
|
@@ -701,6 +958,7 @@ export async function saveSecretAsync(account, value) {
|
|
|
701
958
|
} catch (err) {
|
|
702
959
|
throw saveSecretError(account, err);
|
|
703
960
|
}
|
|
961
|
+
await publishSecretChangeAsync(account);
|
|
704
962
|
}
|
|
705
963
|
|
|
706
964
|
function saveSecretError(account, err) {
|
|
@@ -725,11 +983,49 @@ function saveSecretError(account, err) {
|
|
|
725
983
|
/** Remove a secret from the OS keychain (if present) and the Linux fallback file. */
|
|
726
984
|
export function deleteSecret(account) {
|
|
727
985
|
_deleteSecret(account);
|
|
986
|
+
publishSecretChange(account, true);
|
|
728
987
|
}
|
|
729
988
|
|
|
730
989
|
/** Non-blocking deleteSecret: Linux secret-tool runs off the event loop. */
|
|
731
990
|
export async function deleteSecretAsync(account) {
|
|
732
991
|
await _deleteSecretAsync(account);
|
|
992
|
+
await publishSecretChangeAsync(account, true);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* The keychain as a credential store for modules that keep their own secrets
|
|
997
|
+
* in it (MCP OAuth tokens and client secrets, Gmail app passwords). Reads are
|
|
998
|
+
* the keychain's own; every write publishes the secret change marker exactly
|
|
999
|
+
* as saveSecret/deleteSecret do, so other processes learn of it. Write errors
|
|
1000
|
+
* stay the keychain's: the provider-key environment hint of saveSecret does
|
|
1001
|
+
* not apply to these accounts. Calls reach the keychain module at call time.
|
|
1002
|
+
* `{ announce: false }` writes without the marker, for a change other
|
|
1003
|
+
* processes observe by their own means (an MCP OAuth token refresh: its
|
|
1004
|
+
* revision marker), so it does not reach every runtime as a settings change.
|
|
1005
|
+
* Reads first apply the marker, so a process without a config-change
|
|
1006
|
+
* subscriber never serves a secret changed or forgotten elsewhere.
|
|
1007
|
+
*/
|
|
1008
|
+
export const keychainSecretStore = Object.freeze({
|
|
1009
|
+
getSecretAsync(account) {
|
|
1010
|
+
syncSecretsMarkerIfMoved();
|
|
1011
|
+
return _keychain.getSecretAsync(account);
|
|
1012
|
+
},
|
|
1013
|
+
invalidateSecretCache: (account) => _keychain.invalidateSecretCache?.(account),
|
|
1014
|
+
async setSecretAsync(account, value, { announce = true } = {}) {
|
|
1015
|
+
await _keychain.setSecretAsync(account, value);
|
|
1016
|
+
if (announce) await publishSecretChangeAsync(account);
|
|
1017
|
+
},
|
|
1018
|
+
async deleteSecretAsync(account, { announce = true } = {}) {
|
|
1019
|
+
await _keychain.deleteSecretAsync(account);
|
|
1020
|
+
if (announce) await publishSecretChangeAsync(account, true);
|
|
1021
|
+
},
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
/** Announce a change of state kept outside mixdog-config.json that settings
|
|
1025
|
+
* depend on (`plugins`: the plugin registry), under a reserved `#` key of the
|
|
1026
|
+
* secret change marker, so it reaches other processes on the same path. */
|
|
1027
|
+
export function announceStoredStateChange(key) {
|
|
1028
|
+
publishSecretChange(`#${key}`);
|
|
733
1029
|
}
|
|
734
1030
|
|
|
735
1031
|
/** Retry a failed Linux keyring read; true when it just recovered. */
|
|
@@ -763,6 +1059,7 @@ export function getKeychainStatus() {
|
|
|
763
1059
|
*/
|
|
764
1060
|
export function hasStoredSecret(account) {
|
|
765
1061
|
try {
|
|
1062
|
+
syncSecretsMarkerIfMoved();
|
|
766
1063
|
if (typeof _hasSecret === 'function') return !!_hasSecret(account);
|
|
767
1064
|
return !!_getSecret(account);
|
|
768
1065
|
} catch {
|
|
@@ -37,11 +37,7 @@ export const CONNECTOR_IDS: readonly string[];
|
|
|
37
37
|
export function connectorById(id: unknown): ConnectorCatalogEntry | null;
|
|
38
38
|
export function connectorOfServer(cfg: unknown): string;
|
|
39
39
|
export function connectorToolDefault(connectorId: string, tool: string): 'auto' | 'needs_approval';
|
|
40
|
-
export function connectorServerInput(
|
|
41
|
-
entry: ConnectorCatalogEntry,
|
|
42
|
-
name: string,
|
|
43
|
-
key?: string
|
|
44
|
-
): Record<string, unknown>;
|
|
40
|
+
export function connectorServerInput(entry: ConnectorCatalogEntry, name: string, key?: string): Record<string, unknown>;
|
|
45
41
|
export function connectorAppPasswordOnly(entry: ConnectorCatalogEntry | null | undefined): boolean;
|
|
46
42
|
export function connectorUsesGoogleClient(entry: ConnectorCatalogEntry | null | undefined): boolean;
|
|
47
43
|
export function connectorMultiAccount(entry: ConnectorCatalogEntry | null | undefined): boolean;
|
|
@@ -54,6 +50,8 @@ export function gmailAppPasswordServerInput(name: string, email: string, passwor
|
|
|
54
50
|
/** One stored account of a connector server. */
|
|
55
51
|
export interface ConnectorAccountEntry {
|
|
56
52
|
name: string;
|
|
53
|
+
/** Its identity, given when it was added (connectorAccountId). */
|
|
54
|
+
id?: string;
|
|
57
55
|
/** Absent: http. */
|
|
58
56
|
type?: string;
|
|
59
57
|
url?: string;
|
|
@@ -73,6 +71,7 @@ export function connectorAccountServer(
|
|
|
73
71
|
cfg: Record<string, unknown>,
|
|
74
72
|
account: ConnectorAccountEntry
|
|
75
73
|
): Record<string, unknown>;
|
|
74
|
+
export function connectorAccountId(cfg: Record<string, unknown>, account: ConnectorAccountEntry): string;
|
|
76
75
|
export function connectorAccountServers(cfg: unknown): Record<string, unknown>[];
|
|
77
76
|
export function connectorAccountEnabled(account: ConnectorAccountEntry): boolean;
|
|
78
77
|
export function connectorLiveAccountServers(cfg: unknown): Record<string, unknown>[];
|
|
@@ -193,8 +193,9 @@ export function connectorMultiAccount(entry) {
|
|
|
193
193
|
// A catalog connector is ONE MCP server (`gmail`, `notion`, `postgres`…)
|
|
194
194
|
// holding its accounts:
|
|
195
195
|
// { type: 'http', url: connectorAccountsUrl(id), connector: id,
|
|
196
|
-
// accounts: [{ name, type?, url?, headers?, oauthClientId?, oauthKey?,
|
|
196
|
+
// accounts: [{ name, id?, type?, url?, headers?, oauthClientId?, oauthKey?,
|
|
197
197
|
// command?, args?, env? }, …] }
|
|
198
|
+
// (`id`: the account's random identity; connectorAccountId)
|
|
198
199
|
// Each account keeps the transport it had as a server of its own (its remote
|
|
199
200
|
// URL and sign-in, its key header, its app-password address, or its local
|
|
200
201
|
// command and environment), so its keychain entries are untouched. A remote
|
|
@@ -241,6 +242,15 @@ export function connectorServerSettings(cfg) {
|
|
|
241
242
|
return Object.fromEntries(Object.entries(cfg).filter(([key]) => key !== 'accounts' && !ACCOUNT_FIELDS.includes(key)));
|
|
242
243
|
}
|
|
243
244
|
|
|
245
|
+
/** The identity of stored account `account`, which a chosen account is
|
|
246
|
+
* remembered by: the random `id` stored with it (given when it is added, or
|
|
247
|
+
* on load to one stored without: connector-accounts-mcp.mjs
|
|
248
|
+
* ensureConnectorAccountIds). A new account given a removed one's name gets
|
|
249
|
+
* a new id; an edit keeps it. '' while it has none (nothing is bound to it). */
|
|
250
|
+
export function connectorAccountId(_cfg, account) {
|
|
251
|
+
return typeof account?.id === 'string' ? account.id : '';
|
|
252
|
+
}
|
|
253
|
+
|
|
244
254
|
/** Every account of `cfg` as its one-account server config. */
|
|
245
255
|
export function connectorAccountServers(cfg) {
|
|
246
256
|
return connectorAccountsOf(cfg).map((account) => connectorAccountServer(cfg, account));
|
|
@@ -114,9 +114,14 @@ async function runAsync(steps) {
|
|
|
114
114
|
|
|
115
115
|
function* readLockOwner(lockPath) {
|
|
116
116
|
try {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const text = String(yield ['read', lockPath]).trim();
|
|
118
|
+
// v1.4.2 pg-spawn.lock wrote `{"pid":N,"startedAt":T}`; an older process
|
|
119
|
+
// may still hold one, so its pid must count as an owner.
|
|
120
|
+
if (text.startsWith('{')) {
|
|
121
|
+
const legacyPid = Number(JSON.parse(text)?.pid);
|
|
122
|
+
return { pid: Number.isInteger(legacyPid) && legacyPid > 0 ? legacyPid : null, token: null };
|
|
123
|
+
}
|
|
124
|
+
const parts = text.split(/\s+/);
|
|
120
125
|
const pid = Number.parseInt(parts[0], 10);
|
|
121
126
|
return {
|
|
122
127
|
pid: Number.isFinite(pid) && pid > 0 ? pid : null,
|
|
@@ -15,7 +15,12 @@ const requestGit = createWorkerRequestClient(new URL('./git-runner-worker.mjs',
|
|
|
15
15
|
* timeoutMs (the child is killed), EMAXBUFFER once stdout+stderr exceed
|
|
16
16
|
* maxBytes, or the spawn error itself. `stdoutEncoding: 'base64'` returns the
|
|
17
17
|
* exact stdout bytes base64-encoded instead of decoded as UTF-8.
|
|
18
|
+
* `waitForExitMs`: a timeout rejects only once the killed child exited (or
|
|
19
|
+
* after that bound), with `childExited` and `pid` on the error (run-child.mjs).
|
|
18
20
|
*/
|
|
19
|
-
export function runGitOffThread(
|
|
20
|
-
|
|
21
|
+
export function runGitOffThread(
|
|
22
|
+
args,
|
|
23
|
+
{ cwd, input = '', env = null, timeoutMs, maxBytes, stdoutEncoding = 'utf8', waitForExitMs = 0 }
|
|
24
|
+
) {
|
|
25
|
+
return requestGit({ args, cwd, input, env, timeoutMs, maxBytes, stdoutEncoding, waitForExitMs });
|
|
21
26
|
}
|
|
@@ -73,7 +73,12 @@ export async function collectGarbageNow() {
|
|
|
73
73
|
* @param {(message: string) => void} [options.log]
|
|
74
74
|
* @param {string} [options.label] Log prefix.
|
|
75
75
|
*/
|
|
76
|
-
export function createIdleGc({
|
|
76
|
+
export function createIdleGc({
|
|
77
|
+
isBusy,
|
|
78
|
+
log = () => {},
|
|
79
|
+
label = 'idle gc',
|
|
80
|
+
memoryUsage = () => process.memoryUsage(),
|
|
81
|
+
} = {}) {
|
|
77
82
|
const enabled = !DISABLED.test(String(process.env.MIXDOG_IDLE_GC || ''));
|
|
78
83
|
// Long enough that the pause between two turns of one conversation never
|
|
79
84
|
// triggers a sweep, short enough that a desktop left alone reclaims soon.
|
|
@@ -90,6 +95,11 @@ export function createIdleGc({ isBusy, log = () => {}, label = 'idle gc' } = {})
|
|
|
90
95
|
// between committed and used is itself a reason to sweep — the collection is
|
|
91
96
|
// what lets V8 release those pages — so either signal alone arms the sweep.
|
|
92
97
|
const minSlackBytes = envNumber('MIXDOG_IDLE_GC_MIN_SLACK_MB', 64) * MB;
|
|
98
|
+
// Buffers, ArrayBuffers and external strings live outside the JS heap, so a
|
|
99
|
+
// daemon can sit on gigabytes of dead attachment data behind a tiny heap
|
|
100
|
+
// (external 2.8GB beside heapTotal 7MB was measured) and never trip the heap
|
|
101
|
+
// floors above. Only a collection frees them.
|
|
102
|
+
const minExternalBytes = envNumber('MIXDOG_IDLE_GC_MIN_EXTERNAL_MB', 96) * MB;
|
|
93
103
|
// Re-sweeping a process that has not allocated since the last sweep only
|
|
94
104
|
// burns CPU; require real growth (or an announced release, see
|
|
95
105
|
// noteMemoryRelease) before running again.
|
|
@@ -98,6 +108,7 @@ export function createIdleGc({ isBusy, log = () => {}, label = 'idle gc' } = {})
|
|
|
98
108
|
let timer = null;
|
|
99
109
|
let lastBusyAt = Date.now();
|
|
100
110
|
let lastSweptHeap = 0;
|
|
111
|
+
let lastSweptExternal = 0;
|
|
101
112
|
let sweptReleaseSeq = releaseSeq;
|
|
102
113
|
let appliedReleaseAt = lastReleaseAt;
|
|
103
114
|
let sweeping = false;
|
|
@@ -123,12 +134,22 @@ export function createIdleGc({ isBusy, log = () => {}, label = 'idle gc' } = {})
|
|
|
123
134
|
lastBusyAt = Math.max(lastBusyAt, Math.min(lastReleaseAt, Date.now()));
|
|
124
135
|
}
|
|
125
136
|
if (Date.now() - lastBusyAt < idleMs) return 'settling';
|
|
126
|
-
const usageBefore =
|
|
137
|
+
const usageBefore = memoryUsage();
|
|
127
138
|
const before = usageBefore.heapUsed;
|
|
139
|
+
const externalBefore = usageBefore.external || 0;
|
|
128
140
|
const slackBefore = Math.max(0, usageBefore.heapTotal - before);
|
|
129
|
-
if (before < minHeapBytes && slackBefore < minSlackBytes)
|
|
141
|
+
if (before < minHeapBytes && slackBefore < minSlackBytes && externalBefore < minExternalBytes) {
|
|
142
|
+
return 'small';
|
|
143
|
+
}
|
|
130
144
|
const released = releaseSeq !== sweptReleaseSeq;
|
|
131
|
-
if (
|
|
145
|
+
if (
|
|
146
|
+
lastSweptHeap &&
|
|
147
|
+
!released &&
|
|
148
|
+
before < lastSweptHeap + growthBytes &&
|
|
149
|
+
externalBefore < lastSweptExternal + growthBytes
|
|
150
|
+
) {
|
|
151
|
+
return 'unchanged';
|
|
152
|
+
}
|
|
132
153
|
|
|
133
154
|
sweeping = true;
|
|
134
155
|
const releaseSeqAtSweep = releaseSeq;
|
|
@@ -139,9 +160,10 @@ export function createIdleGc({ isBusy, log = () => {}, label = 'idle gc' } = {})
|
|
|
139
160
|
disarm();
|
|
140
161
|
return 'unavailable';
|
|
141
162
|
}
|
|
142
|
-
const usageAfter =
|
|
163
|
+
const usageAfter = memoryUsage();
|
|
143
164
|
const after = usageAfter.heapUsed;
|
|
144
165
|
lastSweptHeap = after;
|
|
166
|
+
lastSweptExternal = usageAfter.external || 0;
|
|
145
167
|
// Releases announced while this sweep ran still get their own.
|
|
146
168
|
sweptReleaseSeq = releaseSeqAtSweep;
|
|
147
169
|
const mb = (bytes) => (bytes / MB).toFixed(1);
|
|
@@ -150,7 +172,8 @@ export function createIdleGc({ isBusy, log = () => {}, label = 'idle gc' } = {})
|
|
|
150
172
|
log(
|
|
151
173
|
`${label}: heapUsed ${mb(before)} -> ${mb(after)} MB` +
|
|
152
174
|
` (reclaimed ${mb(before - after)} MB) in ${Date.now() - startedAt}ms` +
|
|
153
|
-
`, committed ${mb(usageBefore.heapTotal)} -> ${mb(usageAfter.heapTotal)} MB`
|
|
175
|
+
`, committed ${mb(usageBefore.heapTotal)} -> ${mb(usageAfter.heapTotal)} MB` +
|
|
176
|
+
`, external ${mb(externalBefore)} -> ${mb(usageAfter.external || 0)} MB`
|
|
154
177
|
);
|
|
155
178
|
return 'swept';
|
|
156
179
|
} catch (e) {
|
|
@@ -185,6 +208,6 @@ export function createIdleGc({ isBusy, log = () => {}, label = 'idle gc' } = {})
|
|
|
185
208
|
},
|
|
186
209
|
/** Drives one cycle synchronously for tests; returns the decision taken. */
|
|
187
210
|
_tickForTest: tick,
|
|
188
|
-
_configForTest: { enabled, idleMs, checkMs, minHeapBytes, minSlackBytes, growthBytes },
|
|
211
|
+
_configForTest: { enabled, idleMs, checkMs, minHeapBytes, minSlackBytes, minExternalBytes, growthBytes },
|
|
189
212
|
};
|
|
190
213
|
}
|