mixdog 0.8.1 → 0.9.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/README.md +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- 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/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -254,23 +254,77 @@ export async function init(db, dims) {
|
|
|
254
254
|
)
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
// dimCount from the current model config. Call after schema is confirmed
|
|
259
|
-
// complete and before any embedding operations.
|
|
260
|
-
export async function validateEmbeddingDims(db, dimCount) {
|
|
257
|
+
async function getEmbeddingColumnDims(db, tableName) {
|
|
261
258
|
const r = await db.query(`
|
|
262
|
-
SELECT atttypmod
|
|
259
|
+
SELECT a.atttypmod
|
|
263
260
|
FROM pg_attribute a
|
|
264
261
|
JOIN pg_class c ON c.oid = a.attrelid
|
|
265
|
-
|
|
262
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
263
|
+
WHERE n.nspname = current_schema()
|
|
264
|
+
AND c.relname = $1
|
|
266
265
|
AND a.attname = 'embedding'
|
|
267
266
|
AND a.attnum > 0
|
|
268
267
|
AND NOT a.attisdropped
|
|
269
|
-
|
|
268
|
+
`, [tableName])
|
|
270
269
|
const row = r.rows[0]
|
|
271
|
-
|
|
270
|
+
return row ? Number(row.atttypmod) : null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function ensureHotActiveSearchObjects(db) {
|
|
274
|
+
await db.exec(`
|
|
275
|
+
CREATE MATERIALIZED VIEW IF NOT EXISTS mv_hot_active AS
|
|
276
|
+
SELECT id, element, summary, category, status, score, last_seen_at, promoted_at,
|
|
277
|
+
project_id, embedding, search_tsv
|
|
278
|
+
FROM entries
|
|
279
|
+
WHERE is_root = 1 AND status = 'active' AND embedding IS NOT NULL
|
|
280
|
+
WITH NO DATA
|
|
281
|
+
`)
|
|
282
|
+
await db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS mv_hot_active_id ON mv_hot_active(id)`)
|
|
283
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS mv_hot_active_hnsw ON mv_hot_active USING hnsw (embedding halfvec_cosine_ops)`)
|
|
284
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS mv_hot_active_tsv ON mv_hot_active USING GIN (search_tsv)`)
|
|
285
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS mv_hot_active_score ON mv_hot_active(score DESC)`)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function resetEmbeddingColumnsForDims(db, dimCount) {
|
|
289
|
+
const entriesDims = await getEmbeddingColumnDims(db, 'entries')
|
|
290
|
+
const coreDims = await getEmbeddingColumnDims(db, 'core_entries')
|
|
291
|
+
const needsEntriesReset = entriesDims != null && entriesDims !== dimCount
|
|
292
|
+
const needsCoreReset = coreDims != null && coreDims !== dimCount
|
|
293
|
+
if (!needsEntriesReset && !needsCoreReset) return false
|
|
294
|
+
|
|
295
|
+
__mixdogMemoryLog(
|
|
296
|
+
`[memory] embedding dimension changed; resetting vectors for halfvec(${dimCount}) ` +
|
|
297
|
+
`(entries=${entriesDims ?? 'missing'}, core_entries=${coreDims ?? 'missing'})\n`,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
await db.exec(`DROP MATERIALIZED VIEW IF EXISTS mv_hot_active CASCADE`)
|
|
301
|
+
await db.exec(`DROP INDEX IF EXISTS idx_entries_embedding_hnsw`)
|
|
302
|
+
await db.exec(`DROP INDEX IF EXISTS core_entries_embedding_hnsw`)
|
|
303
|
+
|
|
304
|
+
if (needsEntriesReset) {
|
|
305
|
+
await db.exec(`ALTER TABLE entries ALTER COLUMN embedding TYPE halfvec(${dimCount}) USING NULL::halfvec(${dimCount})`)
|
|
306
|
+
await db.exec(`UPDATE entries SET summary_hash = NULL WHERE summary_hash IS NOT NULL`)
|
|
307
|
+
}
|
|
308
|
+
if (needsCoreReset) {
|
|
309
|
+
await db.exec(`ALTER TABLE core_entries ALTER COLUMN embedding TYPE halfvec(${dimCount}) USING NULL::halfvec(${dimCount})`)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await db.exec(`DROP TABLE IF EXISTS memory.embedding_cache`)
|
|
313
|
+
await db.query(
|
|
314
|
+
`INSERT INTO meta(key, value) VALUES ($1, $2::jsonb)
|
|
315
|
+
ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value`,
|
|
316
|
+
['embedding.current_dims', JSON.stringify(dimCount)],
|
|
317
|
+
)
|
|
318
|
+
return true
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Validate that the halfvec column dimension stored in the DB matches
|
|
322
|
+
// dimCount from the current model config. Call after schema is confirmed
|
|
323
|
+
// complete and before any embedding operations.
|
|
324
|
+
export async function validateEmbeddingDims(db, dimCount) {
|
|
325
|
+
const colDims = await getEmbeddingColumnDims(db, 'entries')
|
|
326
|
+
if (colDims == null) return // column absent — pre-schema DB; bootstrapSchema will handle
|
|
272
327
|
// pgvector halfvec stores dimension as atttypmod directly (unlike varchar which uses dims+4).
|
|
273
|
-
const colDims = row.atttypmod
|
|
274
328
|
if (colDims !== dimCount) {
|
|
275
329
|
throw new Error(
|
|
276
330
|
`Embedding dimension mismatch: DB column halfvec(${colDims}) vs model config ${dimCount} dims. ` +
|
|
@@ -285,6 +339,7 @@ export async function ensureCurrentSchemaExtensions(db, dims) {
|
|
|
285
339
|
// idempotent and define the current runtime schema.
|
|
286
340
|
if (Number.isInteger(dims) && dims > 0) {
|
|
287
341
|
await db.exec(`ALTER TABLE core_entries ADD COLUMN IF NOT EXISTS embedding halfvec(${dims})`)
|
|
342
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_entries_embedding_hnsw ON entries USING hnsw (embedding halfvec_cosine_ops) WHERE is_root = 1 AND embedding IS NOT NULL`)
|
|
288
343
|
await db.exec(`CREATE INDEX IF NOT EXISTS core_entries_embedding_hnsw ON core_entries USING hnsw (embedding halfvec_cosine_ops) WHERE embedding IS NOT NULL`)
|
|
289
344
|
}
|
|
290
345
|
await db.exec(`ALTER TABLE entries ADD COLUMN IF NOT EXISTS core_summary text`)
|
|
@@ -319,6 +374,16 @@ export async function ensureCurrentSchemaExtensions(db, dims) {
|
|
|
319
374
|
__mixdogMemoryLog(`[memory] ensureCurrentSchemaExtensions: core_entries_unique_proj_elem creation failed — duplicate rows must be deduplicated before this index can be created: ${err?.message || err}\n`)
|
|
320
375
|
throw err
|
|
321
376
|
}
|
|
377
|
+
|
|
378
|
+
if (Number.isInteger(dims) && dims > 0) {
|
|
379
|
+
await db.query(
|
|
380
|
+
`INSERT INTO meta(key, value) VALUES ($1, $2::jsonb)
|
|
381
|
+
ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value`,
|
|
382
|
+
['embedding.current_dims', JSON.stringify(dims)],
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
await ensureHotActiveSearchObjects(db)
|
|
322
387
|
}
|
|
323
388
|
|
|
324
389
|
export async function openDatabase(dataDir, dims) {
|
|
@@ -346,6 +411,9 @@ export async function openDatabase(dataDir, dims) {
|
|
|
346
411
|
}
|
|
347
412
|
})
|
|
348
413
|
}
|
|
414
|
+
if (await isBootstrapComplete(db)) {
|
|
415
|
+
await resetEmbeddingColumnsForDims(db, Number(dims))
|
|
416
|
+
}
|
|
349
417
|
await ensureCurrentSchemaExtensions(db, Number(dims))
|
|
350
418
|
await validateEmbeddingDims(db, Number(dims))
|
|
351
419
|
|
|
@@ -412,6 +480,17 @@ export async function setMetaValue(db, key, value) {
|
|
|
412
480
|
)
|
|
413
481
|
}
|
|
414
482
|
|
|
483
|
+
// Shallow-merge patch into an existing JSON object meta row (jsonb ||). Avoids
|
|
484
|
+
// read-modify-write lost updates when concurrent writers touch different keys.
|
|
485
|
+
export async function mergeMetaValue(db, key, patch) {
|
|
486
|
+
const patchJson = typeof patch === 'string' ? patch : JSON.stringify(patch ?? {})
|
|
487
|
+
await db.query(
|
|
488
|
+
`INSERT INTO meta(key, value) VALUES ($1, $2::jsonb)
|
|
489
|
+
ON CONFLICT(key) DO UPDATE SET value = COALESCE(meta.value, '{}'::jsonb) || EXCLUDED.value`,
|
|
490
|
+
[key, patchJson],
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
|
|
415
494
|
export function embeddingToSql(arr) {
|
|
416
495
|
if (!arr || !Array.isArray(arr)) return null
|
|
417
496
|
return `[${arr.map((n) => Number(n).toFixed(6)).join(',')}]`
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Writes one JSONL line per measurement point to
|
|
5
5
|
* `history/model-profile.jsonl` (mirrors `retrieval-trace.jsonl`). Meant for
|
|
6
6
|
* the embedding provider. Fire-and-forget: any I/O error is swallowed so
|
|
7
|
-
* instrumentation never breaks a live
|
|
7
|
+
* instrumentation never breaks a live agent or memory-cycle call.
|
|
8
8
|
*
|
|
9
9
|
* Schema:
|
|
10
10
|
* {
|
|
@@ -86,7 +86,7 @@ async function _initClient(client, schema) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Wrapper around pool.connect() that runs per-client init before returning.
|
|
89
|
-
// Exported so external callers (e.g. trace-store.
|
|
89
|
+
// Exported so external callers (e.g. trace-store.insertAgentCalls) can obtain
|
|
90
90
|
// a connection with search_path already set; raw _pool.connect() leaves it at
|
|
91
91
|
// the PG default and unqualified table lookups resolve in the wrong schema.
|
|
92
92
|
export async function checkedConnect(pgPool, schema) {
|
|
@@ -64,6 +64,15 @@ const LOCK_POLL_MS = 100;
|
|
|
64
64
|
const LOCK_WARN_MS = 5_000;
|
|
65
65
|
const LOCK_WAIT_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY']);
|
|
66
66
|
|
|
67
|
+
function envFlagEnabled(name) {
|
|
68
|
+
const raw = String(process.env[name] ?? '').trim().toLowerCase();
|
|
69
|
+
return raw === '1' || raw === 'true' || raw === 'on' || raw === 'yes';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function attachOnlyMode() {
|
|
73
|
+
return envFlagEnabled('MIXDOG_PG_ATTACH_ONLY') || envFlagEnabled('MIXDOG_MEMORY_SECONDARY');
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
// ── File lock (O_EXCL pattern from dispatch-persist.mjs / run-mcp.mjs) ───────
|
|
68
77
|
|
|
69
78
|
/**
|
|
@@ -356,6 +365,9 @@ async function _doEnsure(dataDir) {
|
|
|
356
365
|
source: 'prelock',
|
|
357
366
|
});
|
|
358
367
|
if (prelockReuse) return prelockReuse;
|
|
368
|
+
if (attachOnlyMode()) {
|
|
369
|
+
throw new Error('secondary memory runtime requires an existing PG instance')
|
|
370
|
+
}
|
|
359
371
|
|
|
360
372
|
// ── Acquire spawn lock to serialize initdb races ─────────────────────────
|
|
361
373
|
const acquired = await acquireSpawnLock(dataDir, () => tryReusePgInstance({
|
|
@@ -37,6 +37,7 @@ const BUNDLED_MANIFEST_PATH = fileURLToPath(new URL('../data/runtime-manifest.js
|
|
|
37
37
|
|
|
38
38
|
// GitHub raw URL fallback — used only when no cached or bundled manifest exists.
|
|
39
39
|
const MANIFEST_URL = 'https://raw.githubusercontent.com/tribgames/mixdog/main/src/runtime/memory/data/runtime-manifest.json'
|
|
40
|
+
const LEGACY_RUNTIME_RELEASE_REPOSITORY = 'trib-plugin/mixdog'
|
|
40
41
|
|
|
41
42
|
// ---------------------------------------------------------------------------
|
|
42
43
|
// Platform key
|
|
@@ -47,6 +48,15 @@ function platformKey() {
|
|
|
47
48
|
return `${os}-${process.arch}`
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
function platformKeyCandidates() {
|
|
52
|
+
const primary = platformKey()
|
|
53
|
+
const candidates = [primary]
|
|
54
|
+
if (process.platform === 'win32' && process.arch === 'arm64') {
|
|
55
|
+
candidates.push('win32-x64')
|
|
56
|
+
}
|
|
57
|
+
return candidates
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
// Fail-closed asset validation. A selected manifest asset is usable only if it
|
|
51
61
|
// is not explicitly marked unsupported AND carries a real downloadable payload:
|
|
52
62
|
// non-empty url, a well-formed 64-hex sha256, and a positive integer size.
|
|
@@ -154,6 +164,26 @@ async function downloadWithRetry(url, destPath) {
|
|
|
154
164
|
throw lastErr
|
|
155
165
|
}
|
|
156
166
|
|
|
167
|
+
function runtimeAssetUrlCandidates(url) {
|
|
168
|
+
const value = String(url || '')
|
|
169
|
+
const out = []
|
|
170
|
+
const add = (candidate) => {
|
|
171
|
+
if (candidate && !out.includes(candidate)) out.push(candidate)
|
|
172
|
+
}
|
|
173
|
+
add(value)
|
|
174
|
+
const match = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/releases\/download\//i.exec(value)
|
|
175
|
+
if (!match) return out
|
|
176
|
+
const releaseRepo = match[1]
|
|
177
|
+
const overrides = String(process.env.MIXDOG_RUNTIME_RELEASE_REPOSITORY || process.env.RUNTIME_RELEASE_REPOSITORY || '')
|
|
178
|
+
.split(/[\s,;]+/u)
|
|
179
|
+
.map((repo) => repo.trim())
|
|
180
|
+
.filter(Boolean)
|
|
181
|
+
for (const repo of [...overrides, LEGACY_RUNTIME_RELEASE_REPOSITORY]) {
|
|
182
|
+
if (repo && repo !== releaseRepo) add(value.replace(`/github.com/${releaseRepo}/`, `/github.com/${repo}/`))
|
|
183
|
+
}
|
|
184
|
+
return out
|
|
185
|
+
}
|
|
186
|
+
|
|
157
187
|
// ---------------------------------------------------------------------------
|
|
158
188
|
// Tar entry path validation + extraction
|
|
159
189
|
// ---------------------------------------------------------------------------
|
|
@@ -344,25 +374,35 @@ export async function ensureRuntime(dataDir) {
|
|
|
344
374
|
gcRuntimeDir(runtimeBaseDir, readActiveVersion(runtimeBaseDir))
|
|
345
375
|
|
|
346
376
|
const manifest = await loadManifest(key)
|
|
347
|
-
const pkey
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
.join(', ') || '(none)'
|
|
358
|
-
throw new Error(
|
|
359
|
-
`[runtime-fetcher] memory runtime not available on ${pkey}: ` +
|
|
360
|
-
`no runtime asset for this platform/arch in the manifest. ` +
|
|
361
|
-
`Supported: ${supported}. ` +
|
|
362
|
-
`Memory is disabled on this platform; the rest of mixdog continues to work.`
|
|
363
|
-
)
|
|
377
|
+
const pkey = platformKey()
|
|
378
|
+
let selectedKey = null
|
|
379
|
+
let asset = null
|
|
380
|
+
for (const candidateKey of platformKeyCandidates()) {
|
|
381
|
+
const candidateAsset = manifest.assets?.[candidateKey]
|
|
382
|
+
if (isUsableAsset(candidateAsset)) {
|
|
383
|
+
selectedKey = candidateKey
|
|
384
|
+
asset = candidateAsset
|
|
385
|
+
break
|
|
386
|
+
}
|
|
364
387
|
}
|
|
365
|
-
if (!
|
|
388
|
+
if (!asset) {
|
|
389
|
+
const primaryAsset = manifest.assets?.[pkey]
|
|
390
|
+
if (!primaryAsset) {
|
|
391
|
+
// Platform/arch absent from the manifest entirely (e.g. an exotic arch).
|
|
392
|
+
// The memory PG runtime cannot start here; fail with a single clear,
|
|
393
|
+
// actionable message. The memory worker's init().catch reports this as
|
|
394
|
+
// degraded and the rest of mixdog (agent, tools) keeps working without
|
|
395
|
+
// memory.
|
|
396
|
+
const supported = Object.keys(manifest.assets || {})
|
|
397
|
+
.filter((k) => isUsableAsset(manifest.assets[k]))
|
|
398
|
+
.join(', ') || '(none)'
|
|
399
|
+
throw new Error(
|
|
400
|
+
`[runtime-fetcher] memory runtime not available on ${pkey}: ` +
|
|
401
|
+
`no runtime asset for this platform/arch in the manifest. ` +
|
|
402
|
+
`Supported: ${supported}. ` +
|
|
403
|
+
`Memory is disabled on this platform; the rest of mixdog continues to work.`
|
|
404
|
+
)
|
|
405
|
+
}
|
|
366
406
|
// Platform/arch present but explicitly marked unsupported or carrying a
|
|
367
407
|
// placeholder/TBD payload (e.g. linux-arm64). Same graceful-degrade path.
|
|
368
408
|
throw new Error(
|
|
@@ -372,6 +412,12 @@ export async function ensureRuntime(dataDir) {
|
|
|
372
412
|
)
|
|
373
413
|
}
|
|
374
414
|
|
|
415
|
+
if (selectedKey !== pkey) {
|
|
416
|
+
__mixdogMemoryLog(
|
|
417
|
+
'[runtime-fetcher] win32-arm64 has no native runtime; using win32-x64 under emulation\n'
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
375
421
|
const { url, sha256, size } = asset
|
|
376
422
|
const version = `pg${manifest.pg?.major}.${manifest.pg?.minor}+pgvector-${manifest.pgvector?.version}`
|
|
377
423
|
|
|
@@ -412,10 +458,23 @@ export async function ensureRuntime(dataDir) {
|
|
|
412
458
|
const stagingLock = acquireStagingLock(stagingDir)
|
|
413
459
|
try {
|
|
414
460
|
let downloadOk = false
|
|
461
|
+
let lastDownloadErr = null
|
|
415
462
|
try {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
463
|
+
for (const candidateUrl of runtimeAssetUrlCandidates(url)) {
|
|
464
|
+
try {
|
|
465
|
+
if (candidateUrl !== url) {
|
|
466
|
+
__mixdogMemoryLog(`[runtime-fetcher] retrying runtime download from fallback release repo — ${candidateUrl}\n`)
|
|
467
|
+
}
|
|
468
|
+
await downloadWithRetry(candidateUrl, tarPath)
|
|
469
|
+
await verifySha256(tarPath, sha256)
|
|
470
|
+
downloadOk = true
|
|
471
|
+
break
|
|
472
|
+
} catch (err) {
|
|
473
|
+
lastDownloadErr = err
|
|
474
|
+
try { rmSync(tarPath, { force: true }) } catch {}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (!downloadOk && lastDownloadErr) throw lastDownloadErr
|
|
419
478
|
extractTarGz(tarPath, stagingDir, stagingDir)
|
|
420
479
|
} finally {
|
|
421
480
|
try { rmSync(tarPath, { force: true }) } catch {}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
// Side-effect-free helpers for ingest_session (recall fast-track hydration).
|
|
4
|
+
// Extracted from memory/index.mjs so the pure logic (stable identity, sensitive
|
|
5
|
+
// redaction, role/content shaping) can be unit-tested without importing the
|
|
6
|
+
// MCP server entrypoint and its heavy boot-time side effects.
|
|
7
|
+
|
|
8
|
+
// Roles we persist from an in-memory session transcript (conversation only).
|
|
9
|
+
// Map provider/runtime spellings onto canonical roles; only user/assistant are
|
|
10
|
+
// kept so recall-fasttrack memory does not duplicate protected system prefix.
|
|
11
|
+
const INGEST_SESSION_ROLES = new Set(['user', 'assistant'])
|
|
12
|
+
|
|
13
|
+
export function normalizeIngestRole(role) {
|
|
14
|
+
const raw = String(role || '').trim().toLowerCase()
|
|
15
|
+
if (!raw) return null
|
|
16
|
+
let canonical = raw
|
|
17
|
+
if (raw === 'tool_result' || raw === 'function' || raw === 'tool-result') canonical = 'tool'
|
|
18
|
+
else if (raw === 'human') canonical = 'user'
|
|
19
|
+
else if (raw === 'ai' || raw === 'model') canonical = 'assistant'
|
|
20
|
+
return INGEST_SESSION_ROLES.has(canonical) ? canonical : null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Extract the first textual content block from a message content field.
|
|
24
|
+
export function firstTextContent(content) {
|
|
25
|
+
if (typeof content === 'string') return content
|
|
26
|
+
if (!Array.isArray(content)) return ''
|
|
27
|
+
for (const item of content) {
|
|
28
|
+
if (typeof item === 'string') return item
|
|
29
|
+
if (item?.type === 'text' && typeof item.text === 'string') return item.text
|
|
30
|
+
}
|
|
31
|
+
return ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Collect durable tool-call / tool-result ids for identity + pairing.
|
|
35
|
+
function toolIdentityIds(m) {
|
|
36
|
+
const ids = []
|
|
37
|
+
if (Array.isArray(m?.toolCalls)) {
|
|
38
|
+
for (const tc of m.toolCalls) { if (tc?.id) ids.push(String(tc.id)) }
|
|
39
|
+
}
|
|
40
|
+
if (m?.toolCallId) ids.push(String(m.toolCallId))
|
|
41
|
+
return ids
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Build a stable, content-derived source_ref for a session message so the
|
|
45
|
+
// ON CONFLICT DO NOTHING insert is idempotent across compaction reindexing.
|
|
46
|
+
//
|
|
47
|
+
// Identity hashes ONLY durable/original fields: role, tool-call/result ids, the
|
|
48
|
+
// ORIGINAL message timestamp (m.ts / m.timestamp) when actually present, and the
|
|
49
|
+
// shaped content. It never folds in a synthesized Date.now() fallback ts or the
|
|
50
|
+
// volatile loop index, so the same untimestamped message produces the same ref
|
|
51
|
+
// regardless of its position after compaction shrinks/reindexes the transcript.
|
|
52
|
+
// Two textually identical untimestamped plain messages intentionally dedupe to
|
|
53
|
+
// one row (stable dedupe preferred over positional separation).
|
|
54
|
+
export function stableSessionSourceRef(sessionId, m, role, content) {
|
|
55
|
+
const toolIds = toolIdentityIds(m)
|
|
56
|
+
// Only an ORIGINAL, caller-supplied timestamp counts as durable identity.
|
|
57
|
+
const rawTs = m?.ts ?? m?.timestamp
|
|
58
|
+
const originalTs = (typeof rawTs === 'number' && Number.isFinite(rawTs)) || (typeof rawTs === 'string' && rawTs.trim())
|
|
59
|
+
? String(rawTs)
|
|
60
|
+
: ''
|
|
61
|
+
const identity = [role, originalTs, toolIds.join(','), content].join('\u0000')
|
|
62
|
+
const hash = crypto.createHash('sha256').update(identity).digest('hex').slice(0, 24)
|
|
63
|
+
return `session:${sessionId}:${hash}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Monotonic source_turn allocator for ingest_session. source_turn must be a
|
|
67
|
+
// running continuation order INDEPENDENT of the current (post-compaction)
|
|
68
|
+
// array index: a freshly appended turn must sort AFTER every previously
|
|
69
|
+
// ingested row, even though compaction reset its array index to a low value.
|
|
70
|
+
// Seed `prevMaxTurn` from MAX(source_turn) for the session, then call
|
|
71
|
+
// next() once per actually-inserted row. Re-ingested (ON CONFLICT) rows keep
|
|
72
|
+
// their original turn and must NOT advance the counter, so the caller only
|
|
73
|
+
// invokes next() when a row was truly inserted.
|
|
74
|
+
export function createIngestTurnAllocator(prevMaxTurn = 0) {
|
|
75
|
+
let turn = Number.isFinite(Number(prevMaxTurn)) ? Math.max(0, Math.floor(Number(prevMaxTurn))) : 0
|
|
76
|
+
return {
|
|
77
|
+
peekNext() { return turn + 1 },
|
|
78
|
+
next() { turn += 1; return turn },
|
|
79
|
+
current() { return turn },
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Sensitive tool-arg key matcher (mirrors compact.mjs SENSITIVE_TOOL_ARG_KEY_RE):
|
|
84
|
+
// api_key/authorization/auth/cookie/credential/password/secret/token, etc.
|
|
85
|
+
const SENSITIVE_ARG_KEY_RE = /(?:^|[_-])(?:api[_-]?key|authorization|auth|cookie|credential|passwd|password|refresh[_-]?token|secret|token)(?:$|[_-])/i
|
|
86
|
+
const SENSITIVE_KEY_WORD = '(?:api[_-]?key|authorization|auth|cookie|credential|passwd|password|refresh[_-]?token|secret|token)'
|
|
87
|
+
// Full key matcher: the sensitive WORD may carry a prefix and/or suffix segment
|
|
88
|
+
// joined by `_`/`-` so prefixed variants like `access_token`, `access-token`,
|
|
89
|
+
// `x-api-key`, and `bearer_token` are matched as whole keys (not just the bare
|
|
90
|
+
// word at key start). Mirrors compact.mjs SENSITIVE_TOOL_ARG_KEY_FULL.
|
|
91
|
+
const SENSITIVE_KEY_FULL = `(?:[A-Za-z0-9_-]*[_-])?${SENSITIVE_KEY_WORD}(?:[_-][A-Za-z0-9_-]*)?`
|
|
92
|
+
const TOOL_ARG_MAX_CHARS = 400
|
|
93
|
+
|
|
94
|
+
// Recursively redact sensitive values by key. Sensitive keys collapse to
|
|
95
|
+
// [redacted] regardless of value shape so nested secrets never survive.
|
|
96
|
+
export function redactToolArgValue(value, key = '', depth = 0) {
|
|
97
|
+
if (SENSITIVE_ARG_KEY_RE.test(String(key || ''))) return '[redacted]'
|
|
98
|
+
if (value == null) return value
|
|
99
|
+
if (typeof value === 'bigint') return String(value)
|
|
100
|
+
// Defense-in-depth: a non-sensitive KEY can still carry a secret embedded in
|
|
101
|
+
// its string VALUE (e.g. `{ headers: "authorization: Bearer ..." }`). Run raw
|
|
102
|
+
// key:value redaction on string values so embedded secrets never survive.
|
|
103
|
+
if (typeof value === 'string') return redactRawArgString(value)
|
|
104
|
+
if (typeof value !== 'object') return value
|
|
105
|
+
if (depth >= 4) return Array.isArray(value) ? `[array:${value.length}]` : '[object]'
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return value.slice(0, 8).map((item, idx) => redactToolArgValue(item, String(idx), depth + 1))
|
|
108
|
+
}
|
|
109
|
+
const out = {}
|
|
110
|
+
for (const k of Object.keys(value)) out[k] = redactToolArgValue(value[k], k, depth + 1)
|
|
111
|
+
return out
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Redact `key: value` / `key=value` pairs inside a raw (non-JSON) arg string.
|
|
115
|
+
// Unlike a single greedy regex, this consumes the WHOLE secret value after the
|
|
116
|
+
// key — including spaces, `Bearer ` prefixes, quoted values with internal
|
|
117
|
+
// spaces, and `;`-separated cookie pairs — so no secret fragment leaks.
|
|
118
|
+
function redactRawArgString(text) {
|
|
119
|
+
const keyRe = new RegExp(`((?:^|[\\s,{(])["']?${SENSITIVE_KEY_FULL}["']?\\s*[:=]\\s*)`, 'gi')
|
|
120
|
+
let out = ''
|
|
121
|
+
let last = 0
|
|
122
|
+
let match
|
|
123
|
+
while ((match = keyRe.exec(text)) !== null) {
|
|
124
|
+
const prefixEnd = match.index + match[0].length
|
|
125
|
+
out += text.slice(last, prefixEnd)
|
|
126
|
+
// Determine where the secret value ends. Quoted values run to the closing
|
|
127
|
+
// quote; unquoted values run to the next separator that ends a field. For
|
|
128
|
+
// Authorization we also swallow a leading `Bearer `/`Basic ` scheme word so
|
|
129
|
+
// nothing after it survives.
|
|
130
|
+
let i = prefixEnd
|
|
131
|
+
const quote = text[i] === '"' || text[i] === "'" ? text[i] : ''
|
|
132
|
+
if (quote) {
|
|
133
|
+
i += 1
|
|
134
|
+
while (i < text.length && text[i] !== quote) i += 1
|
|
135
|
+
if (i < text.length) i += 1 // include closing quote
|
|
136
|
+
} else {
|
|
137
|
+
// Unquoted: stop at a comma, closing brace/paren, or newline. Spaces and
|
|
138
|
+
// `;` inside the value are part of the secret (Bearer tokens, cookies).
|
|
139
|
+
while (i < text.length && !/[,)}\n]/.test(text[i])) i += 1
|
|
140
|
+
}
|
|
141
|
+
out += '[redacted]'
|
|
142
|
+
last = i
|
|
143
|
+
keyRe.lastIndex = i
|
|
144
|
+
}
|
|
145
|
+
out += text.slice(last)
|
|
146
|
+
return out
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Produce a readable, redacted, length-capped string for tool-call arguments.
|
|
150
|
+
// Object/JSON args are walked key-by-key so nested secrets are caught; a raw
|
|
151
|
+
// non-JSON string has its key:value secret pairs redacted before truncation.
|
|
152
|
+
export function redactToolArgString(rawArgs) {
|
|
153
|
+
if (rawArgs == null) return ''
|
|
154
|
+
let parsed = rawArgs
|
|
155
|
+
if (typeof rawArgs === 'string') {
|
|
156
|
+
const trimmed = rawArgs.trim()
|
|
157
|
+
if (/^[[{]/.test(trimmed)) {
|
|
158
|
+
try { parsed = JSON.parse(trimmed) } catch { parsed = trimmed }
|
|
159
|
+
} else {
|
|
160
|
+
parsed = trimmed
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
let out
|
|
164
|
+
if (parsed && typeof parsed === 'object') {
|
|
165
|
+
try { out = JSON.stringify(redactToolArgValue(parsed)) }
|
|
166
|
+
catch { out = '[unserializable args]' }
|
|
167
|
+
} else {
|
|
168
|
+
out = redactRawArgString(String(parsed))
|
|
169
|
+
}
|
|
170
|
+
return out.slice(0, TOOL_ARG_MAX_CHARS)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build a readable, role-aware content string for a session message so the
|
|
174
|
+
// structured handoff preserves assistant tool_calls and tool_result bodies
|
|
175
|
+
// (not just plain text). Keeps valid tool-call/tool-result pairing legible by
|
|
176
|
+
// tagging each with its toolCallId, while redacting sensitive argument values.
|
|
177
|
+
export function sessionMessageContent(m) {
|
|
178
|
+
const parts = []
|
|
179
|
+
const base = firstTextContent(m?.content)
|
|
180
|
+
if (base && base.trim()) parts.push(base.trim())
|
|
181
|
+
if (m?.role === 'assistant' && Array.isArray(m.toolCalls) && m.toolCalls.length) {
|
|
182
|
+
for (const tc of m.toolCalls.slice(0, 8)) {
|
|
183
|
+
const name = tc?.name || tc?.function?.name || tc?.id || 'tool'
|
|
184
|
+
const id = tc?.id ? ` id=${tc.id}` : ''
|
|
185
|
+
const argStr = redactToolArgString(tc?.arguments ?? tc?.function?.arguments)
|
|
186
|
+
parts.push(`[tool_call ${name}${id}]${argStr ? ` ${argStr}` : ''}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const isTool = m?.role === 'tool' || normalizeIngestRole(m?.role) === 'tool'
|
|
190
|
+
if (isTool && m?.toolCallId && parts.length) {
|
|
191
|
+
parts[0] = `[tool_result id=${m.toolCallId}] ${parts[0]}`
|
|
192
|
+
} else if (isTool && m?.toolCallId) {
|
|
193
|
+
parts.push(`[tool_result id=${m.toolCallId}]`)
|
|
194
|
+
}
|
|
195
|
+
return parts.join('\n')
|
|
196
|
+
}
|