mixdog 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +20 -0
- package/.gitattributes +34 -0
- package/.mcp.json +14 -0
- package/ARCHITECTURE.md +77 -0
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +45 -0
- package/DATA-FLOW.md +79 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/SECURITY.md +138 -0
- package/UNINSTALL.md +112 -0
- package/agents/maintenance.md +5 -0
- package/agents/memory-classification.md +30 -0
- package/agents/scheduler-task.md +18 -0
- package/agents/webhook-handler.md +27 -0
- package/agents/worker.md +24 -0
- package/bin/bridge +133 -0
- package/bin/statusline-launcher.mjs +78 -0
- package/bin/statusline-lib.mjs +550 -0
- package/bin/statusline.mjs +607 -0
- package/bun.lock +802 -0
- package/commands/config.md +16 -0
- package/commands/doctor.md +13 -0
- package/commands/setup.md +17 -0
- package/defaults/cycle3-review-prompt.md +90 -0
- package/defaults/hidden-roles.json +65 -0
- package/defaults/memory-chunk-prompt.md +63 -0
- package/defaults/memory-promote-prompt.md +135 -0
- package/defaults/mixdog-config.template.json +27 -0
- package/defaults/user-workflow.json +8 -0
- package/defaults/user-workflow.md +12 -0
- package/hooks/hooks.json +73 -0
- package/hooks/lib/active-instance.cjs +77 -0
- package/hooks/lib/permission-evaluator.cjs +411 -0
- package/hooks/lib/permission-route.cjs +63 -0
- package/hooks/lib/permission-rules.cjs +170 -0
- package/hooks/lib/settings-loader.cjs +116 -0
- package/hooks/post-tool-use.cjs +84 -0
- package/hooks/pre-mcp-sandbox.cjs +158 -0
- package/hooks/pre-tool-subagent.cjs +253 -0
- package/hooks/session-start.cjs +1372 -0
- package/hooks/turn-timer.cjs +82 -0
- package/lib/claude-md-writer.cjs +386 -0
- package/lib/config-cjs.cjs +61 -0
- package/lib/hook-pipe-path.cjs +10 -0
- package/lib/keychain-cjs.cjs +263 -0
- package/lib/plugin-paths.cjs +61 -0
- package/lib/rules-builder.cjs +241 -0
- package/lib/text-utils.cjs +61 -0
- package/native/README.md +117 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +107 -0
- package/prompts/code-review.txt +16 -0
- package/prompts/security-audit.txt +17 -0
- package/rules/bridge/00-common.md +39 -0
- package/rules/bridge/20-skip-protocol.md +18 -0
- package/rules/bridge/30-explorer.md +33 -0
- package/rules/bridge/40-cycle1-agent.md +52 -0
- package/rules/bridge/41-cycle2-agent.md +62 -0
- package/rules/bridge/42-cycle3-agent.md +44 -0
- package/rules/lead/00-tool-lead.md +61 -0
- package/rules/lead/01-general.md +23 -0
- package/rules/lead/02-channels.md +49 -0
- package/rules/lead/03-team.md +27 -0
- package/rules/lead/04-workflow.md +20 -0
- package/rules/shared/00-language.md +14 -0
- package/rules/shared/01-tool.md +138 -0
- package/scripts/bootstrap.mjs +184 -0
- package/scripts/bridge-unify-smoke.mjs +308 -0
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/builtin-utils-smoke.mjs +392 -0
- package/scripts/check-json.mjs +45 -0
- package/scripts/check-syntax-changed.mjs +102 -0
- package/scripts/check-syntax.mjs +58 -0
- package/scripts/code-graph-batch.test.mjs +33 -0
- package/scripts/config-preserve-smoke.mjs +180 -0
- package/scripts/doctor.mjs +484 -0
- package/scripts/edit-normalize-fuzz.mjs +130 -0
- package/scripts/edit-normalize-smoke.mjs +401 -0
- package/scripts/edit-operation-smoke.mjs +369 -0
- package/scripts/edit2-smoke.mjs +63 -0
- package/scripts/fuzzy-e2e.mjs +28 -0
- package/scripts/fuzzy-smoke.mjs +26 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/guard-smoke.mjs +66 -0
- package/scripts/hidden-role-schema-smoke.mjs +162 -0
- package/scripts/hook-routing-smoke.mjs +29 -0
- package/scripts/inject-input.ps1 +204 -0
- package/scripts/io-complex-smoke.mjs +667 -0
- package/scripts/io-explore-bench.mjs +424 -0
- package/scripts/io-guardrails-smoke.mjs +205 -0
- package/scripts/io-mini-bench-baseline.json +11 -0
- package/scripts/io-mini-bench.mjs +216 -0
- package/scripts/io-route-harness.mjs +933 -0
- package/scripts/io-telemetry-report.mjs +691 -0
- package/scripts/mutation-bench.mjs +564 -0
- package/scripts/mutation-io-smoke.mjs +1081 -0
- package/scripts/native-patch-bridge-smoke.mjs +288 -0
- package/scripts/native-patch-smoke.mjs +304 -0
- package/scripts/patch-interior-context-smoke.mjs +49 -0
- package/scripts/patch-newline-utf8-smoke.mjs +157 -0
- package/scripts/perf-hook-smoke.mjs +71 -0
- package/scripts/permission-eval-smoke.mjs +426 -0
- package/scripts/prep-patch.mjs +53 -0
- package/scripts/prep-shim.mjs +96 -0
- package/scripts/provider-cache-smoke.mjs +687 -0
- package/scripts/report-runtime-health.mjs +132 -0
- package/scripts/run-mcp.mjs +1547 -0
- package/scripts/salvage-v4a-shatter.test.mjs +58 -0
- package/scripts/scoped-cache-io-smoke.mjs +103 -0
- package/scripts/shell-policy-round3-smoke.mjs +46 -0
- package/scripts/smoke-runtime-negative.ps1 +100 -0
- package/scripts/smoke-runtime-negative.sh +95 -0
- package/scripts/stall-policy-smoke.mjs +50 -0
- package/scripts/start-memory-worker.mjs +23 -0
- package/scripts/statusline-launcher-smoke.mjs +82 -0
- package/scripts/stress-atomic-write.mjs +1028 -0
- package/scripts/test-config-rmw-restore.mjs +122 -0
- package/scripts/test-fault-inject.mjs +164 -0
- package/scripts/test-large-file.mjs +174 -0
- package/scripts/tool-edge-smoke.mjs +209 -0
- package/scripts/uninstall.mjs +201 -0
- package/scripts/webhook-selfheal-smoke.mjs +29 -0
- package/scripts/write-overwrite-guard-smoke.mjs +56 -0
- package/server-main.mjs +3055 -0
- package/server.mjs +468 -0
- package/setup/config-merge.mjs +254 -0
- package/setup/install.mjs +120 -0
- package/setup/launch-core.mjs +507 -0
- package/setup/launch.mjs +101 -0
- package/setup/setup-server.mjs +3206 -0
- package/setup/setup.html +3693 -0
- package/skills/retro-skill-proposer/SKILL.md +92 -0
- package/skills/schedule-add/SKILL.md +77 -0
- package/skills/setup/SKILL.md +346 -0
- package/skills/webhook-add/SKILL.md +81 -0
- package/src/agent/bridge-stall-watchdog.mjs +337 -0
- package/src/agent/index.mjs +2138 -0
- package/src/agent/orchestrator/activity-bus.mjs +38 -0
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
- package/src/agent/orchestrator/bridge-retry.mjs +220 -0
- package/src/agent/orchestrator/bridge-trace.mjs +583 -0
- package/src/agent/orchestrator/cache-mtime.mjs +58 -0
- package/src/agent/orchestrator/config.mjs +358 -0
- package/src/agent/orchestrator/context/collect.mjs +651 -0
- package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
- package/src/agent/orchestrator/drain-registry.mjs +50 -0
- package/src/agent/orchestrator/explore-validator.mjs +8 -0
- package/src/agent/orchestrator/internal-roles.mjs +118 -0
- package/src/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/agent/orchestrator/jobs.mjs +116 -0
- package/src/agent/orchestrator/mcp/client.mjs +364 -0
- package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
- package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
- package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
- package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
- package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
- package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
- package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
- package/src/agent/orchestrator/providers/registry.mjs +192 -0
- package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
- package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
- package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
- package/src/agent/orchestrator/session/cache/util.mjs +49 -0
- package/src/agent/orchestrator/session/loop.mjs +1478 -0
- package/src/agent/orchestrator/session/manager.mjs +1975 -0
- package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
- package/src/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
- package/src/agent/orchestrator/session/store.mjs +624 -0
- package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
- package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/agent/orchestrator/session/trim.mjs +491 -0
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
- package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
- package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
- package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
- package/src/agent/orchestrator/stall-policy.mjs +195 -0
- package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
- package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
- package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
- package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
- package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
- package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
- package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
- package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
- package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
- package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
- package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
- package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
- package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
- package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
- package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
- package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
- package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
- package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
- package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
- package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
- package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
- package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
- package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
- package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
- package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
- package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
- package/src/agent/orchestrator/tools/builtin.mjs +500 -0
- package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
- package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
- package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
- package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
- package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/agent/orchestrator/tools/host-input.mjs +204 -0
- package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
- package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
- package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
- package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
- package/src/agent/orchestrator/tools/patch.mjs +2754 -0
- package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
- package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
- package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
- package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
- package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
- package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
- package/src/agent/orchestrator/workflow-store.mjs +93 -0
- package/src/agent/tool-defs.mjs +103 -0
- package/src/channels/backends/discord.mjs +784 -0
- package/src/channels/data/voice-runtime-manifest.json +138 -0
- package/src/channels/index.mjs +3229 -0
- package/src/channels/lib/cli-worker-host.mjs +12 -0
- package/src/channels/lib/config-lock.mjs +13 -0
- package/src/channels/lib/config.mjs +292 -0
- package/src/channels/lib/drop-trace.mjs +71 -0
- package/src/channels/lib/event-pipeline.mjs +81 -0
- package/src/channels/lib/event-queue.mjs +345 -0
- package/src/channels/lib/executor.mjs +168 -0
- package/src/channels/lib/format.mjs +188 -0
- package/src/channels/lib/holidays.mjs +138 -0
- package/src/channels/lib/hook-pipe-server.mjs +802 -0
- package/src/channels/lib/interaction-workflows.mjs +184 -0
- package/src/channels/lib/memory-client.mjs +149 -0
- package/src/channels/lib/output-forwarder.mjs +765 -0
- package/src/channels/lib/runtime-paths.mjs +479 -0
- package/src/channels/lib/scheduler.mjs +723 -0
- package/src/channels/lib/session-control.mjs +36 -0
- package/src/channels/lib/session-discovery.mjs +103 -0
- package/src/channels/lib/settings.mjs +11 -0
- package/src/channels/lib/state-file.mjs +68 -0
- package/src/channels/lib/status-snapshot.mjs +219 -0
- package/src/channels/lib/tool-format.mjs +140 -0
- package/src/channels/lib/transcript-discovery.mjs +195 -0
- package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/channels/lib/webhook.mjs +1179 -0
- package/src/channels/lib/whisper-server.mjs +477 -0
- package/src/channels/tool-defs.mjs +170 -0
- package/src/daemon/host.mjs +118 -0
- package/src/daemon/mcp-transport.mjs +47 -0
- package/src/daemon/session.mjs +100 -0
- package/src/daemon/thin-client.mjs +71 -0
- package/src/daemon/transport.mjs +163 -0
- package/src/memory/data/runtime-manifest.json +40 -0
- package/src/memory/index.mjs +3305 -0
- package/src/memory/lib/agent-ipc.mjs +93 -0
- package/src/memory/lib/bridge-trace-queries.mjs +120 -0
- package/src/memory/lib/core-memory-store.mjs +330 -0
- package/src/memory/lib/embedding-provider.mjs +269 -0
- package/src/memory/lib/embedding-worker.mjs +323 -0
- package/src/memory/lib/llm-worker-host.mjs +17 -0
- package/src/memory/lib/memory-cycle.mjs +11 -0
- package/src/memory/lib/memory-cycle1.mjs +641 -0
- package/src/memory/lib/memory-cycle2.mjs +1284 -0
- package/src/memory/lib/memory-cycle3.mjs +540 -0
- package/src/memory/lib/memory-embed.mjs +299 -0
- package/src/memory/lib/memory-extraction.mjs +5 -0
- package/src/memory/lib/memory-maintenance-store.mjs +32 -0
- package/src/memory/lib/memory-ops-policy.mjs +190 -0
- package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
- package/src/memory/lib/memory-recall-read-query.mjs +7 -0
- package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
- package/src/memory/lib/memory-recall-store.mjs +621 -0
- package/src/memory/lib/memory-retrievers.mjs +112 -0
- package/src/memory/lib/memory-score.mjs +71 -0
- package/src/memory/lib/memory-text-utils.mjs +58 -0
- package/src/memory/lib/memory.mjs +412 -0
- package/src/memory/lib/model-profile.mjs +85 -0
- package/src/memory/lib/pg/adapter.mjs +308 -0
- package/src/memory/lib/pg/process.mjs +360 -0
- package/src/memory/lib/pg/supervisor.mjs +396 -0
- package/src/memory/lib/project-id-resolver.mjs +86 -0
- package/src/memory/lib/runtime-fetcher.mjs +442 -0
- package/src/memory/lib/trace-store.mjs +728 -0
- package/src/memory/tool-defs.mjs +79 -0
- package/src/search/index.mjs +1173 -0
- package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
- package/src/search/lib/backends/exa.mjs +50 -0
- package/src/search/lib/backends/firecrawl.mjs +61 -0
- package/src/search/lib/backends/gemini-api.mjs +83 -0
- package/src/search/lib/backends/grok-oauth.mjs +86 -0
- package/src/search/lib/backends/index.mjs +150 -0
- package/src/search/lib/backends/openai-api.mjs +144 -0
- package/src/search/lib/backends/openai-oauth.mjs +98 -0
- package/src/search/lib/backends/openai-web-search.mjs +76 -0
- package/src/search/lib/backends/tavily.mjs +55 -0
- package/src/search/lib/backends/xai-api.mjs +113 -0
- package/src/search/lib/cache.mjs +131 -0
- package/src/search/lib/config.mjs +192 -0
- package/src/search/lib/formatter.mjs +115 -0
- package/src/search/lib/provider-usage.mjs +67 -0
- package/src/search/lib/providers.mjs +47 -0
- package/src/search/lib/search-intent.mjs +109 -0
- package/src/search/lib/setup-handler.mjs +261 -0
- package/src/search/lib/state.mjs +201 -0
- package/src/search/lib/web-tools.mjs +1207 -0
- package/src/search/tool-defs.mjs +83 -0
- package/src/setup/defender-exclusion.mjs +183 -0
- package/src/shared/abort-controller.mjs +15 -0
- package/src/shared/atomic-file.mjs +420 -0
- package/src/shared/config.mjs +350 -0
- package/src/shared/daemon-recycle.mjs +108 -0
- package/src/shared/disable-claude-builtins.mjs +88 -0
- package/src/shared/err-text.mjs +12 -0
- package/src/shared/llm/cost.mjs +66 -0
- package/src/shared/llm/http-agent.mjs +123 -0
- package/src/shared/llm/index.mjs +41 -0
- package/src/shared/llm/pid-cleanup.mjs +27 -0
- package/src/shared/llm/usage-log.mjs +47 -0
- package/src/shared/plugin-paths.mjs +58 -0
- package/src/shared/schedules-store.mjs +70 -0
- package/src/shared/seed.mjs +119 -0
- package/src/shared/user-cwd.mjs +213 -0
- package/src/shared/user-data-guard.mjs +238 -0
- package/src/status/aggregator.mjs +584 -0
- package/src/status/server.mjs +413 -0
- package/tools.json +1653 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bridge-unify-smoke.mjs — arg-routing checks for the unified `bridge` tool.
|
|
3
|
+
//
|
|
4
|
+
// Exercises the type-discriminator routing and tag-registry guard rails on the
|
|
5
|
+
// fast-fail branches (no provider/model needed): list on an empty store,
|
|
6
|
+
// send/close against an unknown tag, spawn missing role, and the type-less
|
|
7
|
+
// (default spawn) backward-compat path. Does NOT spin a real worker.
|
|
8
|
+
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
13
|
+
|
|
14
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
+
process.env.CLAUDE_PLUGIN_ROOT ||= ROOT;
|
|
16
|
+
// MUST be unconditional (`=`, not `||=`): when the smoke runs inside a real
|
|
17
|
+
// mixdog environment CLAUDE_PLUGIN_DATA already points at the LIVE plugin data
|
|
18
|
+
// dir, so `||=` would write the synthetic `sess_<pid>_<scenario>_<ts>` fixtures
|
|
19
|
+
// straight into the production session store. Those fixtures (provider/model
|
|
20
|
+
// `none`, closed:false, no `owner`) slip past both sweeps and linger forever in
|
|
21
|
+
// `bridge list` / the statusline. Force an isolated per-pid temp dir, always.
|
|
22
|
+
const _SMOKE_DATA_DIR = join(tmpdir(), `mixdog-bridge-unify-smoke-${process.pid}`);
|
|
23
|
+
process.env.CLAUDE_PLUGIN_DATA = _SMOKE_DATA_DIR;
|
|
24
|
+
process.env.MIXDOG_SKIP_USER_DATA_BACKUP = '1';
|
|
25
|
+
|
|
26
|
+
const { handleToolCall } = await import('../src/agent/index.mjs');
|
|
27
|
+
// updateSessionStage drives the in-memory runtime stage the send busy-guard
|
|
28
|
+
// consults when session.status is still unset — exercised by the startup-window
|
|
29
|
+
// race case (#1) below.
|
|
30
|
+
const { updateSessionStage, drainPendingMessages } = await import('../src/agent/orchestrator/session/manager.mjs');
|
|
31
|
+
// initProviders lets the cold-tag respawn case register a network-free dummy
|
|
32
|
+
// provider so the spawn path passes its synchronous provider check and returns
|
|
33
|
+
// a detached jobId (the real API call is deferred to the IIFE, never reached).
|
|
34
|
+
const { initProviders } = await import('../src/agent/orchestrator/providers/registry.mjs');
|
|
35
|
+
|
|
36
|
+
// Write a minimal on-disk session fixture so the unified `bridge type=send`
|
|
37
|
+
// path (which resolves a raw sess_ id via getSession/loadSession) can be
|
|
38
|
+
// exercised without spinning a real provider-backed worker. The send handler's
|
|
39
|
+
// busy-guard reads session.status BEFORE calling askSession, so a fixture is
|
|
40
|
+
// enough to assert the resumability guard rails (bugs #1/#2).
|
|
41
|
+
const _sessionsDir = join(process.env.CLAUDE_PLUGIN_DATA, 'sessions');
|
|
42
|
+
function writeSessionFixture(id, status) {
|
|
43
|
+
mkdirSync(_sessionsDir, { recursive: true });
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const session = {
|
|
46
|
+
id,
|
|
47
|
+
status,
|
|
48
|
+
closed: false,
|
|
49
|
+
generation: 0,
|
|
50
|
+
provider: 'none', // unknown provider → askSession fails fast (no hang)
|
|
51
|
+
model: 'none',
|
|
52
|
+
messages: [],
|
|
53
|
+
tools: [],
|
|
54
|
+
createdAt: now,
|
|
55
|
+
updatedAt: now,
|
|
56
|
+
};
|
|
57
|
+
writeFileSync(join(_sessionsDir, `${id}.json`), JSON.stringify(session));
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let passed = 0;
|
|
62
|
+
let failed = 0;
|
|
63
|
+
function check(name, fn) {
|
|
64
|
+
return Promise.resolve()
|
|
65
|
+
.then(fn)
|
|
66
|
+
.then(() => { passed++; console.log(` PASS ${name}`); })
|
|
67
|
+
.catch((e) => { failed++; console.log(` FAIL ${name}\n ${e?.message || e}`); });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Tool results are MCP content blocks: { content: [{ type:'text', text }], isError? }.
|
|
71
|
+
function textOf(res) {
|
|
72
|
+
if (res && Array.isArray(res.content)) {
|
|
73
|
+
return res.content.map((c) => (c && typeof c.text === 'string' ? c.text : '')).join('');
|
|
74
|
+
}
|
|
75
|
+
return typeof res === 'string' ? res : JSON.stringify(res);
|
|
76
|
+
}
|
|
77
|
+
function assert(cond, msg) { if (!cond) throw new Error(msg || 'assertion failed'); }
|
|
78
|
+
|
|
79
|
+
await check('type=list on empty store → "No active sessions."', async () => {
|
|
80
|
+
const res = await handleToolCall('bridge', { type: 'list' }, {});
|
|
81
|
+
const t = textOf(res);
|
|
82
|
+
assert(/No active sessions/.test(t), `unexpected: ${t}`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await check('type=send with unknown tag → clear error', async () => {
|
|
86
|
+
const res = await handleToolCall('bridge', { type: 'send', tag: 'nope1', message: 'hi' }, {});
|
|
87
|
+
const t = textOf(res);
|
|
88
|
+
assert(res.isError === true, 'expected isError');
|
|
89
|
+
assert(/not found \(may have been reaped after 5min\) — include a role to respawn fresh/.test(t), `unexpected: ${t}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await check('type=send unknown tag WITH role → respawns fresh (jobId)', async () => {
|
|
93
|
+
// Register a network-free dummy provider so the spawn path passes its
|
|
94
|
+
// synchronous provider check. The bench escape hatch (provider+model) builds
|
|
95
|
+
// an ad-hoc preset so no user-workflow.json role mapping is needed. spawn
|
|
96
|
+
// returns the jobId synchronously; the real API call (which would fail on the
|
|
97
|
+
// dummy key) is deferred to the detached IIFE and never asserted here.
|
|
98
|
+
await initProviders({ anthropic: { enabled: true, apiKey: 'dummy-smoke-key' } });
|
|
99
|
+
const res = await handleToolCall('bridge', { type: 'send', tag: 'respawn1', role: 'worker', provider: 'anthropic', model: 'claude-3-haiku', message: 'hi' }, {});
|
|
100
|
+
const t = textOf(res);
|
|
101
|
+
assert(res.isError !== true, `expected success, got: ${t}`);
|
|
102
|
+
assert(/"jobId"\s*:\s*"bridge_/.test(t), `expected a jobId, got: ${t}`);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await check('type=send missing message → error', async () => {
|
|
106
|
+
const res = await handleToolCall('bridge', { type: 'send', tag: 'x' }, {});
|
|
107
|
+
assert(res.isError === true, 'expected isError');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await check('type=close with unknown tag → error', async () => {
|
|
111
|
+
const res = await handleToolCall('bridge', { type: 'close', tag: 'nope2' }, {});
|
|
112
|
+
const t = textOf(res);
|
|
113
|
+
assert(res.isError === true, 'expected isError');
|
|
114
|
+
assert(/does not map to a live session/.test(t), `unexpected: ${t}`);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await check('type=close missing tag → error', async () => {
|
|
118
|
+
const res = await handleToolCall('bridge', { type: 'close' }, {});
|
|
119
|
+
assert(res.isError === true, 'expected isError');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await check('unknown type → error', async () => {
|
|
123
|
+
const res = await handleToolCall('bridge', { type: 'bogus', role: 'worker' }, {});
|
|
124
|
+
const t = textOf(res);
|
|
125
|
+
assert(res.isError === true, 'expected isError');
|
|
126
|
+
assert(/unknown type/.test(t), `unexpected: ${t}`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await check('default (no type) + no role → spawn path "role is required"', async () => {
|
|
130
|
+
const res = await handleToolCall('bridge', {}, {});
|
|
131
|
+
const t = textOf(res);
|
|
132
|
+
assert(res.isError === true, 'expected isError');
|
|
133
|
+
assert(/role is required/.test(t), `unexpected: ${t}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await check('type=spawn + role but no prompt/message → prompt resolution error', async () => {
|
|
137
|
+
const res = await handleToolCall('bridge', { type: 'spawn', role: 'nonexistent-role-xyz' }, {});
|
|
138
|
+
const t = textOf(res);
|
|
139
|
+
assert(res.isError === true, 'expected isError');
|
|
140
|
+
// Either prompt-missing or role-not-found surfaces — both prove routing into spawn.
|
|
141
|
+
assert(/prompt|role|provide exactly one/.test(t), `unexpected: ${t}`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// --- Resumability guard rails (bugs #1/#2) ---------------------------------
|
|
145
|
+
// These drive the unified `bridge type=send` path against an on-disk session
|
|
146
|
+
// fixture addressed by raw sess_ id (the tag→sessionId registry is module
|
|
147
|
+
// private; raw-id passthrough exercises the same handler code).
|
|
148
|
+
|
|
149
|
+
await check('type=send while worker running → queued (not rejected)', async () => {
|
|
150
|
+
// pendingMessages pattern: a send during the in-flight turn must NOT become
|
|
151
|
+
// the first user message and must NOT be rejected — it enqueues and drains
|
|
152
|
+
// after the current turn. A non-terminal status trips the queue branch
|
|
153
|
+
// before askSession.
|
|
154
|
+
const sid = writeSessionFixture(`sess_${process.pid}_running_${Date.now()}`, 'running');
|
|
155
|
+
const res = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'race' }, {});
|
|
156
|
+
const t = textOf(res);
|
|
157
|
+
assert(res.isError !== true, `expected success (queued), got: ${t}`);
|
|
158
|
+
assert(/"queued"\s*:\s*true/.test(t), `expected queued:true, got: ${t}`);
|
|
159
|
+
assert(!/still starting|retry shortly/.test(t), `must not reject a busy send anymore: ${t}`);
|
|
160
|
+
// The message must actually be parked on the pending queue.
|
|
161
|
+
const drained = drainPendingMessages(sid);
|
|
162
|
+
assert(drained.length === 1 && drained[0] === 'race', `expected ['race'] queued, got: ${JSON.stringify(drained)}`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await check('type=send to non-terminal (connecting) status → queued', async () => {
|
|
166
|
+
// Same branch, mid-startup stage label — queues instead of rejecting. Since
|
|
167
|
+
// queuing preserves order, the connecting/startup window can queue safely:
|
|
168
|
+
// the queued send runs after the worker's first turn.
|
|
169
|
+
const sid = writeSessionFixture(`sess_${process.pid}_connecting_${Date.now()}`, 'connecting');
|
|
170
|
+
const res = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'race' }, {});
|
|
171
|
+
const t = textOf(res);
|
|
172
|
+
assert(res.isError !== true, `expected success (queued), got: ${t}`);
|
|
173
|
+
assert(/"queued"\s*:\s*true/.test(t), `expected queued:true, got: ${t}`);
|
|
174
|
+
assert(!/still starting|retry shortly/.test(t), `must not reject a connecting send anymore: ${t}`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await check('type=send to idle session → returns a jobId synchronously (detached, no inline response)', async () => {
|
|
178
|
+
// Bug #1 + async detach: once the initial turn has settled to a terminal
|
|
179
|
+
// status the send must NOT be rejected by the busy-guard — it passes the
|
|
180
|
+
// guard, cancels the pending 120s reap, and (now) returns IMMEDIATELY with a
|
|
181
|
+
// detached jobId instead of blocking on askSession. The provider failure on
|
|
182
|
+
// the dummy session now surfaces asynchronously via notifyFn, NOT in this
|
|
183
|
+
// synchronous return value.
|
|
184
|
+
const sid = writeSessionFixture(`sess_${process.pid}_idle_${Date.now()}`, 'idle');
|
|
185
|
+
const res = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'resume' }, {});
|
|
186
|
+
const t = textOf(res);
|
|
187
|
+
assert(res.isError !== true, `expected a synchronous jobId return (not an error), got: ${t}`);
|
|
188
|
+
// Must be the immediate detached return, NOT the busy-guard.
|
|
189
|
+
assert(!/still starting|retry shortly/.test(t), `guard wrongly rejected an idle resume: ${t}`);
|
|
190
|
+
assert(/bridge_\d+_/.test(t), `expected a detached jobId in the sync return, got: ${t}`);
|
|
191
|
+
assert(/"detached":\s*true/.test(t), `expected detached:true in the sync return, got: ${t}`);
|
|
192
|
+
// The resume reply is delivered later via dispatch_result/notifyFn — the
|
|
193
|
+
// synchronous return must NOT carry an inline response/usage payload.
|
|
194
|
+
assert(!/"response"/.test(t), `send must not return an inline response now: ${t}`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await check('type=send during startup window (tag bound, status not yet running) → queued', async () => {
|
|
198
|
+
// Bug #1 (round 2): spawn binds the tag and retry rebinds it BEFORE the
|
|
199
|
+
// session status is written 'running' / the runtime entry reaches askSession.
|
|
200
|
+
// A send landing in that gap must NOT slip through as the first user turn.
|
|
201
|
+
// The runtime stage is marked 'connecting' at the moment the tag is bound, so
|
|
202
|
+
// the queue branch's active-status set catches the window even though
|
|
203
|
+
// session.status is still unwritten — the send is QUEUED and drained after
|
|
204
|
+
// the worker's first turn, preserving order. Simulate by writing a fixture
|
|
205
|
+
// with NO status, then setting the runtime stage to 'connecting' the way the
|
|
206
|
+
// spawn/retry bind now does.
|
|
207
|
+
const sid = writeSessionFixture(`sess_${process.pid}_startwin_${Date.now()}`, undefined);
|
|
208
|
+
updateSessionStage(sid, 'connecting');
|
|
209
|
+
const res = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'race' }, {});
|
|
210
|
+
const t = textOf(res);
|
|
211
|
+
assert(res.isError !== true, `expected success (queued), got: ${t}`);
|
|
212
|
+
assert(/"queued"\s*:\s*true/.test(t), `expected queued:true, got: ${t}`);
|
|
213
|
+
assert(!/still starting|retry shortly/.test(t), `startup-window send must queue, not reject: ${t}`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await check('failed resume re-arms reap → session stays usable for a follow-up send', async () => {
|
|
217
|
+
// Bug #2 (round 2): send cancels the pending reap before resuming, but the
|
|
218
|
+
// reap was previously re-armed only on the success path — a failed resumed
|
|
219
|
+
// turn left the reap canceled, so the idle/error session was never terminally
|
|
220
|
+
// reaped. The fix re-arms _scheduleBridgeReap in a finally (idempotent). The
|
|
221
|
+
// reap timer is module-private, so assert the observable consequence: after a
|
|
222
|
+
// FIRST resume that fails on the dummy provider, a SECOND send to the same
|
|
223
|
+
// session still passes the busy-guard and reaches askSession (the failed
|
|
224
|
+
// resume left the session addressable + the reap re-armed, not orphaned).
|
|
225
|
+
const sid = writeSessionFixture(`sess_${process.pid}_failresume_${Date.now()}`, 'idle');
|
|
226
|
+
const first = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'resume-1' }, {});
|
|
227
|
+
const t1 = textOf(first);
|
|
228
|
+
assert(first.isError !== true, `expected first resume to detach (jobId), got: ${t1}`);
|
|
229
|
+
assert(!/still starting|retry shortly/.test(t1), `first resume wrongly guard-rejected: ${t1}`);
|
|
230
|
+
assert(/bridge_\d+_/.test(t1), `expected first resume jobId, got: ${t1}`);
|
|
231
|
+
// The detached resume fails on the dummy provider in the background and
|
|
232
|
+
// re-arms the reap in its finally. Yield so that async tail can settle.
|
|
233
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
234
|
+
// Re-mark idle (real path: failed turn flips status to 'idle'/'error'; the
|
|
235
|
+
// fixture write here restores a terminal status so the second send is guarded
|
|
236
|
+
// the same way) and re-send.
|
|
237
|
+
writeSessionFixture(sid, 'idle');
|
|
238
|
+
const second = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'resume-2' }, {});
|
|
239
|
+
const t2 = textOf(second);
|
|
240
|
+
assert(second.isError !== true, `expected second resume to detach (jobId), got: ${t2}`);
|
|
241
|
+
assert(!/still starting|retry shortly/.test(t2), `second resume wrongly guard-rejected (session orphaned by failed resume?): ${t2}`);
|
|
242
|
+
assert(/bridge_\d+_/.test(t2), `expected second resume jobId (session addressable + reap re-armed, not orphaned), got: ${t2}`);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await check('type=send in post-askSession/pre-idle gap (persisted running, runtime done) → NOT stranded', async () => {
|
|
246
|
+
// Bug #2: spawn keeps persisted status 'running' until AFTER the completion
|
|
247
|
+
// emit (updateSessionStatus(idle) runs OUTSIDE askSession, past the drain
|
|
248
|
+
// point). A send landing after askSession returned but before status flips
|
|
249
|
+
// to 'idle' used to see persisted 'running' and ENQUEUE — but askSession had
|
|
250
|
+
// already drained and returned, so nothing consumed it: stranded + out of
|
|
251
|
+
// order. The busy-guard now prefers the RUNTIME terminal stage over the
|
|
252
|
+
// stale persisted 'running', so the send does a fresh detached resume
|
|
253
|
+
// instead of queuing into the void. Simulate: persisted status 'running'
|
|
254
|
+
// (not yet flipped), runtime stage 'done' (markSessionDone ran before the
|
|
255
|
+
// turn-loop drain).
|
|
256
|
+
const sid = writeSessionFixture(`sess_${process.pid}_gap_${Date.now()}`, 'running');
|
|
257
|
+
updateSessionStage(sid, 'done');
|
|
258
|
+
const res = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'gap' }, {});
|
|
259
|
+
const t = textOf(res);
|
|
260
|
+
assert(res.isError !== true, `expected a detached resume, got: ${t}`);
|
|
261
|
+
assert(!/"queued"\s*:\s*true/.test(t), `must NOT enqueue in the post-askSession/pre-idle gap (would strand): ${t}`);
|
|
262
|
+
assert(/bridge_\d+_/.test(t), `expected a detached jobId (fresh resume), got: ${t}`);
|
|
263
|
+
// Nothing parked on the queue — proves the message was not stranded.
|
|
264
|
+
assert(drainPendingMessages(sid).length === 0, 'queue must be empty (message not stranded)');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await check('type=send raw MISSING sess_ id WITH role → respawns fresh (jobId), not a dead-session resume', async () => {
|
|
268
|
+
// Bug #5: a raw `sess_...` id that does NOT resolve to a LIVE session must
|
|
269
|
+
// fall into the reaped/unknown branch — with a role present, that means a
|
|
270
|
+
// fresh respawn (spawn path), NOT an async askSession launched against a
|
|
271
|
+
// missing/closed session. The id below is never written to disk, so
|
|
272
|
+
// _isLiveSession() rejects it and the precheck flips bridgeType→spawn.
|
|
273
|
+
await initProviders({ anthropic: { enabled: true, apiKey: 'dummy-smoke-key' } });
|
|
274
|
+
const missing = `sess_${process.pid}_ghost_${Date.now()}`;
|
|
275
|
+
const res = await handleToolCall('bridge', { type: 'send', sessionId: missing, role: 'worker', provider: 'anthropic', model: 'claude-3-haiku', message: 'hi' }, {});
|
|
276
|
+
const t = textOf(res);
|
|
277
|
+
assert(res.isError !== true, `expected a fresh respawn, got: ${t}`);
|
|
278
|
+
assert(/"jobId"\s*:\s*"bridge_/.test(t), `expected a jobId (fresh spawn), got: ${t}`);
|
|
279
|
+
// A fresh spawn allocates a NEW session id — it must not echo the dead id.
|
|
280
|
+
assert(!t.includes(missing), `respawn must not resume the missing/dead session id: ${t}`);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await check('multiple sends to a running worker → queued FIFO, drained in order', async () => {
|
|
284
|
+
// Order preservation: two sends landing during the same in-flight turn must
|
|
285
|
+
// queue in arrival order and drain in that order (the askSession turn loop
|
|
286
|
+
// shifts them off the front as follow-up turns). queueDepth reflects the
|
|
287
|
+
// growing queue.
|
|
288
|
+
const sid = writeSessionFixture(`sess_${process.pid}_fifo_${Date.now()}`, 'running');
|
|
289
|
+
const r1 = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'first' }, {});
|
|
290
|
+
const r2 = await handleToolCall('bridge', { type: 'send', sessionId: sid, message: 'second' }, {});
|
|
291
|
+
const t1 = textOf(r1); const t2 = textOf(r2);
|
|
292
|
+
assert(/"queued"\s*:\s*true/.test(t1) && /"queued"\s*:\s*true/.test(t2), `both sends must queue, got: ${t1} | ${t2}`);
|
|
293
|
+
assert(/"queueDepth"\s*:\s*1/.test(t1), `first queued send should report queueDepth 1, got: ${t1}`);
|
|
294
|
+
assert(/"queueDepth"\s*:\s*2/.test(t2), `second queued send should report queueDepth 2, got: ${t2}`);
|
|
295
|
+
// Drain mirrors what askSession does after each turn — FIFO order.
|
|
296
|
+
const drained = drainPendingMessages(sid);
|
|
297
|
+
assert(drained.length === 2 && drained[0] === 'first' && drained[1] === 'second',
|
|
298
|
+
`expected ['first','second'] in order, got: ${JSON.stringify(drained)}`);
|
|
299
|
+
// Queue is emptied after drain.
|
|
300
|
+
assert(drainPendingMessages(sid).length === 0, 'queue must be empty after drain');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
console.log(`\nbridge-unify-smoke: ${passed} passed, ${failed} failed`);
|
|
304
|
+
// Remove the isolated data dir so no fixture survives the run. Belt-and-braces
|
|
305
|
+
// with the unconditional CLAUDE_PLUGIN_DATA override above: even the isolated
|
|
306
|
+
// temp store should not accumulate per-pid dirs across repeated smoke runs.
|
|
307
|
+
try { rmSync(_SMOKE_DATA_DIR, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
308
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# build-runtime-linux.sh — Build self-contained PostgreSQL 16 + pgvector runtime on Linux.
|
|
3
|
+
# Designed to run inside ubuntu:20.04 container (glibc 2.31 floor) so produced
|
|
4
|
+
# binaries run on every distro from Ubuntu 20.04 / Debian 11 / RHEL 8 forward.
|
|
5
|
+
# Targets the runner's native arch (x64 or arm64).
|
|
6
|
+
# Produces: dist/mixdog-runtime-linux-{arch}-pg{pgver}-pgvector{vecver}.tar.gz
|
|
7
|
+
# Bundles foreign dyn deps via ldd transitive closure (binaries + ALL extension
|
|
8
|
+
# modules under lib/postgresql/) + patchelf rpath rewrite. Final smoke: initdb,
|
|
9
|
+
# pg_ctl start, CREATE EXTENSION vector, distance query, stop.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
PG_VERSION="16.4"
|
|
14
|
+
PGVECTOR_VERSION="0.8.2"
|
|
15
|
+
TARGET_OS="${TARGET_OS:-linux}"
|
|
16
|
+
TARGET_ARCH="${TARGET_ARCH:-x64}"
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
20
|
+
BUILD_DIR="$ROOT_DIR/build/runtime-linux-$TARGET_ARCH"
|
|
21
|
+
STAGE_DIR="$BUILD_DIR/stage"
|
|
22
|
+
DIST_DIR="$ROOT_DIR/dist"
|
|
23
|
+
RUNTIME_DIR="$BUILD_DIR/runtime"
|
|
24
|
+
|
|
25
|
+
OUTPUT_NAME="mixdog-runtime-${TARGET_OS}-${TARGET_ARCH}-pg${PG_VERSION}-pgvector${PGVECTOR_VERSION}.tar.gz"
|
|
26
|
+
|
|
27
|
+
mkdir -p "$BUILD_DIR" "$STAGE_DIR" "$DIST_DIR" "$RUNTIME_DIR"/{bin,lib,share}
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Build deps. SUDO=sudo iff non-root; inside ubuntu:20.04 container we are root.
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
SUDO=""
|
|
33
|
+
if [[ "$(id -u)" -ne 0 ]]; then SUDO="sudo"; fi
|
|
34
|
+
# Export rather than inline-assign before $SUDO: when SUDO is empty (root),
|
|
35
|
+
# `$SUDO DEBIAN_FRONTEND=... apt-get` would parse the env-assignment as a
|
|
36
|
+
# command name and fail with "command not found".
|
|
37
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
38
|
+
|
|
39
|
+
echo "==> Installing build dependencies"
|
|
40
|
+
$SUDO apt-get update -qq
|
|
41
|
+
$SUDO apt-get install -y --no-install-recommends \
|
|
42
|
+
build-essential libreadline-dev zlib1g-dev libssl-dev \
|
|
43
|
+
libicu-dev libxml2-dev libzstd-dev liblz4-dev \
|
|
44
|
+
pkg-config curl git ca-certificates patchelf file \
|
|
45
|
+
bsdmainutils
|
|
46
|
+
|
|
47
|
+
if [[ -x "$STAGE_DIR/bin/postgres" ]]; then
|
|
48
|
+
echo "==> Cache hit: PG already built at $STAGE_DIR — skipping configure/make"
|
|
49
|
+
unset TARGET_OS TARGET_ARCH
|
|
50
|
+
else
|
|
51
|
+
echo "==> Downloading PostgreSQL $PG_VERSION source"
|
|
52
|
+
cd "$BUILD_DIR"
|
|
53
|
+
if [[ ! -f "postgresql-${PG_VERSION}.tar.gz" ]]; then
|
|
54
|
+
curl -fsSL "https://ftp.postgresql.org/pub/source/v${PG_VERSION}/postgresql-${PG_VERSION}.tar.gz" \
|
|
55
|
+
-o "postgresql-${PG_VERSION}.tar.gz"
|
|
56
|
+
fi
|
|
57
|
+
rm -rf "postgresql-${PG_VERSION}"
|
|
58
|
+
tar xzf "postgresql-${PG_VERSION}.tar.gz"
|
|
59
|
+
|
|
60
|
+
echo "==> Configuring PostgreSQL"
|
|
61
|
+
cd "postgresql-${PG_VERSION}"
|
|
62
|
+
./configure \
|
|
63
|
+
--prefix="$STAGE_DIR" \
|
|
64
|
+
--without-perl \
|
|
65
|
+
--without-python \
|
|
66
|
+
--without-tcl \
|
|
67
|
+
--with-openssl \
|
|
68
|
+
--with-libxml \
|
|
69
|
+
--with-icu \
|
|
70
|
+
--with-readline \
|
|
71
|
+
--enable-thread-safety \
|
|
72
|
+
CFLAGS="-O2"
|
|
73
|
+
|
|
74
|
+
echo "==> Building PostgreSQL"
|
|
75
|
+
# PG Makefile.global has its own TARGET_ARCH var; env-passed TARGET_ARCH=x64
|
|
76
|
+
# would collide as a make-variable override and leak into compile commands.
|
|
77
|
+
unset TARGET_OS TARGET_ARCH
|
|
78
|
+
make -j"$(nproc)"
|
|
79
|
+
make install
|
|
80
|
+
make -C contrib/pgcrypto install
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
PG_CONFIG="$STAGE_DIR/bin/pg_config"
|
|
84
|
+
export PATH="$STAGE_DIR/bin:$PATH"
|
|
85
|
+
|
|
86
|
+
echo "==> Stripping PostgreSQL binaries"
|
|
87
|
+
find "$STAGE_DIR" -name '*.so*' -type f -exec strip --strip-debug {} \; 2>/dev/null || true
|
|
88
|
+
find "$STAGE_DIR/bin" -type f -exec strip --strip-all {} \; 2>/dev/null || true
|
|
89
|
+
|
|
90
|
+
if [[ -f "$STAGE_DIR/lib/postgresql/vector.so" ]]; then
|
|
91
|
+
echo "==> Cache hit: pgvector already installed — skipping clone/build"
|
|
92
|
+
else
|
|
93
|
+
echo "==> Cloning pgvector $PGVECTOR_VERSION"
|
|
94
|
+
cd "$BUILD_DIR"
|
|
95
|
+
rm -rf pgvector
|
|
96
|
+
git clone --branch "v${PGVECTOR_VERSION}" --depth 1 \
|
|
97
|
+
https://github.com/pgvector/pgvector.git pgvector
|
|
98
|
+
|
|
99
|
+
echo "==> Building pgvector"
|
|
100
|
+
cd pgvector
|
|
101
|
+
make PG_CONFIG="$PG_CONFIG" -j"$(nproc)"
|
|
102
|
+
make PG_CONFIG="$PG_CONFIG" install
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo "==> Assembling runtime layout"
|
|
106
|
+
rm -rf "$RUNTIME_DIR"
|
|
107
|
+
mkdir -p "$RUNTIME_DIR"/{bin,lib,share}
|
|
108
|
+
cp -a "$STAGE_DIR/bin/postgres" "$RUNTIME_DIR/bin/"
|
|
109
|
+
cp -a "$STAGE_DIR/bin/pg_ctl" "$RUNTIME_DIR/bin/"
|
|
110
|
+
cp -a "$STAGE_DIR/bin/pg_dump" "$RUNTIME_DIR/bin/"
|
|
111
|
+
cp -a "$STAGE_DIR/bin/pg_restore" "$RUNTIME_DIR/bin/"
|
|
112
|
+
cp -a "$STAGE_DIR/bin/psql" "$RUNTIME_DIR/bin/"
|
|
113
|
+
cp -a "$STAGE_DIR/bin/initdb" "$RUNTIME_DIR/bin/"
|
|
114
|
+
|
|
115
|
+
cp -a "$STAGE_DIR/lib"/. "$RUNTIME_DIR/lib/" 2>/dev/null || true
|
|
116
|
+
cp -a "$STAGE_DIR/share"/. "$RUNTIME_DIR/share/" 2>/dev/null || true
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Bundle foreign dyn deps — seed from binaries AND every extension module
|
|
120
|
+
# under lib/postgresql/. dlopen-loaded modules (extensions) need the same
|
|
121
|
+
# treatment as direct binary deps; missing them = runtime "ERROR: could not
|
|
122
|
+
# load library" on CREATE EXTENSION.
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
echo "==> Bundling foreign dynamic dependencies (binaries + extensions)"
|
|
125
|
+
|
|
126
|
+
KEEP_RE='^(linux-vdso|ld-linux|libc\.so|libm\.so|libpthread\.so|libdl\.so|librt\.so|libnsl\.so|libresolv\.so)'
|
|
127
|
+
|
|
128
|
+
collect_deps() {
|
|
129
|
+
ldd "$1" 2>/dev/null \
|
|
130
|
+
| awk '/=>/{print $3}' \
|
|
131
|
+
| grep -E '^/' \
|
|
132
|
+
| while read -r path; do
|
|
133
|
+
bn=$(basename "$path")
|
|
134
|
+
if echo "$bn" | grep -Eq "$KEEP_RE"; then
|
|
135
|
+
continue # system lib — keep on host
|
|
136
|
+
fi
|
|
137
|
+
echo "$path"
|
|
138
|
+
done \
|
|
139
|
+
| sort -u
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
declare -A SEEN
|
|
143
|
+
declare -a QUEUE
|
|
144
|
+
# Seeds: binaries + every .so under lib/postgresql/ (extensions, contrib mods).
|
|
145
|
+
for seed in "$RUNTIME_DIR/bin/postgres" "$RUNTIME_DIR/bin/psql" \
|
|
146
|
+
"$RUNTIME_DIR/bin/pg_ctl"; do
|
|
147
|
+
[[ -f "$seed" ]] && QUEUE+=("$seed")
|
|
148
|
+
done
|
|
149
|
+
while IFS= read -r -d '' ext; do
|
|
150
|
+
QUEUE+=("$ext")
|
|
151
|
+
done < <(find "$RUNTIME_DIR/lib/postgresql" -name '*.so' -print0 2>/dev/null)
|
|
152
|
+
|
|
153
|
+
declare -a FOREIGN
|
|
154
|
+
while [[ ${#QUEUE[@]} -gt 0 ]]; do
|
|
155
|
+
current="${QUEUE[0]}"
|
|
156
|
+
QUEUE=("${QUEUE[@]:1}")
|
|
157
|
+
while IFS= read -r dep; do
|
|
158
|
+
real="$(readlink -f "$dep")"
|
|
159
|
+
[[ -n "${SEEN[$real]+x}" ]] && continue
|
|
160
|
+
SEEN[$real]=1
|
|
161
|
+
FOREIGN+=("$dep")
|
|
162
|
+
QUEUE+=("$dep")
|
|
163
|
+
done < <(collect_deps "$current")
|
|
164
|
+
done
|
|
165
|
+
|
|
166
|
+
# Sanity: glibc / loader must never be bundled — if KEEP_RE regresses this catches it.
|
|
167
|
+
if find "$RUNTIME_DIR/lib" -maxdepth 1 \
|
|
168
|
+
\( -name 'libc.so*' -o -name 'ld-linux*' -o -name 'libpthread.so*' \) \
|
|
169
|
+
| grep -q .; then
|
|
170
|
+
echo "FATAL: bundled glibc/loader detected — KEEP_RE filter did not work"
|
|
171
|
+
exit 1
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
echo " bundling ${#FOREIGN[@]} foreign libraries"
|
|
175
|
+
for so_path in "${FOREIGN[@]}"; do
|
|
176
|
+
real_path="$(readlink -f "$so_path")"
|
|
177
|
+
real_name="$(basename "$real_path")"
|
|
178
|
+
if [[ ! -f "$RUNTIME_DIR/lib/$real_name" ]]; then
|
|
179
|
+
cp -L "$real_path" "$RUNTIME_DIR/lib/$real_name"
|
|
180
|
+
chmod u+w "$RUNTIME_DIR/lib/$real_name"
|
|
181
|
+
echo " + $real_name"
|
|
182
|
+
fi
|
|
183
|
+
link="$so_path"
|
|
184
|
+
while [[ -L "$link" ]]; do
|
|
185
|
+
link_name="$(basename "$link")"
|
|
186
|
+
link_target="$(readlink "$link")"
|
|
187
|
+
if [[ ! -e "$RUNTIME_DIR/lib/$link_name" ]]; then
|
|
188
|
+
ln -sf "$(basename "$link_target")" "$RUNTIME_DIR/lib/$link_name"
|
|
189
|
+
fi
|
|
190
|
+
link="$(dirname "$link")/$link_target"
|
|
191
|
+
done
|
|
192
|
+
done
|
|
193
|
+
|
|
194
|
+
echo "==> Stripping static archives from lib/"
|
|
195
|
+
find "$RUNTIME_DIR/lib" -name '*.a' -delete
|
|
196
|
+
|
|
197
|
+
echo "==> Patching rpath"
|
|
198
|
+
# Binaries: $ORIGIN/../lib (from bin/ to lib/)
|
|
199
|
+
find "$RUNTIME_DIR/bin" -type f -executable | while read -r bin; do
|
|
200
|
+
if file "$bin" 2>/dev/null | grep -q ELF; then
|
|
201
|
+
patchelf --set-rpath '$ORIGIN/../lib' "$bin" 2>/dev/null || true
|
|
202
|
+
fi
|
|
203
|
+
done
|
|
204
|
+
# Top-level lib/*.so*: $ORIGIN
|
|
205
|
+
find "$RUNTIME_DIR/lib" -maxdepth 1 -type f -name '*.so*' | while read -r so; do
|
|
206
|
+
if file "$so" 2>/dev/null | grep -q ELF; then
|
|
207
|
+
patchelf --set-rpath '$ORIGIN' "$so" 2>/dev/null || true
|
|
208
|
+
fi
|
|
209
|
+
done
|
|
210
|
+
# Every extension module under lib/postgresql/: $ORIGIN/.. (=lib/) and $ORIGIN/../.. (=runtime root)
|
|
211
|
+
find "$RUNTIME_DIR/lib/postgresql" -name '*.so' 2>/dev/null | while read -r ext; do
|
|
212
|
+
if file "$ext" 2>/dev/null | grep -q ELF; then
|
|
213
|
+
patchelf --set-rpath '$ORIGIN/..:$ORIGIN/../..' "$ext" 2>/dev/null || true
|
|
214
|
+
fi
|
|
215
|
+
done
|
|
216
|
+
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
218
|
+
# Self-contained smoke — full PG lifecycle, not just --version
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
echo "==> Self-contained smoke test (initdb + CREATE EXTENSION vector + distance query)"
|
|
221
|
+
unset LD_LIBRARY_PATH
|
|
222
|
+
SMOKE_DATA="$BUILD_DIR/smoke-pgdata"
|
|
223
|
+
SMOKE_LOG="$BUILD_DIR/smoke-pg.log"
|
|
224
|
+
SMOKE_PORT=55899
|
|
225
|
+
|
|
226
|
+
# PG initdb refuses to run as root. Inside ubuntu:20.04 container we are root,
|
|
227
|
+
# so create an unprivileged user and run the smoke under it.
|
|
228
|
+
if [[ "$(id -u)" -eq 0 ]]; then
|
|
229
|
+
if ! id pguser >/dev/null 2>&1; then useradd -m -s /bin/bash pguser; fi
|
|
230
|
+
chown -R pguser:pguser "$BUILD_DIR"
|
|
231
|
+
RUN_AS=(runuser -u pguser --)
|
|
232
|
+
else
|
|
233
|
+
RUN_AS=()
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/postgres" --version || { echo "FAIL: postgres --version"; exit 1; }
|
|
237
|
+
MISSING="$(ldd "$RUNTIME_DIR/bin/postgres" 2>&1 | grep 'not found' || true)"
|
|
238
|
+
if [[ -n "$MISSING" ]]; then echo "FAIL: missing deps in postgres:"; echo "$MISSING"; exit 1; fi
|
|
239
|
+
|
|
240
|
+
SMOKE_OK=""
|
|
241
|
+
for attempt in 1 2 3; do
|
|
242
|
+
echo "==> Self-contained smoke (attempt $attempt/3)"
|
|
243
|
+
rm -rf "$SMOKE_DATA"
|
|
244
|
+
set +e
|
|
245
|
+
(
|
|
246
|
+
set -e
|
|
247
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/initdb" -D "$SMOKE_DATA" --auth-local=trust --no-locale -E UTF8 -U postgres > /dev/null
|
|
248
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/pg_ctl" -D "$SMOKE_DATA" -o "-p $SMOKE_PORT -h 127.0.0.1" -l "$SMOKE_LOG" -w start
|
|
249
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -c "CREATE EXTENSION vector;" > /dev/null
|
|
250
|
+
EXTV="$("${RUN_AS[@]}" "$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -tAc "SELECT extversion FROM pg_extension WHERE extname='vector';")"
|
|
251
|
+
DIST="$("${RUN_AS[@]}" "$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -tAc "SELECT '[1,2,3]'::vector <-> '[1,2,4]'::vector;")"
|
|
252
|
+
echo " vector extension version: $EXTV"
|
|
253
|
+
echo " distance query result: $DIST"
|
|
254
|
+
[[ "$EXTV" == "$PGVECTOR_VERSION" ]] || { echo "FAIL: extversion=$EXTV expected=$PGVECTOR_VERSION"; exit 1; }
|
|
255
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/pg_ctl" -D "$SMOKE_DATA" -m fast stop > /dev/null
|
|
256
|
+
)
|
|
257
|
+
attempt_rc=$?
|
|
258
|
+
set -e
|
|
259
|
+
if [[ $attempt_rc -eq 0 ]]; then
|
|
260
|
+
rm -rf "$SMOKE_DATA"
|
|
261
|
+
echo " PASS smoke (extension load + vector distance)"
|
|
262
|
+
SMOKE_OK=1
|
|
263
|
+
break
|
|
264
|
+
fi
|
|
265
|
+
echo " attempt $attempt failed (rc=$attempt_rc) — cleaning up"
|
|
266
|
+
echo " -- last 20 lines of $SMOKE_LOG --"
|
|
267
|
+
tail -20 "$SMOKE_LOG" 2>/dev/null || true
|
|
268
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/pg_ctl" -D "$SMOKE_DATA" -m immediate stop > /dev/null 2>&1 || true
|
|
269
|
+
pkill -f "$RUNTIME_DIR/bin/postgres" > /dev/null 2>&1 || true
|
|
270
|
+
rm -rf "$SMOKE_DATA"
|
|
271
|
+
done
|
|
272
|
+
if [[ -z "${SMOKE_OK:-}" ]]; then
|
|
273
|
+
echo "FAIL: smoke failed all 3 attempts"
|
|
274
|
+
exit 1
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Licenses
|
|
278
|
+
curl -fsSL "https://raw.githubusercontent.com/postgres/postgres/REL_16_STABLE/COPYRIGHT" \
|
|
279
|
+
-o "$RUNTIME_DIR/LICENSE.postgresql"
|
|
280
|
+
cp "$BUILD_DIR/pgvector/LICENSE" "$RUNTIME_DIR/LICENSE.pgvector"
|
|
281
|
+
|
|
282
|
+
echo "==> Creating tarball: $OUTPUT_NAME"
|
|
283
|
+
tar czf "$DIST_DIR/$OUTPUT_NAME" -C "$RUNTIME_DIR" .
|
|
284
|
+
|
|
285
|
+
echo "==> Generating sha256 sidecar"
|
|
286
|
+
cd "$DIST_DIR"
|
|
287
|
+
sha256sum "$OUTPUT_NAME" > "${OUTPUT_NAME}.sha256"
|
|
288
|
+
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
# Phase A: Re-smoke from EXTRACTED tarball with hostile env. Catches false-pass
|
|
291
|
+
# where the build host happens to satisfy a dep that the tarball is missing.
|
|
292
|
+
# Uses `env -i` to clear all variables, minimal PATH, fresh data dir.
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
echo "==> Re-smoke from extracted tarball (hostile env, verify self-contained)"
|
|
295
|
+
EXTRACT_DIR="$BUILD_DIR/extract-smoke"
|
|
296
|
+
rm -rf "$EXTRACT_DIR"; mkdir -p "$EXTRACT_DIR"
|
|
297
|
+
tar xzf "$DIST_DIR/$OUTPUT_NAME" -C "$EXTRACT_DIR"
|
|
298
|
+
if [[ "$(id -u)" -eq 0 ]]; then chown -R pguser:pguser "$EXTRACT_DIR"; fi
|
|
299
|
+
EXTRACT_DATA="$EXTRACT_DIR/extract-pgdata"
|
|
300
|
+
EXTRACT_LOG="$EXTRACT_DIR/extract-pg.log"
|
|
301
|
+
EXTRACT_PORT=55898
|
|
302
|
+
|
|
303
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
304
|
+
"$EXTRACT_DIR/bin/postgres" --version
|
|
305
|
+
|
|
306
|
+
EXTRACT_SMOKE_OK=""
|
|
307
|
+
for attempt in 1 2 3; do
|
|
308
|
+
echo "==> Re-smoke from extracted tarball (attempt $attempt/3)"
|
|
309
|
+
rm -rf "$EXTRACT_DATA"
|
|
310
|
+
set +e
|
|
311
|
+
(
|
|
312
|
+
set -e
|
|
313
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
314
|
+
"$EXTRACT_DIR/bin/initdb" -D "$EXTRACT_DATA" --auth-local=trust --no-locale -E UTF8 -U postgres > /dev/null
|
|
315
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
316
|
+
"$EXTRACT_DIR/bin/pg_ctl" -D "$EXTRACT_DATA" -o "-p $EXTRACT_PORT -h 127.0.0.1" -l "$EXTRACT_LOG" -w start
|
|
317
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
318
|
+
"$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -c "CREATE EXTENSION vector;" > /dev/null
|
|
319
|
+
EXTV2="$("${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
320
|
+
"$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -tAc \
|
|
321
|
+
"SELECT extversion FROM pg_extension WHERE extname='vector';")"
|
|
322
|
+
[[ "$EXTV2" == "$PGVECTOR_VERSION" ]] || { echo "FAIL: extracted-smoke extversion=$EXTV2"; exit 1; }
|
|
323
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
324
|
+
"$EXTRACT_DIR/bin/pg_ctl" -D "$EXTRACT_DATA" -m fast stop > /dev/null
|
|
325
|
+
)
|
|
326
|
+
attempt_rc=$?
|
|
327
|
+
set -e
|
|
328
|
+
if [[ $attempt_rc -eq 0 ]]; then
|
|
329
|
+
rm -rf "$EXTRACT_DIR"
|
|
330
|
+
echo " PASS extracted-tarball smoke"
|
|
331
|
+
EXTRACT_SMOKE_OK=1
|
|
332
|
+
break
|
|
333
|
+
fi
|
|
334
|
+
echo " attempt $attempt failed (rc=$attempt_rc) — cleaning up"
|
|
335
|
+
echo " -- last 20 lines of $EXTRACT_LOG --"
|
|
336
|
+
tail -20 "$EXTRACT_LOG" 2>/dev/null || true
|
|
337
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
338
|
+
"$EXTRACT_DIR/bin/pg_ctl" -D "$EXTRACT_DATA" -m immediate stop > /dev/null 2>&1 || true
|
|
339
|
+
pkill -f "$EXTRACT_DIR/bin/postgres" > /dev/null 2>&1 || true
|
|
340
|
+
rm -rf "$EXTRACT_DATA"
|
|
341
|
+
done
|
|
342
|
+
if [[ -z "${EXTRACT_SMOKE_OK:-}" ]]; then
|
|
343
|
+
echo "FAIL: extracted-tarball smoke failed all 3 attempts"
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
echo "==> Done: $DIST_DIR/$OUTPUT_NAME"
|
|
348
|
+
ls -lh "$DIST_DIR/$OUTPUT_NAME"
|