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
|
@@ -2,7 +2,7 @@ import { __mixdogMemoryLog } from './memory-log.mjs';
|
|
|
2
2
|
|
|
3
3
|
import { embedTexts, getEmbeddingModelId, holdEmbeddingWarm } from './embedding-provider.mjs';
|
|
4
4
|
import { onnxRuntimeSupported } from '../../shared/onnx-runtime-support.mjs';
|
|
5
|
-
import { embeddingToSql } from './memory.mjs';
|
|
5
|
+
import { embeddingToSql, ROOT_EMBED_SWEEP_META_KEY } from './memory.mjs';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { pruneEmbeddingCache, resolveEmbeddingCacheMaxRows } from './embedding-cache-retention.mjs';
|
|
8
8
|
import { throwIfAborted, markStoreFault } from './memory-cycle-shared.mjs';
|
|
@@ -524,6 +524,18 @@ async function syncRawBatchEmbeddings(db, ids, options = {}) {
|
|
|
524
524
|
return writtenIds;
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
// A chunk root is also the chunk's first message: cycle1 promotes that raw row
|
|
528
|
+
// in place, and every other member keeps its own content vector. Embedding only
|
|
529
|
+
// element/summary left the first message's own words with no vector, so its
|
|
530
|
+
// details were reachable only through the lossy summary. The summary leads so a
|
|
531
|
+
// long message truncated at the model window still keeps it.
|
|
532
|
+
export function rootEmbedText(row) {
|
|
533
|
+
const head = [row?.element, row?.summary].filter(Boolean).join(' — ').trim();
|
|
534
|
+
if (!head) return '';
|
|
535
|
+
const content = isRawEmbeddable(row?.role, row?.content) ? row.content.trim() : '';
|
|
536
|
+
return content ? `${head}\n\n${content}` : head;
|
|
537
|
+
}
|
|
538
|
+
|
|
527
539
|
// Batch-embed all ids in one DB round-trip for fetching, one API call per unique text,
|
|
528
540
|
// then one VALUES UPDATE for all results.
|
|
529
541
|
// Returns the list of ids that were actually UPDATEd with a fresh embedding.
|
|
@@ -535,9 +547,10 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
|
535
547
|
throwIfAborted(signal);
|
|
536
548
|
if (!onnxRuntimeSupported()) return [];
|
|
537
549
|
const rows = (
|
|
538
|
-
await db.query(
|
|
539
|
-
|
|
540
|
-
|
|
550
|
+
await db.query(
|
|
551
|
+
`SELECT id, element, summary, content, role FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`,
|
|
552
|
+
[ids]
|
|
553
|
+
)
|
|
541
554
|
).rows;
|
|
542
555
|
throwIfAborted(signal);
|
|
543
556
|
if (rows.length === 0) return [];
|
|
@@ -546,8 +559,9 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
|
546
559
|
throwIfAborted(signal);
|
|
547
560
|
const expected = Number(dimsRow?.value ?? 0);
|
|
548
561
|
|
|
549
|
-
const texts = rows.map(
|
|
550
|
-
const
|
|
562
|
+
const texts = rows.map(rootEmbedText);
|
|
563
|
+
const embedBatch = options.embedBatch ?? cachedEmbedTextBatch;
|
|
564
|
+
const vectors = await embedBatch(db, texts.filter(Boolean), { signal });
|
|
551
565
|
throwIfAborted(signal);
|
|
552
566
|
// Re-map: only non-empty texts were passed to cachedEmbedTextBatch
|
|
553
567
|
let vecIdx = 0;
|
|
@@ -562,28 +576,29 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
|
562
576
|
__mixdogMemoryLog(`[embed] dim mismatch (id=${row.id} got=${vector.length} expected=${expected})\n`);
|
|
563
577
|
continue;
|
|
564
578
|
}
|
|
565
|
-
updates.push({ id: Number(row.id), element: row.element, summary: row.summary, vector });
|
|
579
|
+
updates.push({ id: Number(row.id), element: row.element, summary: row.summary, content: row.content, vector });
|
|
566
580
|
}
|
|
567
581
|
|
|
568
582
|
if (updates.length === 0) return [];
|
|
569
583
|
|
|
570
584
|
// The VALUES update is one SQL batch; do not split it with an abort checkpoint.
|
|
571
585
|
const valClauses = updates.map((_u, i) => {
|
|
572
|
-
const base = i *
|
|
573
|
-
return `($${base + 1}::bigint, $${base + 2}::halfvec, $${base + 3}::text, $${base + 4}::text)`;
|
|
586
|
+
const base = i * 5;
|
|
587
|
+
return `($${base + 1}::bigint, $${base + 2}::halfvec, $${base + 3}::text, $${base + 4}::text, $${base + 5}::text)`;
|
|
574
588
|
});
|
|
575
589
|
const params = [];
|
|
576
590
|
for (const u of updates) {
|
|
577
|
-
params.push(u.id, embeddingToSql(u.vector), u.element, u.summary);
|
|
591
|
+
params.push(u.id, embeddingToSql(u.vector), u.element, u.summary, u.content);
|
|
578
592
|
}
|
|
579
593
|
const res = await db.query(
|
|
580
594
|
`UPDATE memory.entries AS e
|
|
581
595
|
SET embedding = t.vector
|
|
582
|
-
FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, element, summary)
|
|
596
|
+
FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, element, summary, content)
|
|
583
597
|
WHERE e.id = t.id AND e.is_root = 1
|
|
584
|
-
AND e.embedding IS NULL
|
|
598
|
+
AND e.embedding IS ${options.replace ? 'NOT ' : ''}NULL
|
|
585
599
|
AND e.element IS NOT DISTINCT FROM t.element
|
|
586
600
|
AND e.summary IS NOT DISTINCT FROM t.summary
|
|
601
|
+
AND e.content IS NOT DISTINCT FROM t.content
|
|
587
602
|
RETURNING e.id`,
|
|
588
603
|
params
|
|
589
604
|
);
|
|
@@ -596,7 +611,7 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
|
596
611
|
const curRows = missedIds.length
|
|
597
612
|
? (
|
|
598
613
|
await db.query(
|
|
599
|
-
`SELECT id, embedding IS NOT NULL AS has_embedding, element, summary
|
|
614
|
+
`SELECT id, embedding IS NOT NULL AS has_embedding, element, summary, content
|
|
600
615
|
FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`,
|
|
601
616
|
[missedIds]
|
|
602
617
|
)
|
|
@@ -610,7 +625,8 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
|
610
625
|
if (!cur) continue;
|
|
611
626
|
const textUnchanged =
|
|
612
627
|
(cur.element === u.element || (cur.element == null && u.element == null)) &&
|
|
613
|
-
(cur.summary === u.summary || (cur.summary == null && u.summary == null))
|
|
628
|
+
(cur.summary === u.summary || (cur.summary == null && u.summary == null)) &&
|
|
629
|
+
(cur.content === u.content || (cur.content == null && u.content == null));
|
|
614
630
|
if (!textUnchanged) {
|
|
615
631
|
staleSkips++;
|
|
616
632
|
continue;
|
|
@@ -631,6 +647,59 @@ async function syncBatchEmbeddings(db, ids, options = {}) {
|
|
|
631
647
|
return writtenIds;
|
|
632
648
|
}
|
|
633
649
|
|
|
650
|
+
// One paced step of the root-text sweep (startRootEmbedSweep in memory.mjs).
|
|
651
|
+
// Existing vectors stay searchable until replaced: the step claims the next
|
|
652
|
+
// roots below the stored cursor that still HAVE a vector, embeds their current
|
|
653
|
+
// text, replaces each vector only where the text is unchanged, and advances the
|
|
654
|
+
// cursor in the same transaction. The meta row lock serializes concurrent
|
|
655
|
+
// sweepers; any failure rolls back both vectors and cursor. Roots without a
|
|
656
|
+
// vector are left to the ordinary flush, which already uses the new text.
|
|
657
|
+
const ROOT_SWEEP_BATCH = 8;
|
|
658
|
+
export async function sweepRootEmbedText(db, options = {}) {
|
|
659
|
+
const { batchSize = ROOT_SWEEP_BATCH, signal, embedBatch } = options;
|
|
660
|
+
throwIfAborted(signal);
|
|
661
|
+
if (!onnxRuntimeSupported()) return { claimed: 0, replaced: 0, done: true };
|
|
662
|
+
await ensureEmbCacheTable(db);
|
|
663
|
+
const client = await db._pool.connect();
|
|
664
|
+
let committed = false;
|
|
665
|
+
try {
|
|
666
|
+
await client.query('BEGIN');
|
|
667
|
+
const state = await client.query(`SELECT value FROM memory.meta WHERE key = $1 FOR UPDATE`, [
|
|
668
|
+
ROOT_EMBED_SWEEP_META_KEY,
|
|
669
|
+
]);
|
|
670
|
+
const value = state.rows[0]?.value;
|
|
671
|
+
const cursor = Number(value?.cursor);
|
|
672
|
+
if (!value || value.done === true || !Number.isFinite(cursor)) {
|
|
673
|
+
await client.query('COMMIT');
|
|
674
|
+
committed = true;
|
|
675
|
+
return { claimed: 0, replaced: 0, done: true };
|
|
676
|
+
}
|
|
677
|
+
const claim = await client.query(
|
|
678
|
+
`SELECT id FROM memory.entries
|
|
679
|
+
WHERE is_root = 1 AND id < $1 AND embedding IS NOT NULL
|
|
680
|
+
AND (element IS NOT NULL OR summary IS NOT NULL)
|
|
681
|
+
ORDER BY id DESC
|
|
682
|
+
LIMIT $2
|
|
683
|
+
FOR UPDATE`,
|
|
684
|
+
[cursor, batchSize]
|
|
685
|
+
);
|
|
686
|
+
const ids = claim.rows.map((r) => Number(r.id));
|
|
687
|
+
throwIfAborted(signal);
|
|
688
|
+
const written = ids.length ? await syncBatchEmbeddings(client, ids, { signal, replace: true, embedBatch }) : [];
|
|
689
|
+
const done = ids.length < batchSize;
|
|
690
|
+
await client.query(
|
|
691
|
+
`UPDATE memory.meta SET value = jsonb_build_object('cursor', $2::bigint, 'done', $3::boolean) WHERE key = $1`,
|
|
692
|
+
[ROOT_EMBED_SWEEP_META_KEY, ids.length ? Math.min(...ids) : cursor, done]
|
|
693
|
+
);
|
|
694
|
+
await client.query('COMMIT');
|
|
695
|
+
committed = true;
|
|
696
|
+
return { claimed: ids.length, replaced: written.length, done };
|
|
697
|
+
} finally {
|
|
698
|
+
if (!committed) await client.query('ROLLBACK').catch(() => {});
|
|
699
|
+
client.release();
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
634
703
|
// Explicit maintenance must propagate partial failures and caller cancellation,
|
|
635
704
|
// including callers sharing a flush that was started without their signal.
|
|
636
705
|
export async function flushSearchEmbeddings(db, options = {}) {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Process lock for the standalone memory MCP entry.
|
|
2
2
|
// A direct entry (including the isolated daemon child) owns this lock.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { killProcessTreesSync } from '../../agent/orchestrator/mcp/child-tree.mjs';
|
|
6
|
+
|
|
7
|
+
// Windows: the tree kill's whole pass (a table read under load included).
|
|
8
|
+
const PREVIOUS_SERVER_KILL_TIMEOUT_MS = 10_000;
|
|
5
9
|
|
|
6
10
|
export function parsePositivePid(value) {
|
|
7
11
|
const pid = Number(value);
|
|
@@ -18,42 +22,158 @@ export function isPidAliveLocal(pid) {
|
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
const POSIX_ENV = () => ({ ...process.env, LC_ALL: 'C', LANG: 'C' });
|
|
26
|
+
|
|
27
|
+
// When a process was created, as epoch-ms bounds { low, high } around the true
|
|
28
|
+
// time, or null when it cannot be read. Linux: /proc/<pid>/stat starttime
|
|
29
|
+
// (clock ticks since boot) against /proc/uptime, both read at `now`; the
|
|
30
|
+
// margin covers one tick, uptime's centiseconds and the read skew.
|
|
31
|
+
export function linuxCreatedBounds(statText, uptimeText, clockTicks, now) {
|
|
32
|
+
const ticks = linuxStartTicks(statText);
|
|
33
|
+
const startTicks = Number(ticks);
|
|
34
|
+
const uptime = Number(String(uptimeText || '').split(' ')[0]);
|
|
35
|
+
if (ticks === null || !Number.isFinite(uptime) || !(clockTicks > 0)) return null;
|
|
36
|
+
const created = now - (uptime - startTicks / clockTicks) * 1000;
|
|
37
|
+
const margin = 1000 / clockTicks + 20;
|
|
38
|
+
return { low: created - margin, high: created + margin };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Elsewhere: `ps -o lstart=` under the C locale, whole seconds.
|
|
42
|
+
export function lstartCreatedBounds(text) {
|
|
43
|
+
const started = Date.parse(String(text || '').trim());
|
|
44
|
+
return Number.isFinite(started) ? { low: started, high: started + 1000 } : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// /proc/<pid>/stat's starttime field (clock ticks since boot), as its digits.
|
|
48
|
+
function linuxStartTicks(statText) {
|
|
49
|
+
const stat = String(statText || '');
|
|
50
|
+
if (!stat.includes(')')) return null;
|
|
51
|
+
const ticks = stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
|
|
52
|
+
return /^\d+$/.test(ticks || '') ? ticks : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A process's start token, as recorded in the lock and compared on
|
|
56
|
+
* recovery, or null when it cannot be read: Linux `/proc/<pid>/stat`
|
|
57
|
+
* starttime, elsewhere `ps -o lstart=` under the C locale. The two sources
|
|
58
|
+
* never mix (each is prefixed), and both are read from the same host. */
|
|
59
|
+
export function linuxStartToken(statText) {
|
|
60
|
+
const ticks = linuxStartTicks(statText);
|
|
61
|
+
return ticks ? `proc:${ticks}` : null;
|
|
62
|
+
}
|
|
63
|
+
export function lstartStartToken(text) {
|
|
64
|
+
const started = String(text || '').trim();
|
|
65
|
+
return Number.isFinite(Date.parse(started)) ? `lstart:${started}` : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function posixStartToken(pid) {
|
|
69
|
+
try {
|
|
70
|
+
if (process.platform === 'linux') return linuxStartToken(fs.readFileSync(`/proc/${pid}/stat`, 'utf8'));
|
|
71
|
+
const r = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
env: POSIX_ENV(),
|
|
74
|
+
timeout: 5000,
|
|
75
|
+
});
|
|
76
|
+
return lstartStartToken(r.stdout);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function posixCreatedBounds(pid) {
|
|
83
|
+
const env = POSIX_ENV();
|
|
84
|
+
try {
|
|
85
|
+
if (process.platform === 'linux') {
|
|
86
|
+
const ticks = Number(spawnSync('getconf', ['CLK_TCK'], { encoding: 'utf8', env, timeout: 5000 }).stdout);
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
return linuxCreatedBounds(
|
|
89
|
+
fs.readFileSync(`/proc/${pid}/stat`, 'utf8'),
|
|
90
|
+
fs.readFileSync('/proc/uptime', 'utf8'),
|
|
91
|
+
ticks,
|
|
92
|
+
now
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const r = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8', env, timeout: 5000 });
|
|
96
|
+
return lstartCreatedBounds(r.stdout);
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Who holds a lock's pid, from the start token the writer recorded and the
|
|
103
|
+
* pid's current one: 'writer' (the same token: the server that wrote the
|
|
104
|
+
* lock), 'other' (a different one: the writer is gone and its pid reused) or
|
|
105
|
+
* 'unproven' (the current token cannot be read). */
|
|
106
|
+
export function lockHolderTokenVerdict(recordedToken, currentToken) {
|
|
107
|
+
if (!currentToken) return 'unproven';
|
|
108
|
+
return currentToken === recordedToken ? 'writer' : 'other';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The legacy verdict, for a lock written without a start token: from the
|
|
112
|
+
* holder's creation bounds, 'writer' (created before the lock was written),
|
|
113
|
+
* 'other' (created after) or 'unproven' (unreadable, or too close to the
|
|
114
|
+
* write to tell). Only 'writer' may be signalled, and only 'other' or a dead
|
|
115
|
+
* pid lets the lock be recovered. */
|
|
116
|
+
export function lockHolderVerdict(bounds, lockWrittenAt) {
|
|
117
|
+
if (!bounds) return 'unproven';
|
|
118
|
+
if (bounds.high <= lockWrittenAt) return 'writer';
|
|
119
|
+
if (bounds.low > lockWrittenAt) return 'other';
|
|
120
|
+
return 'unproven';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The lock's text: the writer's pid, then (POSIX) its start token on the
|
|
124
|
+
* next line. */
|
|
125
|
+
export function formatLock(pid, token) {
|
|
126
|
+
return token ? `${pid}\n${token}` : String(pid);
|
|
127
|
+
}
|
|
128
|
+
export function parseLock(text) {
|
|
129
|
+
const [pidLine = '', token = ''] = String(text || '').split(/\r?\n/);
|
|
130
|
+
return { pid: Number(pidLine.trim()), token: token.trim() || null };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The lock names its writer only while that writer lives. A POSIX writer
|
|
134
|
+
// recorded its start token; the pid's holder is the writer exactly when its
|
|
135
|
+
// token is still the same. Accepted: lstart has whole seconds, so a writer
|
|
136
|
+
// that died within its first second, its pid reused in that same second,
|
|
137
|
+
// would pass as the writer; sequential pid allocation makes that impractical,
|
|
138
|
+
// and treating same-second tokens as unproven would block every restart.
|
|
139
|
+
// Windows compares the holder's creation time against
|
|
140
|
+
// the lock's write time inside the shared tree kill: its creation times are
|
|
141
|
+
// sub-millisecond, and a pid is only reused after the writer died, after the
|
|
142
|
+
// write. A lock without a token (an older writer) keeps the creation-bounds
|
|
143
|
+
// verdict. A pid taken over since is never signalled; one whose identity
|
|
144
|
+
// cannot be proven is neither signalled nor evicted (fail closed).
|
|
145
|
+
function killPreviousServer(pid, recordedToken, lockWrittenAt, log = () => {}) {
|
|
22
146
|
if (pid <= 0 || pid === process.pid) return false;
|
|
23
147
|
if (process.platform === 'win32') {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
148
|
+
const status = killProcessTreesSync([pid], {
|
|
149
|
+
aliveAt: lockWrittenAt,
|
|
150
|
+
timeoutMs: PREVIOUS_SERVER_KILL_TIMEOUT_MS,
|
|
151
|
+
});
|
|
152
|
+
if (status === 0) {
|
|
30
153
|
log(`[memory-service] Killed previous server PID ${pid}\n`);
|
|
31
154
|
return true;
|
|
32
|
-
} catch (e) {
|
|
33
|
-
// Exit code 128 = process not found; treat stale lock as already-dead = success.
|
|
34
|
-
// Status 128 reliably means "process not found" regardless of locale; no text match needed.
|
|
35
|
-
// Status 1 with English text match handles edge cases on some Windows versions.
|
|
36
|
-
const notFoundText =
|
|
37
|
-
/not found|no running instance/i.test(e.stdout || '') ||
|
|
38
|
-
/not found|no running instance/i.test(e.stderr || '') ||
|
|
39
|
-
/not found|no running instance/i.test(e.message || '');
|
|
40
|
-
const alreadyDead = e.status === 128 || (e.status === 1 && notFoundText);
|
|
41
|
-
if (alreadyDead) {
|
|
42
|
-
log(`[memory-service] PID ${pid} already dead (stale lock), proceeding\n`);
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
log(`[memory-service] taskkill failed for PID ${pid}: ${e.message}\n`);
|
|
46
|
-
return false;
|
|
47
155
|
}
|
|
156
|
+
if (status === 128) {
|
|
157
|
+
log(`[memory-service] PID ${pid} is not the lock's writer any more (stale lock), proceeding\n`);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
log(`[memory-service] Killing previous server PID ${pid} failed (status ${status})\n`);
|
|
161
|
+
return false;
|
|
48
162
|
} else {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
163
|
+
const verdict = !isPidAliveLocal(pid)
|
|
164
|
+
? 'gone'
|
|
165
|
+
: recordedToken
|
|
166
|
+
? lockHolderTokenVerdict(recordedToken, posixStartToken(pid))
|
|
167
|
+
: lockHolderVerdict(posixCreatedBounds(pid), lockWrittenAt);
|
|
168
|
+
if (verdict === 'gone' || verdict === 'other' || (verdict === 'unproven' && !isPidAliveLocal(pid))) {
|
|
169
|
+
log(`[memory-service] PID ${pid} is not the lock's writer any more (stale lock), proceeding\n`);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (verdict === 'unproven') {
|
|
173
|
+
log(
|
|
174
|
+
`[memory-service] PID ${pid} may still be the lock's writer and cannot be proven otherwise; leaving it running\n`
|
|
175
|
+
);
|
|
176
|
+
return false;
|
|
57
177
|
}
|
|
58
178
|
try {
|
|
59
179
|
process.kill(pid, 'SIGTERM');
|
|
@@ -86,9 +206,10 @@ export function acquireLock(lockFile, log = () => {}) {
|
|
|
86
206
|
if (process.env.MIXDOG_MULTI_INSTANCE === '1') return;
|
|
87
207
|
try {
|
|
88
208
|
if (fs.existsSync(lockFile)) {
|
|
89
|
-
const
|
|
209
|
+
const lockWrittenAt = fs.statSync(lockFile).mtimeMs;
|
|
210
|
+
const { pid: lockedPid, token } = parseLock(fs.readFileSync(lockFile, 'utf8'));
|
|
90
211
|
if (lockedPid > 0 && lockedPid !== process.pid) {
|
|
91
|
-
const killed = killPreviousServer(lockedPid, log);
|
|
212
|
+
const killed = killPreviousServer(lockedPid, token, lockWrittenAt, log);
|
|
92
213
|
if (!killed) {
|
|
93
214
|
log(`[memory-service] Could not kill previous server PID ${lockedPid}, aborting\n`);
|
|
94
215
|
process.exit(1);
|
|
@@ -98,9 +219,12 @@ export function acquireLock(lockFile, log = () => {}) {
|
|
|
98
219
|
} catch {}
|
|
99
220
|
}
|
|
100
221
|
}
|
|
222
|
+
// Read before the lock exists: an unreadable token leaves a pid-only lock,
|
|
223
|
+
// which a successor judges by the legacy verdict.
|
|
224
|
+
const token = process.platform === 'win32' ? null : posixStartToken(process.pid);
|
|
101
225
|
const fd = fs.openSync(lockFile, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
|
|
102
226
|
try {
|
|
103
|
-
fs.writeSync(fd,
|
|
227
|
+
fs.writeSync(fd, formatLock(process.pid, token));
|
|
104
228
|
} finally {
|
|
105
229
|
fs.closeSync(fd);
|
|
106
230
|
}
|
|
@@ -116,7 +240,6 @@ export function acquireLock(lockFile, log = () => {}) {
|
|
|
116
240
|
|
|
117
241
|
export function releaseLock(lockFile) {
|
|
118
242
|
try {
|
|
119
|
-
|
|
120
|
-
if (Number(content) === process.pid) fs.unlinkSync(lockFile);
|
|
243
|
+
if (parseLock(fs.readFileSync(lockFile, 'utf8')).pid === process.pid) fs.unlinkSync(lockFile);
|
|
121
244
|
} catch {}
|
|
122
245
|
}
|
|
@@ -130,7 +130,12 @@ export function mergeRecallConceptTokens(text, conceptTokens, limit = 24) {
|
|
|
130
130
|
const entityConcepts = concepts.filter(isEntityConcept);
|
|
131
131
|
const rankingEntityConcepts = entityConcepts.filter((token) => !isStructuralTimeToken(token));
|
|
132
132
|
const useEntityConcepts = rankingEntityConcepts.some(isIdentifierToken) || rankingEntityConcepts.length > 2;
|
|
133
|
-
|
|
133
|
+
// Entity-heavy queries rank their entities first, but the Korean content
|
|
134
|
+
// words stay: dropping them left a mixed query ("웹 검색 원인 Claude Codex
|
|
135
|
+
// Hermes 비교") matching only on product names that half the history shares.
|
|
136
|
+
const rankingConcepts = useEntityConcepts
|
|
137
|
+
? [...entityConcepts, ...concepts.filter((token) => !isEntityConcept(token))]
|
|
138
|
+
: concepts;
|
|
134
139
|
const source =
|
|
135
140
|
concepts.length > 0
|
|
136
141
|
? [...raw.filter((token) => isIdentifierToken(token) || isConceptCompound(token, concepts)), ...rankingConcepts]
|
|
@@ -309,6 +309,22 @@ async function removeReviewAndPromotionCycleState(db) {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// Root vectors now cover the chunk's first message as well as its summary
|
|
313
|
+
// (memory-embed.mjs rootEmbedText). Vectors written under the older text stay
|
|
314
|
+
// in place and are replaced by a paced background sweep (sweepRootEmbedText)
|
|
315
|
+
// that walks roots below this starting cursor, newest first. One statement, so
|
|
316
|
+
// the start is atomic; ON CONFLICT keeps an existing sweep's progress.
|
|
317
|
+
export const ROOT_EMBED_SWEEP_META_KEY = 'embedding.root_text_v2';
|
|
318
|
+
export async function startRootEmbedSweep(db) {
|
|
319
|
+
await db.query(
|
|
320
|
+
`INSERT INTO meta(key, value)
|
|
321
|
+
SELECT $1, jsonb_build_object('cursor', COALESCE(MAX(id), 0) + 1, 'done', false)
|
|
322
|
+
FROM entries WHERE is_root = 1
|
|
323
|
+
ON CONFLICT (key) DO NOTHING`,
|
|
324
|
+
[ROOT_EMBED_SWEEP_META_KEY]
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
312
328
|
export async function ensureCurrentSchemaExtensions(db, dims, embeddingIdentity = null) {
|
|
313
329
|
await removeAttachmentPlaceholderRows(db);
|
|
314
330
|
await removeRuntimeNotificationRowsOnce(db);
|
|
@@ -413,6 +429,7 @@ export async function openDatabase(dataDir, dims, embeddingIdentity = null) {
|
|
|
413
429
|
}
|
|
414
430
|
await ensureCurrentSchemaExtensions(db, Number(dims), embeddingIdentity);
|
|
415
431
|
await validateEmbeddingDims(db, Number(dims));
|
|
432
|
+
await withSchemaBootstrapLock(pool, () => startRootEmbedSweep(db));
|
|
416
433
|
|
|
417
434
|
dbs.set(key, db);
|
|
418
435
|
return db;
|
|
@@ -400,7 +400,7 @@ async function startAndWaitReady({ pgctl, startArgs, env, pgdataDir, port }) {
|
|
|
400
400
|
// so the postmaster is already reparented OUT of the Node process tree —
|
|
401
401
|
// detached only isolated the short-lived pg_ctl. Shutdown/reuse target
|
|
402
402
|
// pgdata/postmaster.pid (pg_ctl stop -D, tryReusePgInstance), not a
|
|
403
|
-
// Node-tree
|
|
403
|
+
// Node-tree kill, so `windowsHide` alone is correct on all platforms.
|
|
404
404
|
const child = spawn(pgctl, startArgs, { env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
405
405
|
child.unref?.();
|
|
406
406
|
const run = { stdout: '', stderr: '', closed: false, exitCode: null };
|
|
@@ -20,7 +20,7 @@ import { __mixdogMemoryLog } from '../memory-log.mjs';
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { createServer } from 'node:net';
|
|
23
|
-
import { existsSync, unlinkSync, readFileSync,
|
|
23
|
+
import { existsSync, unlinkSync, readFileSync, renameSync, statSync, mkdirSync } from 'node:fs';
|
|
24
24
|
import { join, resolve } from 'node:path';
|
|
25
25
|
import {
|
|
26
26
|
discoveryPath as _pgDiscoveryPath,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
writeServiceAdvert as _writePgServiceAdvert,
|
|
29
29
|
} from '../../../shared/service-discovery.mjs';
|
|
30
30
|
import { withFileLockSync } from '../../../shared/atomic-file.mjs';
|
|
31
|
+
import { withFileLock } from '../../../shared/file-lock.mjs';
|
|
31
32
|
import { isPidAlive } from '../../../shared/pid-liveness.mjs';
|
|
32
33
|
import { envFlag } from '../../../shared/env.mjs';
|
|
33
34
|
import { createSharedAbortable } from '../../../shared/abort-race.mjs';
|
|
@@ -64,92 +65,11 @@ let _ownedExitStopInstalled = false;
|
|
|
64
65
|
const PG_PORT_MIN = 55432;
|
|
65
66
|
const PG_PORT_MAX = 55632;
|
|
66
67
|
const PG_LOG_MAX_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
67
|
-
const SPAWN_LOCK_NAME = 'pg-spawn.lock';
|
|
68
|
-
const LOCK_WAIT_MS = 30_000;
|
|
69
|
-
const LOCK_POLL_MS = 100;
|
|
70
|
-
const LOCK_WARN_MS = 5_000;
|
|
71
|
-
const LOCK_WAIT_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY']);
|
|
72
68
|
|
|
73
69
|
function attachOnlyMode() {
|
|
74
70
|
return envFlag('MIXDOG_PG_ATTACH_ONLY') || envFlag('MIXDOG_MEMORY_SECONDARY');
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
// ── File lock (O_EXCL pattern from dispatch-persist.mjs / run-mcp.mjs) ───────
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Acquire `${dataDir}/pg-spawn.lock`.
|
|
81
|
-
* Returns the lock-file path on success, or null on timeout (caller proceeds
|
|
82
|
-
* best-effort — worst case: two supervisors race initdb on the same pgdata,
|
|
83
|
-
* but the second will fail pg_ctl start gracefully and healthcheck will
|
|
84
|
-
* converge on the first's port).
|
|
85
|
-
*/
|
|
86
|
-
async function acquireSpawnLock(dataDir, blockedProbe = null) {
|
|
87
|
-
const lp = join(dataDir, SPAWN_LOCK_NAME);
|
|
88
|
-
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
89
|
-
const body = JSON.stringify({ pid: process.pid, startedAt: Date.now() });
|
|
90
|
-
let warned = false;
|
|
91
|
-
for (;;) {
|
|
92
|
-
try {
|
|
93
|
-
// 'wx' = O_CREAT | O_EXCL — atomic, fails with EEXIST if held.
|
|
94
|
-
writeFileSync(lp, body, { flag: 'wx' });
|
|
95
|
-
return { lockPath: lp, reuse: null };
|
|
96
|
-
} catch (err) {
|
|
97
|
-
if (!LOCK_WAIT_CODES.has(err?.code)) {
|
|
98
|
-
__mixdogMemoryLog(`[supervisor-pg] spawn lock error: ${err?.code || err?.message}\n`);
|
|
99
|
-
return { lockPath: null, reuse: null };
|
|
100
|
-
}
|
|
101
|
-
if (blockedProbe) {
|
|
102
|
-
try {
|
|
103
|
-
const reuse = await blockedProbe();
|
|
104
|
-
if (reuse) return { lockPath: null, reuse };
|
|
105
|
-
} catch {}
|
|
106
|
-
}
|
|
107
|
-
// Dead-holder detection: same as run-mcp.mjs acquireLock().
|
|
108
|
-
try {
|
|
109
|
-
const holder = JSON.parse(readFileSync(lp, 'utf8'));
|
|
110
|
-
if (holder?.pid) {
|
|
111
|
-
try {
|
|
112
|
-
process.kill(holder.pid, 0);
|
|
113
|
-
} catch (ke) {
|
|
114
|
-
if (ke.code === 'ESRCH') {
|
|
115
|
-
try {
|
|
116
|
-
unlinkSync(lp);
|
|
117
|
-
} catch {}
|
|
118
|
-
continue; // retry immediately after removing stale lock
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const ageMs = Date.now() - Number(holder?.startedAt || 0);
|
|
123
|
-
if (!warned && ageMs >= LOCK_WARN_MS) {
|
|
124
|
-
warned = true;
|
|
125
|
-
__mixdogMemoryLog(
|
|
126
|
-
`[supervisor-pg] waiting for spawn lock holder pid=${holder?.pid || 'unknown'} ageMs=${ageMs}\n`
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
} catch {
|
|
130
|
-
/* unreadable — fall through and wait */
|
|
131
|
-
}
|
|
132
|
-
if (Date.now() >= deadline) {
|
|
133
|
-
if (blockedProbe) {
|
|
134
|
-
try {
|
|
135
|
-
const reuse = await blockedProbe();
|
|
136
|
-
if (reuse) return { lockPath: null, reuse };
|
|
137
|
-
} catch {}
|
|
138
|
-
}
|
|
139
|
-
throw new Error(`[supervisor-pg] spawn-lock acquire timeout (concurrent supervisor risk)`);
|
|
140
|
-
}
|
|
141
|
-
await new Promise((r) => setTimeout(r, LOCK_POLL_MS));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function releaseSpawnLock(lp) {
|
|
147
|
-
if (!lp) return;
|
|
148
|
-
try {
|
|
149
|
-
unlinkSync(lp);
|
|
150
|
-
} catch {}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
73
|
// ── Port allocation ──────────────────────────────────────────────────────────
|
|
154
74
|
|
|
155
75
|
function _probePort(port) {
|
|
@@ -602,18 +522,10 @@ async function _doEnsure(dataDir, signal) {
|
|
|
602
522
|
throw new Error('secondary memory runtime requires an existing PG instance');
|
|
603
523
|
}
|
|
604
524
|
|
|
605
|
-
// ──
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
runtimeDir,
|
|
610
|
-
healthcheckPg,
|
|
611
|
-
source: 'lock-wait',
|
|
612
|
-
})
|
|
613
|
-
);
|
|
614
|
-
if (acquired?.reuse) return acquired.reuse;
|
|
615
|
-
const lp = acquired?.lockPath ?? null;
|
|
616
|
-
try {
|
|
525
|
+
// ── Spawn lock serializes initdb races (see withFileLock below) ──────────
|
|
526
|
+
let lockEntered = false;
|
|
527
|
+
const spawnUnderLock = async () => {
|
|
528
|
+
lockEntered = true;
|
|
617
529
|
// ── Reuse path: another supervisor already started PG ─────────────────
|
|
618
530
|
const lockedReuse = await tryReusePgInstance({
|
|
619
531
|
pgdata,
|
|
@@ -701,8 +613,16 @@ async function _doEnsure(dataDir, signal) {
|
|
|
701
613
|
// ── Allocate a fresh port and spawn ───────────────────────────────────
|
|
702
614
|
const port = await allocatePort();
|
|
703
615
|
return await _startFresh(dataDir, pgdata, port, runtimeDir);
|
|
704
|
-
}
|
|
705
|
-
|
|
616
|
+
};
|
|
617
|
+
try {
|
|
618
|
+
return await withFileLock(join(dataDir, 'pg-spawn.lock'), spawnUnderLock, { timeoutMs: 30_000 });
|
|
619
|
+
} catch (err) {
|
|
620
|
+
// Only an acquisition timeout (spawnUnderLock never ran) falls back to
|
|
621
|
+
// reuse; failures from inside the locked region propagate untouched.
|
|
622
|
+
if (lockEntered || err?.code !== 'ELOCKTIMEOUT') throw err;
|
|
623
|
+
const reuse = await tryReusePgInstance({ pgdata, runtimeDir, healthcheckPg, source: 'lock-wait' });
|
|
624
|
+
if (reuse) return reuse;
|
|
625
|
+
throw new Error('[supervisor-pg] spawn-lock acquire timeout (concurrent supervisor risk)', { cause: err });
|
|
706
626
|
}
|
|
707
627
|
}
|
|
708
628
|
|
|
@@ -119,6 +119,11 @@ exact AS (
|
|
|
119
119
|
WHERE false
|
|
120
120
|
),`;
|
|
121
121
|
}
|
|
122
|
+
// Each row is lowercased once (OFFSET 0 keeps the subquery from being
|
|
123
|
+
// inlined into the per-term condition) and every term is a literal strpos
|
|
124
|
+
// on that text. Per-term ILIKE re-lowercased the whole content for every
|
|
125
|
+
// term, so a natural-language query's 10+ terms multiplied the scan cost.
|
|
126
|
+
// Terms arrive lowercased from buildExactTerms.
|
|
122
127
|
return `
|
|
123
128
|
exact_matches AS (
|
|
124
129
|
SELECT ee.id,
|
|
@@ -126,10 +131,14 @@ exact_matches AS (
|
|
|
126
131
|
q.term,
|
|
127
132
|
COUNT(*) OVER (PARTITION BY q.term)::float8 AS term_df
|
|
128
133
|
FROM entries ee
|
|
134
|
+
CROSS JOIN LATERAL (
|
|
135
|
+
SELECT lower(ee.content) AS content, lower(ee.element) AS element, lower(ee.summary) AS summary
|
|
136
|
+
OFFSET 0
|
|
137
|
+
) lc
|
|
129
138
|
JOIN LATERAL unnest($${exactTermsParam}::text[]) AS q(term) ON (
|
|
130
|
-
|
|
131
|
-
OR
|
|
132
|
-
OR
|
|
139
|
+
strpos(lc.content, q.term) > 0
|
|
140
|
+
OR strpos(lc.element, q.term) > 0
|
|
141
|
+
OR strpos(lc.summary, q.term) > 0
|
|
133
142
|
)
|
|
134
143
|
WHERE true
|
|
135
144
|
${filters.exactFilterClause}
|
|
@@ -17,7 +17,7 @@ import { renderPdfPages } from '../pdf/pdf-render.mjs';
|
|
|
17
17
|
const PX_TO_PT = 0.75;
|
|
18
18
|
|
|
19
19
|
/** Artifacts kept beside the document: its HTML and the browser's print of it. */
|
|
20
|
-
|
|
20
|
+
function docxHtmlArtifacts(target) {
|
|
21
21
|
return {
|
|
22
22
|
source: `${target}.mixdog-source.html`,
|
|
23
23
|
print: `${target}.mixdog-html.pdf`,
|
|
@@ -34,7 +34,7 @@ const PPTX_AUTHOR_NEEDS_SOURCE =
|
|
|
34
34
|
'author requires script: HTML slides or a pptxgenjs script (without one it re-authors from <deck>.pptx.mixdog-source.html, and none is there). Load the `pptx` Skill first (Skill name:"pptx"): it carries the authoring workflow, the HTML contract, the composition grammar, and the device kit, then call author again with path and script.';
|
|
35
35
|
|
|
36
36
|
// HTML opens with markup (a doctype, a comment, an element); a pptxgenjs script never does.
|
|
37
|
-
|
|
37
|
+
function isHtmlSource(source) {
|
|
38
38
|
return /^\s*</.test(String(source || ''));
|
|
39
39
|
}
|
|
40
40
|
|