@vellumai/assistant 0.8.9-staging.1 → 0.8.9-staging.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +6 -6
- package/docs/activation-funnel-telemetry.md +310 -0
- package/openapi.yaml +6 -7
- package/package.json +1 -1
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +33 -18
- package/src/__tests__/agent-loop-output-hooks.test.ts +13 -13
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/compaction-direct.test.ts +32 -18
- package/src/__tests__/compaction-events.test.ts +2 -2
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +6 -6
- package/src/__tests__/context-window-manager-compact-retry.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +19 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +48 -29
- package/src/__tests__/conversation-agent-loop.test.ts +48 -96
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +40 -0
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +53 -50
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -0
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +44 -0
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/history-repair-hook.test.ts +2 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +69 -32
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +5 -7
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- package/src/acp/__tests__/helpers/exec-file-stub.ts +12 -7
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -40
- package/src/acp/__tests__/session-manager-resume.test.ts +179 -27
- package/src/acp/auto-install.test.ts +130 -46
- package/src/acp/auto-install.ts +86 -31
- package/src/acp/prepare-agent-env.ts +8 -9
- package/src/acp/resolve-agent.test.ts +26 -179
- package/src/acp/resolve-agent.ts +35 -122
- package/src/acp/resume-hint.ts +1 -3
- package/src/acp/session-manager.ts +32 -20
- package/src/acp/types.ts +0 -8
- package/src/agent/loop.ts +47 -45
- package/src/cli/lib/__tests__/install-from-github.test.ts +232 -29
- package/src/cli/lib/__tests__/plugin-details.test.ts +28 -19
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +57 -7
- package/src/cli/lib/__tests__/search-plugins.test.ts +17 -10
- package/src/cli/lib/install-from-github.ts +258 -41
- package/src/cli/lib/plugin-details.ts +20 -13
- package/src/cli/lib/plugin-marketplace.ts +23 -5
- package/src/cli/lib/search-plugins.ts +14 -8
- package/src/config/acp-defaults.ts +3 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +16 -29
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
- package/src/config/bundled-skills/app-builder/TOOLS.json +43 -2
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +4 -19
- package/src/config/schemas/call-site-catalog.ts +0 -14
- package/src/config/schemas/llm.ts +0 -2
- package/src/context/compactor.ts +22 -4
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +8 -1
- package/src/daemon/conversation-agent-loop.ts +93 -135
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +14 -5
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +37 -1
- package/src/daemon/conversation-surfaces.ts +141 -3
- package/src/daemon/conversation.ts +66 -11
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +0 -7
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/memory/__tests__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/adapters/telegram.ts +7 -1
- package/src/plugin-api/constants.ts +2 -2
- package/src/plugin-api/index.ts +2 -2
- package/src/plugin-api/types.ts +19 -5
- package/src/plugins/defaults/compaction/compact.ts +24 -15
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +10 -10
- package/src/plugins/defaults/compaction/manager-store.ts +14 -1
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +12 -12
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +6 -23
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +36 -43
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +37 -2
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +25 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +71 -39
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +29 -4
- package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
- package/src/runtime/routes/acp-routes.test.ts +3 -20
- package/src/runtime/routes/conversation-routes.ts +2 -0
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +10 -9
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/list-agents.test.ts +4 -20
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +103 -235
- package/src/tools/acp/spawn.ts +10 -120
- package/src/tools/apps/executors.ts +153 -42
- package/src/tools/browser/browser-execution.ts +12 -2
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/ui-surface/definitions.ts +7 -0
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
|
@@ -102,23 +102,26 @@ mock.module("../context/token-estimator.js", () => ({
|
|
|
102
102
|
estimateToolsTokens: () => 0,
|
|
103
103
|
}));
|
|
104
104
|
|
|
105
|
-
mock.module(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
105
|
+
mock.module(
|
|
106
|
+
"../plugins/defaults/compaction/context-overflow-reducer.js",
|
|
107
|
+
() => ({
|
|
108
|
+
createInitialReducerState: () => ({
|
|
109
|
+
appliedTiers: [],
|
|
110
|
+
injectionMode: "full" as const,
|
|
111
|
+
exhausted: false,
|
|
112
|
+
}),
|
|
113
|
+
reduceContextOverflow: async (msgs: Message[]) => ({
|
|
114
|
+
messages: msgs,
|
|
115
|
+
tier: "forced_compaction",
|
|
116
|
+
state: {
|
|
117
|
+
appliedTiers: ["forced_compaction"],
|
|
118
|
+
injectionMode: "full",
|
|
119
|
+
exhausted: true,
|
|
120
|
+
},
|
|
121
|
+
estimatedTokens: 1000,
|
|
122
|
+
}),
|
|
120
123
|
}),
|
|
121
|
-
|
|
124
|
+
);
|
|
122
125
|
|
|
123
126
|
mock.module("../daemon/context-overflow-policy.js", () => ({
|
|
124
127
|
resolveOverflowAction: () => "fail_gracefully",
|
|
@@ -39,6 +39,20 @@ const conversationRuntimeAssemblyRealSnapshot = {
|
|
|
39
39
|
|
|
40
40
|
// ── Module mocks (must precede imports of the module under test) ─────
|
|
41
41
|
|
|
42
|
+
// The real AgentLoop resolves the per-conversation ContextWindowManager from
|
|
43
|
+
// the compaction store keyed by conversationId. These overflow tests build
|
|
44
|
+
// fake conversations whose manager is a canned stub, so register each stub in a
|
|
45
|
+
// map the mocked store reads from.
|
|
46
|
+
const fakeContextWindowManagers = new Map<string, unknown>();
|
|
47
|
+
mock.module("../plugins/defaults/compaction/manager-store.js", () => ({
|
|
48
|
+
createContextWindowManager: () => undefined,
|
|
49
|
+
getContextWindowManager: (conversationId: string) =>
|
|
50
|
+
fakeContextWindowManagers.get(conversationId),
|
|
51
|
+
disposeContextWindowManager: (conversationId: string) => {
|
|
52
|
+
fakeContextWindowManagers.delete(conversationId);
|
|
53
|
+
},
|
|
54
|
+
}));
|
|
55
|
+
|
|
42
56
|
mock.module("../util/logger.js", () => ({
|
|
43
57
|
getLogger: () =>
|
|
44
58
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
@@ -137,35 +151,38 @@ mock.module("../context/token-estimator.js", () => ({
|
|
|
137
151
|
let mockReducerStepFn:
|
|
138
152
|
| ((msgs: Message[], cfg: unknown, state: unknown) => unknown)
|
|
139
153
|
| null = null;
|
|
140
|
-
mock.module(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
154
|
+
mock.module(
|
|
155
|
+
"../plugins/defaults/compaction/context-overflow-reducer.js",
|
|
156
|
+
() => ({
|
|
157
|
+
createInitialReducerState: () => ({
|
|
158
|
+
appliedTiers: [],
|
|
159
|
+
injectionMode: "full" as const,
|
|
160
|
+
exhausted: false,
|
|
161
|
+
}),
|
|
162
|
+
reduceContextOverflow: async (
|
|
163
|
+
msgs: Message[],
|
|
164
|
+
cfg: unknown,
|
|
165
|
+
state: unknown,
|
|
166
|
+
) => {
|
|
167
|
+
if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
|
|
168
|
+
return {
|
|
169
|
+
messages: msgs,
|
|
170
|
+
tier: "forced_compaction",
|
|
171
|
+
state: {
|
|
172
|
+
appliedTiers: [
|
|
173
|
+
"forced_compaction",
|
|
174
|
+
"tool_result_truncation",
|
|
175
|
+
"media_stubbing",
|
|
176
|
+
"injection_downgrade",
|
|
177
|
+
],
|
|
178
|
+
injectionMode: "full",
|
|
179
|
+
exhausted: true,
|
|
180
|
+
},
|
|
181
|
+
estimatedTokens: 1000,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
145
184
|
}),
|
|
146
|
-
|
|
147
|
-
msgs: Message[],
|
|
148
|
-
cfg: unknown,
|
|
149
|
-
state: unknown,
|
|
150
|
-
) => {
|
|
151
|
-
if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
|
|
152
|
-
return {
|
|
153
|
-
messages: msgs,
|
|
154
|
-
tier: "forced_compaction",
|
|
155
|
-
state: {
|
|
156
|
-
appliedTiers: [
|
|
157
|
-
"forced_compaction",
|
|
158
|
-
"tool_result_truncation",
|
|
159
|
-
"media_stubbing",
|
|
160
|
-
"injection_downgrade",
|
|
161
|
-
],
|
|
162
|
-
injectionMode: "full",
|
|
163
|
-
exhausted: true,
|
|
164
|
-
},
|
|
165
|
-
estimatedTokens: 1000,
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
}));
|
|
185
|
+
);
|
|
169
186
|
|
|
170
187
|
// Policy: default to fail_gracefully
|
|
171
188
|
let mockOverflowAction: string = "fail_gracefully";
|
|
@@ -471,7 +488,7 @@ function makeCtx(
|
|
|
471
488
|
toolExecutor,
|
|
472
489
|
});
|
|
473
490
|
|
|
474
|
-
|
|
491
|
+
const ctx = {
|
|
475
492
|
conversationId: "test-conv",
|
|
476
493
|
messages: [
|
|
477
494
|
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
@@ -582,6 +599,8 @@ function makeCtx(
|
|
|
582
599
|
|
|
583
600
|
...ctxOverrides,
|
|
584
601
|
} as unknown as Conversation;
|
|
602
|
+
fakeContextWindowManagers.set(conversationId, ctx.contextWindowManager);
|
|
603
|
+
return ctx;
|
|
585
604
|
}
|
|
586
605
|
|
|
587
606
|
/**
|
|
@@ -28,6 +28,20 @@ let mockUiConfig: { userTimezone?: string; detectedTimezone?: string } = {};
|
|
|
28
28
|
|
|
29
29
|
// ── Module mocks (must precede imports of the module under test) ─────
|
|
30
30
|
|
|
31
|
+
// The real AgentLoop resolves the per-conversation ContextWindowManager from
|
|
32
|
+
// the compaction store keyed by conversationId. These orchestrator tests build
|
|
33
|
+
// fake conversations whose manager is a canned stub, so register each stub in a
|
|
34
|
+
// map the mocked store reads from.
|
|
35
|
+
const fakeContextWindowManagers = new Map<string, unknown>();
|
|
36
|
+
mock.module("../plugins/defaults/compaction/manager-store.js", () => ({
|
|
37
|
+
createContextWindowManager: () => undefined,
|
|
38
|
+
getContextWindowManager: (conversationId: string) =>
|
|
39
|
+
fakeContextWindowManagers.get(conversationId),
|
|
40
|
+
disposeContextWindowManager: (conversationId: string) => {
|
|
41
|
+
fakeContextWindowManagers.delete(conversationId);
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
31
45
|
mock.module("../util/logger.js", () => ({
|
|
32
46
|
getLogger: () =>
|
|
33
47
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
@@ -100,35 +114,38 @@ mock.module("../context/token-estimator.js", () => ({
|
|
|
100
114
|
let mockReducerStepFn:
|
|
101
115
|
| ((msgs: Message[], cfg: unknown, state: unknown) => unknown)
|
|
102
116
|
| null = null;
|
|
103
|
-
mock.module(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
mock.module(
|
|
118
|
+
"../plugins/defaults/compaction/context-overflow-reducer.js",
|
|
119
|
+
() => ({
|
|
120
|
+
createInitialReducerState: () => ({
|
|
121
|
+
appliedTiers: [],
|
|
122
|
+
injectionMode: "full" as const,
|
|
123
|
+
exhausted: false,
|
|
124
|
+
}),
|
|
125
|
+
reduceContextOverflow: async (
|
|
126
|
+
msgs: Message[],
|
|
127
|
+
cfg: unknown,
|
|
128
|
+
state: unknown,
|
|
129
|
+
) => {
|
|
130
|
+
if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
|
|
131
|
+
return {
|
|
132
|
+
messages: msgs,
|
|
133
|
+
tier: "forced_compaction",
|
|
134
|
+
state: {
|
|
135
|
+
appliedTiers: [
|
|
136
|
+
"forced_compaction",
|
|
137
|
+
"tool_result_truncation",
|
|
138
|
+
"media_stubbing",
|
|
139
|
+
"injection_downgrade",
|
|
140
|
+
],
|
|
141
|
+
injectionMode: "full",
|
|
142
|
+
exhausted: true,
|
|
143
|
+
},
|
|
144
|
+
estimatedTokens: 1000,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
108
147
|
}),
|
|
109
|
-
|
|
110
|
-
msgs: Message[],
|
|
111
|
-
cfg: unknown,
|
|
112
|
-
state: unknown,
|
|
113
|
-
) => {
|
|
114
|
-
if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
|
|
115
|
-
return {
|
|
116
|
-
messages: msgs,
|
|
117
|
-
tier: "forced_compaction",
|
|
118
|
-
state: {
|
|
119
|
-
appliedTiers: [
|
|
120
|
-
"forced_compaction",
|
|
121
|
-
"tool_result_truncation",
|
|
122
|
-
"media_stubbing",
|
|
123
|
-
"injection_downgrade",
|
|
124
|
-
],
|
|
125
|
-
injectionMode: "full",
|
|
126
|
-
exhausted: true,
|
|
127
|
-
},
|
|
128
|
-
estimatedTokens: 1000,
|
|
129
|
-
};
|
|
130
|
-
},
|
|
131
|
-
}));
|
|
148
|
+
);
|
|
132
149
|
|
|
133
150
|
// Policy: default to fail_gracefully
|
|
134
151
|
let mockOverflowAction: string = "fail_gracefully";
|
|
@@ -520,17 +537,6 @@ mock.module("../memory/llm-request-log-store.js", () => ({
|
|
|
520
537
|
setAgentLoopExitReasonOnLatestLog: setAgentLoopExitReasonOnLatestLogMock,
|
|
521
538
|
}));
|
|
522
539
|
|
|
523
|
-
let mockHasProactiveArtifactCompleted = true;
|
|
524
|
-
let mockTryClaimProactiveArtifactTrigger = false;
|
|
525
|
-
const runProactiveArtifactJobMock = mock(
|
|
526
|
-
async (_params: Record<string, unknown>) => {},
|
|
527
|
-
);
|
|
528
|
-
mock.module("../proactive-artifact/index.js", () => ({
|
|
529
|
-
hasProactiveArtifactCompleted: () => mockHasProactiveArtifactCompleted,
|
|
530
|
-
tryClaimProactiveArtifactTrigger: () => mockTryClaimProactiveArtifactTrigger,
|
|
531
|
-
runProactiveArtifactJob: runProactiveArtifactJobMock,
|
|
532
|
-
}));
|
|
533
|
-
|
|
534
540
|
// ── Imports (after mocks) ────────────────────────────────────────────
|
|
535
541
|
|
|
536
542
|
import { AgentLoop } from "../agent/loop.js";
|
|
@@ -594,7 +600,7 @@ function makeCtx(
|
|
|
594
600
|
toolExecutor,
|
|
595
601
|
});
|
|
596
602
|
|
|
597
|
-
|
|
603
|
+
const ctx = {
|
|
598
604
|
conversationId: "test-conv",
|
|
599
605
|
messages: [
|
|
600
606
|
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
@@ -710,6 +716,8 @@ function makeCtx(
|
|
|
710
716
|
|
|
711
717
|
...ctxOverrides,
|
|
712
718
|
} as unknown as Conversation;
|
|
719
|
+
fakeContextWindowManagers.set(conversationId, ctx.contextWindowManager);
|
|
720
|
+
return ctx;
|
|
713
721
|
}
|
|
714
722
|
|
|
715
723
|
type CompactionResult = Parameters<typeof applyCompactionResult>[1];
|
|
@@ -765,9 +773,6 @@ beforeEach(() => {
|
|
|
765
773
|
title: null,
|
|
766
774
|
};
|
|
767
775
|
mockMessageById = null;
|
|
768
|
-
mockHasProactiveArtifactCompleted = true;
|
|
769
|
-
mockTryClaimProactiveArtifactTrigger = false;
|
|
770
|
-
runProactiveArtifactJobMock.mockClear();
|
|
771
776
|
setConversationHistoryStrippedAtMock.mockClear();
|
|
772
777
|
setConversationHistoryStrippedAtMock.mockImplementation(() => {});
|
|
773
778
|
applyRuntimeInjectionsMock.mockClear();
|
|
@@ -862,59 +867,6 @@ describe("session-agent-loop", () => {
|
|
|
862
867
|
});
|
|
863
868
|
});
|
|
864
869
|
|
|
865
|
-
describe("proactive artifact trigger", () => {
|
|
866
|
-
test("does not start proactive artifact jobs after foreground user turns", async () => {
|
|
867
|
-
mockConversationRow = {
|
|
868
|
-
...mockConversationRow,
|
|
869
|
-
id: "test-conv",
|
|
870
|
-
conversationType: "standard",
|
|
871
|
-
};
|
|
872
|
-
mockMessageById = {
|
|
873
|
-
id: "user-msg-1",
|
|
874
|
-
conversationId: "test-conv",
|
|
875
|
-
createdAt: 1000,
|
|
876
|
-
};
|
|
877
|
-
mockHasProactiveArtifactCompleted = false;
|
|
878
|
-
mockTryClaimProactiveArtifactTrigger = true;
|
|
879
|
-
|
|
880
|
-
// A two-call agent turn: the model invokes `app_create`, then wraps up
|
|
881
|
-
// with a final text reply.
|
|
882
|
-
const ctx = makeCtx({
|
|
883
|
-
conversationId: "test-conv",
|
|
884
|
-
providerResponses: [
|
|
885
|
-
{
|
|
886
|
-
content: [
|
|
887
|
-
{ type: "text", text: "I'll build that app." },
|
|
888
|
-
{
|
|
889
|
-
type: "tool_use",
|
|
890
|
-
id: "tool-1",
|
|
891
|
-
name: "app_create",
|
|
892
|
-
input: { name: "Flow" },
|
|
893
|
-
},
|
|
894
|
-
],
|
|
895
|
-
model: "mock-model",
|
|
896
|
-
usage: { inputTokens: 10, outputTokens: 5 },
|
|
897
|
-
stopReason: "tool_use",
|
|
898
|
-
},
|
|
899
|
-
textResponse("Done."),
|
|
900
|
-
],
|
|
901
|
-
toolExecutor: async () => ({ content: "{}", isError: false }),
|
|
902
|
-
});
|
|
903
|
-
await runAgentLoopImpl(
|
|
904
|
-
ctx,
|
|
905
|
-
"build a kanban app",
|
|
906
|
-
"user-msg-1",
|
|
907
|
-
() => {},
|
|
908
|
-
{
|
|
909
|
-
isUserMessage: true,
|
|
910
|
-
},
|
|
911
|
-
);
|
|
912
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
913
|
-
|
|
914
|
-
expect(runProactiveArtifactJobMock).toHaveBeenCalledTimes(0);
|
|
915
|
-
});
|
|
916
|
-
});
|
|
917
|
-
|
|
918
870
|
describe("disk pressure injection context", () => {
|
|
919
871
|
// The loop sets `ctx.diskPressureCleanupModeActive` for the duration of the
|
|
920
872
|
// turn (the disk-pressure-warning injector reads it via the per-conversation
|
|
@@ -164,7 +164,7 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
164
164
|
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
165
165
|
}));
|
|
166
166
|
|
|
167
|
-
mock.module("../
|
|
167
|
+
mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
168
168
|
ContextWindowManager: class {
|
|
169
169
|
constructor() {}
|
|
170
170
|
updateConfig() {}
|
|
@@ -319,7 +319,6 @@ describe("classifyConversationError", () => {
|
|
|
319
319
|
expect(result.errorCategory).toBe("image_dimensions_too_large");
|
|
320
320
|
expect(result.retryable).toBe(false);
|
|
321
321
|
expect(result.userMessage).toContain("image");
|
|
322
|
-
expect(result.userMessage).toContain("8000");
|
|
323
322
|
});
|
|
324
323
|
|
|
325
324
|
it("matches the singular 'image dimension exceeds' phrasing as well", () => {
|
|
@@ -333,6 +332,21 @@ describe("classifyConversationError", () => {
|
|
|
333
332
|
expect(result.retryable).toBe(false);
|
|
334
333
|
});
|
|
335
334
|
|
|
335
|
+
it("classifies the base64 payload 'exceeds 5 MB maximum' variant as IMAGE_TOO_LARGE (JARVIS-1041)", () => {
|
|
336
|
+
// This is the exact shape that wedged the reporting user: an oversized
|
|
337
|
+
// image nested in a tool_result. It must route to IMAGE_TOO_LARGE so the
|
|
338
|
+
// recovery path fires, not to a retryable PROVIDER_API loop.
|
|
339
|
+
const err = new ProviderError(
|
|
340
|
+
'Anthropic API error (400): 400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.28.content.1.tool_result.content.1.image.source.base64: image exceeds 5 MB maximum: 7465044 bytes > 5242880 bytes"},"request_id":"req_011CbbFqvxBmDQdhVlqxtAfW"}',
|
|
341
|
+
"anthropic",
|
|
342
|
+
400,
|
|
343
|
+
);
|
|
344
|
+
const result = classifyConversationError(err, baseCtx);
|
|
345
|
+
expect(result.code).toBe("IMAGE_TOO_LARGE");
|
|
346
|
+
expect(result.errorCategory).toBe("image_dimensions_too_large");
|
|
347
|
+
expect(result.retryable).toBe(false);
|
|
348
|
+
});
|
|
349
|
+
|
|
336
350
|
it("does not steal generic 400s that happen to mention 'image'", () => {
|
|
337
351
|
const err = new ProviderError(
|
|
338
352
|
"invalid request: image source is missing",
|
|
@@ -807,7 +807,12 @@ describe("web_search_tool_result structural guard", () => {
|
|
|
807
807
|
// Media token counting iterates tool_result.contentBlocks for nested
|
|
808
808
|
// image/file blocks. web_search_tool_result has opaque content with no
|
|
809
809
|
// contentBlocks property, so it cannot contain nested media.
|
|
810
|
-
"
|
|
810
|
+
"plugins/defaults/compaction/context-overflow-reducer.ts",
|
|
811
|
+
|
|
812
|
+
// Downgrades permanently-unsendable images nested in tool_result.contentBlocks
|
|
813
|
+
// (e.g. an oversized browser screenshot). web_search_tool_result has opaque
|
|
814
|
+
// content with no contentBlocks property, so it cannot hold such an image.
|
|
815
|
+
"daemon/persist-unsendable-image.ts",
|
|
811
816
|
|
|
812
817
|
// Final orphan-pair safety pass in the Slack transcript renderer.
|
|
813
818
|
// Server-side block types (`server_tool_use`, `web_search_tool_result`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import { createContextSummaryMessage } from "../context/window-manager.js";
|
|
4
3
|
import { stripMediaPayloadsForRetry } from "../daemon/conversation-media-retry.js";
|
|
4
|
+
import { createContextSummaryMessage } from "../plugins/defaults/compaction/window-manager.js";
|
|
5
5
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
@@ -185,6 +185,7 @@ function makeQueuedMessage(opts: {
|
|
|
185
185
|
requestId: string;
|
|
186
186
|
content?: string;
|
|
187
187
|
turnInterfaceContext?: TurnInterfaceContext;
|
|
188
|
+
sourceActorPrincipalId?: string;
|
|
188
189
|
}): QueuedMessage {
|
|
189
190
|
return {
|
|
190
191
|
content: opts.content ?? "follow up",
|
|
@@ -194,6 +195,10 @@ function makeQueuedMessage(opts: {
|
|
|
194
195
|
metadata: {},
|
|
195
196
|
sentAt: Date.now(),
|
|
196
197
|
turnInterfaceContext: opts.turnInterfaceContext,
|
|
198
|
+
sourceActorPrincipalId: opts.sourceActorPrincipalId,
|
|
199
|
+
authContext: opts.sourceActorPrincipalId
|
|
200
|
+
? ({ actorPrincipalId: opts.sourceActorPrincipalId } as never)
|
|
201
|
+
: undefined,
|
|
197
202
|
};
|
|
198
203
|
}
|
|
199
204
|
|
|
@@ -332,6 +337,7 @@ describe("drainQueue preactivation re-add for host-proxy interfaces", () => {
|
|
|
332
337
|
makeQueuedMessage({
|
|
333
338
|
requestId: "req-web-1",
|
|
334
339
|
turnInterfaceContext: ifCtx,
|
|
340
|
+
sourceActorPrincipalId: "user-1",
|
|
335
341
|
}),
|
|
336
342
|
);
|
|
337
343
|
const ctx = makeFakeContext({ queue, turnInterfaceContext: ifCtx });
|
|
@@ -346,6 +352,39 @@ describe("drainQueue preactivation re-add for host-proxy interfaces", () => {
|
|
|
346
352
|
expect(ctx.preactivatedSkillIdCalls).toContain("computer-use");
|
|
347
353
|
});
|
|
348
354
|
|
|
355
|
+
test("drainSingleMessage filters cross-client preactivation by the queued requester, not guardian owner", async () => {
|
|
356
|
+
mockCapabilityClients = [
|
|
357
|
+
{ clientId: "owner-macos-client", actorPrincipalId: "owner-user" },
|
|
358
|
+
];
|
|
359
|
+
const queue = new MessageQueue();
|
|
360
|
+
const ifCtx: TurnInterfaceContext = {
|
|
361
|
+
userMessageInterface: "web",
|
|
362
|
+
assistantMessageInterface: "web",
|
|
363
|
+
};
|
|
364
|
+
queue.push(
|
|
365
|
+
makeQueuedMessage({
|
|
366
|
+
requestId: "req-web-attacker",
|
|
367
|
+
turnInterfaceContext: ifCtx,
|
|
368
|
+
sourceActorPrincipalId: "trusted-contact-user",
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
const ctx = makeFakeContext({ queue, turnInterfaceContext: ifCtx });
|
|
372
|
+
ctx.trustContext = {
|
|
373
|
+
trustClass: "trusted_contact",
|
|
374
|
+
guardianPrincipalId: "owner-user",
|
|
375
|
+
sourceChannel: "vellum",
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
await drainQueue(ctx);
|
|
379
|
+
|
|
380
|
+
expect(ctx.preactivatedSkillIdCalls).not.toContain("computer-use");
|
|
381
|
+
expect(ctx.preactivatedSkillIdCalls).not.toContain("app-control");
|
|
382
|
+
expect(ctx.currentTurnAuthContext?.actorPrincipalId).toBe(
|
|
383
|
+
"trusted-contact-user",
|
|
384
|
+
);
|
|
385
|
+
expect(ctx.currentTurnSourceActorPrincipalId).toBe("trusted-contact-user");
|
|
386
|
+
});
|
|
387
|
+
|
|
349
388
|
test("drainSingleMessage does NOT re-add 'app-control' for web-sourced message when no capable client is connected", async () => {
|
|
350
389
|
// mockCapabilityClients remains [] (reset by afterEach from prior test)
|
|
351
390
|
const queue = new MessageQueue();
|
|
@@ -380,6 +419,7 @@ describe("drainQueue preactivation re-add for host-proxy interfaces", () => {
|
|
|
380
419
|
makeQueuedMessage({
|
|
381
420
|
requestId: "req-web-3",
|
|
382
421
|
turnInterfaceContext: ifCtx,
|
|
422
|
+
sourceActorPrincipalId: "user-1",
|
|
383
423
|
}),
|
|
384
424
|
);
|
|
385
425
|
const ctx = makeFakeContext({ queue, turnInterfaceContext: ifCtx });
|
|
@@ -249,7 +249,7 @@ mock.module("../agent/loop.js", () => ({
|
|
|
249
249
|
},
|
|
250
250
|
}));
|
|
251
251
|
|
|
252
|
-
mock.module("../
|
|
252
|
+
mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
253
253
|
ContextWindowManager: class {
|
|
254
254
|
constructor() {}
|
|
255
255
|
updateConfig() {}
|
|
@@ -100,56 +100,59 @@ mock.module("../context/token-estimator.js", () => ({
|
|
|
100
100
|
// but these tests exercise the Conversation-level flow, not the reducer internals.
|
|
101
101
|
// The reducer mock delegates to the compactFn to simulate a forced compaction
|
|
102
102
|
// tier, matching the real reducer's behavior for Tier 1.
|
|
103
|
-
mock.module(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
103
|
+
mock.module(
|
|
104
|
+
"../plugins/defaults/compaction/context-overflow-reducer.js",
|
|
105
|
+
() => ({
|
|
106
|
+
createInitialReducerState: () => ({
|
|
107
|
+
appliedTiers: [],
|
|
108
|
+
injectionMode: "full" as const,
|
|
109
|
+
exhausted: false,
|
|
110
|
+
}),
|
|
111
|
+
reduceContextOverflow: async (
|
|
112
|
+
msgs: Message[],
|
|
113
|
+
_cfg: unknown,
|
|
114
|
+
_state: unknown,
|
|
115
|
+
compactFn?: (
|
|
116
|
+
m: Message[],
|
|
117
|
+
s: AbortSignal | undefined,
|
|
118
|
+
o: Record<string, unknown>,
|
|
119
|
+
) => Promise<{
|
|
120
|
+
compacted: boolean;
|
|
121
|
+
messages: Message[];
|
|
122
|
+
compactedPersistedMessages?: number;
|
|
123
|
+
summaryText?: string;
|
|
124
|
+
[k: string]: unknown;
|
|
125
|
+
}>,
|
|
126
|
+
signal?: AbortSignal,
|
|
127
|
+
) => {
|
|
128
|
+
let resultMessages = msgs;
|
|
129
|
+
let compactionResult;
|
|
130
|
+
if (compactFn) {
|
|
131
|
+
const cr = await compactFn(msgs, signal, { force: true });
|
|
132
|
+
if (cr.compacted) {
|
|
133
|
+
resultMessages = cr.messages;
|
|
134
|
+
compactionResult = cr;
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
|
|
137
|
+
return {
|
|
138
|
+
messages: resultMessages,
|
|
139
|
+
tier: "forced_compaction",
|
|
140
|
+
state: {
|
|
141
|
+
appliedTiers: [
|
|
142
|
+
"forced_compaction",
|
|
143
|
+
"tool_result_truncation",
|
|
144
|
+
"media_stubbing",
|
|
145
|
+
"injection_downgrade",
|
|
146
|
+
],
|
|
147
|
+
injectionMode: "full",
|
|
148
|
+
exhausted: true,
|
|
149
|
+
},
|
|
150
|
+
estimatedTokens: 1000,
|
|
151
|
+
compactionResult,
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
153
156
|
|
|
154
157
|
mock.module("../daemon/context-overflow-policy.js", () => ({
|
|
155
158
|
resolveOverflowAction: () => "fail_gracefully",
|
|
@@ -218,7 +221,7 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
218
221
|
let maybeCompactCalls: Array<{ force: boolean }> = [];
|
|
219
222
|
let forceCompactionEnabled = false;
|
|
220
223
|
|
|
221
|
-
mock.module("../
|
|
224
|
+
mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
222
225
|
ContextWindowManager: class {
|
|
223
226
|
constructor() {}
|
|
224
227
|
updateConfig() {}
|
|
@@ -262,7 +262,7 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
262
262
|
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
263
263
|
}));
|
|
264
264
|
|
|
265
|
-
mock.module("../
|
|
265
|
+
mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
266
266
|
ContextWindowManager: class {
|
|
267
267
|
constructor() {}
|
|
268
268
|
updateConfig() {}
|