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
|
@@ -7,10 +7,16 @@ import {
|
|
|
7
7
|
_compactTriggerForSession as compactTriggerForSession,
|
|
8
8
|
_preserveBufferConfigFields as preserveBufferConfigFields,
|
|
9
9
|
} from '../src/runtime/agent/orchestrator/session/manager.mjs';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
_autoCompactTokenLimit as routeMetaAutoCompactTokenLimit,
|
|
12
|
+
autoCompactWindowForRoute,
|
|
13
|
+
compactBoundaryDenominator,
|
|
14
|
+
summarizeGatewayUsage,
|
|
15
|
+
} from '../src/vendor/statusline/src/gateway/route-meta.mjs';
|
|
11
16
|
import {
|
|
12
17
|
_routeContextMeta as routeContextMeta,
|
|
13
18
|
_resolveStatusAutoCompactTokenLimit as resolveStatusAutoCompactTokenLimit,
|
|
19
|
+
compactBoundaryForStatus,
|
|
14
20
|
} from '../src/vendor/statusline/bin/statusline-route.mjs';
|
|
15
21
|
|
|
16
22
|
function assert(condition, message) {
|
|
@@ -184,4 +190,64 @@ function assert(condition, message) {
|
|
|
184
190
|
`status: configured explicit limit should surface, got ${configuredExplicit}`);
|
|
185
191
|
}
|
|
186
192
|
|
|
193
|
+
// 10) Boundary / window denominators ignore autoCompactTokenLimit (trigger-only).
|
|
194
|
+
{
|
|
195
|
+
const boundary = 200000;
|
|
196
|
+
const trigger = 150000;
|
|
197
|
+
const route = {
|
|
198
|
+
contextWindow: boundary,
|
|
199
|
+
compactBoundaryTokens: boundary,
|
|
200
|
+
rawContextWindow: boundary,
|
|
201
|
+
autoCompactTokenLimit: trigger,
|
|
202
|
+
};
|
|
203
|
+
assert(compactBoundaryForStatus(route) === boundary,
|
|
204
|
+
`status boundary should be ${boundary}, got ${compactBoundaryForStatus(route)}`);
|
|
205
|
+
assert(compactBoundaryForStatus({ contextWindow: boundary, autoCompactTokenLimit: trigger }) === boundary,
|
|
206
|
+
`status boundary should use contextWindow ${boundary}, got ${compactBoundaryForStatus({ contextWindow: boundary, autoCompactTokenLimit: trigger })}`);
|
|
207
|
+
assert(autoCompactWindowForRoute({ contextWindow: boundary, autoCompactTokenLimit: trigger }) === boundary,
|
|
208
|
+
`route compact window should be ${boundary}, got ${autoCompactWindowForRoute({ contextWindow: boundary, autoCompactTokenLimit: trigger })}`);
|
|
209
|
+
const trig = compactTriggerForSession({ autoCompactTokenLimit: trigger, compaction: {} }, boundary);
|
|
210
|
+
assert(trig === trigger, `trigger resolver should stay ${trigger}, got ${trig}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 11) Boundary fallback order and compact boundary fields.
|
|
214
|
+
{
|
|
215
|
+
const context = 200000;
|
|
216
|
+
const smallerBudget = 150000;
|
|
217
|
+
assert(
|
|
218
|
+
compactBoundaryForStatus({ contextWindow: context }, { budgetWindow: smallerBudget, compactLimitTokens: smallerBudget }) === context,
|
|
219
|
+
`contextWindow ${context} must win over smaller budgetWindow ${smallerBudget}`,
|
|
220
|
+
);
|
|
221
|
+
assert(
|
|
222
|
+
compactBoundaryDenominator({ contextWindow: context }, { budgetWindow: smallerBudget }) === context,
|
|
223
|
+
`route denominator: contextWindow must win over budgetWindow`,
|
|
224
|
+
);
|
|
225
|
+
assert(
|
|
226
|
+
compactBoundaryForStatus({ contextWindow: context }, { boundaryTokens: 180000 }) === 180000,
|
|
227
|
+
'compact.boundaryTokens should be honored',
|
|
228
|
+
);
|
|
229
|
+
assert(
|
|
230
|
+
compactBoundaryForStatus({ contextWindow: context }, { compactBoundaryTokens: 190000 }) === 190000,
|
|
231
|
+
'compact.compactBoundaryTokens should be honored',
|
|
232
|
+
);
|
|
233
|
+
assert(
|
|
234
|
+
compactBoundaryForStatus({ boundaryTokens: 175000, contextWindow: context }, { budgetWindow: smallerBudget }) === 175000,
|
|
235
|
+
'routeInfo.boundaryTokens should be honored',
|
|
236
|
+
);
|
|
237
|
+
const withLimitOnly = compactBoundaryForStatus(
|
|
238
|
+
{ contextWindow: context, autoCompactTokenLimit: smallerBudget },
|
|
239
|
+
{ compactLimitTokens: smallerBudget, budgetWindow: smallerBudget },
|
|
240
|
+
);
|
|
241
|
+
assert(withLimitOnly === context,
|
|
242
|
+
`compactLimitTokens/budget must not shrink boundary; expected ${context}, got ${withLimitOnly}`);
|
|
243
|
+
const usage = summarizeGatewayUsage(
|
|
244
|
+
{ provider: 'openai-oauth', model: 'gpt-5.5', contextWindow: context },
|
|
245
|
+
{ usage: { inputTokens: 1, outputTokens: 1 } },
|
|
246
|
+
{ boundaryTokens: 180000, budgetWindow: smallerBudget, compactLimitTokens: smallerBudget, afterTokens: 90000 },
|
|
247
|
+
1,
|
|
248
|
+
);
|
|
249
|
+
assert(usage.contextUsedPct === 50,
|
|
250
|
+
`gateway usage pct should use compact.boundaryTokens denominator 180000; got ${usage.contextUsedPct}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
187
253
|
process.stdout.write('compact-trigger-migration smoke passed ✓\n');
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Explore quality bench — round-based tuning harness.
|
|
2
|
+
//
|
|
3
|
+
// Runs a fixed query set through runExplore against this repo, then reads
|
|
4
|
+
// agent-trace.jsonl to report per-session tool-call counts, wall time, and
|
|
5
|
+
// anchor quality. Usage:
|
|
6
|
+
// node scripts/explore-bench.mjs [roundLabel]
|
|
7
|
+
// Fresh process per run = no in-memory result cache carryover.
|
|
8
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { runExplore } from '../src/standalone/explore-tool.mjs';
|
|
12
|
+
|
|
13
|
+
const ROUND = process.argv[2] || 'r0';
|
|
14
|
+
const CWD = 'C:/Project/mixdog';
|
|
15
|
+
|
|
16
|
+
// Representative locator queries with ground truth: expect = substrings, at
|
|
17
|
+
// least one must appear in the anchor output (quality hit). expectFail = the
|
|
18
|
+
// correct answer is EXPLORATION_FAILED (miss discipline).
|
|
19
|
+
const QUERIES = [
|
|
20
|
+
{
|
|
21
|
+
q: 'where is the tool-call stall watchdog policy resolved and what defaults does it use',
|
|
22
|
+
expect: ['agent-progress-watchdog', 'stall-policy'],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
q: 'session compact / smart compact trigger threshold logic',
|
|
26
|
+
expect: ['compact'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
q: 'where are hidden agent tool schema profiles defined (read / read-write-search)',
|
|
30
|
+
expect: ['agent-dispatch', 'agents.json', 'internal-agents'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
q: 'recall memory chunk importance scoring implementation',
|
|
34
|
+
expect: ['importance', 'memory'],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
q: 'websocket reconnect backoff handling',
|
|
38
|
+
expect: ['openai-oauth-ws', 'retry-classifier'],
|
|
39
|
+
},
|
|
40
|
+
// ── extended set: symbol-exact, filename, error-string, korean, flow, config ──
|
|
41
|
+
{
|
|
42
|
+
q: 'buildExplorerPrompt definition', // exact known symbol
|
|
43
|
+
expect: ['explore-tool'],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
q: 'where does the explore tool cache results and what is the TTL',
|
|
47
|
+
expect: ['explore-tool'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
q: 'error text "maintenance route unresolved for agent" — where is it thrown',
|
|
51
|
+
expect: ['agent-dispatch'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
q: '에이전트 세션의 최대 루프 반복 횟수는 어디서 정해져?', // korean concept query
|
|
55
|
+
expect: ['agent-loop-policy'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
q: 'how does a fixed agent slot map to a workflow slot (explore/explorer)',
|
|
59
|
+
expect: ['mixdog-session-runtime', 'internal-agents'],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
q: 'where is the mixdog config json file path resolved (data dir)',
|
|
63
|
+
expect: ['config', 'plugin-paths'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
q: 'GraphQL schema stitching resolver implementation', // not in this repo
|
|
67
|
+
expectFail: true,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
function tracePath() {
|
|
72
|
+
return process.env.MIXDOG_AGENT_TRACE_PATH
|
|
73
|
+
|| join(process.env.MIXDOG_DATA_DIR || join(process.env.MIXDOG_HOME || join(homedir(), '.mixdog'), 'data'), 'history', 'agent-trace.jsonl');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readTraceSince(ts) {
|
|
77
|
+
const p = tracePath();
|
|
78
|
+
if (!existsSync(p)) return [];
|
|
79
|
+
const rows = [];
|
|
80
|
+
for (const line of readFileSync(p, 'utf8').split('\n')) {
|
|
81
|
+
if (!line) continue;
|
|
82
|
+
try {
|
|
83
|
+
const r = JSON.parse(line);
|
|
84
|
+
if (r.ts >= ts && r.agent === 'explorer') rows.push(r);
|
|
85
|
+
} catch { /* skip */ }
|
|
86
|
+
}
|
|
87
|
+
return rows;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const t0 = Date.now();
|
|
91
|
+
const results = [];
|
|
92
|
+
for (const { q, expect, expectFail } of QUERIES) {
|
|
93
|
+
const qt0 = Date.now();
|
|
94
|
+
const res = await runExplore({ query: q, cwd: CWD }, { callerCwd: CWD });
|
|
95
|
+
const text = res?.content?.[0]?.text || '';
|
|
96
|
+
const anchors = text.split('\n').filter((l) => /:\d+/.test(l)).length;
|
|
97
|
+
const failed = /EXPLORATION_FAILED/.test(text);
|
|
98
|
+
const lower = text.toLowerCase();
|
|
99
|
+
const hit = expectFail
|
|
100
|
+
? (failed && anchors === 0)
|
|
101
|
+
: (!failed && (expect || []).some((e) => lower.includes(e.toLowerCase())));
|
|
102
|
+
results.push({ q: q.slice(0, 48), ms: Date.now() - qt0, anchors, failed, hit, bytes: text.length });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Attribute tool calls per explorer session spawned during this run.
|
|
106
|
+
const rows = readTraceSince(t0);
|
|
107
|
+
const bySession = new Map();
|
|
108
|
+
for (const r of rows) {
|
|
109
|
+
if (r.kind !== 'tool') continue;
|
|
110
|
+
const s = bySession.get(r.session_id) || { calls: 0, tools: [] };
|
|
111
|
+
s.calls++; s.tools.push(r.tool_name);
|
|
112
|
+
bySession.set(r.session_id, s);
|
|
113
|
+
}
|
|
114
|
+
const callCounts = [...bySession.values()].map((s) => s.calls).sort((a, b) => a - b);
|
|
115
|
+
const p50 = callCounts[Math.floor(callCounts.length / 2)] ?? 0;
|
|
116
|
+
|
|
117
|
+
console.log(`\n=== explore-bench round=${ROUND} ===`);
|
|
118
|
+
for (const r of results) {
|
|
119
|
+
console.log(` [${String(r.ms).padStart(6)}ms] ${r.hit ? 'HIT ' : 'MISS'} anchors=${r.anchors} failed=${r.failed} bytes=${r.bytes} ${r.q}`);
|
|
120
|
+
}
|
|
121
|
+
console.log(` quality: ${results.filter((r) => r.hit).length}/${results.length} hits`);
|
|
122
|
+
console.log(` sessions=${bySession.size} toolcalls p50=${p50} max=${callCounts[callCounts.length - 1] ?? 0} total=${callCounts.reduce((a, b) => a + b, 0)}`);
|
|
123
|
+
for (const [id, s] of bySession) console.log(` ${id.slice(-8)}: ${s.tools.join(',')}`);
|
|
124
|
+
console.log(` wall total=${((Date.now() - t0) / 1000).toFixed(1)}s`);
|
|
@@ -328,3 +328,194 @@ console.log(JSON.stringify({ hookSpecificOutput: {
|
|
|
328
328
|
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
329
329
|
}
|
|
330
330
|
});
|
|
331
|
+
|
|
332
|
+
test('mcp_tool handler routes output through parseHandlerOutput', async () => {
|
|
333
|
+
const root = tempRoot();
|
|
334
|
+
const hooksFile = join(root, 'hooks.json');
|
|
335
|
+
writeJson(hooksFile, {
|
|
336
|
+
hooks: {
|
|
337
|
+
PreToolUse: [{
|
|
338
|
+
matcher: 'shell',
|
|
339
|
+
hooks: [{ type: 'mcp_tool', server: 'guard', tool: 'check' }],
|
|
340
|
+
}],
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
344
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
345
|
+
try {
|
|
346
|
+
let seenName = null;
|
|
347
|
+
const bus = createStandaloneHookBus({
|
|
348
|
+
dataDir: root,
|
|
349
|
+
mcpToolRunner: async ({ name }) => {
|
|
350
|
+
seenName = name;
|
|
351
|
+
return JSON.stringify({ hookSpecificOutput: {
|
|
352
|
+
hookEventName: 'PreToolUse',
|
|
353
|
+
permissionDecision: 'deny',
|
|
354
|
+
permissionDecisionReason: 'mcp denied',
|
|
355
|
+
}});
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
const denied = await bus.beforeTool({ sessionId: 's', cwd: root, name: 'shell', args: { command: 'x' } });
|
|
359
|
+
assert.equal(seenName, 'mcp__guard__check');
|
|
360
|
+
assert.equal(denied.action, 'deny');
|
|
361
|
+
assert.match(denied.reason, /mcp denied/);
|
|
362
|
+
} finally {
|
|
363
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
364
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test('prompt handler ok:false maps to deny', async () => {
|
|
369
|
+
const root = tempRoot();
|
|
370
|
+
const hooksFile = join(root, 'hooks.json');
|
|
371
|
+
writeJson(hooksFile, {
|
|
372
|
+
hooks: {
|
|
373
|
+
PreToolUse: [{
|
|
374
|
+
matcher: 'shell',
|
|
375
|
+
hooks: [{ type: 'prompt', prompt: 'is this safe?' }],
|
|
376
|
+
}],
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
380
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
381
|
+
try {
|
|
382
|
+
const bus = createStandaloneHookBus({
|
|
383
|
+
dataDir: root,
|
|
384
|
+
promptRunner: async () => JSON.stringify({ ok: false, reason: 'unsafe' }),
|
|
385
|
+
});
|
|
386
|
+
const denied = await bus.beforeTool({ sessionId: 's', cwd: root, name: 'shell', args: { command: 'x' } });
|
|
387
|
+
assert.equal(denied.action, 'deny');
|
|
388
|
+
assert.match(denied.reason, /unsafe/);
|
|
389
|
+
} finally {
|
|
390
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
391
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('unconfigured prompt/mcp_tool and unsupported agent emit hook:error and skip', async () => {
|
|
396
|
+
const root = tempRoot();
|
|
397
|
+
const hooksFile = join(root, 'hooks.json');
|
|
398
|
+
writeJson(hooksFile, {
|
|
399
|
+
hooks: {
|
|
400
|
+
PreToolUse: [{
|
|
401
|
+
matcher: 'shell',
|
|
402
|
+
hooks: [
|
|
403
|
+
{ type: 'prompt', prompt: 'x' },
|
|
404
|
+
{ type: 'mcp_tool', server: 'g', tool: 't' },
|
|
405
|
+
{ type: 'agent', agent: 'a' },
|
|
406
|
+
],
|
|
407
|
+
}],
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
411
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
412
|
+
try {
|
|
413
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
414
|
+
const result = await bus.beforeTool({ sessionId: 's', cwd: root, name: 'shell', args: { command: 'x' } });
|
|
415
|
+
assert.equal(result, null);
|
|
416
|
+
const errs = bus.status().recent.filter((e) => e.name === 'hook:error');
|
|
417
|
+
assert.ok(errs.some((e) => /prompt not configured/.test(e.payload?.error || '')));
|
|
418
|
+
assert.ok(errs.some((e) => /mcp_tool not configured/.test(e.payload?.error || '')));
|
|
419
|
+
assert.ok(errs.some((e) => /unsupported hook type: agent/.test(e.payload?.error || '')));
|
|
420
|
+
} finally {
|
|
421
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
422
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('setRewakeHandler fires on async child exit code 2', async () => {
|
|
427
|
+
const root = tempRoot();
|
|
428
|
+
const hookScript = join(root, 'rewake.mjs');
|
|
429
|
+
writeFileSync(hookScript, `process.stderr.write('needs rewake'); process.exit(2);`, 'utf8');
|
|
430
|
+
const hooksFile = join(root, 'hooks.json');
|
|
431
|
+
writeJson(hooksFile, {
|
|
432
|
+
hooks: {
|
|
433
|
+
Stop: [{
|
|
434
|
+
hooks: [{ type: 'command', command: process.execPath, args: [hookScript], asyncRewake: true }],
|
|
435
|
+
}],
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
439
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
440
|
+
try {
|
|
441
|
+
const bus = createStandaloneHookBus({ dataDir: root });
|
|
442
|
+
const fired = new Promise((resolve) => bus.setRewakeHandler((info) => resolve(info)));
|
|
443
|
+
await bus.dispatch('Stop', { session_id: 's', cwd: root });
|
|
444
|
+
const info = await fired;
|
|
445
|
+
assert.equal(info.eventName, 'Stop');
|
|
446
|
+
assert.match(info.text, /needs rewake/);
|
|
447
|
+
} finally {
|
|
448
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
449
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test('prompt handler plain-text "no" denies', async () => {
|
|
454
|
+
const root = tempRoot();
|
|
455
|
+
const hooksFile = join(root, 'hooks.json');
|
|
456
|
+
writeJson(hooksFile, {
|
|
457
|
+
hooks: {
|
|
458
|
+
PreToolUse: [{ matcher: 'shell', hooks: [{ type: 'prompt', prompt: 'safe?' }] }],
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
462
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
463
|
+
try {
|
|
464
|
+
const bus = createStandaloneHookBus({ dataDir: root, promptRunner: async () => 'no' });
|
|
465
|
+
const denied = await bus.beforeTool({ sessionId: 's', cwd: root, name: 'shell', args: { command: 'x' } });
|
|
466
|
+
assert.equal(denied.action, 'deny');
|
|
467
|
+
assert.match(denied.reason, /no/i);
|
|
468
|
+
} finally {
|
|
469
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
470
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('prompt handler timeout does not hang and reports error', async () => {
|
|
475
|
+
const root = tempRoot();
|
|
476
|
+
const hooksFile = join(root, 'hooks.json');
|
|
477
|
+
writeJson(hooksFile, {
|
|
478
|
+
hooks: {
|
|
479
|
+
PreToolUse: [{ matcher: 'shell', hooks: [{ type: 'prompt', prompt: 'x', timeout: 0.05 }] }],
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
483
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
484
|
+
try {
|
|
485
|
+
const bus = createStandaloneHookBus({
|
|
486
|
+
dataDir: root,
|
|
487
|
+
promptRunner: () => new Promise(() => {}),
|
|
488
|
+
});
|
|
489
|
+
const result = await bus.beforeTool({ sessionId: 's', cwd: root, name: 'shell', args: { command: 'x' } });
|
|
490
|
+
assert.equal(result, null);
|
|
491
|
+
const errs = bus.status().recent.filter((e) => e.name === 'hook:error');
|
|
492
|
+
assert.ok(errs.some((e) => /timed out/i.test(e.payload?.error || '')));
|
|
493
|
+
} finally {
|
|
494
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
495
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test('prompt deny uses universal exit-2 path on Stop event', async () => {
|
|
500
|
+
const root = tempRoot();
|
|
501
|
+
const hooksFile = join(root, 'hooks.json');
|
|
502
|
+
writeJson(hooksFile, {
|
|
503
|
+
hooks: {
|
|
504
|
+
Stop: [{ hooks: [{ type: 'prompt', prompt: 'done?' }] }],
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
const prev = process.env.MIXDOG_HOOKS_FILE;
|
|
508
|
+
process.env.MIXDOG_HOOKS_FILE = hooksFile;
|
|
509
|
+
try {
|
|
510
|
+
const bus = createStandaloneHookBus({
|
|
511
|
+
dataDir: root,
|
|
512
|
+
promptRunner: async () => JSON.stringify({ ok: false, reason: 'not done' }),
|
|
513
|
+
});
|
|
514
|
+
const result = await bus.dispatch('Stop', { session_id: 's', cwd: root });
|
|
515
|
+
assert.equal(result.blocked, true);
|
|
516
|
+
assert.match(result.reason, /not done/);
|
|
517
|
+
} finally {
|
|
518
|
+
if (prev == null) delete process.env.MIXDOG_HOOKS_FILE;
|
|
519
|
+
else process.env.MIXDOG_HOOKS_FILE = prev;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import {
|
|
4
|
+
normalizeIngestRole,
|
|
5
|
+
sessionMessageContent,
|
|
6
|
+
sessionMessageContentForIngest,
|
|
7
|
+
shouldExcludeIngestMessage,
|
|
8
|
+
} from '../src/runtime/memory/lib/session-ingest.mjs'
|
|
9
|
+
|
|
10
|
+
// cleanMemoryText is the SAME downstream cleaner the real ingest loop applies
|
|
11
|
+
// (memory/index.mjs:795). text-utils.cjs is a dependency-free CJS module, so we
|
|
12
|
+
// require it directly to mirror the pipeline without booting the MCP server.
|
|
13
|
+
const require = createRequire(import.meta.url)
|
|
14
|
+
const { cleanMemoryText } = require('../src/lib/text-utils.cjs')
|
|
15
|
+
|
|
16
|
+
function assert(condition, message) {
|
|
17
|
+
if (!condition) throw new Error(message)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Mirror memory/index.mjs ingestSessionMessages row handling for one message:
|
|
21
|
+
// role gate → exclusion predicate → ingest shaper → cleanMemoryText → trim.
|
|
22
|
+
// Returns the persisted content string, or null when the row is NOT ingested.
|
|
23
|
+
function ingestRow(m) {
|
|
24
|
+
const role = normalizeIngestRole(m.role)
|
|
25
|
+
if (!role) return null
|
|
26
|
+
if (shouldExcludeIngestMessage(m)) return null
|
|
27
|
+
const content = cleanMemoryText(sessionMessageContentForIngest(m))
|
|
28
|
+
if (!content || !content.trim()) return null
|
|
29
|
+
return content
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SUMMARY_PREFIX = '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.'
|
|
33
|
+
|
|
34
|
+
// ── Synthetic session.messages covering every audited case ──────────────────
|
|
35
|
+
// (i) assistant prose + an `agent` toolCall (tool-call trace must be stripped).
|
|
36
|
+
const asstWithTool = {
|
|
37
|
+
role: 'assistant',
|
|
38
|
+
content: 'Here is my plan for the refactor.',
|
|
39
|
+
toolCalls: [{ id: 'call_42', name: 'agent', arguments: '{"type":"send","text":"go"}' }],
|
|
40
|
+
}
|
|
41
|
+
// (ii) the real human turn carrying the full manager-style prefix envelope.
|
|
42
|
+
const sessionBlock = '# Session\nCwd: C:/Project/mixdog\nModel: GPT-5 · HIGH'
|
|
43
|
+
const prefixedUser = {
|
|
44
|
+
role: 'user',
|
|
45
|
+
content:
|
|
46
|
+
`${sessionBlock}\n\n` +
|
|
47
|
+
'# Additional context\nsome context body line one\nline two\n\n' +
|
|
48
|
+
'# Prefetch\nprefetched file snippet\n\n' +
|
|
49
|
+
'# Task\n실제 질문',
|
|
50
|
+
}
|
|
51
|
+
// (iii) Reference files synthetic row.
|
|
52
|
+
const referenceRow = { role: 'user', content: 'Reference files:\n- a.mjs\n- b.mjs' }
|
|
53
|
+
// (iv) compaction summary row.
|
|
54
|
+
const summaryRow = { role: 'user', content: `${SUMMARY_PREFIX}\nmessages=10 sha256=abcd roles=user:5` }
|
|
55
|
+
// (v) protected-context `.` ack assistant row.
|
|
56
|
+
const ackRow = { role: 'assistant', content: '.' }
|
|
57
|
+
// (vi) internal runtime nudge user row.
|
|
58
|
+
const nudgeRow = { role: 'user', content: '[mixdog-runtime] Your previous response was empty. Continue.' }
|
|
59
|
+
// (vii) normal human + normal assistant.
|
|
60
|
+
const normalUser = { role: 'user', content: '안녕' }
|
|
61
|
+
const normalAsst = { role: 'assistant', content: '네 안녕하세요' }
|
|
62
|
+
|
|
63
|
+
const before = {
|
|
64
|
+
asstWithTool: sessionMessageContent(asstWithTool),
|
|
65
|
+
prefixedUser: prefixedUser.content,
|
|
66
|
+
referenceRow: referenceRow.content,
|
|
67
|
+
summaryRow: summaryRow.content,
|
|
68
|
+
ackRow: ackRow.content,
|
|
69
|
+
nudgeRow: nudgeRow.content,
|
|
70
|
+
normalUser: normalUser.content,
|
|
71
|
+
normalAsst: normalAsst.content,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const after = {
|
|
75
|
+
asstWithTool: ingestRow(asstWithTool),
|
|
76
|
+
prefixedUser: ingestRow(prefixedUser),
|
|
77
|
+
referenceRow: ingestRow(referenceRow),
|
|
78
|
+
summaryRow: ingestRow(summaryRow),
|
|
79
|
+
ackRow: ingestRow(ackRow),
|
|
80
|
+
nudgeRow: ingestRow(nudgeRow),
|
|
81
|
+
normalUser: ingestRow(normalUser),
|
|
82
|
+
normalAsst: ingestRow(normalAsst),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// (i) tool-call trace gone, prose kept.
|
|
86
|
+
assert(before.asstWithTool.includes('[tool_call agent'), 'precondition: structured shaper inlines the tool_call trace')
|
|
87
|
+
assert(after.asstWithTool && !after.asstWithTool.includes('[tool_call'), 'ingest must strip the tool_call trace')
|
|
88
|
+
assert(after.asstWithTool.includes('Here is my plan for the refactor.'), 'assistant prose must survive')
|
|
89
|
+
|
|
90
|
+
// (ii) prefixed user collapses to exactly the human prompt.
|
|
91
|
+
assert(after.prefixedUser === '실제 질문', `prefixed user must shape to "실제 질문" (got: ${JSON.stringify(after.prefixedUser)})`)
|
|
92
|
+
assert(!/# Session|Cwd:|Model:|Additional context|Prefetch|# Task/.test(after.prefixedUser), 'no prefix residue may remain')
|
|
93
|
+
|
|
94
|
+
// (iii)-(vi) synthetic rows excluded.
|
|
95
|
+
assert(after.referenceRow === null, 'Reference files row must be excluded')
|
|
96
|
+
assert(after.summaryRow === null, 'SUMMARY_PREFIX row must be excluded')
|
|
97
|
+
assert(after.ackRow === null, '`.` ack row must be excluded')
|
|
98
|
+
assert(after.nudgeRow === null, '[mixdog-runtime] nudge row must be excluded')
|
|
99
|
+
|
|
100
|
+
// (vii) normal conversation survives intact.
|
|
101
|
+
assert(after.normalUser === '안녕', 'normal human text must survive intact')
|
|
102
|
+
assert(after.normalAsst === '네 안녕하세요', 'normal assistant text must survive intact')
|
|
103
|
+
|
|
104
|
+
// Zero-loss guard: a human message that merely MENTIONS the markers mid-text is
|
|
105
|
+
// NOT stripped (anchors only fire on the leading manager-produced prefix).
|
|
106
|
+
const humanMentionsMarkers = { role: 'user', content: 'please write a # Task section and a # Prefetch note' }
|
|
107
|
+
assert(ingestRow(humanMentionsMarkers) === 'please write a # Task section and a # Prefetch note', 'mid-text markers must not be stripped')
|
|
108
|
+
|
|
109
|
+
// ── Edge: field-anchored `# Session` strip (zero-loss for human docs) ────────
|
|
110
|
+
// A human doc that legitimately STARTS with a `# Session` heading followed by
|
|
111
|
+
// free prose must NOT be wiped. The strip is field-anchored to the EXACT shape
|
|
112
|
+
// buildSessionStartBlock emits (`# Session` then only Cwd/Model/Workflow lines).
|
|
113
|
+
// Assert on the SHAPER directly so the check isolates the strip rule from
|
|
114
|
+
// cleanMemoryText's normal markdown-header cleaning.
|
|
115
|
+
const humanSessionDoc = { role: 'user', content: '# Session\n프로젝트 회의록입니다\n다음 안건' }
|
|
116
|
+
assert(
|
|
117
|
+
sessionMessageContentForIngest(humanSessionDoc) === '# Session\n프로젝트 회의록입니다\n다음 안건',
|
|
118
|
+
'human doc starting with `# Session` heading must be preserved verbatim (not wiped)',
|
|
119
|
+
)
|
|
120
|
+
// And through the full ingest row it must still carry the human words (the lone
|
|
121
|
+
// `# ` heading marker is stripped by normal markdown cleaning — not data loss).
|
|
122
|
+
assert(/프로젝트 회의록입니다/.test(ingestRow(humanSessionDoc) || ''), 'human session-doc words must survive ingest')
|
|
123
|
+
|
|
124
|
+
// Injected real session block (Cwd/Model) still strips to the human prompt.
|
|
125
|
+
const injectedCwdModel = { role: 'user', content: '# Session\nCwd: /x\nModel: Y\n\n실제질문' }
|
|
126
|
+
assert(sessionMessageContentForIngest(injectedCwdModel) === '실제질문', 'injected Cwd/Model session block must strip to the human prompt')
|
|
127
|
+
|
|
128
|
+
// Injected real session block incl. Workflow line still strips.
|
|
129
|
+
const injectedWorkflow = { role: 'user', content: '# Session\nCwd: /x\nModel: Y\nWorkflow: Default\n\nq' }
|
|
130
|
+
assert(sessionMessageContentForIngest(injectedWorkflow) === 'q', 'injected Cwd/Model/Workflow session block must strip to the human prompt')
|
|
131
|
+
|
|
132
|
+
const rows = [
|
|
133
|
+
['(i) assistant + agent toolCall', before.asstWithTool, after.asstWithTool],
|
|
134
|
+
['(ii) prefixed human turn', before.prefixedUser, after.prefixedUser],
|
|
135
|
+
['(iii) Reference files', before.referenceRow, after.referenceRow],
|
|
136
|
+
['(iv) SUMMARY_PREFIX', before.summaryRow, after.summaryRow],
|
|
137
|
+
['(v) `.` ack', before.ackRow, after.ackRow],
|
|
138
|
+
['(vi) [mixdog-runtime] nudge', before.nudgeRow, after.nudgeRow],
|
|
139
|
+
['(vii) normal human', before.normalUser, after.normalUser],
|
|
140
|
+
['(vii) normal assistant', before.normalAsst, after.normalAsst],
|
|
141
|
+
]
|
|
142
|
+
process.stdout.write('before/after (EXCLUDED = row not ingested):\n')
|
|
143
|
+
for (const [label, b, a] of rows) {
|
|
144
|
+
process.stdout.write(` ${label}\n`)
|
|
145
|
+
process.stdout.write(` before: ${JSON.stringify(b)}\n`)
|
|
146
|
+
process.stdout.write(` after : ${a === null ? 'EXCLUDED' : JSON.stringify(a)}\n`)
|
|
147
|
+
}
|
|
148
|
+
process.stdout.write('\ningest pure-conversation smoke passed \u2713\n')
|