@vellumai/assistant 0.8.9-staging.2 → 0.8.9-staging.4
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/openapi.yaml +16 -115
- 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__/anthropic-provider.test.ts +23 -8
- 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 +121 -15
- 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-tool-setup-app-refresh.test.ts +73 -4
- 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__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/history-repair-hook.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +73 -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__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/published-app-updater.test.ts +138 -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/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/memory-v3.ts +57 -199
- 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/call-site-defaults.ts +0 -1
- package/src/config/feature-flag-registry.json +3 -18
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/llm.ts +17 -1
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- 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 +87 -95
- 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/external-plugins-bootstrap.ts +8 -3
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/tool-side-effects.ts +15 -0
- 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/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -72
- package/src/memory/v2/types.ts +5 -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 +68 -12
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +12 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +19 -66
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +139 -129
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +332 -164
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +516 -293
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +51 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +66 -11
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +271 -110
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +0 -22
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +26 -62
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +3 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +415 -181
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +196 -56
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +39 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +15 -7
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +75 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +111 -78
- package/src/plugins/defaults/memory-v3-shadow/types.ts +52 -19
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +4 -1
- package/src/plugins/external-api.ts +114 -0
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +37 -2
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -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/app-management-routes.ts +3 -0
- package/src/runtime/routes/conversation-routes.ts +2 -0
- package/src/runtime/routes/memory-v3-routes.ts +64 -314
- 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/services/published-app-updater.ts +30 -8
- 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/skills/load.ts +1 -1
- 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/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +0 -242
- package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +0 -39
- package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +0 -219
- package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +0 -107
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +0 -13
- package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +0 -274
- package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +0 -337
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +0 -470
- package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +0 -168
- package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +0 -192
- package/src/plugins/defaults/memory-v3-shadow/assign.ts +0 -272
- package/src/plugins/defaults/memory-v3-shadow/core.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
- package/src/plugins/defaults/memory-v3-shadow/needle.ts +0 -115
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +0 -26
- package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +0 -527
- package/src/plugins/defaults/memory-v3-shadow/router.ts +0 -190
- package/src/plugins/defaults/memory-v3-shadow/selector.ts +0 -226
- package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +0 -209
- package/src/plugins/defaults/memory-v3-shadow/tree.ts +0 -174
- package/src/util/map-limit.ts +0 -27
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { optimizeImageForTransport } from "../../agent/image-optimize.js";
|
|
1
2
|
import { getConfig } from "../../config/loader.js";
|
|
2
3
|
import { HostBrowserProxy } from "../../daemon/host-browser-proxy.js";
|
|
3
4
|
import type { ImageContent } from "../../providers/types.js";
|
|
@@ -1206,13 +1207,22 @@ export async function executeBrowserScreenshot(
|
|
|
1206
1207
|
{ quality: 80, fullPage },
|
|
1207
1208
|
context.signal,
|
|
1208
1209
|
);
|
|
1209
|
-
const
|
|
1210
|
+
const rawBase64 = buffer.toString("base64");
|
|
1211
|
+
|
|
1212
|
+
// Downscale before handing the screenshot to the model. A full-page
|
|
1213
|
+
// capture of a tall or high-DPI page can exceed Anthropic's 5 MB
|
|
1214
|
+
// per-image cap, which would otherwise persist into the conversation
|
|
1215
|
+
// history inside this tool_result and reject every subsequent turn.
|
|
1216
|
+
const { data: base64Data, mediaType } = optimizeImageForTransport(
|
|
1217
|
+
rawBase64,
|
|
1218
|
+
"image/jpeg",
|
|
1219
|
+
);
|
|
1210
1220
|
|
|
1211
1221
|
const imageBlock: ImageContent = {
|
|
1212
1222
|
type: "image" as const,
|
|
1213
1223
|
source: {
|
|
1214
1224
|
type: "base64" as const,
|
|
1215
|
-
media_type:
|
|
1225
|
+
media_type: mediaType,
|
|
1216
1226
|
data: base64Data,
|
|
1217
1227
|
},
|
|
1218
1228
|
};
|
|
@@ -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,
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -128,7 +128,7 @@ export const skillLoadTool = {
|
|
|
128
128
|
name: "skill_load",
|
|
129
129
|
|
|
130
130
|
description:
|
|
131
|
-
'Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills. For app, website, dashboard, game, calculator, tracker, visualization, or interactive tool requests, load `app-builder` with `skill: "app-builder"`.',
|
|
131
|
+
'Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills. Bundled/first-party skills (like `app-builder`) are already installed — loading only activates their instructions for the current conversation, since skills unload between turns; treat "load" as activation, not installation, and don\'t tell users a bundled skill needs installing. Loading can still fail (e.g. a feature-gated skill returns "currently unavailable", or the load errors) — if it does, relay that specific error rather than claiming the skill is not installed or offering to install it. For app, website, dashboard, game, calculator, tracker, visualization, or interactive tool requests, load `app-builder` with `skill: "app-builder"`.',
|
|
132
132
|
|
|
133
133
|
category: "skills",
|
|
134
134
|
|
|
@@ -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
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the v3 page→leaf classifier. Uses a stub provider so no live LLM
|
|
3
|
-
* is hit. Pages are written into a temp workspace.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
-
|
|
11
|
-
import { readPage, writePage } from "../../../../memory/v2/page-store.js";
|
|
12
|
-
import type { ConceptPageFrontmatter } from "../../../../memory/v2/types.js";
|
|
13
|
-
import type {
|
|
14
|
-
Message,
|
|
15
|
-
Provider,
|
|
16
|
-
ProviderResponse,
|
|
17
|
-
SendMessageOptions,
|
|
18
|
-
ToolUseContent,
|
|
19
|
-
} from "../../../../providers/types.js";
|
|
20
|
-
import type { LeafNode, LeafTree } from "../types.js";
|
|
21
|
-
|
|
22
|
-
// Stub the provider layer before importing `assign.js` so its static import
|
|
23
|
-
// chain (which transitively pulls in CES/credential workspace packages that
|
|
24
|
-
// aren't built in a fresh worktree) never loads. The classifier is
|
|
25
|
-
// provider-injectable, so every test passes its own stub provider — the real
|
|
26
|
-
// `getConfiguredProvider` is never exercised here.
|
|
27
|
-
mock.module("../../../../providers/provider-send-message.js", () => ({
|
|
28
|
-
getConfiguredProvider: async () => null,
|
|
29
|
-
extractToolUse: (response: ProviderResponse) =>
|
|
30
|
-
response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
|
|
31
|
-
userMessage: (text: string): Message => ({
|
|
32
|
-
role: "user",
|
|
33
|
-
content: [{ type: "text", text }],
|
|
34
|
-
}),
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
const { assignPages } = await import("../assign.js");
|
|
38
|
-
|
|
39
|
-
function makeLeaf(path: string, description: string): LeafNode {
|
|
40
|
-
return {
|
|
41
|
-
path,
|
|
42
|
-
frontmatter: { path, in_core: false },
|
|
43
|
-
description,
|
|
44
|
-
members: [],
|
|
45
|
-
domain: path.split("/")[0],
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function makeTree(leaves: LeafNode[]): LeafTree {
|
|
50
|
-
return {
|
|
51
|
-
leaves: new Map(leaves.map((l) => [l.path, l])),
|
|
52
|
-
byPage: new Map(),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Sorted by path: [1] domain-a/topic-x, [2] domain-a/topic-y, [3] domain-b/topic-z */
|
|
57
|
-
function threeLeafTree(): LeafTree {
|
|
58
|
-
return makeTree([
|
|
59
|
-
makeLeaf("domain-b/topic-z", "about z"),
|
|
60
|
-
makeLeaf("domain-a/topic-x", "about x"),
|
|
61
|
-
makeLeaf("domain-a/topic-y", "about y"),
|
|
62
|
-
]);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function stubProvider(
|
|
66
|
-
handler: (
|
|
67
|
-
messages: Message[],
|
|
68
|
-
options?: SendMessageOptions,
|
|
69
|
-
) => ProviderResponse | Promise<ProviderResponse>,
|
|
70
|
-
): Provider {
|
|
71
|
-
return {
|
|
72
|
-
name: "stub",
|
|
73
|
-
sendMessage: async (messages, options) => handler(messages, options),
|
|
74
|
-
} as Provider;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function response(content: ProviderResponse["content"]): ProviderResponse {
|
|
78
|
-
return {
|
|
79
|
-
content,
|
|
80
|
-
model: "stub-model",
|
|
81
|
-
stopReason: "tool_use",
|
|
82
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function toolResponse(ids: unknown[]): ProviderResponse {
|
|
87
|
-
return response([
|
|
88
|
-
{ type: "tool_use", id: "tu-1", name: "assign_leaves", input: { ids } },
|
|
89
|
-
]);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function textResponse(text: string): ProviderResponse {
|
|
93
|
-
return response([{ type: "text", text }]);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let workspaceDir: string;
|
|
97
|
-
|
|
98
|
-
beforeEach(async () => {
|
|
99
|
-
workspaceDir = await mkdtemp(join(tmpdir(), "v3-assign-"));
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
afterEach(async () => {
|
|
103
|
-
await rm(workspaceDir, { recursive: true, force: true });
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
async function makePage(
|
|
107
|
-
slug: string,
|
|
108
|
-
frontmatter: Partial<ConceptPageFrontmatter>,
|
|
109
|
-
body = "page body",
|
|
110
|
-
): Promise<void> {
|
|
111
|
-
await writePage(workspaceDir, {
|
|
112
|
-
slug,
|
|
113
|
-
frontmatter: { edges: [], ref_files: [], ref_urls: [], ...frontmatter },
|
|
114
|
-
body,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
describe("assignPages", () => {
|
|
119
|
-
test("unions classified picks with prior picks, never overwriting", async () => {
|
|
120
|
-
await makePage("page-a", { leaves: ["domain-a/topic-x"] });
|
|
121
|
-
// Classifier returns [2] -> domain-a/topic-y and [1] -> already present.
|
|
122
|
-
const provider = stubProvider(() => toolResponse([2, 1]));
|
|
123
|
-
|
|
124
|
-
const results = await assignPages({
|
|
125
|
-
tree: threeLeafTree(),
|
|
126
|
-
workspaceDir,
|
|
127
|
-
slugs: ["page-a"],
|
|
128
|
-
provider,
|
|
129
|
-
throttleMs: 0,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
expect(results).toEqual([
|
|
133
|
-
{
|
|
134
|
-
slug: "page-a",
|
|
135
|
-
before: ["domain-a/topic-x"],
|
|
136
|
-
after: ["domain-a/topic-x", "domain-a/topic-y"],
|
|
137
|
-
failed: false,
|
|
138
|
-
},
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
const written = await readPage(workspaceDir, "page-a");
|
|
142
|
-
expect(written?.frontmatter.leaves).toEqual([
|
|
143
|
-
"domain-a/topic-x",
|
|
144
|
-
"domain-a/topic-y",
|
|
145
|
-
]);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("classification failure leaves the page's leaves unchanged", async () => {
|
|
149
|
-
await makePage("page-a", { leaves: ["domain-b/topic-z"] });
|
|
150
|
-
|
|
151
|
-
// (a) provider throws
|
|
152
|
-
const throwing = stubProvider(() => {
|
|
153
|
-
throw new Error("boom");
|
|
154
|
-
});
|
|
155
|
-
let results = await assignPages({
|
|
156
|
-
tree: threeLeafTree(),
|
|
157
|
-
workspaceDir,
|
|
158
|
-
slugs: ["page-a"],
|
|
159
|
-
provider: throwing,
|
|
160
|
-
throttleMs: 0,
|
|
161
|
-
});
|
|
162
|
-
expect(results[0]).toEqual({
|
|
163
|
-
slug: "page-a",
|
|
164
|
-
before: ["domain-b/topic-z"],
|
|
165
|
-
after: ["domain-b/topic-z"],
|
|
166
|
-
failed: true,
|
|
167
|
-
});
|
|
168
|
-
expect(
|
|
169
|
-
(await readPage(workspaceDir, "page-a"))?.frontmatter.leaves,
|
|
170
|
-
).toEqual(["domain-b/topic-z"]);
|
|
171
|
-
|
|
172
|
-
// (b) no tool use
|
|
173
|
-
results = await assignPages({
|
|
174
|
-
tree: threeLeafTree(),
|
|
175
|
-
workspaceDir,
|
|
176
|
-
slugs: ["page-a"],
|
|
177
|
-
provider: stubProvider(() => textResponse("no tool here")),
|
|
178
|
-
throttleMs: 0,
|
|
179
|
-
});
|
|
180
|
-
expect(results[0].failed).toBe(true);
|
|
181
|
-
expect(
|
|
182
|
-
(await readPage(workspaceDir, "page-a"))?.frontmatter.leaves,
|
|
183
|
-
).toEqual(["domain-b/topic-z"]);
|
|
184
|
-
|
|
185
|
-
// (c) empty ids
|
|
186
|
-
results = await assignPages({
|
|
187
|
-
tree: threeLeafTree(),
|
|
188
|
-
workspaceDir,
|
|
189
|
-
slugs: ["page-a"],
|
|
190
|
-
provider: stubProvider(() => toolResponse([])),
|
|
191
|
-
throttleMs: 0,
|
|
192
|
-
});
|
|
193
|
-
expect(results[0].failed).toBe(true);
|
|
194
|
-
expect(
|
|
195
|
-
(await readPage(workspaceDir, "page-a"))?.frontmatter.leaves,
|
|
196
|
-
).toEqual(["domain-b/topic-z"]);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("deterministic id->path mapping, dropping out-of-range and 0 ids", async () => {
|
|
200
|
-
await makePage("page-a", {});
|
|
201
|
-
// 0 and 99 are invalid; 3 -> domain-b/topic-z; 1 -> domain-a/topic-x.
|
|
202
|
-
const provider = stubProvider(() => toolResponse([0, 99, 3, 1, -1, 2.5]));
|
|
203
|
-
|
|
204
|
-
const results = await assignPages({
|
|
205
|
-
tree: threeLeafTree(),
|
|
206
|
-
workspaceDir,
|
|
207
|
-
slugs: ["page-a"],
|
|
208
|
-
provider,
|
|
209
|
-
throttleMs: 0,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
expect(results[0].after).toEqual(["domain-b/topic-z", "domain-a/topic-x"]);
|
|
213
|
-
expect(results[0].failed).toBe(false);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("defaults to pages with empty or missing leaves", async () => {
|
|
217
|
-
await makePage("page-empty", { leaves: [] });
|
|
218
|
-
await makePage("page-missing", {});
|
|
219
|
-
await makePage("page-classified", { leaves: ["domain-a/topic-x"] });
|
|
220
|
-
|
|
221
|
-
const seen: string[] = [];
|
|
222
|
-
const provider = stubProvider((messages) => {
|
|
223
|
-
const text = messages[0]?.content
|
|
224
|
-
.map((b) => (b.type === "text" ? b.text : ""))
|
|
225
|
-
.join("");
|
|
226
|
-
seen.push(text);
|
|
227
|
-
return toolResponse([2]);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const results = await assignPages({
|
|
231
|
-
tree: threeLeafTree(),
|
|
232
|
-
workspaceDir,
|
|
233
|
-
provider,
|
|
234
|
-
throttleMs: 0,
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const slugs = results.map((r) => r.slug).sort();
|
|
238
|
-
expect(slugs).toEqual(["page-empty", "page-missing"]);
|
|
239
|
-
// The classified page was never sent to the provider.
|
|
240
|
-
expect(seen.length).toBe(2);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
5
|
-
|
|
6
|
-
import { loadCore } from "../core.js";
|
|
7
|
-
|
|
8
|
-
const BUNDLED_DATA_DIR = join(import.meta.dir, "..", "data");
|
|
9
|
-
|
|
10
|
-
describe("loadCore", () => {
|
|
11
|
-
let tmpDirs: string[] = [];
|
|
12
|
-
|
|
13
|
-
afterEach(async () => {
|
|
14
|
-
await Promise.all(
|
|
15
|
-
tmpDirs.map((d) => rm(d, { recursive: true, force: true })),
|
|
16
|
-
);
|
|
17
|
-
tmpDirs = [];
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
async function makeTmpDir(): Promise<string> {
|
|
21
|
-
const dir = await mkdtemp(join(tmpdir(), "v3-core-"));
|
|
22
|
-
tmpDirs.push(dir);
|
|
23
|
-
return dir;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
test("loads alwaysOn from the supplied dataDir's core.json into a Set", async () => {
|
|
27
|
-
const core = await loadCore(BUNDLED_DATA_DIR);
|
|
28
|
-
expect(core).toBeInstanceOf(Set);
|
|
29
|
-
expect(core.has("domain-a/topic-x")).toBe(true);
|
|
30
|
-
expect(core.size).toBe(1);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("returns empty set when core.json is missing", async () => {
|
|
34
|
-
const emptyDataDir = await makeTmpDir();
|
|
35
|
-
const core = await loadCore(emptyDataDir);
|
|
36
|
-
expect(core).toBeInstanceOf(Set);
|
|
37
|
-
expect(core.size).toBe(0);
|
|
38
|
-
});
|
|
39
|
-
});
|