mixdog 0.9.63 → 0.9.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/package.json +9 -2
- package/scripts/code-graph-description-contract.mjs +3 -3
- package/scripts/verify-release-assets.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +11 -14
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +19 -124
- package/src/runtime/agent/orchestrator/context/collect.mjs +6 -6
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +2 -2
- package/src/runtime/agent/orchestrator/internal-agents.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +333 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -277
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +460 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +14 -593
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -280
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +357 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-headers.mjs +216 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +2 -188
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/compact/file-reattach.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +17 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +18 -26
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +260 -0
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +163 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -561
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -154
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/cwd-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/path-locks.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +519 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +5 -449
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +212 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +1 -7
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +9 -36
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +429 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +2 -395
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/mutation-planner.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/next-call-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +72 -9
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +2 -444
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +505 -0
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/backend-dispatch.mjs +20 -5
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +1 -1
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +4 -4
- package/src/runtime/channels/tool-defs.mjs +2 -3
- package/src/runtime/memory/index.mjs +1 -5
- package/src/runtime/memory/lib/core-memory-candidates.mjs +357 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +7 -357
- package/src/runtime/memory/lib/cycle-signatures.mjs +1 -1
- package/src/runtime/memory/lib/embedding-model-config.mjs +1 -1
- package/src/runtime/memory/lib/embedding-provider.mjs +1 -5
- package/src/runtime/memory/lib/embedding-worker.mjs +1 -5
- package/src/runtime/memory/lib/ko-morph.mjs +2 -2
- package/src/runtime/memory/lib/memory-config-flags.mjs +2 -5
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +3 -7
- package/src/runtime/memory/lib/memory-cycle1.mjs +4 -8
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +2 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -5
- package/src/runtime/memory/lib/memory-embed.mjs +2 -6
- package/src/runtime/memory/lib/memory-log.mjs +8 -0
- package/src/runtime/memory/lib/memory-ops-policy.mjs +4 -8
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-store.mjs +2 -193
- package/src/runtime/memory/lib/memory-score.mjs +1 -1
- package/src/runtime/memory/lib/memory-text-utils.mjs +2 -2
- package/src/runtime/memory/lib/memory.mjs +2 -6
- package/src/runtime/memory/lib/pg/adapter.mjs +2 -6
- package/src/runtime/memory/lib/pg/process.mjs +1 -5
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -5
- package/src/runtime/memory/lib/recall-format.mjs +2 -2
- package/src/runtime/memory/lib/recall-scoring.mjs +195 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -5
- package/src/runtime/memory/lib/trace-store.mjs +2 -6
- package/src/runtime/search/lib/http-fetch.mjs +6 -6
- package/src/runtime/search/lib/web-tools.mjs +2 -2
- package/src/runtime/shared/buffered-appender.mjs +1 -1
- package/src/runtime/shared/child-guardian.mjs +1 -1
- package/src/runtime/shared/child-spawn-gate.mjs +1 -1
- package/src/runtime/shared/config.mjs +4 -4
- package/src/runtime/shared/launcher-control.mjs +9 -10
- package/src/runtime/shared/llm/index.mjs +5 -20
- package/src/runtime/shared/markdown-frontmatter.mjs +1 -1
- package/src/runtime/shared/memory-snapshot.mjs +4 -4
- package/src/runtime/shared/pristine-execution.mjs +1 -1
- package/src/runtime/shared/process-lifecycle.mjs +0 -1
- package/src/runtime/shared/process-shutdown.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +2 -2
- package/src/runtime/shared/schedule-session-run.mjs +1 -1
- package/src/runtime/shared/schedules-db.mjs +1 -1
- package/src/runtime/shared/service-discovery.mjs +2 -2
- package/src/runtime/shared/staged-update.mjs +6 -6
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/tool-primitives.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +2 -2
- package/src/runtime/shared/turn-snapshot.mjs +184 -0
- package/src/runtime/shared/update-checker.mjs +1 -1
- package/src/runtime/shared/user-cwd.mjs +3 -3
- package/src/runtime/shared/user-data-guard.mjs +4 -4
- package/src/runtime/shared/workspace-router.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +2 -3
- package/src/session-runtime/config-lifecycle.mjs +1 -1
- package/src/session-runtime/hitch-profile.mjs +1 -1
- package/src/session-runtime/lifecycle-api.mjs +6 -4
- package/src/session-runtime/model-recency.mjs +5 -5
- package/src/session-runtime/plugin-mcp.mjs +1 -1
- package/src/session-runtime/provider-request-snapshot.mjs +338 -0
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/session-text.mjs +4 -4
- package/src/session-runtime/session-turn-api.mjs +7 -0
- package/src/session-runtime/tool-catalog-schema.mjs +197 -0
- package/src/session-runtime/tool-catalog.mjs +6 -483
- package/src/session-runtime/tool-defs.mjs +1 -1
- package/src/standalone/agent-task-status.mjs +1 -1
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/channel-admin.mjs +4 -4
- package/src/standalone/explore-tool.mjs +3 -3
- package/src/standalone/hook-bus/config.mjs +1 -1
- package/src/standalone/hook-bus/constants.mjs +1 -1
- package/src/standalone/hook-bus/rules.mjs +1 -1
- package/src/standalone/projects.mjs +1 -1
- package/src/standalone/provider-admin.mjs +2 -2
- package/src/tui/App.jsx +1 -1
- package/src/tui/app/app-format.mjs +2 -2
- package/src/tui/app/clipboard.mjs +3 -3
- package/src/tui/app/input-parsers.mjs +1 -1
- package/src/tui/app/text-layout.mjs +1 -1
- package/src/tui/app/transcript-row-estimate.mjs +360 -0
- package/src/tui/app/transcript-window.mjs +5 -347
- package/src/tui/app/use-mouse-input.mjs +1 -1
- package/src/tui/app/use-prompt-handlers.mjs +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +9 -9
- package/src/tui/components/tool-execution/text-format.mjs +2 -2
- package/src/tui/components/tool-output-format.mjs +7 -7
- package/src/tui/display-width.mjs +4 -4
- package/src/tui/dist/index.mjs +187 -99
- package/src/tui/engine/agent-envelope.mjs +5 -5
- package/src/tui/engine/live-share.mjs +100 -0
- package/src/tui/engine/notification-plan.mjs +1 -1
- package/src/tui/engine/queue-helpers.mjs +3 -3
- package/src/tui/engine/render-timing.mjs +4 -5
- package/src/tui/engine/session-api-ext.mjs +6 -3
- package/src/tui/engine/tool-result-status.mjs +5 -5
- package/src/tui/engine/tool-result-text.mjs +1 -1
- package/src/tui/engine/transcript-spill.mjs +585 -0
- package/src/tui/engine/tui-steering-persist.mjs +1 -1
- package/src/tui/engine.mjs +29 -474
- package/src/tui/lib/voice-setup.mjs +1 -1
- package/src/tui/markdown/format-token.mjs +1 -1
- package/src/tui/markdown/render-ansi.mjs +1 -1
- package/src/tui/markdown/table-layout.mjs +4 -4
- package/src/tui/paste-attachments.mjs +1 -1
- package/src/tui/prompt-history-store.mjs +3 -3
- package/src/tui/statusline-ansi-bridge.mjs +2 -2
- package/src/tui/theme.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +2 -2
- package/src/ui/ansi.mjs +0 -9
- package/src/ui/statusline-format.mjs +2 -4
- package/scripts/_bench-cwc.json +0 -20
- package/scripts/_jitter-fuzz.jsx +0 -42
- package/scripts/_jitter-fuzz2.jsx +0 -30
- package/scripts/_jitter-probe.jsx +0 -35
- package/scripts/_jp2.jsx +0 -16
- package/scripts/_smoke_wd.mjs +0 -7
- package/scripts/abort-recovery-test.mjs +0 -175
- package/scripts/agent-dispatch-abort-compose-test.mjs +0 -31
- package/scripts/agent-live-arg-guard-smoke.mjs +0 -20
- package/scripts/agent-live-path-suffix-smoke.mjs +0 -34
- package/scripts/agent-live-toolcall-args-smoke.mjs +0 -45
- package/scripts/agent-loop-policy-test.mjs +0 -37
- package/scripts/agent-model-liveness-test.mjs +0 -763
- package/scripts/agent-parallel-smoke.mjs +0 -435
- package/scripts/agent-route-batch-test.mjs +0 -40
- package/scripts/agent-tag-reuse-smoke.mjs +0 -441
- package/scripts/agent-terminal-reap-test.mjs +0 -252
- package/scripts/agent-trace-io-test.mjs +0 -133
- package/scripts/ansi-color-capability-test.mjs +0 -90
- package/scripts/anthropic-admission-retry-integration-test.mjs +0 -119
- package/scripts/anthropic-maxtokens-test.mjs +0 -119
- package/scripts/anthropic-oauth-refresh-race-test.mjs +0 -397
- package/scripts/anthropic-transport-policy-test.mjs +0 -726
- package/scripts/apply-patch-edit-smoke.mjs +0 -71
- package/scripts/arg-guard-test.mjs +0 -93
- package/scripts/async-notify-settlement-test.mjs +0 -99
- package/scripts/atomic-lock-tryonce-test.mjs +0 -125
- package/scripts/background-task-meta-smoke.mjs +0 -38
- package/scripts/bench-run.mjs +0 -508
- package/scripts/boot-smoke.mjs +0 -137
- package/scripts/build-runtime-windows.ps1 +0 -242
- package/scripts/channel-daemon-smoke.mjs +0 -1103
- package/scripts/code-graph-aggregate-cwd-test.mjs +0 -158
- package/scripts/code-graph-disk-hit-test.mjs +0 -290
- package/scripts/code-graph-dispatch-test.mjs +0 -96
- package/scripts/code-graph-root-federation-test.mjs +0 -273
- package/scripts/compact-active-turn-test.mjs +0 -68
- package/scripts/compact-file-reattach-test.mjs +0 -88
- package/scripts/compact-pressure-test.mjs +0 -788
- package/scripts/compact-prior-context-flatten-test.mjs +0 -252
- package/scripts/compact-recall-digest-test.mjs +0 -57
- package/scripts/compact-smoke.mjs +0 -1017
- package/scripts/compact-trigger-migration-smoke.mjs +0 -261
- package/scripts/compacted-placeholder-scrub-test.mjs +0 -63
- package/scripts/context-mcp-metering-test.mjs +0 -1406
- package/scripts/debounced-skills-async-save-test.mjs +0 -57
- package/scripts/deferred-tool-loading-test.mjs +0 -250
- package/scripts/desktop-session-bridge-test.mjs +0 -1013
- package/scripts/dispatch-persist-recovery-test.mjs +0 -141
- package/scripts/embedding-worker-exit-test.mjs +0 -76
- package/scripts/execution-completion-dedup-test.mjs +0 -205
- package/scripts/execution-pending-resume-kick-test.mjs +0 -151
- package/scripts/execution-resume-esc-integration-test.mjs +0 -176
- package/scripts/explore-bench-tmp.mjs +0 -36
- package/scripts/explore-bench.mjs +0 -248
- package/scripts/explore-prompt-policy-test.mjs +0 -256
- package/scripts/explore-timeout-cancel-test.mjs +0 -345
- package/scripts/find-fuzzy-hidden-test.mjs +0 -267
- package/scripts/forwarder-rebind-tail-test.mjs +0 -67
- package/scripts/freevar-smoke.mjs +0 -98
- package/scripts/gemini-provider-test.mjs +0 -1448
- package/scripts/generate-oc-icons.mjs +0 -11
- package/scripts/grok-oauth-refresh-race-test.mjs +0 -273
- package/scripts/headless-pristine-execution-test.mjs +0 -614
- package/scripts/hook-bus-test.mjs +0 -552
- package/scripts/ingest-pure-conversation-smoke.mjs +0 -175
- package/scripts/internal-comms-bench-test.mjs +0 -226
- package/scripts/internal-comms-bench.mjs +0 -853
- package/scripts/internal-comms-smoke.mjs +0 -277
- package/scripts/internal-tools-normalization-test.mjs +0 -62
- package/scripts/interrupted-turn-history-test.mjs +0 -399
- package/scripts/lead-workflow-smoke.mjs +0 -592
- package/scripts/lifecycle-api-test.mjs +0 -137
- package/scripts/live-share-test.mjs +0 -148
- package/scripts/live-worker-smoke.mjs +0 -333
- package/scripts/log-writer-guard-smoke.mjs +0 -131
- package/scripts/maintenance-default-routes-test.mjs +0 -164
- package/scripts/max-output-recovery-persist-test.mjs +0 -81
- package/scripts/max-output-recovery-test.mjs +0 -368
- package/scripts/mcp-client-normalization-test.mjs +0 -45
- package/scripts/mcp-grace-deferred-test.mjs +0 -225
- package/scripts/memory-core-input-test.mjs +0 -167
- package/scripts/memory-cycle-routing-test.mjs +0 -111
- package/scripts/memory-meta-concurrency-test.mjs +0 -20
- package/scripts/memory-pg-recovery-test.mjs +0 -59
- package/scripts/memory-rule-contract-test.mjs +0 -93
- package/scripts/model-list-sanitize-test.mjs +0 -37
- package/scripts/mouse-tracking-restore-smoke.mjs +0 -107
- package/scripts/native-edit-wire-test.mjs +0 -152
- package/scripts/notify-completion-mirror-test.mjs +0 -73
- package/scripts/openai-oauth-refresh-race-test.mjs +0 -120
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +0 -954
- package/scripts/openai-ws-early-settle-test.mjs +0 -216
- package/scripts/output-style-bench.mjs +0 -292
- package/scripts/output-style-smoke.mjs +0 -145
- package/scripts/parent-abort-link-test.mjs +0 -90
- package/scripts/patch-binary-cache-test.mjs +0 -181
- package/scripts/path-suffix-test.mjs +0 -57
- package/scripts/pending-completion-drop-test.mjs +0 -239
- package/scripts/pending-messages-lock-nonblocking-test.mjs +0 -62
- package/scripts/pretool-ask-runtime-test.mjs +0 -54
- package/scripts/process-lifecycle-test.mjs +0 -461
- package/scripts/prompt-immediate-render-test.mjs +0 -89
- package/scripts/prompt-input-parity-test.mjs +0 -145
- package/scripts/provider-admission-scheduler-test.mjs +0 -681
- package/scripts/provider-contract-test.mjs +0 -632
- package/scripts/provider-stream-stall-test.mjs +0 -276
- package/scripts/provider-toolcall-test.mjs +0 -3063
- package/scripts/reactive-compact-persist-smoke.mjs +0 -203
- package/scripts/recall-bench.mjs +0 -359
- package/scripts/resource-admission-test.mjs +0 -792
- package/scripts/result-classification-test.mjs +0 -75
- package/scripts/rg-runner-test.mjs +0 -240
- package/scripts/routing-corpus-test.mjs +0 -349
- package/scripts/sanitize-tool-pairs-test.mjs +0 -260
- package/scripts/session-bench-cache-break-test.mjs +0 -102
- package/scripts/session-bench.mjs +0 -1708
- package/scripts/session-context-bench.mjs +0 -344
- package/scripts/session-heartbeat-lifecycle-test.mjs +0 -68
- package/scripts/session-ingest-compaction-smoke.mjs +0 -279
- package/scripts/session-ingest-smoke.mjs +0 -177
- package/scripts/session-orphan-sweep-test.mjs +0 -109
- package/scripts/set-effort-config-test.mjs +0 -41
- package/scripts/shell-failure-diagnostics-test.mjs +0 -280
- package/scripts/shell-hardening-test.mjs +0 -209
- package/scripts/shell-jobs-windows-hide-test.mjs +0 -164
- package/scripts/ship-mode-test.mjs +0 -98
- package/scripts/smoke-loop-failure-summary-test.mjs +0 -38
- package/scripts/smoke-loop-failure-summary.mjs +0 -16
- package/scripts/smoke-loop-report.mjs +0 -221
- package/scripts/smoke-loop.mjs +0 -202
- package/scripts/smoke-runtime-negative.ps1 +0 -106
- package/scripts/statusline-quota-hysteresis-test.mjs +0 -62
- package/scripts/steering-drain-buckets-test.mjs +0 -467
- package/scripts/steering-fold-provenance-test.mjs +0 -71
- package/scripts/streaming-tail-window-test.mjs +0 -175
- package/scripts/submit-commandbusy-race-test.mjs +0 -257
- package/scripts/task-bench.mjs +0 -205
- package/scripts/terminal-bench-isolation-guards-test.mjs +0 -102
- package/scripts/termio-input-smoke.mjs +0 -199
- package/scripts/tool-result-hook-test.mjs +0 -48
- package/scripts/tool-smoke.mjs +0 -2453
- package/scripts/tool-tui-presentation-test.mjs +0 -286
- package/scripts/toolcall-args-test.mjs +0 -239
- package/scripts/tui-ambiguous-width-test.mjs +0 -113
- package/scripts/tui-background-failure-smoke.mjs +0 -73
- package/scripts/tui-perf-run.ps1 +0 -26
- package/scripts/tui-render-smoke.mjs +0 -90
- package/scripts/tui-runtime-load-bench-entry.jsx +0 -608
- package/scripts/tui-runtime-load-bench.mjs +0 -50
- package/scripts/tui-store-frame-batch-test.mjs +0 -99
- package/scripts/tui-transcript-jitter-harness-entry.jsx +0 -383
- package/scripts/tui-transcript-perf-test.mjs +0 -934
- package/scripts/usage-metrics-epoch-smoke.mjs +0 -114
- package/scripts/verify-release-assets-test.mjs +0 -281
- package/scripts/web-fetch-routing-test.mjs +0 -158
- package/scripts/webhook-smoke.mjs +0 -201
- package/scripts/windows-hide-spawn-options-test.mjs +0 -19
- package/scripts/worker-notify-rejection-test.mjs +0 -51
- package/scripts/write-backpressure-test.mjs +0 -147
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import {
|
|
4
|
-
acceptQuotaSnapshot,
|
|
5
|
-
fallbackStatusline,
|
|
6
|
-
resolveContextUsedPct,
|
|
7
|
-
} from '../src/ui/statusline.mjs';
|
|
8
|
-
|
|
9
|
-
// Monotonic hysteresis for the 5H/7D usage segment: once a value has rendered,
|
|
10
|
-
// an OLDER shared-cache snapshot (written by another mixdog instance) must not
|
|
11
|
-
// displace it, but a NEWER one — or confirmed own-instance live data — must.
|
|
12
|
-
test('older shared-cache snapshot is rejected, newer is accepted', () => {
|
|
13
|
-
const displayedOwnLive = { segments: ['5H 12%'], asOf: 2000, owned: true };
|
|
14
|
-
|
|
15
|
-
// Another instance overwrites the shared cache with an OLDER snapshot →
|
|
16
|
-
// metricsMatch flips false, source alternates to that unowned older snapshot.
|
|
17
|
-
assert.equal(
|
|
18
|
-
acceptQuotaSnapshot(displayedOwnLive, { asOf: 1000, owned: false }),
|
|
19
|
-
false,
|
|
20
|
-
'older unowned snapshot must not displace displayed own-live value',
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
// A strictly newer shared snapshot is allowed to advance the value.
|
|
24
|
-
assert.equal(
|
|
25
|
-
acceptQuotaSnapshot(displayedOwnLive, { asOf: 3000, owned: false }),
|
|
26
|
-
true,
|
|
27
|
-
'newer snapshot must replace the displayed value',
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('own-instance live data always wins; empty timestamps preserve prior behavior', () => {
|
|
32
|
-
const displayedShared = { segments: ['5H 12%'], asOf: 5000, owned: false };
|
|
33
|
-
// Own-instance live data replaces even an apparently newer shared snapshot.
|
|
34
|
-
assert.equal(acceptQuotaSnapshot(displayedShared, { asOf: 1, owned: true }), true);
|
|
35
|
-
// Nothing displayed yet → accept.
|
|
36
|
-
assert.equal(acceptQuotaSnapshot(undefined, { asOf: 1000, owned: false }), true);
|
|
37
|
-
// No comparable timestamps → accept (byte-for-byte prior behavior).
|
|
38
|
-
assert.equal(acceptQuotaSnapshot({ asOf: 0, owned: true }, { asOf: 0, owned: false }), true);
|
|
39
|
-
// Shared→shared same asOf → accept (idempotent refresh).
|
|
40
|
-
assert.equal(acceptQuotaSnapshot({ asOf: 2000, owned: false }, { asOf: 2000, owned: false }), true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('context percentage uses compaction pressure over trigger and ignores gateway boundary percent', () => {
|
|
44
|
-
const args = {
|
|
45
|
-
stats: {
|
|
46
|
-
currentEstimatedContextTokens: 75_000,
|
|
47
|
-
currentContextSource: 'estimated',
|
|
48
|
-
},
|
|
49
|
-
contextWindow: 100_000,
|
|
50
|
-
compactBoundaryTokens: 100_000,
|
|
51
|
-
autoCompactTokenLimit: 75_000,
|
|
52
|
-
};
|
|
53
|
-
assert.equal(resolveContextUsedPct({
|
|
54
|
-
...args,
|
|
55
|
-
gatewayStatus: { contextUsedPct: 75 },
|
|
56
|
-
}), 100);
|
|
57
|
-
assert.match(
|
|
58
|
-
fallbackStatusline({ provider: 'openai', model: 'test', ...args })
|
|
59
|
-
.replace(/\x1b\[[0-9;]*m/g, ''),
|
|
60
|
-
/\b100%/,
|
|
61
|
-
);
|
|
62
|
-
});
|
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { createSessionFlow } from '../src/tui/engine/session-flow.mjs';
|
|
4
|
-
import { createRunTurn } from '../src/tui/engine/turn.mjs';
|
|
5
|
-
|
|
6
|
-
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
-
|
|
8
|
-
// Minimal bag: drainPendingSteering only touches pending, the queue helpers,
|
|
9
|
-
// and commitSteeringQueueEntries (which no-ops on disk when runtime.id is not
|
|
10
|
-
// a valid session key). No provider/runTurn wiring needed.
|
|
11
|
-
function makeFlow() {
|
|
12
|
-
let seq = 0;
|
|
13
|
-
let runTurns = 0;
|
|
14
|
-
const state = { queued: [], busy: false };
|
|
15
|
-
const bag = {
|
|
16
|
-
runtime: { id: null },
|
|
17
|
-
nextId: () => `id_${++seq}`,
|
|
18
|
-
tuiDebug: () => {},
|
|
19
|
-
flags: {},
|
|
20
|
-
pending: [],
|
|
21
|
-
pendingNotificationKeys: new Set(),
|
|
22
|
-
displayedExecutionNotificationKeys: new Set(),
|
|
23
|
-
getState: () => state,
|
|
24
|
-
set: (patch) => Object.assign(state, patch),
|
|
25
|
-
pushItem: () => {},
|
|
26
|
-
replaceItems: () => {},
|
|
27
|
-
pushNotice: () => {},
|
|
28
|
-
pushUserOrSyntheticItem: () => {},
|
|
29
|
-
autoClearState: () => ({ enabled: false }),
|
|
30
|
-
agentStatusState: {},
|
|
31
|
-
routeState: {},
|
|
32
|
-
syncContextStats: () => {},
|
|
33
|
-
flushDeferredExecutionPendingResumeKick: () => {},
|
|
34
|
-
runTurn: async () => {
|
|
35
|
-
runTurns += 1;
|
|
36
|
-
return 'done';
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
return { flow: createSessionFlow(bag), bag, getRunTurns: () => runTurns };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
test('drainPendingSteering drains prompt/next mid-turn but leaves later notifications', () => {
|
|
43
|
-
const { flow, bag } = makeFlow();
|
|
44
|
-
bag.pending.push(flow.makeQueueEntry('steer the turn', { mode: 'prompt' }));
|
|
45
|
-
bag.pending.push(flow.makeQueueEntry('task finished', { mode: 'task-notification', key: 'task-1' }));
|
|
46
|
-
|
|
47
|
-
const out = flow.drainPendingSteering();
|
|
48
|
-
|
|
49
|
-
assert.equal(out.length, 1, 'next-priority prompt is injected into the active continuation');
|
|
50
|
-
assert.equal(out[0], 'steer the turn');
|
|
51
|
-
assert.equal(bag.pending.length, 1, 'later task notification waits for post-turn or explicit later flush');
|
|
52
|
-
assert.equal(bag.pending[0].content, 'task finished');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('drainPendingSteering can explicitly flush later notifications', () => {
|
|
56
|
-
const { flow, bag } = makeFlow();
|
|
57
|
-
bag.pending.push(flow.makeQueueEntry('task finished', { mode: 'task-notification', key: 'task-1' }));
|
|
58
|
-
|
|
59
|
-
const out = flow.drainPendingSteering({ maxPriority: 'later' });
|
|
60
|
-
|
|
61
|
-
assert.equal(out.length, 1);
|
|
62
|
-
assert.equal(out[0], 'task finished');
|
|
63
|
-
assert.equal(bag.pending.length, 0);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('drainPendingSteering accepts runtime callback signature', () => {
|
|
67
|
-
const { flow, bag } = makeFlow();
|
|
68
|
-
bag.pending.push(flow.makeQueueEntry('task finished', { mode: 'task-notification', key: 'task-1' }));
|
|
69
|
-
|
|
70
|
-
const out = flow.drainPendingSteering('session-id', { maxPriority: 'later' });
|
|
71
|
-
|
|
72
|
-
assert.deepEqual(out, ['task finished']);
|
|
73
|
-
assert.equal(bag.pending.length, 0);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('drained notification key is released while displayed key is not cleared', () => {
|
|
77
|
-
const { flow, bag } = makeFlow();
|
|
78
|
-
bag.getState().busy = true;
|
|
79
|
-
assert.equal(flow.enqueue('task finished', { mode: 'task-notification', key: 'task-1' }), true);
|
|
80
|
-
bag.displayedExecutionNotificationKeys.add('task-1');
|
|
81
|
-
|
|
82
|
-
const out = flow.drainPendingSteering({ maxPriority: 'later' });
|
|
83
|
-
|
|
84
|
-
assert.deepEqual(out, ['task finished']);
|
|
85
|
-
assert.equal(flow.enqueue('task duplicate', { mode: 'task-notification', key: 'task-1' }), true);
|
|
86
|
-
assert.equal(bag.displayedExecutionNotificationKeys.has('task-1'), true);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('drainPendingSteering leaves slash commands pending', () => {
|
|
90
|
-
const { flow, bag } = makeFlow();
|
|
91
|
-
bag.pending.push(flow.makeQueueEntry('/clear', { mode: 'prompt' }));
|
|
92
|
-
bag.pending.push(flow.makeQueueEntry('steer text', { mode: 'prompt' }));
|
|
93
|
-
|
|
94
|
-
const out = flow.drainPendingSteering();
|
|
95
|
-
|
|
96
|
-
assert.equal(out.length, 1, 'non-slash prompt drains');
|
|
97
|
-
assert.equal(out[0], 'steer text');
|
|
98
|
-
assert.equal(bag.pending.length, 1, 'slash command stays queued for post-turn processing');
|
|
99
|
-
assert.equal(bag.pending[0].content, '/clear');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('enqueue while busy does not start a parallel Lead turn', async () => {
|
|
103
|
-
const { flow, bag, getRunTurns } = makeFlow();
|
|
104
|
-
bag.getState().busy = true;
|
|
105
|
-
|
|
106
|
-
assert.equal(flow.enqueue('scheduled message', { mode: 'task-notification' }), true);
|
|
107
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
108
|
-
|
|
109
|
-
assert.equal(getRunTurns(), 0, 'busy enqueue must wait for active turn boundary');
|
|
110
|
-
assert.equal(bag.pending.length, 1, 'message remains pending for post-turn drain');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('post-turn drain does not send queued slash command to model', async () => {
|
|
114
|
-
const { flow, bag, getRunTurns } = makeFlow();
|
|
115
|
-
bag.pending.push(flow.makeQueueEntry('/clear', { mode: 'prompt' }));
|
|
116
|
-
|
|
117
|
-
await flow.drain();
|
|
118
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
119
|
-
|
|
120
|
-
assert.equal(getRunTurns(), 0, 'slash command must not be runTurn model text');
|
|
121
|
-
assert.equal(bag.pending.length, 1, 'slash command remains for command dispatcher');
|
|
122
|
-
assert.equal(bag.pending[0].content, '/clear');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('restoreQueued can edit one visible steering entry without draining its siblings', () => {
|
|
126
|
-
const { flow, bag } = makeFlow();
|
|
127
|
-
const first = flow.makeQueueEntry('first follow-up', { mode: 'prompt' });
|
|
128
|
-
const second = flow.makeQueueEntry('second follow-up', { mode: 'prompt' });
|
|
129
|
-
bag.pending.push(first, second);
|
|
130
|
-
bag.getState().queued = [first, second];
|
|
131
|
-
|
|
132
|
-
const restored = flow.restoreQueued('current draft', second.id);
|
|
133
|
-
|
|
134
|
-
assert.equal(restored.count, 1);
|
|
135
|
-
assert.equal(restored.text, 'second follow-up\ncurrent draft');
|
|
136
|
-
assert.deepEqual(bag.pending.map((entry) => entry.id), [first.id]);
|
|
137
|
-
assert.deepEqual(bag.getState().queued.map((entry) => entry.id), [first.id]);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Minimal store bag for createRunTurn: only the surface the streaming/steering
|
|
141
|
-
// finalize path touches. runtime.ask is a caller-supplied mock that drives the
|
|
142
|
-
// text-delta / steer-message callbacks.
|
|
143
|
-
function makeTurnBag(ask, {
|
|
144
|
-
timeoutMs = 300000,
|
|
145
|
-
getTurnLiveness,
|
|
146
|
-
abort,
|
|
147
|
-
} = {}) {
|
|
148
|
-
let seq = 0;
|
|
149
|
-
const state = {
|
|
150
|
-
items: [],
|
|
151
|
-
stats: { turns: 0, inputTokens: 0, outputTokens: 0 },
|
|
152
|
-
busy: false,
|
|
153
|
-
spinner: null,
|
|
154
|
-
thinking: null,
|
|
155
|
-
};
|
|
156
|
-
const itemIndexById = new Map();
|
|
157
|
-
const findIndexById = (id) => state.items.findIndex((it) => it.id === id);
|
|
158
|
-
const runtime = { id: null, toolMode: 'auto', ask, abort: abort || (() => {}) };
|
|
159
|
-
if (typeof getTurnLiveness === 'function') runtime.getTurnLiveness = getTurnLiveness;
|
|
160
|
-
const bag = {
|
|
161
|
-
runtime,
|
|
162
|
-
nextId: () => `id_${++seq}`,
|
|
163
|
-
tuiDebug: () => {},
|
|
164
|
-
LEAD_TURN_TIMEOUT_MS: timeoutMs,
|
|
165
|
-
flags: { leadTurnEpoch: 0 },
|
|
166
|
-
pending: [],
|
|
167
|
-
itemIndexById,
|
|
168
|
-
getState: () => state,
|
|
169
|
-
set: (patch) => Object.assign(state, patch),
|
|
170
|
-
pushItem: (spec) => {
|
|
171
|
-
state.items = [...state.items, spec];
|
|
172
|
-
if (spec?.id != null) itemIndexById.set(spec.id, state.items.length - 1);
|
|
173
|
-
},
|
|
174
|
-
patchItem: (id, patch) => {
|
|
175
|
-
const idx = findIndexById(id);
|
|
176
|
-
if (idx < 0) return;
|
|
177
|
-
const items = state.items.slice();
|
|
178
|
-
items[idx] = { ...items[idx], ...patch };
|
|
179
|
-
state.items = items;
|
|
180
|
-
},
|
|
181
|
-
pushNotice: () => {},
|
|
182
|
-
pushUserOrSyntheticItem: (text) => {
|
|
183
|
-
state.items = [...state.items, { kind: 'user', id: `u_${++seq}`, text }];
|
|
184
|
-
},
|
|
185
|
-
markToolCallActive: () => {},
|
|
186
|
-
markToolCallDone: () => {},
|
|
187
|
-
clearActiveToolSummary: () => {},
|
|
188
|
-
agentStatusState: () => ({}),
|
|
189
|
-
routeState: () => ({}),
|
|
190
|
-
syncContextStats: () => {},
|
|
191
|
-
denyAllToolApprovals: () => {},
|
|
192
|
-
requestToolApproval: async () => ({ approved: false }),
|
|
193
|
-
patchToolCardResult: () => {},
|
|
194
|
-
flushToolResults: () => {},
|
|
195
|
-
flushDeferredExecutionPendingResumeKick: () => {},
|
|
196
|
-
drain: async () => {},
|
|
197
|
-
drainPendingSteering: () => [],
|
|
198
|
-
};
|
|
199
|
-
return { bag, getState: () => state };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
test('non-cancellation turn exceptions publish and return failed lifecycle status', async () => {
|
|
203
|
-
const notices = [];
|
|
204
|
-
const { bag, getState } = makeTurnBag(async () => {
|
|
205
|
-
throw new Error('provider request failed');
|
|
206
|
-
});
|
|
207
|
-
bag.pushNotice = (text, tone) => notices.push({ text, tone });
|
|
208
|
-
|
|
209
|
-
const result = await createRunTurn(bag)('do a thing');
|
|
210
|
-
const completion = getState().items.find((item) => item.kind === 'turndone');
|
|
211
|
-
|
|
212
|
-
assert.equal(result, 'failed');
|
|
213
|
-
assert.equal(completion?.status, 'failed');
|
|
214
|
-
assert.equal(getState().busy, false);
|
|
215
|
-
assert.equal(getState().spinner, null);
|
|
216
|
-
assert.equal(notices.at(-1)?.tone, 'error');
|
|
217
|
-
assert.match(notices.at(-1)?.text || '', /provider request failed/);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test('watchdog accepts fresh runtime liveness without tripping', async () => {
|
|
221
|
-
let aborts = 0;
|
|
222
|
-
const ask = async () => {
|
|
223
|
-
await wait(45);
|
|
224
|
-
return { result: { content: '' }, session: { messages: [] } };
|
|
225
|
-
};
|
|
226
|
-
const { bag } = makeTurnBag(ask, {
|
|
227
|
-
timeoutMs: 15,
|
|
228
|
-
abort: () => { aborts += 1; },
|
|
229
|
-
getTurnLiveness: () => ({
|
|
230
|
-
stage: 'tool_running',
|
|
231
|
-
lastProgressAt: Date.now(),
|
|
232
|
-
toolStartedAt: Date.now(),
|
|
233
|
-
toolSelfDeadlineMs: 0,
|
|
234
|
-
}),
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
assert.equal(await createRunTurn(bag)('do a thing'), 'done');
|
|
238
|
-
assert.equal(aborts, 0, 'fresh orchestrator heartbeats defer the watchdog');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test('watchdog trips at the tool ceiling despite fresh liveness', async () => {
|
|
242
|
-
const previousMax = process.env.MIXDOG_LEAD_TOOL_MAX_MS;
|
|
243
|
-
const timeoutMs = 30;
|
|
244
|
-
const ceilingMs = 95;
|
|
245
|
-
process.env.MIXDOG_LEAD_TOOL_MAX_MS = String(ceilingMs);
|
|
246
|
-
let rejectAsk;
|
|
247
|
-
let heartbeatTimer = null;
|
|
248
|
-
let heartbeatStartTimer = null;
|
|
249
|
-
let aborts = 0;
|
|
250
|
-
let abortAt = 0;
|
|
251
|
-
let livenessCalls = 0;
|
|
252
|
-
const toolStartedAt = Date.now();
|
|
253
|
-
const ask = (_userText, options) => new Promise((_resolve, reject) => {
|
|
254
|
-
rejectAsk = reject;
|
|
255
|
-
// Let the first watchdog probe defer from orchestrator liveness, then keep
|
|
256
|
-
// the TUI's local progress fresh so the ceiling-bounded re-arm is exercised.
|
|
257
|
-
heartbeatStartTimer = setTimeout(() => {
|
|
258
|
-
heartbeatTimer = setInterval(() => options.onStreamDelta(), 5);
|
|
259
|
-
}, 40);
|
|
260
|
-
});
|
|
261
|
-
const { bag } = makeTurnBag(ask, {
|
|
262
|
-
timeoutMs,
|
|
263
|
-
abort: () => {
|
|
264
|
-
aborts += 1;
|
|
265
|
-
abortAt = Date.now();
|
|
266
|
-
clearTimeout(heartbeatStartTimer);
|
|
267
|
-
clearInterval(heartbeatTimer);
|
|
268
|
-
const error = new Error('interrupted');
|
|
269
|
-
error.name = 'SessionClosedError';
|
|
270
|
-
rejectAsk(error);
|
|
271
|
-
},
|
|
272
|
-
getTurnLiveness: () => {
|
|
273
|
-
livenessCalls += 1;
|
|
274
|
-
return {
|
|
275
|
-
stage: 'tool_running',
|
|
276
|
-
lastProgressAt: Date.now(),
|
|
277
|
-
toolStartedAt,
|
|
278
|
-
toolSelfDeadlineMs: 0,
|
|
279
|
-
};
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const result = await Promise.race([
|
|
285
|
-
createRunTurn(bag)('do a thing'),
|
|
286
|
-
wait(180).then(() => 'watchdog did not trip at the ceiling'),
|
|
287
|
-
]);
|
|
288
|
-
assert.equal(result, 'cancelled');
|
|
289
|
-
assert.equal(aborts, 1);
|
|
290
|
-
assert.ok(livenessCalls >= 2, 'first probe deferred, then the ceiling probe rechecked liveness');
|
|
291
|
-
assert.ok(abortAt - toolStartedAt < ceilingMs + timeoutMs, 'ceiling trips before an unbounded watchdog interval');
|
|
292
|
-
} finally {
|
|
293
|
-
clearTimeout(heartbeatStartTimer);
|
|
294
|
-
clearInterval(heartbeatTimer);
|
|
295
|
-
if (previousMax === undefined) delete process.env.MIXDOG_LEAD_TOOL_MAX_MS;
|
|
296
|
-
else process.env.MIXDOG_LEAD_TOOL_MAX_MS = previousMax;
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
for (const [name, getTurnLiveness] of [
|
|
301
|
-
['throws', () => { throw new Error('liveness unavailable'); }],
|
|
302
|
-
['returns null', () => null],
|
|
303
|
-
]) {
|
|
304
|
-
test(`watchdog ${name} probe falls back to abort`, async () => {
|
|
305
|
-
let rejectAsk;
|
|
306
|
-
let aborts = 0;
|
|
307
|
-
const ask = () => new Promise((_resolve, reject) => { rejectAsk = reject; });
|
|
308
|
-
const { bag } = makeTurnBag(ask, {
|
|
309
|
-
timeoutMs: 15,
|
|
310
|
-
getTurnLiveness,
|
|
311
|
-
abort: () => {
|
|
312
|
-
aborts += 1;
|
|
313
|
-
const error = new Error('interrupted');
|
|
314
|
-
error.name = 'SessionClosedError';
|
|
315
|
-
rejectAsk(error);
|
|
316
|
-
},
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const [result] = await Promise.all([createRunTurn(bag)('do a thing'), wait(40)]);
|
|
320
|
-
assert.equal(result, 'cancelled');
|
|
321
|
-
assert.equal(aborts, 1);
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
test('stream-delta progress keeps the watchdog alive', async () => {
|
|
326
|
-
let aborts = 0;
|
|
327
|
-
let streamDeltas = 0;
|
|
328
|
-
const ask = async (_userText, options) => {
|
|
329
|
-
options.onStreamDelta();
|
|
330
|
-
streamDeltas += 1;
|
|
331
|
-
await wait(12);
|
|
332
|
-
options.onStreamDelta();
|
|
333
|
-
streamDeltas += 1;
|
|
334
|
-
await wait(12);
|
|
335
|
-
return { result: { content: '' }, session: { messages: [] } };
|
|
336
|
-
};
|
|
337
|
-
const { bag } = makeTurnBag(ask, {
|
|
338
|
-
timeoutMs: 20,
|
|
339
|
-
abort: () => { aborts += 1; },
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
assert.equal(await createRunTurn(bag)('do a thing'), 'done');
|
|
343
|
-
assert.equal(streamDeltas, 2);
|
|
344
|
-
assert.equal(aborts, 0);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test('onSteerMessage commits a streamed no-newline assistant tail into items', async () => {
|
|
348
|
-
// A terminal no-tool response streams a single line WITHOUT a trailing '\n',
|
|
349
|
-
// so no assistant row/currentAssistantId exists yet. A steering injection
|
|
350
|
-
// races finalization and must seal the pending tail instead of dropping it.
|
|
351
|
-
const TAIL = 'partial answer with no trailing newline';
|
|
352
|
-
const ask = async (_userText, opts) => {
|
|
353
|
-
opts.onTextDelta(TAIL);
|
|
354
|
-
opts.onSteerMessage('steer now');
|
|
355
|
-
return { result: { content: '' }, session: { messages: [] } };
|
|
356
|
-
};
|
|
357
|
-
const { bag, getState } = makeTurnBag(ask);
|
|
358
|
-
const runTurn = createRunTurn(bag);
|
|
359
|
-
|
|
360
|
-
await runTurn('do a thing');
|
|
361
|
-
|
|
362
|
-
const assistant = getState().items.find((it) => it.kind === 'assistant');
|
|
363
|
-
assert.ok(assistant, 'streamed no-newline tail must be committed as an assistant item');
|
|
364
|
-
assert.equal(assistant.text, TAIL);
|
|
365
|
-
assert.equal(assistant.streaming, false);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
test('finalization does not duplicate a steer-committed tail when result.content repeats it', async () => {
|
|
369
|
-
// Same steer race, but the provider's final content equals the already-
|
|
370
|
-
// committed tail. Finalization must NOT re-emit it as a second item.
|
|
371
|
-
const TAIL = 'partial answer with no trailing newline';
|
|
372
|
-
const ask = async (_userText, opts) => {
|
|
373
|
-
opts.onTextDelta(TAIL);
|
|
374
|
-
opts.onSteerMessage('steer now');
|
|
375
|
-
return { result: { content: TAIL }, session: { messages: [] } };
|
|
376
|
-
};
|
|
377
|
-
const { bag, getState } = makeTurnBag(ask);
|
|
378
|
-
const runTurn = createRunTurn(bag);
|
|
379
|
-
|
|
380
|
-
await runTurn('do a thing');
|
|
381
|
-
|
|
382
|
-
const assistants = getState().items.filter((it) => it.kind === 'assistant');
|
|
383
|
-
assert.equal(assistants.length, 1, 'the committed tail must not be duplicated at finalize');
|
|
384
|
-
assert.equal(assistants[0].text, TAIL);
|
|
385
|
-
assert.equal(assistants[0].streaming, false);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
test('finalization appends only the uncommitted remainder past a steer-committed tail', async () => {
|
|
389
|
-
// Provider content extends past the committed tail: only the new remainder
|
|
390
|
-
// becomes a fresh item, ordered after the committed segment (+ steering row).
|
|
391
|
-
const TAIL = 'partial answer with no trailing newline';
|
|
392
|
-
const REMAINDER = '\nmore text arriving after the steer';
|
|
393
|
-
const ask = async (_userText, opts) => {
|
|
394
|
-
opts.onTextDelta(TAIL);
|
|
395
|
-
opts.onSteerMessage('steer now');
|
|
396
|
-
return { result: { content: TAIL + REMAINDER }, session: { messages: [] } };
|
|
397
|
-
};
|
|
398
|
-
const { bag, getState } = makeTurnBag(ask);
|
|
399
|
-
const runTurn = createRunTurn(bag);
|
|
400
|
-
|
|
401
|
-
await runTurn('do a thing');
|
|
402
|
-
|
|
403
|
-
const assistants = getState().items.filter((it) => it.kind === 'assistant');
|
|
404
|
-
assert.equal(assistants.length, 2, 'committed tail + remainder are two distinct items');
|
|
405
|
-
assert.equal(assistants[0].text, TAIL, 'committed tail stays first, unchanged');
|
|
406
|
-
assert.equal(assistants[1].text, REMAINDER, 'only the uncommitted remainder is appended');
|
|
407
|
-
assert.equal(assistants[1].streaming, false);
|
|
408
|
-
const steerIdx = getState().items.findIndex((it) => it.kind === 'user' && it.text === 'steer now');
|
|
409
|
-
const remainderIdx = getState().items.findIndex((it) => it.kind === 'assistant' && it.text === REMAINDER);
|
|
410
|
-
assert.ok(steerIdx >= 0 && remainderIdx > steerIdx, 'remainder appends after the injected steering row');
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
// Two committed segments this turn: a prior preamble P, then a no-newline TAIL
|
|
414
|
-
// sealed by the steer race. The provider's final content may OMIT P — the
|
|
415
|
-
// per-segment strip must still peel BOTH out (a single concatenated 'P+TAIL'
|
|
416
|
-
// prefix would fail to match and duplicate TAIL after the steering row).
|
|
417
|
-
const P = 'preamble sealed before the tail';
|
|
418
|
-
const TAIL2 = 'terminal tail with no trailing newline';
|
|
419
|
-
function makeTwoSegmentAsk(finalContent) {
|
|
420
|
-
return async (_userText, opts) => {
|
|
421
|
-
opts.onTextDelta(P);
|
|
422
|
-
opts.onSteerMessage('steer one'); // seals P as its own item
|
|
423
|
-
opts.onTextDelta(TAIL2);
|
|
424
|
-
opts.onSteerMessage('steer two'); // seals TAIL2 as its own item
|
|
425
|
-
return { result: { content: finalContent }, session: { messages: [] } };
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
test('per-segment strip: final content = TAIL only (P omitted) → no new item', async () => {
|
|
430
|
-
const { bag, getState } = makeTurnBag(makeTwoSegmentAsk(TAIL2));
|
|
431
|
-
await createRunTurn(bag)('do a thing');
|
|
432
|
-
const assistants = getState().items.filter((it) => it.kind === 'assistant');
|
|
433
|
-
assert.deepEqual(assistants.map((it) => it.text), [P, TAIL2], 'only the two committed segments exist');
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test('per-segment strip: final content = P + newline + TAIL → no new item', async () => {
|
|
437
|
-
const { bag, getState } = makeTurnBag(makeTwoSegmentAsk(`${P}\n${TAIL2}`));
|
|
438
|
-
await createRunTurn(bag)('do a thing');
|
|
439
|
-
const assistants = getState().items.filter((it) => it.kind === 'assistant');
|
|
440
|
-
assert.deepEqual(assistants.map((it) => it.text), [P, TAIL2], 'both segments stripped; nothing re-emitted');
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
test('per-segment strip: final content extends past both committed segments → only remainder', async () => {
|
|
444
|
-
const { bag, getState } = makeTurnBag(makeTwoSegmentAsk(`${P}\n${TAIL2}\nmore`));
|
|
445
|
-
await createRunTurn(bag)('do a thing');
|
|
446
|
-
const assistants = getState().items.filter((it) => it.kind === 'assistant');
|
|
447
|
-
assert.equal(assistants.length, 3, 'committed P + TAIL + the new remainder');
|
|
448
|
-
assert.equal(assistants[2].text, '\nmore', 'only the uncommitted remainder is appended');
|
|
449
|
-
assert.equal(assistants[2].streaming, false);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
test('per-segment strip: segment sealed WITH a leading newline still peels', async () => {
|
|
453
|
-
// The committed segment carries its own leading '\n' ('\nTAIL'); the provider
|
|
454
|
-
// returns the trimmed 'TAIL'. Trimming the segment before the compare lets it
|
|
455
|
-
// peel so it is not duplicated after the steering row.
|
|
456
|
-
const LEAD_TAIL = '\nterminal tail sealed with a leading newline';
|
|
457
|
-
const ask = async (_userText, opts) => {
|
|
458
|
-
opts.onTextDelta(LEAD_TAIL);
|
|
459
|
-
opts.onSteerMessage('steer now');
|
|
460
|
-
return { result: { content: LEAD_TAIL.replace(/^\s+/, '') }, session: { messages: [] } };
|
|
461
|
-
};
|
|
462
|
-
const { bag, getState } = makeTurnBag(ask);
|
|
463
|
-
await createRunTurn(bag)('do a thing');
|
|
464
|
-
const assistants = getState().items.filter((it) => it.kind === 'assistant');
|
|
465
|
-
assert.equal(assistants.length, 1, 'leading-newline segment must not be duplicated at finalize');
|
|
466
|
-
assert.equal(assistants[0].text, LEAD_TAIL);
|
|
467
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { foldUserTextIntoToolResultTail } from '../src/runtime/agent/orchestrator/session/context-utils.mjs';
|
|
4
|
-
import { _buildRequestBodyForCacheSmoke } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
|
|
5
|
-
|
|
6
|
-
// A tool_result turn followed by a plain user text turn.
|
|
7
|
-
function baseTranscript(userMeta) {
|
|
8
|
-
return [
|
|
9
|
-
{ role: 'user', content: 'do the thing' },
|
|
10
|
-
{ role: 'assistant', content: '', toolCalls: [{ id: 't1', name: 'read', arguments: {} }] },
|
|
11
|
-
{ role: 'tool', toolCallId: 't1', content: 'file contents here' },
|
|
12
|
-
{ role: 'user', content: 'actually stop and do X instead', ...(userMeta ? { meta: userMeta } : {}) },
|
|
13
|
-
];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
test('plain user text after tool_result is folded into the tool_result turn', () => {
|
|
17
|
-
const body = _buildRequestBodyForCacheSmoke(baseTranscript(null), 'claude-sonnet-4');
|
|
18
|
-
const msgs = body.messages;
|
|
19
|
-
const lastUser = msgs[msgs.length - 1];
|
|
20
|
-
assert.equal(lastUser.role, 'user');
|
|
21
|
-
const hasToolResult = lastUser.content.some((b) => b.type === 'tool_result');
|
|
22
|
-
assert.ok(hasToolResult, 'trailing user turn carries the tool_result');
|
|
23
|
-
assert.ok(JSON.stringify(lastUser.content).includes('do X instead'), 'plain text folded into tool_result');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('steering-tagged user text after tool_result stays a separate user turn', () => {
|
|
27
|
-
const body = _buildRequestBodyForCacheSmoke(baseTranscript({ source: 'steering' }), 'claude-sonnet-4');
|
|
28
|
-
const msgs = body.messages;
|
|
29
|
-
const lastUser = msgs[msgs.length - 1];
|
|
30
|
-
assert.equal(lastUser.role, 'user', 'steering message is its own user turn');
|
|
31
|
-
const isToolResultTurn = Array.isArray(lastUser.content)
|
|
32
|
-
&& lastUser.content.some((b) => b.type === 'tool_result');
|
|
33
|
-
assert.equal(isToolResultTurn, false, 'steering turn is not a tool_result turn');
|
|
34
|
-
assert.ok(JSON.stringify(lastUser.content).includes('do X instead'), 'steering text preserved as user input');
|
|
35
|
-
const prev = msgs[msgs.length - 2];
|
|
36
|
-
assert.ok(
|
|
37
|
-
Array.isArray(prev.content) && prev.content.some((b) => b.type === 'tool_result'),
|
|
38
|
-
'tool_result stays on its own preceding turn',
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('foldUserTextIntoToolResultTail unit: folds plain text tail', () => {
|
|
43
|
-
const result = [{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 't1', content: 'r' }] }];
|
|
44
|
-
assert.equal(foldUserTextIntoToolResultTail(result, 'hi'), true);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// --- API-key Anthropic provider (anthropic.mjs) path ---
|
|
48
|
-
import { _toAnthropicMessagesForTest } from '../src/runtime/agent/orchestrator/providers/anthropic.mjs';
|
|
49
|
-
|
|
50
|
-
test('anthropic.mjs: plain user text after tool_result is folded', () => {
|
|
51
|
-
const msgs = _toAnthropicMessagesForTest(baseTranscript(null));
|
|
52
|
-
const lastUser = msgs[msgs.length - 1];
|
|
53
|
-
assert.equal(lastUser.role, 'user');
|
|
54
|
-
assert.ok(lastUser.content.some((b) => b.type === 'tool_result'), 'trailing turn carries tool_result');
|
|
55
|
-
assert.ok(JSON.stringify(lastUser.content).includes('do X instead'), 'plain text folded into tool_result');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('anthropic.mjs: steering-tagged user text stays a separate user turn', () => {
|
|
59
|
-
const msgs = _toAnthropicMessagesForTest(baseTranscript({ source: 'steering' }));
|
|
60
|
-
const lastUser = msgs[msgs.length - 1];
|
|
61
|
-
assert.equal(lastUser.role, 'user', 'steering message is its own user turn');
|
|
62
|
-
const isToolResultTurn = Array.isArray(lastUser.content)
|
|
63
|
-
&& lastUser.content.some((b) => b.type === 'tool_result');
|
|
64
|
-
assert.equal(isToolResultTurn, false, 'steering turn is not a tool_result turn');
|
|
65
|
-
assert.ok(JSON.stringify(lastUser.content).includes('do X instead'), 'steering text preserved as user input');
|
|
66
|
-
const prev = msgs[msgs.length - 2];
|
|
67
|
-
assert.ok(
|
|
68
|
-
Array.isArray(prev.content) && prev.content.some((b) => b.type === 'tool_result'),
|
|
69
|
-
'tool_result stays on its own preceding turn',
|
|
70
|
-
);
|
|
71
|
-
});
|