mixdog 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto'
|
|
2
|
+
import { isInternalRuntimeNotificationText } from '../../shared/tool-execution-contract.mjs'
|
|
2
3
|
|
|
3
4
|
// Side-effect-free helpers for ingest_session (recall fast-track hydration).
|
|
4
5
|
// Extracted from memory/index.mjs so the pure logic (stable identity, sensitive
|
|
@@ -194,3 +195,109 @@ export function sessionMessageContent(m) {
|
|
|
194
195
|
}
|
|
195
196
|
return parts.join('\n')
|
|
196
197
|
}
|
|
198
|
+
|
|
199
|
+
// ── Pure-conversation ingest shaping ──────────────────────────────────────
|
|
200
|
+
//
|
|
201
|
+
// ingest_session persists ONLY real conversation (human prompts + model reply
|
|
202
|
+
// prose). sessionMessageContent (above) is the structured-handoff shaper that
|
|
203
|
+
// inlines tool_call / tool_result traces and is consumed by smoke tests; it is
|
|
204
|
+
// intentionally LEFT UNCHANGED. The functions below are ingest-only and strip
|
|
205
|
+
// everything that is mechanical/synthetic so memory episode rows are pure
|
|
206
|
+
// conversation with zero loss of genuine human/model text.
|
|
207
|
+
|
|
208
|
+
// Mirror of compact.mjs SUMMARY_PREFIX (the compaction handoff anchor). Copied
|
|
209
|
+
// locally rather than imported because compact.mjs lives under
|
|
210
|
+
// agent/orchestrator/session and pulls in heavy context/offload modules —
|
|
211
|
+
// importing it into the memory layer would create a layering dependency (memory
|
|
212
|
+
// → orchestrator) and risk a boot-time cycle. Keep this string byte-identical
|
|
213
|
+
// to compact.mjs:9; if that anchor changes, update this copy.
|
|
214
|
+
const SUMMARY_PREFIX_INGEST = 'A previous model worked on this task and produced the compacted handoff summary below. Build on the work already done and avoid duplicating it; treat the summary as authoritative context for continuing the task. You also retain the preserved recent turns that follow.'
|
|
215
|
+
|
|
216
|
+
// Anchored strip of the deterministic user-turn prefix envelopes that
|
|
217
|
+
// manager.mjs prepends to the SINGLE real user message (manager.mjs:3166-3201
|
|
218
|
+
// via prefixUserTurnContent / prefixSessionStartContent / buildSessionStartBlock).
|
|
219
|
+
// Zero-loss design: every rule is anchored to the START of the message and only
|
|
220
|
+
// removes the EXACT shapes manager.mjs produces. A `# Task` / `# Session` etc.
|
|
221
|
+
// appearing mid-message in the human's own text is never touched. When in
|
|
222
|
+
// doubt the rules UNDER-strip (leave content) rather than delete human text.
|
|
223
|
+
function stripUserTurnPrefixEnvelopes(text) {
|
|
224
|
+
let out = String(text ?? '')
|
|
225
|
+
// 1) Leading `# Session` block (buildSessionStartBlock: `# Session\nCwd: ...
|
|
226
|
+
// \nModel: ...\nWorkflow: ...`, joined by prefixSessionStartContent with a
|
|
227
|
+
// trailing `\n\n`). FIELD-ANCHORED: only strip when the line(s) right after
|
|
228
|
+
// `# Session\n` are EXACTLY the fixed fields buildSessionStartBlock emits
|
|
229
|
+
// (`Cwd: `, `Model: `, `Workflow: `, each on its own line) before the
|
|
230
|
+
// blank-line terminator. A human doc that merely STARTS with a `# Session`
|
|
231
|
+
// heading followed by free prose (e.g. `프로젝트 회의록입니다`) does NOT match
|
|
232
|
+
// — its next line is not a `Cwd:/Model:/Workflow:` field — so it is
|
|
233
|
+
// preserved verbatim (zero-loss). Anchored ^.
|
|
234
|
+
out = out.replace(/^# Session\n(?:(?:Cwd|Model|Workflow): [^\n]*\n)+(?:\n|$)/, '')
|
|
235
|
+
// 2) Leading `# Additional context\n<body>\n\n` (manager.mjs:3168). Anchored
|
|
236
|
+
// to start; body runs up to the next `# ` section boundary or end. The
|
|
237
|
+
// `\n\n` separator manager.mjs emits is included.
|
|
238
|
+
out = out.replace(/^# Additional context\n[\s\S]*?(?=\n# |$)/, '')
|
|
239
|
+
out = out.replace(/^\n+/, '')
|
|
240
|
+
// 3) Leading `# Prefetch\n<body>\n\n` (manager.mjs:3171). Same anchoring.
|
|
241
|
+
out = out.replace(/^# Prefetch\n[\s\S]*?(?=\n# |$)/, '')
|
|
242
|
+
out = out.replace(/^\n+/, '')
|
|
243
|
+
// 4) Leading `# Task\n` marker (prefixUserTurnContent: `${contextBlock}# Task\n${content}`).
|
|
244
|
+
// Remove ONLY the marker line; everything after it is the human prompt and
|
|
245
|
+
// is preserved verbatim. Anchored ^ so a `# Task` in the human's own prose
|
|
246
|
+
// (after real text precedes it) is never removed.
|
|
247
|
+
out = out.replace(/^# Task\n/, '')
|
|
248
|
+
return out
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Ingest-only message shaper: returns ONLY the human/model prose text, NEVER
|
|
252
|
+
// inlining tool_call / tool_result traces. For user messages, the deterministic
|
|
253
|
+
// manager.mjs prefix envelopes are stripped so only the human's actual prompt
|
|
254
|
+
// remains. The <system-reminder> block is left in place here because
|
|
255
|
+
// cleanMemoryText already removes it downstream (text-utils.cjs:28).
|
|
256
|
+
export function sessionMessageContentForIngest(m) {
|
|
257
|
+
const base = firstTextContent(m?.content)
|
|
258
|
+
if (!base) return ''
|
|
259
|
+
if (normalizeIngestRole(m?.role) === 'user') {
|
|
260
|
+
return stripUserTurnPrefixEnvelopes(base)
|
|
261
|
+
}
|
|
262
|
+
return base
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Row-exclusion predicate for ingest_session. Synthetic / non-conversation
|
|
266
|
+
// rows (reference-files injections, compaction summaries, protected-context
|
|
267
|
+
// `.` acks, internal runtime nudges) are dropped ENTIRELY — they are noise,
|
|
268
|
+
// not conversation. Mirrors the predicates in manager.mjs / compact.mjs but is
|
|
269
|
+
// reimplemented locally to avoid a memory→orchestrator layering dependency.
|
|
270
|
+
export function shouldExcludeIngestMessage(m) {
|
|
271
|
+
if (!m || typeof m !== 'object') return true
|
|
272
|
+
const role = normalizeIngestRole(m?.role)
|
|
273
|
+
const raw = firstTextContent(m?.content)
|
|
274
|
+
// `Reference files:` synthetic user rows (manager.mjs isReferenceFilesMessage).
|
|
275
|
+
if (role === 'user' && typeof raw === 'string' && /^Reference files:\s*/i.test(raw.trimStart())) {
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
// Attachment-only placeholder rows (e.g. Discord backend discord.mjs:724
|
|
279
|
+
// `"(attachment)"` fallback when a message carries no text, only files).
|
|
280
|
+
// Carries zero retrievable content — excluded so recall isn't polluted
|
|
281
|
+
// with bare "(attachment)" memory rows.
|
|
282
|
+
if (role === 'user' && typeof raw === 'string' && raw.trim() === '(attachment)') {
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
285
|
+
// Compaction summary user rows (compact.mjs isSummaryMessage / SUMMARY_PREFIX).
|
|
286
|
+
if (role === 'user' && typeof raw === 'string' && raw.startsWith(SUMMARY_PREFIX_INGEST)) {
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
// Protected-context `.` ack assistant rows (compact.mjs isProtectedContextAckMessage):
|
|
290
|
+
// a bare `.` with no tool calls. cleanMemoryText leaves a lone `.` non-empty
|
|
291
|
+
// (no \p{L}\p{N}), so it would otherwise survive the empty-skip — exclude it.
|
|
292
|
+
if (role === 'assistant' && typeof raw === 'string' && raw.trim() === '.' && !Array.isArray(m?.toolCalls)) {
|
|
293
|
+
return true
|
|
294
|
+
}
|
|
295
|
+
// Internal runtime nudge `[mixdog-runtime] ...` user rows (loop.mjs:2014-2020)
|
|
296
|
+
// and other internal runtime notifications (tool-execution-contract).
|
|
297
|
+
if (role === 'user') {
|
|
298
|
+
const text = typeof raw === 'string' ? raw : ''
|
|
299
|
+
if (/^\[mixdog-runtime\]/.test(text.trimStart())) return true
|
|
300
|
+
if (isInternalRuntimeNotificationText(text)) return true
|
|
301
|
+
}
|
|
302
|
+
return false
|
|
303
|
+
}
|
|
@@ -56,7 +56,7 @@ async function init(client) {
|
|
|
56
56
|
session_id TEXT,
|
|
57
57
|
iteration INTEGER,
|
|
58
58
|
kind TEXT NOT NULL,
|
|
59
|
-
|
|
59
|
+
agent TEXT,
|
|
60
60
|
model TEXT,
|
|
61
61
|
tool_name TEXT,
|
|
62
62
|
tool_ms INTEGER,
|
|
@@ -103,12 +103,18 @@ async function init(client) {
|
|
|
103
103
|
END $$
|
|
104
104
|
`)
|
|
105
105
|
|
|
106
|
+
// Drift repair MUST run before index creation: an old cluster whose
|
|
107
|
+
// trace_events predates the `agent` column would otherwise die right below
|
|
108
|
+
// at idx_trace_agent_ts (CREATE INDEX references the missing column) before
|
|
109
|
+
// initAgentTables() ever gets a chance to repair it. Reviewer High fix.
|
|
110
|
+
await migrateSchemaDrift(client)
|
|
111
|
+
|
|
106
112
|
// BRIN on ts — ~1000× smaller than btree for append-only timeseries; ideal
|
|
107
113
|
// for time-window range scans where rows arrive in roughly ts order.
|
|
108
114
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_trace_ts_brin ON trace_events USING BRIN (ts) WITH (pages_per_range = 32)`)
|
|
109
115
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_trace_kind_ts ON trace_events(kind, ts DESC)`)
|
|
110
116
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_trace_session ON trace_events(session_id, ts)`)
|
|
111
|
-
await client.query(`CREATE INDEX IF NOT EXISTS
|
|
117
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_trace_agent_ts ON trace_events(agent, ts DESC)`)
|
|
112
118
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_trace_model_ts ON trace_events(model, ts DESC)`)
|
|
113
119
|
await client.query(`CREATE INDEX IF NOT EXISTS idx_trace_tool ON trace_events(tool_name) WHERE kind = 'tool'`)
|
|
114
120
|
// Span-tree and cross-schema recall↔trace correlation — partial indexes so
|
|
@@ -122,13 +128,45 @@ async function init(client) {
|
|
|
122
128
|
await ensureCurrentAndNextMonthPartitions(client)
|
|
123
129
|
}
|
|
124
130
|
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Schema-drift repair — ALTER TABLE ... ADD COLUMN IF NOT EXISTS
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Observed failure: pg.log repeatedly logs
|
|
135
|
+
// column "agent" of relation "trace_events" does not exist
|
|
136
|
+
// and the equivalent for agent_sessions — an older cluster/pgdata created
|
|
137
|
+
// before the `agent` column was added to these two tables, so every INSERT/
|
|
138
|
+
// UPSERT touching it fails forever until manually patched. CREATE TABLE IF
|
|
139
|
+
// NOT EXISTS above is a no-op once the table exists, so it never repairs an
|
|
140
|
+
// already-created table with a stale column set. Run these idempotent
|
|
141
|
+
// ADD COLUMN migrations on every boot (init() and initAgentTables()) so
|
|
142
|
+
// drifted clusters self-heal without a manual ALTER.
|
|
143
|
+
async function migrateSchemaDrift(client) {
|
|
144
|
+
// Bounded lock wait: nullable ADD COLUMN is metadata-only once the ACCESS
|
|
145
|
+
// EXCLUSIVE lock is held, but acquiring that lock can queue behind live
|
|
146
|
+
// readers/writers indefinitely and wedge boot (reviewer Medium). 5s is
|
|
147
|
+
// generous for a metadata change; on timeout we leave the drift in place
|
|
148
|
+
// (inserts keep failing as before — no worse) instead of hanging startup.
|
|
149
|
+
await client.query(`SET LOCAL lock_timeout = '5s'`).catch(() => {})
|
|
150
|
+
try {
|
|
151
|
+
await client.query(`ALTER TABLE IF EXISTS trace_events ADD COLUMN IF NOT EXISTS agent TEXT`)
|
|
152
|
+
await client.query(`ALTER TABLE IF EXISTS agent_sessions ADD COLUMN IF NOT EXISTS agent TEXT`)
|
|
153
|
+
} finally {
|
|
154
|
+
await client.query(`SET LOCAL lock_timeout = DEFAULT`).catch(() => {})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
125
158
|
// ---------------------------------------------------------------------------
|
|
126
159
|
// Agent-specific analytic tables (added post-init via initAgentTables)
|
|
127
160
|
// ---------------------------------------------------------------------------
|
|
128
161
|
|
|
129
|
-
// Called once per openTraceDatabase boot
|
|
130
|
-
//
|
|
162
|
+
// Called once per openTraceDatabase boot, inside the BOOTSTRAP advisory-lock
|
|
163
|
+
// scope (see openTraceDatabase) so concurrent first-boot processes don't race
|
|
164
|
+
// this DDL. All statements remain IF NOT EXISTS as a second line of defense.
|
|
131
165
|
export async function initAgentTables(client) {
|
|
166
|
+
// Repair schema drift on already-created tables before anything else
|
|
167
|
+
// (trace_events already exists at this point via init(); agent_sessions
|
|
168
|
+
// is created just below in this same function).
|
|
169
|
+
await migrateSchemaDrift(client)
|
|
132
170
|
// ── agent_calls: one row per tool invocation ─────────────────────────────
|
|
133
171
|
await client.query(`
|
|
134
172
|
CREATE TABLE IF NOT EXISTS agent_calls (
|
|
@@ -175,7 +213,7 @@ export async function initAgentTables(client) {
|
|
|
175
213
|
await client.query(`
|
|
176
214
|
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
177
215
|
session_id TEXT PRIMARY KEY,
|
|
178
|
-
|
|
216
|
+
agent TEXT,
|
|
179
217
|
model TEXT,
|
|
180
218
|
started_at TIMESTAMPTZ,
|
|
181
219
|
last_seen_at TIMESTAMPTZ,
|
|
@@ -186,6 +224,10 @@ export async function initAgentTables(client) {
|
|
|
186
224
|
total_output_tokens BIGINT NOT NULL DEFAULT 0
|
|
187
225
|
)
|
|
188
226
|
`)
|
|
227
|
+
|
|
228
|
+
// Repair drift again post-creation for agent_sessions (belt-and-suspenders;
|
|
229
|
+
// no-op when the table was freshly created above with the column present).
|
|
230
|
+
await migrateSchemaDrift(client)
|
|
189
231
|
}
|
|
190
232
|
|
|
191
233
|
// ---------------------------------------------------------------------------
|
|
@@ -289,14 +331,14 @@ export async function insertAgentCalls(db, events) {
|
|
|
289
331
|
// Upsert session summaries — accumulate from tool+llm rows in this batch
|
|
290
332
|
const sessionMap = new Map()
|
|
291
333
|
for (const r of toolRows) {
|
|
292
|
-
const s = sessionMap.get(r.session_id) ?? { tool_calls: 0, llm_calls: 0, max_iteration: 0, total_input: 0n, total_output: 0n, ts0: r.ts, ts1: r.ts,
|
|
334
|
+
const s = sessionMap.get(r.session_id) ?? { tool_calls: 0, llm_calls: 0, max_iteration: 0, total_input: 0n, total_output: 0n, ts0: r.ts, ts1: r.ts, agent: null, model: null }
|
|
293
335
|
s.tool_calls += 1
|
|
294
336
|
if (r.iteration != null && r.iteration > s.max_iteration) s.max_iteration = r.iteration
|
|
295
337
|
if (r.ts < s.ts0) s.ts0 = r.ts; if (r.ts > s.ts1) s.ts1 = r.ts
|
|
296
338
|
sessionMap.set(r.session_id, s)
|
|
297
339
|
}
|
|
298
340
|
for (const r of llmRows) {
|
|
299
|
-
const s = sessionMap.get(r.session_id) ?? { tool_calls: 0, llm_calls: 0, max_iteration: 0, total_input: 0n, total_output: 0n, ts0: r.ts, ts1: r.ts,
|
|
341
|
+
const s = sessionMap.get(r.session_id) ?? { tool_calls: 0, llm_calls: 0, max_iteration: 0, total_input: 0n, total_output: 0n, ts0: r.ts, ts1: r.ts, agent: null, model: null }
|
|
300
342
|
s.llm_calls += 1
|
|
301
343
|
s.total_input += BigInt(r.input_tokens ?? 0)
|
|
302
344
|
s.total_output += BigInt(r.output_tokens ?? 0)
|
|
@@ -305,13 +347,13 @@ export async function insertAgentCalls(db, events) {
|
|
|
305
347
|
if (r.ts < s.ts0) s.ts0 = r.ts; if (r.ts > s.ts1) s.ts1 = r.ts
|
|
306
348
|
sessionMap.set(r.session_id, s)
|
|
307
349
|
}
|
|
308
|
-
// Also pick up
|
|
350
|
+
// Also pick up agent from preset_assign events in the same batch
|
|
309
351
|
for (const ev of events) {
|
|
310
|
-
if (ev.kind === 'preset_assign' && ev.
|
|
352
|
+
if (ev.kind === 'preset_assign' && ev.agent) {
|
|
311
353
|
const sid = ev.session_id ?? ev.sessionId ?? null
|
|
312
354
|
if (!sid) continue
|
|
313
355
|
const s = sessionMap.get(sid)
|
|
314
|
-
if (s) s.
|
|
356
|
+
if (s) s.agent = ev.agent
|
|
315
357
|
}
|
|
316
358
|
}
|
|
317
359
|
// Fix 5 — upsert sessions for preset_assign-only batches (no tool/llm rows yet)
|
|
@@ -326,32 +368,32 @@ export async function insertAgentCalls(db, events) {
|
|
|
326
368
|
tool_calls: 0, llm_calls: 0, max_iteration: 0,
|
|
327
369
|
total_input: 0n, total_output: 0n,
|
|
328
370
|
ts0: tsIso, ts1: tsIso,
|
|
329
|
-
|
|
371
|
+
agent: ev.agent ?? null, model: ev.model ?? null,
|
|
330
372
|
})
|
|
331
373
|
}
|
|
332
374
|
|
|
333
375
|
// Coalesce agent_sessions upserts: batch all sessions in one unnest INSERT.
|
|
334
376
|
// Also within the same transaction.
|
|
335
377
|
if (sessionMap.size > 0) {
|
|
336
|
-
const sids = [],
|
|
378
|
+
const sids = [], agents = [], models = [], ts0s = [], ts1s = [],
|
|
337
379
|
tcalls = [], lcalls = [], maxiters = [], tinputs = [], toutputs = []
|
|
338
380
|
for (const [sid, s] of sessionMap) {
|
|
339
|
-
sids.push(sid);
|
|
381
|
+
sids.push(sid); agents.push(s.agent); models.push(s.model)
|
|
340
382
|
ts0s.push(s.ts0); ts1s.push(s.ts1)
|
|
341
383
|
tcalls.push(s.tool_calls); lcalls.push(s.llm_calls)
|
|
342
384
|
maxiters.push(s.max_iteration)
|
|
343
385
|
tinputs.push(String(s.total_input)); toutputs.push(String(s.total_output))
|
|
344
386
|
}
|
|
345
387
|
await client.query(`
|
|
346
|
-
INSERT INTO agent_sessions (session_id,
|
|
347
|
-
SELECT u.session_id, u.
|
|
388
|
+
INSERT INTO agent_sessions (session_id, agent, model, started_at, last_seen_at, tool_calls, llm_calls, max_iteration, total_input_tokens, total_output_tokens)
|
|
389
|
+
SELECT u.session_id, u.agent, u.model,
|
|
348
390
|
u.started_at::timestamptz, u.last_seen_at::timestamptz,
|
|
349
391
|
u.tool_calls::int, u.llm_calls::int, u.max_iteration::int,
|
|
350
392
|
u.total_input_tokens::bigint, u.total_output_tokens::bigint
|
|
351
393
|
FROM unnest($1::text[],$2::text[],$3::text[],$4::text[],$5::text[],$6::int[],$7::int[],$8::int[],$9::text[],$10::text[])
|
|
352
|
-
AS u(session_id,
|
|
394
|
+
AS u(session_id,agent,model,started_at,last_seen_at,tool_calls,llm_calls,max_iteration,total_input_tokens,total_output_tokens)
|
|
353
395
|
ON CONFLICT (session_id) DO UPDATE SET
|
|
354
|
-
|
|
396
|
+
agent = COALESCE(EXCLUDED.agent, agent_sessions.agent),
|
|
355
397
|
model = COALESCE(EXCLUDED.model, agent_sessions.model),
|
|
356
398
|
started_at = LEAST(agent_sessions.started_at, EXCLUDED.started_at),
|
|
357
399
|
last_seen_at = GREATEST(agent_sessions.last_seen_at, EXCLUDED.last_seen_at),
|
|
@@ -360,7 +402,7 @@ export async function insertAgentCalls(db, events) {
|
|
|
360
402
|
max_iteration = GREATEST(agent_sessions.max_iteration, EXCLUDED.max_iteration),
|
|
361
403
|
total_input_tokens = agent_sessions.total_input_tokens + EXCLUDED.total_input_tokens,
|
|
362
404
|
total_output_tokens = agent_sessions.total_output_tokens + EXCLUDED.total_output_tokens
|
|
363
|
-
`, [sids,
|
|
405
|
+
`, [sids, agents, models, ts0s, ts1s, tcalls, lcalls, maxiters, tinputs, toutputs])
|
|
364
406
|
}
|
|
365
407
|
|
|
366
408
|
await client.query('COMMIT')
|
|
@@ -513,14 +555,19 @@ export async function openTraceDatabase(dataDir) {
|
|
|
513
555
|
// are pre-created for this boot.
|
|
514
556
|
await ensureCurrentAndNextMonthPartitions(client)
|
|
515
557
|
}
|
|
558
|
+
// Agent-specific analytic tables — moved inside the advisory-lock
|
|
559
|
+
// scope (was previously run after client.release() with no lock,
|
|
560
|
+
// letting concurrent first-boot processes race the agent_calls/
|
|
561
|
+
// agent_llm/agent_sessions CREATE TABLE + migrateSchemaDrift DDL).
|
|
562
|
+
// Reuses the same lock-holding client/session; still idempotent
|
|
563
|
+
// (IF NOT EXISTS everywhere) but no longer racy across processes.
|
|
564
|
+
await initAgentTables(client)
|
|
516
565
|
} finally {
|
|
517
566
|
await client.query(`SELECT pg_advisory_unlock(${BOOTSTRAP_LOCK_KEY})`)
|
|
518
567
|
}
|
|
519
568
|
} finally {
|
|
520
569
|
client.release()
|
|
521
570
|
}
|
|
522
|
-
// Agent-specific analytic tables — idempotent, no advisory lock needed.
|
|
523
|
-
await initAgentTables(db)
|
|
524
571
|
|
|
525
572
|
dbs.set(key, db)
|
|
526
573
|
|
|
@@ -557,7 +604,7 @@ export async function openTraceDatabase(dataDir) {
|
|
|
557
604
|
// ---------------------------------------------------------------------------
|
|
558
605
|
|
|
559
606
|
const TRACE_COLS = [
|
|
560
|
-
'ts', 'session_id', 'iteration', 'kind', '
|
|
607
|
+
'ts', 'session_id', 'iteration', 'kind', 'agent', 'model',
|
|
561
608
|
'tool_name', 'tool_ms', 'input_tokens', 'output_tokens',
|
|
562
609
|
'cached_tokens', 'cache_write_tokens', 'duration_ms',
|
|
563
610
|
'error_message', 'payload', 'parent_span_id', 'entry_id',
|
|
@@ -755,7 +802,7 @@ export async function insertTraceEvents(db, events) {
|
|
|
755
802
|
ev.session_id ?? null,
|
|
756
803
|
ev.iteration != null ? Number(ev.iteration) : null,
|
|
757
804
|
String(ev.kind ?? 'unknown'),
|
|
758
|
-
ev.
|
|
805
|
+
ev.agent ?? null,
|
|
759
806
|
ev.model ?? null,
|
|
760
807
|
ev.tool_name ?? null,
|
|
761
808
|
ev.tool_ms != null ? Number(ev.tool_ms) : null,
|
|
@@ -8,12 +8,12 @@ export const TOOL_DEFS = [
|
|
|
8
8
|
name: 'memory',
|
|
9
9
|
title: 'Memory Cycle',
|
|
10
10
|
annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
11
|
-
description: 'Memory status/lifecycle or explicit mutation. Use recall for retrieval. Destructive jobs require exact confirm strings.',
|
|
11
|
+
description: 'Memory status/lifecycle or explicit mutation. Use recall for retrieval. When the user explicitly asks to remember something (e.g. "remember this", a stated preference, or a decision), persist it immediately with action:"core" op:"add" — do not wait for a cycle. Destructive jobs require exact confirm strings.',
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
15
15
|
action: { type: 'string', enum: ['core','manage','status','sleep','cycle1','cycle2','cycle3','flush','backfill','prune','rebuild','purge','retro_eval_active'], description: 'Operation.' },
|
|
16
|
-
op: { type: 'string', enum: ['add','edit','delete','list'], description: 'Mutation op.' },
|
|
16
|
+
op: { type: 'string', enum: ['add','edit','delete','list','candidates','promote','dismiss'], description: 'Mutation op. candidates/promote/dismiss drive core-memory proposal approval.' },
|
|
17
17
|
id: { type: 'number', description: 'Exact memory id.' },
|
|
18
18
|
element: { type: 'string', description: 'Memory key/title.' },
|
|
19
19
|
summary: { type: 'string', description: 'Memory content.' },
|
|
@@ -38,13 +38,13 @@ export const TOOL_DEFS = [
|
|
|
38
38
|
name: 'recall',
|
|
39
39
|
title: 'Recall',
|
|
40
40
|
annotations: { title: 'Recall', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
41
|
-
description: 'Retrieve stored memory/session history. Use for earlier work, resumes, or possibly decided items; when in doubt, recall first.',
|
|
41
|
+
description: 'Retrieve stored memory/session history. Use for earlier work, resumes, or possibly decided items; when in doubt, recall first. For "what did we just discuss": pass sessionId with no query to browse the current session (includes in-progress content); period:"last" without sessionId browses the previous completed session only.',
|
|
42
42
|
inputSchema: {
|
|
43
43
|
type: 'object',
|
|
44
44
|
properties: {
|
|
45
45
|
query: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Search text, or array for independent fan-out queries.' },
|
|
46
46
|
id: { anyOf: [{ type: 'number' }, { type: 'array', items: { type: 'number' }, minItems: 1 }], description: 'Exact #id(s) from recall. Do not invent ids.' },
|
|
47
|
-
period: { type: 'string', description:
|
|
47
|
+
period: { type: 'string', description: "last (previous completed session; pair with sessionId instead to include the current one), 24h/7d/30d, all, date, or range." },
|
|
48
48
|
limit: { type: 'number', description: 'Max entries.' },
|
|
49
49
|
offset: { type: 'number', description: 'Skip entries.' },
|
|
50
50
|
sort: { type: 'string', enum: ['importance', 'date'], description: 'importance or date.' },
|
|
@@ -52,7 +52,9 @@ export const TOOL_DEFS = [
|
|
|
52
52
|
includeArchived: { type: 'boolean', description: 'Include archived entries.' },
|
|
53
53
|
sessionId: { type: 'string', description: 'Scoped session id.' },
|
|
54
54
|
session_id: { type: 'string', description: 'Alias for sessionId.' },
|
|
55
|
-
includeMembers: { type: 'boolean', description: 'Include chunk members.' },
|
|
55
|
+
includeMembers: { type: 'boolean', description: 'Include chunk members in output; does not widen the search pool.' },
|
|
56
|
+
includeRaw: { type: 'boolean', description: 'Include unchunked raw/episode rows.' },
|
|
57
|
+
sessionOnly: { type: 'boolean', description: 'Search this session only.' },
|
|
56
58
|
projectScope: { type: 'string', description: 'Project pool selector: inferred from cwd, common, all, or slug.' },
|
|
57
59
|
cwd: { type: 'string', description: 'Infer projectScope.' },
|
|
58
60
|
},
|
|
@@ -74,8 +76,9 @@ export const TOOL_DEFS = [
|
|
|
74
76
|
category: { anyOf: [{ type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, { type: 'array', items: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, minItems: 1 }], description: 'Category filter.' },
|
|
75
77
|
limit: { type: 'number', default: 30, description: 'Max entries.' },
|
|
76
78
|
offset: { type: 'number', default: 0, description: 'Skip entries.' },
|
|
77
|
-
includeMembers: { type: 'boolean', description: 'Include members.' },
|
|
78
|
-
includeRaw: { type: 'boolean', description: 'Include raw rows.' },
|
|
79
|
+
includeMembers: { type: 'boolean', description: 'Include chunk members in output; does not widen the search pool.' },
|
|
80
|
+
includeRaw: { type: 'boolean', description: 'Include unchunked raw/episode rows.' },
|
|
81
|
+
sessionOnly: { type: 'boolean', description: 'Search this session only.' },
|
|
79
82
|
includeArchived: { type: 'boolean', description: 'Include archived.' },
|
|
80
83
|
sessionId: { type: 'string', description: 'Scoped session id.' },
|
|
81
84
|
session_id: { type: 'string', description: 'Alias for sessionId.' },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AbortController helpers
|
|
2
|
+
* AbortController helpers.
|
|
3
3
|
*
|
|
4
4
|
* `createAbortController()` raises the signal's max listener cap so long-running
|
|
5
5
|
* sessions with many per-iteration handlers don't trip Node's default warning.
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
normalizeToolNotifyContext,
|
|
6
6
|
notifyToolCompletion,
|
|
7
7
|
} from './tool-execution-contract.mjs';
|
|
8
|
-
import { presentErrorText } from './err-text.mjs';
|
|
8
|
+
import { presentErrorText, errorLine } from './err-text.mjs';
|
|
9
9
|
|
|
10
10
|
export {
|
|
11
11
|
TOOL_ASYNC_EXECUTION_CONTRACT,
|
|
@@ -465,8 +465,7 @@ export function renderBackgroundTask(taskOrId, { includeResult = false } = {}) {
|
|
|
465
465
|
if (body) {
|
|
466
466
|
lines.push('', body);
|
|
467
467
|
} else if (TERMINAL_STATUSES.has(task.status) && task.error) {
|
|
468
|
-
|
|
469
|
-
lines.push('', /^error\s*:/i.test(errorText) ? errorText : `Error: ${errorText}`);
|
|
468
|
+
lines.push('', errorLine(task.error, { surface: task.surface }));
|
|
470
469
|
} else if (TERMINAL_STATUSES.has(task.status) && task.status === 'completed') {
|
|
471
470
|
// Terminal-completed task with no extractable body: surface a placeholder
|
|
472
471
|
// instead of silently omitting the result so the owner isn't left with a
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-file buffered append queue.
|
|
3
|
+
*
|
|
4
|
+
* Hot paths (transcript writer, channel worker stderr log, etc.) previously
|
|
5
|
+
* called appendFileSync per entry/chunk, which blocks the event loop on
|
|
6
|
+
* every write. This module coalesces writes per path into an in-memory
|
|
7
|
+
* buffer and flushes asynchronously via fs.promises.appendFile once the
|
|
8
|
+
* buffer reaches BUFFER_FLUSH_BYTES or after DEBOUNCE_MS of inactivity.
|
|
9
|
+
* Flushes are serialized per path so ordering is preserved. On process
|
|
10
|
+
* exit, any remaining buffered bytes for every path are drained with one
|
|
11
|
+
* synchronous appendFileSync call each (best-effort, matching the
|
|
12
|
+
* agent-trace.mjs local spool exit-drain pattern).
|
|
13
|
+
*/
|
|
14
|
+
import { appendFileSync } from 'node:fs';
|
|
15
|
+
import { appendFile } from 'node:fs/promises';
|
|
16
|
+
|
|
17
|
+
const BUFFER_FLUSH_BYTES = 32 * 1024;
|
|
18
|
+
const DEBOUNCE_MS = 50;
|
|
19
|
+
|
|
20
|
+
// path -> { chunks: string[], bytes: number, timer, flushing, inFlight, failCount }
|
|
21
|
+
const queues = new Map();
|
|
22
|
+
|
|
23
|
+
function getQueue(path) {
|
|
24
|
+
let q = queues.get(path);
|
|
25
|
+
if (!q) {
|
|
26
|
+
q = { chunks: [], bytes: 0, timer: null, flushing: false, inFlight: null, failCount: 0 };
|
|
27
|
+
queues.set(path, q);
|
|
28
|
+
}
|
|
29
|
+
return q;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function scheduleFlush(path, q) {
|
|
33
|
+
if (q.timer) return;
|
|
34
|
+
q.timer = setTimeout(() => {
|
|
35
|
+
q.timer = null;
|
|
36
|
+
_flush(path, q);
|
|
37
|
+
}, DEBOUNCE_MS);
|
|
38
|
+
q.timer.unref?.();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function _flush(path, q) {
|
|
42
|
+
if (q.flushing) return;
|
|
43
|
+
if (q.chunks.length === 0) return;
|
|
44
|
+
const data = q.chunks.join('');
|
|
45
|
+
q.chunks = [];
|
|
46
|
+
q.bytes = 0;
|
|
47
|
+
// Hold the in-flight payload until the write settles so an exit-time
|
|
48
|
+
// sync drain can still recover it if it fires mid-write (fix: exit
|
|
49
|
+
// drain must not miss data that was dequeued but not yet persisted).
|
|
50
|
+
q.inFlight = data;
|
|
51
|
+
q.flushing = true;
|
|
52
|
+
appendFile(path, data, 'utf8')
|
|
53
|
+
.then(() => {
|
|
54
|
+
q.failCount = 0;
|
|
55
|
+
})
|
|
56
|
+
.catch(() => {
|
|
57
|
+
// Requeue the failed data so a later flush/exit-drain retries
|
|
58
|
+
// once, instead of silently dropping. Cap retries so a
|
|
59
|
+
// permanently broken path (e.g. deleted dir) cannot loop
|
|
60
|
+
// forever accumulating the same failing chunk.
|
|
61
|
+
q.failCount += 1;
|
|
62
|
+
if (q.failCount <= 3) {
|
|
63
|
+
q.chunks.unshift(data);
|
|
64
|
+
q.bytes += Buffer.byteLength(data, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.finally(() => {
|
|
68
|
+
q.inFlight = null;
|
|
69
|
+
q.flushing = false;
|
|
70
|
+
if (q.chunks.length > 0) {
|
|
71
|
+
if (q.bytes >= BUFFER_FLUSH_BYTES) _flush(path, q);
|
|
72
|
+
else scheduleFlush(path, q);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Queue text for buffered append to `path`. Flushes async once the buffer
|
|
79
|
+
* for that path crosses BUFFER_FLUSH_BYTES, or after DEBOUNCE_MS of
|
|
80
|
+
* inactivity, whichever comes first.
|
|
81
|
+
*/
|
|
82
|
+
export function appendBuffered(path, text) {
|
|
83
|
+
if (!path || !text) return;
|
|
84
|
+
const q = getQueue(path);
|
|
85
|
+
q.chunks.push(text);
|
|
86
|
+
q.bytes += Buffer.byteLength(text, 'utf8');
|
|
87
|
+
if (q.bytes >= BUFFER_FLUSH_BYTES) {
|
|
88
|
+
if (q.timer) { clearTimeout(q.timer); q.timer = null; }
|
|
89
|
+
_flush(path, q);
|
|
90
|
+
} else {
|
|
91
|
+
scheduleFlush(path, q);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Synchronously drain any remaining buffered bytes for every known path.
|
|
97
|
+
* Intended for process exit hooks only — best-effort, never throws.
|
|
98
|
+
*/
|
|
99
|
+
export function drainAllSync() {
|
|
100
|
+
for (const [path, q] of queues) {
|
|
101
|
+
drainPathSync(path);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Synchronously drain the queue for a single path. Writes any in-flight
|
|
107
|
+
* (already-dequeued but not yet persisted) payload FIRST, then any
|
|
108
|
+
* still-queued chunks, so nothing buffered is lost. Best-effort, never
|
|
109
|
+
* throws.
|
|
110
|
+
*/
|
|
111
|
+
export function drainPathSync(path) {
|
|
112
|
+
const q = queues.get(path);
|
|
113
|
+
if (!q) return;
|
|
114
|
+
if (q.timer) { clearTimeout(q.timer); q.timer = null; }
|
|
115
|
+
if (q.inFlight) {
|
|
116
|
+
try {
|
|
117
|
+
appendFileSync(path, q.inFlight, 'utf8');
|
|
118
|
+
} catch {
|
|
119
|
+
// Best-effort; nothing to recover from here.
|
|
120
|
+
}
|
|
121
|
+
q.inFlight = null;
|
|
122
|
+
}
|
|
123
|
+
if (q.chunks.length === 0) return;
|
|
124
|
+
const data = q.chunks.join('');
|
|
125
|
+
q.chunks = [];
|
|
126
|
+
q.bytes = 0;
|
|
127
|
+
try {
|
|
128
|
+
appendFileSync(path, data, 'utf8');
|
|
129
|
+
} catch {
|
|
130
|
+
// Best-effort exit drain; nothing to recover from here.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* True if a path currently has an async write in flight. Callers that need
|
|
136
|
+
* to rename/rotate the underlying file (which would race a concurrent
|
|
137
|
+
* appendFile on some platforms, notably Windows) should check this and
|
|
138
|
+
* defer the rename to a later tick rather than racing it.
|
|
139
|
+
*/
|
|
140
|
+
export function hasInFlightWrite(path) {
|
|
141
|
+
const q = queues.get(path);
|
|
142
|
+
return Boolean(q && q.inFlight);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
process.on('exit', drainAllSync);
|
|
147
|
+
} catch {
|
|
148
|
+
// Ignore lifecycle hook failures in embedded runtimes.
|
|
149
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function channelNotificationModelContent(params = {}) {
|
|
2
|
+
const meta = params?.meta && typeof params.meta === 'object' ? params.meta : {};
|
|
3
|
+
if (meta.silent_to_agent === true || meta.silent_to_agent === 'true') return '';
|
|
4
|
+
const instruction = typeof meta.instruction === 'string' ? meta.instruction.trim() : '';
|
|
5
|
+
const content = String(params?.content || '').trim();
|
|
6
|
+
return instruction || content;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function shouldMirrorChannelNotificationToPending(meta = {}) {
|
|
10
|
+
const type = String(meta?.type || '').trim().toLowerCase();
|
|
11
|
+
return type === 'schedule';
|
|
12
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
channelNotificationModelContent,
|
|
5
|
+
shouldMirrorChannelNotificationToPending,
|
|
6
|
+
} from './channel-notification-routing.mjs';
|
|
7
|
+
|
|
8
|
+
test('channel schedule notification uses instruction as model content', () => {
|
|
9
|
+
const content = channelNotificationModelContent({
|
|
10
|
+
content: ' ',
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'schedule',
|
|
13
|
+
instruction: 'scheduled prompt body',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
assert.equal(content, 'scheduled prompt body');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('silent channel notification has no model content', () => {
|
|
20
|
+
const content = channelNotificationModelContent({
|
|
21
|
+
content: 'hidden',
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'schedule',
|
|
24
|
+
silent_to_agent: true,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
assert.equal(content, '');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('non-schedule notification preserves instruction-first routing', () => {
|
|
31
|
+
const content = channelNotificationModelContent({
|
|
32
|
+
content: 'worker result body',
|
|
33
|
+
meta: {
|
|
34
|
+
type: 'webhook',
|
|
35
|
+
instruction: 'relay instruction',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
assert.equal(content, 'relay instruction');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('only schedule channel notifications are mirrored to pending fallback', () => {
|
|
42
|
+
assert.equal(shouldMirrorChannelNotificationToPending({ type: 'schedule' }), true);
|
|
43
|
+
assert.equal(shouldMirrorChannelNotificationToPending({ type: 'webhook' }), false);
|
|
44
|
+
assert.equal(shouldMirrorChannelNotificationToPending({}), false);
|
|
45
|
+
});
|
|
@@ -246,6 +246,7 @@ export function getCapabilities() {
|
|
|
246
246
|
// the migration logic in seed.mjs.
|
|
247
247
|
export const SECRET_ACCOUNTS = Object.freeze({
|
|
248
248
|
discordToken: 'discord.token',
|
|
249
|
+
telegramToken: 'telegram.token',
|
|
249
250
|
webhookAuth: 'webhook.authtoken',
|
|
250
251
|
agentApiKey: (provider) => `agent.${provider}.apiKey`,
|
|
251
252
|
openaiUsageSessionKey: 'agent.openai.usageSessionKey',
|
|
@@ -293,6 +294,14 @@ export function getDiscordToken() {
|
|
|
293
294
|
return _readSecret(SECRET_ACCOUNTS.discordToken)
|
|
294
295
|
}
|
|
295
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Returns the Telegram bot token.
|
|
299
|
+
* Priority: MIXDOG_TELEGRAM_TOKEN → keychain('telegram.token') → null
|
|
300
|
+
*/
|
|
301
|
+
export function getTelegramToken() {
|
|
302
|
+
return _readSecret(SECRET_ACCOUNTS.telegramToken)
|
|
303
|
+
}
|
|
304
|
+
|
|
296
305
|
/**
|
|
297
306
|
* Returns the ngrok/webhook authtoken.
|
|
298
307
|
* Priority: MIXDOG_WEBHOOK_AUTHTOKEN → keychain('webhook.authtoken') → null
|