@vellumai/assistant 0.8.9-staging.2 → 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/docs/activation-funnel-telemetry.md +310 -0
- package/package.json +1 -1
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +13 -13
- 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__/context-overflow-reducer.test.ts +5 -5
- 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-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- 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 +1 -1
- 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 +1 -2
- 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__/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/resolve-agent.test.ts +0 -56
- package/src/acp/resolve-agent.ts +10 -38
- package/src/agent/loop.ts +13 -27
- 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 +4 -17
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/feature-flag-registry.json +3 -18
- 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 +78 -78
- 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 +48 -13
- 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/plugins/defaults/compaction/context-overflow-reducer.ts +4 -4
- package/src/plugins/defaults/compaction/manager-store.ts +1 -1
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +12 -12
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +12 -18
- 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__/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/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 +2 -18
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +0 -10
- 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
|
@@ -152,25 +152,56 @@ function isNullBodyStatus(status: number): boolean {
|
|
|
152
152
|
return status === 204 || status === 205 || status === 304;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// The WHATWG `Response` constructor only accepts 101 or the range 200–599 and
|
|
156
|
+
// throws a `RangeError` for anything else. Some servers — notably anti-bot
|
|
157
|
+
// gateways like LinkedIn and various job boards — reply with non-standard codes
|
|
158
|
+
// such as 999. Map those onto 502 so we can still build a Response; the real
|
|
159
|
+
// upstream status is preserved via `upstreamStatusByResponse` for reporting.
|
|
160
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Response/Response
|
|
161
|
+
const RESPONSE_STATUS_SWITCHING_PROTOCOLS = 101;
|
|
162
|
+
const RESPONSE_STATUS_MIN = 200;
|
|
163
|
+
const RESPONSE_STATUS_MAX = 599;
|
|
164
|
+
|
|
165
|
+
function toConstructableStatus(status: number): number {
|
|
166
|
+
if (status === RESPONSE_STATUS_SWITCHING_PROTOCOLS) return status;
|
|
167
|
+
if (status >= RESPONSE_STATUS_MIN && status <= RESPONSE_STATUS_MAX) {
|
|
168
|
+
return status;
|
|
169
|
+
}
|
|
170
|
+
return 502;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const upstreamStatusByResponse = new WeakMap<Response, number>();
|
|
174
|
+
|
|
175
|
+
// Returns the real status the upstream server sent, which may fall outside the
|
|
176
|
+
// range the `Response` object can represent (e.g. 999). Falls back to
|
|
177
|
+
// `response.status` for responses built by native `fetch`, whose undici
|
|
178
|
+
// implementation already exposes non-standard codes directly.
|
|
179
|
+
export function getUpstreamStatus(response: Response): number {
|
|
180
|
+
return upstreamStatusByResponse.get(response) ?? response.status;
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
export function buildFetchResponseFromNodeResponse(
|
|
156
184
|
res: NodeHttpResponseLike,
|
|
157
185
|
): Response {
|
|
158
|
-
const
|
|
186
|
+
const upstreamStatus = res.statusCode ?? 502;
|
|
187
|
+
const status = toConstructableStatus(upstreamStatus);
|
|
159
188
|
const responseHeaders = buildResponseHeaders(res.headers);
|
|
160
189
|
const statusText = res.statusMessage ?? "";
|
|
161
190
|
|
|
162
|
-
|
|
163
|
-
// Drain any unexpected bytes and produce a valid null-body fetch Response.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
const response = isNullBodyStatus(status)
|
|
192
|
+
? // Drain any unexpected bytes and produce a valid null-body fetch Response.
|
|
193
|
+
(res.resume(),
|
|
194
|
+
new Response(null, { status, statusText, headers: responseHeaders }))
|
|
195
|
+
: new Response(Readable.toWeb(res) as unknown as BodyInit, {
|
|
196
|
+
status,
|
|
197
|
+
statusText,
|
|
198
|
+
headers: responseHeaders,
|
|
199
|
+
});
|
|
167
200
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
headers: responseHeaders,
|
|
173
|
-
});
|
|
201
|
+
if (status !== upstreamStatus) {
|
|
202
|
+
upstreamStatusByResponse.set(response, upstreamStatus);
|
|
203
|
+
}
|
|
204
|
+
return response;
|
|
174
205
|
}
|
|
175
206
|
|
|
176
207
|
function createAbortError(): Error {
|
|
@@ -252,7 +283,16 @@ const defaultRequestExecutor: WebFetchRequestExecutor = async (
|
|
|
252
283
|
|
|
253
284
|
return await new Promise<Response>((resolve, reject) => {
|
|
254
285
|
const req = requestFn(requestOptions, (res) => {
|
|
255
|
-
|
|
286
|
+
// Building the fetch Response runs inside this HTTP `response` event
|
|
287
|
+
// callback. Any synchronous throw here would escape as an uncaught
|
|
288
|
+
// exception and crash the daemon, so funnel failures into `reject` to
|
|
289
|
+
// keep them local to this request.
|
|
290
|
+
try {
|
|
291
|
+
resolve(buildFetchResponseFromNodeResponse(res));
|
|
292
|
+
} catch (err) {
|
|
293
|
+
res.resume();
|
|
294
|
+
reject(err);
|
|
295
|
+
}
|
|
256
296
|
});
|
|
257
297
|
req.once("error", reject);
|
|
258
298
|
req.end();
|
|
@@ -730,9 +770,10 @@ export async function executeWebFetch(
|
|
|
730
770
|
);
|
|
731
771
|
}
|
|
732
772
|
|
|
773
|
+
const upstreamStatus = getUpstreamStatus(response);
|
|
733
774
|
const location = response.headers.get("location");
|
|
734
775
|
const isRedirect =
|
|
735
|
-
|
|
776
|
+
upstreamStatus >= 300 && upstreamStatus < 400 && !!location;
|
|
736
777
|
if (!isRedirect) break;
|
|
737
778
|
|
|
738
779
|
if (redirectCount >= MAX_REDIRECTS) {
|
|
@@ -741,7 +782,7 @@ export async function executeWebFetch(
|
|
|
741
782
|
{
|
|
742
783
|
url: requestedUrl,
|
|
743
784
|
finalUrl: currentUrl.href,
|
|
744
|
-
status: response
|
|
785
|
+
status: getUpstreamStatus(response),
|
|
745
786
|
redirectCount,
|
|
746
787
|
},
|
|
747
788
|
);
|
|
@@ -761,7 +802,7 @@ export async function executeWebFetch(
|
|
|
761
802
|
{
|
|
762
803
|
url: requestedUrl,
|
|
763
804
|
finalUrl: currentUrl.href,
|
|
764
|
-
status: response
|
|
805
|
+
status: getUpstreamStatus(response),
|
|
765
806
|
redirectCount,
|
|
766
807
|
},
|
|
767
808
|
);
|
|
@@ -773,7 +814,7 @@ export async function executeWebFetch(
|
|
|
773
814
|
{
|
|
774
815
|
url: requestedUrl,
|
|
775
816
|
finalUrl: currentUrl.href,
|
|
776
|
-
status: response
|
|
817
|
+
status: getUpstreamStatus(response),
|
|
777
818
|
redirectCount,
|
|
778
819
|
},
|
|
779
820
|
);
|
|
@@ -785,7 +826,7 @@ export async function executeWebFetch(
|
|
|
785
826
|
{
|
|
786
827
|
url: requestedUrl,
|
|
787
828
|
finalUrl: currentUrl.href,
|
|
788
|
-
status: response
|
|
829
|
+
status: getUpstreamStatus(response),
|
|
789
830
|
redirectCount,
|
|
790
831
|
},
|
|
791
832
|
);
|
|
@@ -805,7 +846,7 @@ export async function executeWebFetch(
|
|
|
805
846
|
{
|
|
806
847
|
url: requestedUrl,
|
|
807
848
|
finalUrl: currentUrl.href,
|
|
808
|
-
status: response
|
|
849
|
+
status: getUpstreamStatus(response),
|
|
809
850
|
redirectCount,
|
|
810
851
|
},
|
|
811
852
|
);
|
|
@@ -817,7 +858,7 @@ export async function executeWebFetch(
|
|
|
817
858
|
{
|
|
818
859
|
url: requestedUrl,
|
|
819
860
|
finalUrl: currentUrl.href,
|
|
820
|
-
status: response
|
|
861
|
+
status: getUpstreamStatus(response),
|
|
821
862
|
redirectCount,
|
|
822
863
|
},
|
|
823
864
|
);
|
|
@@ -844,7 +885,7 @@ export async function executeWebFetch(
|
|
|
844
885
|
{
|
|
845
886
|
url: requestedUrl,
|
|
846
887
|
finalUrl: currentUrl.href,
|
|
847
|
-
status: response
|
|
888
|
+
status: getUpstreamStatus(response),
|
|
848
889
|
contentType,
|
|
849
890
|
redirectCount,
|
|
850
891
|
},
|
|
@@ -909,7 +950,7 @@ export async function executeWebFetch(
|
|
|
909
950
|
const content = formatWebFetchOutput({
|
|
910
951
|
requestedUrl: safeRequestedUrl,
|
|
911
952
|
finalUrl: sanitizeUrlForOutput(currentUrl),
|
|
912
|
-
status: response
|
|
953
|
+
status: getUpstreamStatus(response),
|
|
913
954
|
statusText: response.statusText,
|
|
914
955
|
contentType,
|
|
915
956
|
bytesRead: body.bytesRead,
|
|
@@ -931,7 +972,7 @@ export async function executeWebFetch(
|
|
|
931
972
|
const meta: WebFetchMetadata = {
|
|
932
973
|
url: safeRequestedUrl,
|
|
933
974
|
finalUrl: sanitizeUrlForOutput(currentUrl),
|
|
934
|
-
status: response
|
|
975
|
+
status: getUpstreamStatus(response),
|
|
935
976
|
contentType: contentType || undefined,
|
|
936
977
|
byteCount: body.bytesRead,
|
|
937
978
|
charCount: sliced.length,
|
|
@@ -945,7 +986,7 @@ export async function executeWebFetch(
|
|
|
945
986
|
};
|
|
946
987
|
|
|
947
988
|
if (!response.ok) {
|
|
948
|
-
const errorMessage = `Error: HTTP ${response
|
|
989
|
+
const errorMessage = `Error: HTTP ${getUpstreamStatus(response)}`;
|
|
949
990
|
return {
|
|
950
991
|
content: `${errorMessage}\n\n${content}`,
|
|
951
992
|
isError: true,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { RiskLevel } from "../../permissions/types.js";
|
|
11
|
+
import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
|
|
11
12
|
import type {
|
|
12
13
|
ToolContext,
|
|
13
14
|
ToolDefinition,
|
|
@@ -204,6 +205,12 @@ export const uiShowTool = {
|
|
|
204
205
|
description:
|
|
205
206
|
"When true, clicking an action does not dismiss the surface — the card stays visible and only the clicked action is marked as spent. Use for launcher or menu-style cards where multiple buttons may be clicked. Defaults to false.",
|
|
206
207
|
},
|
|
208
|
+
activation_moment: {
|
|
209
|
+
type: "string",
|
|
210
|
+
enum: ACTIVATION_MOMENT_PARAMS,
|
|
211
|
+
description:
|
|
212
|
+
"Activation-rail telemetry tag (cohort only). Set this when this surface IS one of the activation funnel moments; the milestone is recorded automatically when the user commits the surface. Omit for all non-activation surfaces.",
|
|
213
|
+
},
|
|
207
214
|
},
|
|
208
215
|
required: ["surface_type", "data"],
|
|
209
216
|
},
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the ACP feature gate.
|
|
3
|
-
*
|
|
4
|
-
* `isAcpEnabled` is an OR of the `acp` feature flag and the legacy
|
|
5
|
-
* `config.acp.enabled` field: either switch enables the subsystem, and
|
|
6
|
-
* neither implicitly flips the other.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
10
|
-
|
|
11
|
-
import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
|
|
12
|
-
import type { AssistantConfig } from "../config/schema.js";
|
|
13
|
-
import { ACP_FLAG_KEY, isAcpEnabled } from "./feature-gate.js";
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
setOverridesForTesting({});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
setOverridesForTesting({});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
/** Minimal AssistantConfig carrying only the `acp` section the gate reads. */
|
|
24
|
-
function makeConfig(acpEnabled: boolean): AssistantConfig {
|
|
25
|
-
return {
|
|
26
|
-
acp: { enabled: acpEnabled, maxConcurrentSessions: 4, agents: {} },
|
|
27
|
-
} as AssistantConfig;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("isAcpEnabled", () => {
|
|
31
|
-
test("returns false when both the flag and config.acp.enabled are off", () => {
|
|
32
|
-
expect(isAcpEnabled(makeConfig(false))).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("returns true when config.acp.enabled is true and the flag is off", () => {
|
|
36
|
-
expect(isAcpEnabled(makeConfig(true))).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("returns true when the flag is on and config.acp.enabled is false", () => {
|
|
40
|
-
setOverridesForTesting({ [ACP_FLAG_KEY]: true });
|
|
41
|
-
expect(isAcpEnabled(makeConfig(false))).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("explicit flag-off override does not defeat config.acp.enabled", () => {
|
|
45
|
-
setOverridesForTesting({ [ACP_FLAG_KEY]: false });
|
|
46
|
-
expect(isAcpEnabled(makeConfig(true))).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
});
|
package/src/acp/feature-gate.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ACP (Agent Client Protocol) feature gate.
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for whether the ACP subsystem is enabled. Modeled
|
|
5
|
-
* on `credential-execution/feature-gates.ts`: the flag key is declared in
|
|
6
|
-
* `meta/feature-flags/feature-flag-registry.json` and resolved through the
|
|
7
|
-
* unified feature-flag resolver.
|
|
8
|
-
*
|
|
9
|
-
* The gate is an OR of two independent switches:
|
|
10
|
-
* 1. `config.acp.enabled` — the original workspace config field. Preserved
|
|
11
|
-
* so existing workspaces with `acp.enabled: true` keep working without
|
|
12
|
-
* any migration.
|
|
13
|
-
* 2. The `acp` feature flag — adds a UI toggle. Flag state is persisted by
|
|
14
|
-
* the gateway and hot-refreshed in the daemon via the
|
|
15
|
-
* `feature_flags_changed` SSE event, so toggling it takes effect
|
|
16
|
-
* without a restart or config-file edit.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
20
|
-
import type { AssistantConfig } from "../config/schema.js";
|
|
21
|
-
|
|
22
|
-
/** Gate for the ACP coding-agent subsystem (must match the registry). */
|
|
23
|
-
export const ACP_FLAG_KEY = "acp" as const;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Whether ACP agent spawning/steering is enabled, via either the legacy
|
|
27
|
-
* `acp.enabled` config field or the `acp` feature flag (see module doc).
|
|
28
|
-
*/
|
|
29
|
-
export function isAcpEnabled(config: AssistantConfig): boolean {
|
|
30
|
-
return (
|
|
31
|
-
config.acp?.enabled === true ||
|
|
32
|
-
isAssistantFeatureFlagEnabled(ACP_FLAG_KEY, config)
|
|
33
|
-
);
|
|
34
|
-
}
|