@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 0.8.8
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/bun.lock +2 -2
- package/docs/plugins.md +832 -0
- package/examples/plugins/echo/README.md +60 -61
- package/examples/plugins/echo/package.json +2 -1
- package/examples/plugins/echo/register.ts +143 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
- package/openapi.yaml +5 -15
- package/package.json +2 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -4
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
- package/src/__tests__/conversation-agent-loop.test.ts +39 -42
- package/src/__tests__/conversation-clean-command.test.ts +2 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
- package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
- package/src/__tests__/conversation-starter-routes.test.ts +6 -14
- package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
- package/src/__tests__/db-acp-history.test.ts +0 -101
- package/src/__tests__/dynamic-page-surface.test.ts +0 -31
- package/src/__tests__/file-write-tool.test.ts +0 -63
- package/src/__tests__/gateway-only-guard.test.ts +2 -12
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/injector-background-turn.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +6 -34
- package/src/__tests__/injector-disk-pressure.test.ts +34 -77
- package/src/__tests__/injector-document-comments.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
- package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
- package/src/__tests__/pipeline-runner.test.ts +554 -0
- package/src/__tests__/plugin-api-shim.test.ts +6 -3
- package/src/__tests__/plugin-bootstrap.test.ts +23 -12
- package/src/__tests__/plugin-registry.test.ts +49 -3
- package/src/__tests__/plugin-types.test.ts +70 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +1 -4
- package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +3 -1
- package/src/__tests__/subagent-notify-parent.test.ts +3 -1
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +286 -54
- package/src/acp/__tests__/client-handler.test.ts +0 -40
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
- package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
- package/src/acp/agent-process.ts +1 -61
- package/src/acp/client-handler.ts +0 -31
- package/src/acp/prepare-agent-env.ts +29 -83
- package/src/acp/resolve-agent.test.ts +7 -320
- package/src/acp/resolve-agent.ts +18 -182
- package/src/acp/session-manager.ts +73 -495
- package/src/acp/types.ts +0 -8
- package/src/agent/compaction-circuit.ts +102 -60
- package/src/agent/loop.ts +59 -32
- package/src/api/responses/conversation-message.ts +1 -7
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +0 -1
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +31 -83
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
- package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
- package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +5 -14
- package/src/config/schemas/heartbeat.ts +0 -9
- package/src/context/strip-injections.ts +2 -8
- package/src/context/window-manager.ts +1 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
- package/src/daemon/conversation-agent-loop.ts +279 -62
- package/src/daemon/conversation-runtime-assembly.ts +69 -106
- package/src/daemon/conversation-store.ts +90 -9
- package/src/daemon/conversation-workspace.ts +0 -17
- package/src/daemon/conversation.ts +6 -0
- package/src/daemon/external-plugins-bootstrap.ts +11 -11
- package/src/daemon/handlers/conversations.ts +1 -3
- package/src/daemon/handlers/skills.ts +1 -4
- package/src/daemon/lifecycle.ts +0 -21
- package/src/daemon/server.ts +0 -2
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
- package/src/heartbeat/heartbeat-run-store.ts +1 -23
- package/src/heartbeat/heartbeat-service.ts +0 -26
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
- package/src/ipc/skill-routes/memory.ts +2 -4
- package/src/memory/conversation-starter-checkpoints.ts +0 -1
- package/src/memory/db-init.ts +0 -2
- package/src/memory/job-handlers/conversation-starters.ts +2 -13
- package/src/memory/jobs-worker.ts +1 -1
- package/src/memory/migrations/index.ts +0 -1
- package/src/memory/schema/acp.ts +0 -4
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +4 -13
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
- package/src/plugin-api/index.ts +5 -0
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
- package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
- package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +19 -8
- package/src/plugins/defaults/compaction/terminal.ts +73 -0
- package/src/plugins/defaults/index.ts +5 -3
- package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
- package/src/plugins/defaults/overflow-reduce/package.json +15 -0
- package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +293 -6
- package/src/plugins/registry.ts +37 -9
- package/src/plugins/types.ts +336 -32
- package/src/plugins/user-loader.ts +127 -30
- package/src/proactive-artifact/aux-message-injector.ts +1 -1
- package/src/proactive-artifact/job.test.ts +1 -1
- package/src/prompts/__tests__/system-prompt.test.ts +0 -6
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
- package/src/runtime/__tests__/agent-wake.test.ts +5 -5
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +3 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
- package/src/runtime/routes/acp-routes.test.ts +25 -89
- package/src/runtime/routes/acp-routes.ts +29 -81
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +10 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -15
- package/src/runtime/routes/conversation-starter-routes.ts +7 -13
- package/src/runtime/routes/conversations-import-routes.ts +7 -24
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -18
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/memory-v3-routes.ts +6 -16
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
- package/src/runtime/routes/work-items-routes.ts +4 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +4 -2
- package/src/subagent/manager.ts +5 -17
- package/src/tools/acp/list-agents.test.ts +1 -7
- package/src/tools/acp/spawn.test.ts +55 -158
- package/src/tools/acp/spawn.ts +72 -47
- package/src/tools/acp/steer.test.ts +8 -105
- package/src/tools/acp/steer.ts +17 -48
- package/src/tools/apps/executors.ts +8 -13
- package/src/tools/filesystem/write.ts +0 -34
- package/src/tools/subagent/spawn.ts +4 -2
- package/src/tools/ui-surface/definitions.ts +4 -25
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
- package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
- package/examples/plugins/echo/hooks/stop.ts +0 -16
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
- package/examples/plugins/echo/src/emit.ts +0 -19
- package/src/__tests__/compaction-circuit.test.ts +0 -258
- package/src/__tests__/compaction-direct.test.ts +0 -132
- package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
- package/src/acp/__tests__/agent-process.test.ts +0 -161
- package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
- package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
- package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
- package/src/acp/auto-install.test.ts +0 -196
- package/src/acp/auto-install.ts +0 -177
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/acp/resume-hint.ts +0 -25
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
- package/src/daemon/conversation-registry.ts +0 -159
- package/src/daemon/overflow-reduction-loop.ts +0 -230
- package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
- package/src/plugins/defaults/compaction/compact.ts +0 -59
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
- package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
- package/src/tools/acp/context.ts +0 -20
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for `
|
|
3
|
-
* reducer driver.
|
|
2
|
+
* Unit tests for the default `overflowReduce` plugin (PR 23).
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Two goals:
|
|
5
|
+
* 1. The default middleware produces results **identical** to the historical
|
|
6
|
+
* inline tier loop for a golden set of over-budget histories. We exercise
|
|
7
|
+
* this by running the same inputs through two paths — the pipeline and a
|
|
8
|
+
* faithful re-implementation of the pre-PR-23 inline loop — and asserting
|
|
9
|
+
* the final `(messages, runMessages, injectionMode, reducerState,
|
|
10
|
+
* reducerCompacted, attempts)` tuple matches byte-for-byte.
|
|
11
|
+
* 2. A user-registered spy middleware observes **every** reduction attempt
|
|
12
|
+
* when wrapped around the default. This covers the onion-composition
|
|
13
|
+
* contract: the spy sees each call from the outside and can count
|
|
14
|
+
* iterations without changing reducer behavior.
|
|
15
|
+
*
|
|
16
|
+
* The test creates its own plugin registry via
|
|
17
|
+
* `resetPluginRegistryForTests()` and re-registers the default before each
|
|
18
|
+
* case so the registry is deterministic across runs.
|
|
12
19
|
*/
|
|
13
20
|
|
|
14
|
-
import { describe, expect, test } from "bun:test";
|
|
21
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
15
22
|
|
|
16
23
|
import { estimatePromptTokens } from "../context/token-estimator.js";
|
|
17
24
|
import type {
|
|
@@ -25,10 +32,22 @@ import {
|
|
|
25
32
|
type ReducerState,
|
|
26
33
|
} from "../daemon/context-overflow-reducer.js";
|
|
27
34
|
import type { InjectionMode } from "../daemon/conversation-runtime-assembly.js";
|
|
35
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
36
|
+
import defaultOverflowReduceMiddleware from "../plugins/defaults/overflow-reduce/middlewares/overflowReduce.js";
|
|
37
|
+
import { defaultOverflowReducePlugin } from "../plugins/defaults/overflow-reduce/register.js";
|
|
38
|
+
import { runPipeline } from "../plugins/pipeline.js";
|
|
28
39
|
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
getMiddlewaresFor,
|
|
41
|
+
registerPlugin,
|
|
42
|
+
resetPluginRegistryForTests,
|
|
43
|
+
} from "../plugins/registry.js";
|
|
44
|
+
import type {
|
|
45
|
+
Middleware,
|
|
46
|
+
OverflowReduceArgs,
|
|
47
|
+
OverflowReduceResult,
|
|
48
|
+
Plugin,
|
|
49
|
+
TurnContext,
|
|
50
|
+
} from "../plugins/types.js";
|
|
32
51
|
import type { Message } from "../providers/types.js";
|
|
33
52
|
|
|
34
53
|
// ── Fixtures ────────────────────────────────────────────────────────────────
|
|
@@ -68,6 +87,21 @@ const CONTEXT_WINDOW = {
|
|
|
68
87
|
},
|
|
69
88
|
};
|
|
70
89
|
|
|
90
|
+
const TRUST: TrustContext = {
|
|
91
|
+
sourceChannel: "vellum",
|
|
92
|
+
trustClass: "guardian",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function makeTurnContext(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
96
|
+
return {
|
|
97
|
+
requestId: "req-overflow-test",
|
|
98
|
+
conversationId: "conv-overflow-test",
|
|
99
|
+
turnIndex: 0,
|
|
100
|
+
trust: TRUST,
|
|
101
|
+
...overrides,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
71
105
|
/**
|
|
72
106
|
* Minimal compaction stub — always compacts to a one-message summary so the
|
|
73
107
|
* reducer's forced-compaction tier succeeds. Mirrors `makeCompactFn` from
|
|
@@ -112,9 +146,9 @@ function makeCompactFn(
|
|
|
112
146
|
}
|
|
113
147
|
|
|
114
148
|
/**
|
|
115
|
-
* Faithful re-implementation of the
|
|
149
|
+
* Faithful re-implementation of the pre-PR-23 inline tier loop — lives in
|
|
116
150
|
* this test file rather than the production module so we have an immutable
|
|
117
|
-
* baseline
|
|
151
|
+
* baseline the default middleware can be diffed against. If either
|
|
118
152
|
* implementation drifts, the golden-output cases below fail.
|
|
119
153
|
*
|
|
120
154
|
* The function intentionally avoids any side effects on external state — no
|
|
@@ -233,9 +267,9 @@ function buildArgs(messages: Message[]): {
|
|
|
233
267
|
|
|
234
268
|
// Identity reinject: the test harness does not exercise the full
|
|
235
269
|
// `applyRuntimeInjections` pipeline; it simply tracks how many times the
|
|
236
|
-
// orchestrator would have been asked to rebuild `runMessages
|
|
237
|
-
//
|
|
238
|
-
// runtime blocks.
|
|
270
|
+
// orchestrator would have been asked to rebuild `runMessages` so the spy
|
|
271
|
+
// middleware can attribute each iteration. Returns the reducer's latest
|
|
272
|
+
// `messages` untouched — real orchestrator code re-injects runtime blocks.
|
|
239
273
|
const reinjectForMode = async (
|
|
240
274
|
reducedMessages: Message[],
|
|
241
275
|
mode: InjectionMode,
|
|
@@ -261,7 +295,7 @@ function buildArgs(messages: Message[]): {
|
|
|
261
295
|
toolTokenBudget: 0,
|
|
262
296
|
maxAttempts: CONTEXT_WINDOW.overflowRecovery.maxAttempts,
|
|
263
297
|
// `OverflowReduceArgs.compactFn` types `options` as `unknown` to avoid
|
|
264
|
-
// leaking the `ContextWindowCompactOptions` shape into the
|
|
298
|
+
// leaking the `ContextWindowCompactOptions` shape into the plugin
|
|
265
299
|
// surface. The test helper produces a real `ContextWindowCompactOptions`
|
|
266
300
|
// signature, so we trampoline through a widened wrapper.
|
|
267
301
|
compactFn: (msgs, signal, opts) =>
|
|
@@ -281,10 +315,14 @@ function buildArgs(messages: Message[]): {
|
|
|
281
315
|
|
|
282
316
|
// ── Test suite ──────────────────────────────────────────────────────────────
|
|
283
317
|
|
|
284
|
-
describe("
|
|
285
|
-
|
|
318
|
+
describe("overflow-reduce pipeline", () => {
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
resetPluginRegistryForTests();
|
|
321
|
+
registerPlugin(defaultOverflowReducePlugin);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe("default middleware matches historical inline loop", () => {
|
|
286
325
|
test("large tool-result history — identical reduced output", async () => {
|
|
287
|
-
// GIVEN an over-budget history dominated by a large tool result.
|
|
288
326
|
const longToolResult = "r".repeat(8000);
|
|
289
327
|
const goldenHistory: Message[] = [
|
|
290
328
|
msg("user", "Start"),
|
|
@@ -294,13 +332,27 @@ describe("runOverflowReductionLoop", () => {
|
|
|
294
332
|
msg("user", "Next"),
|
|
295
333
|
];
|
|
296
334
|
|
|
297
|
-
|
|
298
|
-
// direct call and the inline baseline never share a `compactFn`.
|
|
299
|
-
const directBuild = buildArgs(goldenHistory);
|
|
335
|
+
const pipelineBuild = buildArgs(goldenHistory);
|
|
300
336
|
const inlineBuild = buildArgs(goldenHistory);
|
|
301
337
|
|
|
302
|
-
//
|
|
303
|
-
|
|
338
|
+
// Run both paths against the SAME fixture. `buildArgs` gives each
|
|
339
|
+
// call its own `compactFn` instance so nothing leaks between runs.
|
|
340
|
+
const pipelineResult = await runPipeline<
|
|
341
|
+
OverflowReduceArgs,
|
|
342
|
+
OverflowReduceResult
|
|
343
|
+
>(
|
|
344
|
+
"overflowReduce",
|
|
345
|
+
getMiddlewaresFor("overflowReduce"),
|
|
346
|
+
// Sentinel terminal — the default middleware doesn't call next,
|
|
347
|
+
// so this must never fire. Assert that invariant here.
|
|
348
|
+
async () => {
|
|
349
|
+
throw new Error("terminal unexpectedly reached");
|
|
350
|
+
},
|
|
351
|
+
pipelineBuild.args,
|
|
352
|
+
makeTurnContext(),
|
|
353
|
+
30000,
|
|
354
|
+
);
|
|
355
|
+
|
|
304
356
|
const inlineResult = await runInlineBaseline({
|
|
305
357
|
messages: goldenHistory,
|
|
306
358
|
runMessages: goldenHistory,
|
|
@@ -315,27 +367,41 @@ describe("runOverflowReductionLoop", () => {
|
|
|
315
367
|
estimatePostInjection: inlineBuild.args.estimatePostInjection,
|
|
316
368
|
});
|
|
317
369
|
|
|
318
|
-
//
|
|
319
|
-
expect(
|
|
320
|
-
expect(
|
|
321
|
-
expect(
|
|
322
|
-
expect(
|
|
323
|
-
expect(
|
|
324
|
-
|
|
370
|
+
// Byte-for-byte match across every field the orchestrator relies on.
|
|
371
|
+
expect(pipelineResult.messages).toEqual(inlineResult.messages);
|
|
372
|
+
expect(pipelineResult.runMessages).toEqual(inlineResult.runMessages);
|
|
373
|
+
expect(pipelineResult.injectionMode).toBe(inlineResult.injectionMode);
|
|
374
|
+
expect(pipelineResult.reducerState).toEqual(inlineResult.reducerState);
|
|
375
|
+
expect(pipelineResult.reducerCompacted).toBe(
|
|
376
|
+
inlineResult.reducerCompacted,
|
|
377
|
+
);
|
|
378
|
+
expect(pipelineResult.attempts).toBe(inlineResult.attempts);
|
|
325
379
|
});
|
|
326
380
|
|
|
327
381
|
test("small conversation that fits after first reduction — single attempt", async () => {
|
|
328
|
-
//
|
|
382
|
+
// A history that's already within budget so the first `applyForcedCompaction`
|
|
383
|
+
// brings us under — the loop must exit without iterating further.
|
|
329
384
|
const smallHistory: Message[] = [
|
|
330
385
|
msg("user", "Hello"),
|
|
331
386
|
msg("assistant", "Hi there — how can I help?"),
|
|
332
387
|
];
|
|
333
388
|
|
|
334
|
-
const
|
|
389
|
+
const pipelineBuild = buildArgs(smallHistory);
|
|
335
390
|
const inlineBuild = buildArgs(smallHistory);
|
|
336
391
|
|
|
337
|
-
|
|
338
|
-
|
|
392
|
+
const pipelineResult = await runPipeline<
|
|
393
|
+
OverflowReduceArgs,
|
|
394
|
+
OverflowReduceResult
|
|
395
|
+
>(
|
|
396
|
+
"overflowReduce",
|
|
397
|
+
getMiddlewaresFor("overflowReduce"),
|
|
398
|
+
async () => {
|
|
399
|
+
throw new Error("terminal unexpectedly reached");
|
|
400
|
+
},
|
|
401
|
+
pipelineBuild.args,
|
|
402
|
+
makeTurnContext(),
|
|
403
|
+
30000,
|
|
404
|
+
);
|
|
339
405
|
const inlineResult = await runInlineBaseline({
|
|
340
406
|
messages: smallHistory,
|
|
341
407
|
runMessages: smallHistory,
|
|
@@ -350,17 +416,149 @@ describe("runOverflowReductionLoop", () => {
|
|
|
350
416
|
estimatePostInjection: inlineBuild.args.estimatePostInjection,
|
|
351
417
|
});
|
|
352
418
|
|
|
353
|
-
|
|
354
|
-
expect(
|
|
355
|
-
expect(
|
|
356
|
-
expect(
|
|
357
|
-
|
|
419
|
+
expect(pipelineResult.attempts).toBe(inlineResult.attempts);
|
|
420
|
+
expect(pipelineResult.attempts).toBeGreaterThanOrEqual(1);
|
|
421
|
+
expect(pipelineResult.messages).toEqual(inlineResult.messages);
|
|
422
|
+
expect(pipelineResult.reducerCompacted).toBe(
|
|
423
|
+
inlineResult.reducerCompacted,
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe("spy middleware observes each reduction attempt", () => {
|
|
429
|
+
test("spy sees one invocation when the default converges in one step", async () => {
|
|
430
|
+
const history: Message[] = [msg("user", "Hello"), msg("assistant", "Hi")];
|
|
431
|
+
|
|
432
|
+
// Spy tracks the args passed into its layer. It must forward via
|
|
433
|
+
// `next` so the default still fires.
|
|
434
|
+
const spyCalls: Array<{
|
|
435
|
+
hadMessages: number;
|
|
436
|
+
budget: number;
|
|
437
|
+
attempts: number;
|
|
438
|
+
}> = [];
|
|
439
|
+
const spy: Middleware<OverflowReduceArgs, OverflowReduceResult> =
|
|
440
|
+
async function spyMiddleware(args, next, _ctx) {
|
|
441
|
+
spyCalls.push({
|
|
442
|
+
hadMessages: args.messages.length,
|
|
443
|
+
budget: args.preflightBudget,
|
|
444
|
+
attempts: 0, // populated after next() from the result
|
|
445
|
+
});
|
|
446
|
+
const result = await next(args);
|
|
447
|
+
spyCalls[spyCalls.length - 1]!.attempts = result.attempts;
|
|
448
|
+
return result;
|
|
449
|
+
};
|
|
450
|
+
const spyPlugin: Plugin = {
|
|
451
|
+
manifest: {
|
|
452
|
+
name: "spy-overflow",
|
|
453
|
+
version: "0.0.1",
|
|
454
|
+
},
|
|
455
|
+
middleware: { overflowReduce: spy },
|
|
456
|
+
};
|
|
457
|
+
// Register spy first so it wraps the default (registration order =
|
|
458
|
+
// outer→inner). The default therefore runs as the spy's downstream.
|
|
459
|
+
resetPluginRegistryForTests();
|
|
460
|
+
registerPlugin(spyPlugin);
|
|
461
|
+
registerPlugin(defaultOverflowReducePlugin);
|
|
462
|
+
|
|
463
|
+
const { args } = buildArgs(history);
|
|
464
|
+
const result = await runPipeline<
|
|
465
|
+
OverflowReduceArgs,
|
|
466
|
+
OverflowReduceResult
|
|
467
|
+
>(
|
|
468
|
+
"overflowReduce",
|
|
469
|
+
getMiddlewaresFor("overflowReduce"),
|
|
470
|
+
async () => {
|
|
471
|
+
throw new Error("terminal unexpectedly reached");
|
|
472
|
+
},
|
|
473
|
+
args,
|
|
474
|
+
makeTurnContext(),
|
|
475
|
+
30000,
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
// Spy was called exactly once — the pipeline invokes each middleware
|
|
479
|
+
// once per pipeline call, not once per reducer iteration. Iteration
|
|
480
|
+
// count shows up in the result.attempts field.
|
|
481
|
+
expect(spyCalls).toHaveLength(1);
|
|
482
|
+
expect(spyCalls[0]?.hadMessages).toBe(2);
|
|
483
|
+
expect(spyCalls[0]?.budget).toBe(1000);
|
|
484
|
+
expect(spyCalls[0]?.attempts).toBe(result.attempts);
|
|
485
|
+
expect(result.attempts).toBeGreaterThanOrEqual(1);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("spy can short-circuit the default by not calling next", async () => {
|
|
489
|
+
const history: Message[] = [msg("user", "Hi")];
|
|
490
|
+
|
|
491
|
+
const shortCircuit: Middleware<OverflowReduceArgs, OverflowReduceResult> =
|
|
492
|
+
async function shortCircuitMiddleware(args, _next, _ctx) {
|
|
493
|
+
// Returns a synthetic "no-op" result — the default is never invoked.
|
|
494
|
+
return {
|
|
495
|
+
messages: args.messages,
|
|
496
|
+
runMessages: args.runMessages,
|
|
497
|
+
injectionMode: "minimal",
|
|
498
|
+
reducerState: {
|
|
499
|
+
appliedTiers: ["injection_downgrade"],
|
|
500
|
+
injectionMode: "minimal",
|
|
501
|
+
exhausted: true,
|
|
502
|
+
},
|
|
503
|
+
reducerCompacted: false,
|
|
504
|
+
attempts: 0,
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
resetPluginRegistryForTests();
|
|
508
|
+
registerPlugin({
|
|
509
|
+
manifest: {
|
|
510
|
+
name: "short-circuit-overflow",
|
|
511
|
+
version: "0.0.1",
|
|
512
|
+
},
|
|
513
|
+
middleware: { overflowReduce: shortCircuit },
|
|
514
|
+
});
|
|
515
|
+
registerPlugin(defaultOverflowReducePlugin);
|
|
516
|
+
|
|
517
|
+
const { args, compactionResults, reinjectCalls } = buildArgs(history);
|
|
518
|
+
const result = await runPipeline<
|
|
519
|
+
OverflowReduceArgs,
|
|
520
|
+
OverflowReduceResult
|
|
521
|
+
>(
|
|
522
|
+
"overflowReduce",
|
|
523
|
+
getMiddlewaresFor("overflowReduce"),
|
|
524
|
+
async () => {
|
|
525
|
+
throw new Error("terminal unexpectedly reached");
|
|
526
|
+
},
|
|
527
|
+
args,
|
|
528
|
+
makeTurnContext(),
|
|
529
|
+
30000,
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
// Because the outer middleware short-circuited, the default never
|
|
533
|
+
// ran — no compactFn invocations, no reinject callbacks.
|
|
534
|
+
expect(result.injectionMode).toBe("minimal");
|
|
535
|
+
expect(result.attempts).toBe(0);
|
|
536
|
+
expect(compactionResults).toHaveLength(0);
|
|
537
|
+
expect(reinjectCalls).toHaveLength(0);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe("direct middleware invocation", () => {
|
|
542
|
+
test("default middleware without the pipeline runner still executes the tier loop", async () => {
|
|
543
|
+
const history: Message[] = [msg("user", "Hi")];
|
|
544
|
+
const { args } = buildArgs(history);
|
|
545
|
+
|
|
546
|
+
const result = await defaultOverflowReduceMiddleware(
|
|
547
|
+
args,
|
|
548
|
+
async () => {
|
|
549
|
+
throw new Error("next should not be invoked by the default");
|
|
550
|
+
},
|
|
551
|
+
makeTurnContext(),
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
expect(result.attempts).toBeGreaterThanOrEqual(1);
|
|
555
|
+
expect(result.reducerState.appliedTiers.length).toBeGreaterThanOrEqual(1);
|
|
358
556
|
});
|
|
359
557
|
});
|
|
360
558
|
|
|
361
559
|
describe("abort signal propagation", () => {
|
|
362
|
-
test("bails between iterations when abortSignal fires", async () => {
|
|
363
|
-
//
|
|
560
|
+
test("middleware bails between iterations when abortSignal fires", async () => {
|
|
561
|
+
// History that won't converge in one step — multiple iterations.
|
|
364
562
|
const longToolResult = "r".repeat(8000);
|
|
365
563
|
const history: Message[] = [
|
|
366
564
|
msg("user", "Start"),
|
|
@@ -371,8 +569,9 @@ describe("runOverflowReductionLoop", () => {
|
|
|
371
569
|
|
|
372
570
|
const controller = new AbortController();
|
|
373
571
|
const build = buildArgs(history);
|
|
374
|
-
//
|
|
375
|
-
//
|
|
572
|
+
// Abort on the first `estimatePostInjection` — simulates the
|
|
573
|
+
// pipeline-level timeout firing mid-turn. The next loop iteration
|
|
574
|
+
// must see the signal and throw rather than starting another round.
|
|
376
575
|
let estimateCalls = 0;
|
|
377
576
|
const aborting: OverflowReduceArgs = {
|
|
378
577
|
...build.args,
|
|
@@ -380,18 +579,26 @@ describe("runOverflowReductionLoop", () => {
|
|
|
380
579
|
estimatePostInjection: () => {
|
|
381
580
|
estimateCalls++;
|
|
382
581
|
if (estimateCalls === 1) controller.abort();
|
|
582
|
+
// Return a value that guarantees another iteration would fire
|
|
583
|
+
// without the abort gate.
|
|
383
584
|
return build.args.preflightBudget + 1_000_000;
|
|
384
585
|
},
|
|
385
586
|
};
|
|
386
587
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
588
|
+
await expect(
|
|
589
|
+
defaultOverflowReduceMiddleware(
|
|
590
|
+
aborting,
|
|
591
|
+
async () => {
|
|
592
|
+
throw new Error("next should not be invoked");
|
|
593
|
+
},
|
|
594
|
+
makeTurnContext(),
|
|
595
|
+
),
|
|
596
|
+
).rejects.toThrow();
|
|
597
|
+
// Exactly one iteration ran; the abort gate stopped the next round.
|
|
390
598
|
expect(estimateCalls).toBe(1);
|
|
391
599
|
});
|
|
392
600
|
|
|
393
|
-
test("refuses to start when abortSignal is already aborted", async () => {
|
|
394
|
-
// GIVEN an already-aborted signal.
|
|
601
|
+
test("middleware refuses to start when abortSignal is already aborted", async () => {
|
|
395
602
|
const history: Message[] = [msg("user", "Hi")];
|
|
396
603
|
const controller = new AbortController();
|
|
397
604
|
controller.abort();
|
|
@@ -401,9 +608,16 @@ describe("runOverflowReductionLoop", () => {
|
|
|
401
608
|
abortSignal: controller.signal,
|
|
402
609
|
};
|
|
403
610
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
611
|
+
await expect(
|
|
612
|
+
defaultOverflowReduceMiddleware(
|
|
613
|
+
args,
|
|
614
|
+
async () => {
|
|
615
|
+
throw new Error("next should not be invoked");
|
|
616
|
+
},
|
|
617
|
+
makeTurnContext(),
|
|
618
|
+
),
|
|
619
|
+
).rejects.toThrow();
|
|
620
|
+
// Reducer never ran — zero compaction and reinject callbacks observed.
|
|
407
621
|
expect(build.compactionResults).toHaveLength(0);
|
|
408
622
|
expect(build.reinjectCalls).toHaveLength(0);
|
|
409
623
|
});
|
|
@@ -411,10 +625,10 @@ describe("runOverflowReductionLoop", () => {
|
|
|
411
625
|
|
|
412
626
|
describe("reinjectForMode two-flag semantics", () => {
|
|
413
627
|
test("stepCompacted reflects current iteration; accumulatedCompacted stays sticky", async () => {
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
// run other tiers (stepCompacted=false), but
|
|
417
|
-
// remain true for slack suppression.
|
|
628
|
+
// Force multiple iterations by returning over-budget until the loop
|
|
629
|
+
// exits on maxAttempts. First iteration compacts (stepCompacted=true);
|
|
630
|
+
// subsequent iterations run other tiers (stepCompacted=false), but
|
|
631
|
+
// accumulatedCompacted must remain true for slack suppression.
|
|
418
632
|
const longToolResult = "r".repeat(8000);
|
|
419
633
|
const history: Message[] = [
|
|
420
634
|
msg("user", "Start"),
|
|
@@ -428,13 +642,19 @@ describe("runOverflowReductionLoop", () => {
|
|
|
428
642
|
estimatePostInjection: () => build.args.preflightBudget + 1_000_000,
|
|
429
643
|
};
|
|
430
644
|
|
|
431
|
-
|
|
432
|
-
|
|
645
|
+
await defaultOverflowReduceMiddleware(
|
|
646
|
+
overBudget,
|
|
647
|
+
async () => {
|
|
648
|
+
throw new Error("next should not be invoked");
|
|
649
|
+
},
|
|
650
|
+
makeTurnContext(),
|
|
651
|
+
);
|
|
433
652
|
|
|
434
|
-
//
|
|
653
|
+
// At least one compaction attempt happened.
|
|
435
654
|
expect(build.reinjectCalls.length).toBeGreaterThanOrEqual(1);
|
|
436
|
-
//
|
|
437
|
-
// every subsequent
|
|
655
|
+
// The first iteration that compacted set accumulatedCompacted=true,
|
|
656
|
+
// and every subsequent call continues to see it true — even when
|
|
657
|
+
// that iteration's own step did NOT compact.
|
|
438
658
|
const firstCompactedAt = build.reinjectCalls.findIndex(
|
|
439
659
|
(c) => c.stepCompacted,
|
|
440
660
|
);
|