@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292116.41f7caa
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/.env.example +2 -0
- package/ARCHITECTURE.md +26 -3
- package/docs/automatic-done.md +32 -0
- package/docs/desktop-browser-cli.md +3 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
- package/node_modules/@vellumai/environments/src/shell.ts +22 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/openapi.yaml +270 -10
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +76 -0
- package/src/__tests__/anthropic-provider.test.ts +2 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
- package/src/__tests__/config-get-vision-flag.test.ts +1 -1
- package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
- package/src/__tests__/conversation-evictor.test.ts +54 -0
- package/src/__tests__/conversation-pairing.test.ts +112 -0
- package/src/__tests__/document-append-idempotency.test.ts +13 -0
- package/src/__tests__/document-create-dedupe.test.ts +13 -0
- package/src/__tests__/document-find-replace.test.ts +13 -0
- package/src/__tests__/document-revision-history.test.ts +390 -0
- package/src/__tests__/document-revision.test.ts +425 -0
- package/src/__tests__/document-tool-security.test.ts +13 -0
- package/src/__tests__/document-update-default-surface.test.ts +13 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
- package/src/__tests__/gateway-flag-listener.test.ts +59 -6
- package/src/__tests__/headless-browser-navigate.test.ts +20 -30
- package/src/__tests__/host-cu-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -2
- package/src/__tests__/identity-routes.test.ts +4 -3
- package/src/__tests__/list-messages-client-message-id.test.ts +43 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -4
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/notification-platform-adapter.test.ts +83 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/openai-provider.test.ts +38 -11
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/public-ingress-urls.test.ts +26 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
- package/src/__tests__/skill-execute-input.test.ts +11 -0
- package/src/__tests__/tool-approval-handler.test.ts +4 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -0
- package/src/__tests__/usage-attribution.test.ts +31 -0
- package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
- package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
- package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
- package/src/acp/agent-process.ts +6 -1
- package/src/acp/client-handler.ts +6 -1
- package/src/agent/loop-unparseable-tool-retry.test.ts +333 -0
- package/src/agent/loop.ts +161 -17
- package/src/api/events/document-editor-show.ts +5 -0
- package/src/api/events/document-editor-update.ts +10 -0
- package/src/api/events/tool-result.ts +1 -0
- package/src/calls/__tests__/call-controller.test.ts +273 -1
- package/src/calls/__tests__/media-stream-server-integration.test.ts +40 -0
- package/src/calls/__tests__/media-stream-stt-session.test.ts +33 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
- package/src/calls/call-constants.ts +10 -0
- package/src/calls/call-controller.ts +359 -25
- package/src/calls/channel-admission-reader.ts +15 -9
- package/src/calls/media-stream-server.ts +18 -0
- package/src/calls/media-stream-stt-session.ts +30 -9
- package/src/calls/twilio-provider.ts +14 -31
- package/src/calls/voice-session-bridge.ts +18 -1
- package/src/cli/commands/browser.ts +1 -2
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
- package/src/cli/lib/bundled-marketplace.json +54 -2
- package/src/cli/lib/inspect-plugin.ts +15 -15
- package/src/cli/lib/list-installed-plugins.ts +29 -28
- package/src/cli/lib/plugin-artifact.ts +74 -63
- package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
- package/src/cli/lib/plugin-details.ts +27 -42
- package/src/cli/lib/plugin-marketplace.ts +6 -8
- package/src/cli/lib/plugin-surfaces.ts +8 -7
- package/src/cli.ts +1 -3
- package/src/config/__tests__/chat-settings.test.ts +121 -0
- package/src/config/assistant-feature-flags.ts +28 -23
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
- package/src/config/chat-settings.ts +100 -0
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/feature-flag-cache.ts +7 -25
- package/src/config/feature-flag-registry.json +9 -9
- package/src/config/loader.ts +5 -1
- package/src/config/profile-text-generation.ts +25 -21
- package/src/config/schemas/conversations.ts +14 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/notifications.ts +8 -1
- package/src/contacts/gateway-channel-read.ts +1 -1
- package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
- package/src/conversations/__tests__/auto-archive.test.ts +304 -0
- package/src/conversations/auto-archive-activity.ts +61 -0
- package/src/conversations/auto-archive.ts +201 -0
- package/src/daemon/computer-use-observation.test.ts +81 -0
- package/src/daemon/computer-use-observation.ts +23 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -2
- package/src/daemon/conversation-agent-loop.ts +14 -6
- package/src/daemon/conversation-evictor.ts +50 -16
- package/src/daemon/conversation-messaging.ts +4 -0
- package/src/daemon/conversation-process.ts +15 -0
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/doordash-steps.ts +1 -3
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
- package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
- package/src/daemon/handlers/config-a2a.ts +75 -2
- package/src/daemon/handlers/shared.ts +13 -2
- package/src/daemon/host-cu-proxy.ts +34 -3
- package/src/daemon/lifecycle.ts +15 -2
- package/src/daemon/process-message.ts +5 -0
- package/src/daemon/shutdown-handlers.ts +3 -0
- package/src/daemon/turn-finalization.ts +4 -0
- package/src/desktop/desktop-browser-endpoint.ts +11 -2
- package/src/desktop/desktop-chrome-policy.test.ts +1 -0
- package/src/desktop/desktop-chrome-policy.ts +7 -1
- package/src/desktop/desktop-computer-use.test.ts +14 -4
- package/src/desktop/desktop-session-manager.ts +5 -3
- package/src/documents/document-revisions-store.ts +153 -0
- package/src/documents/document-store.ts +471 -133
- package/src/followups/followup-store.ts +0 -4
- package/src/i18n/__tests__/i18n.test.ts +25 -0
- package/src/i18n/assistant-text.ts +18 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/messages.ts +11 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
- package/src/inbound/oauth-callback-url.ts +30 -1
- package/src/inbound/public-ingress-urls.ts +12 -2
- package/src/ipc/gateway-client.test.ts +40 -0
- package/src/ipc/gateway-client.ts +12 -29
- package/src/ipc/gateway-flag-listener.ts +7 -10
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
- package/src/live-voice/__tests__/protocol.test.ts +77 -0
- package/src/live-voice/__tests__/share-targets.test.ts +163 -0
- package/src/live-voice/live-voice-session.ts +23 -6
- package/src/live-voice/protocol.ts +22 -0
- package/src/live-voice/share-targets.ts +225 -0
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
- package/src/mcp/client.ts +1 -19
- package/src/mcp/mcp-oauth-provider.ts +66 -49
- package/src/media/openrouter-image-service.ts +39 -22
- package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
- package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
- package/src/monitoring/control.ts +2 -1
- package/src/monitoring/oom-inheritance-sweep.ts +96 -0
- package/src/monitoring/oom-kill-reporter.ts +469 -0
- package/src/monitoring/resource-sampler.ts +30 -0
- package/src/notifications/README.md +53 -4
- package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
- package/src/notifications/__tests__/broadcaster.test.ts +558 -6
- package/src/notifications/__tests__/completion-policy.test.ts +37 -0
- package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
- package/src/notifications/adapters/platform.ts +14 -0
- package/src/notifications/assistant-initiated-thread.ts +101 -0
- package/src/notifications/broadcaster.ts +94 -16
- package/src/notifications/chat-reply-policy.ts +11 -0
- package/src/notifications/completion-policy.ts +12 -2
- package/src/notifications/conversation-pairing.ts +15 -70
- package/src/notifications/emit-signal.ts +34 -3
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connect-orchestrator.ts +1 -2
- package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
- package/src/persistence/conversation-auto-archive.ts +170 -0
- package/src/persistence/conversation-crud.ts +2 -1
- package/src/persistence/conversation-queries.ts +15 -8
- package/src/persistence/embeddings/embed.test.ts +96 -0
- package/src/persistence/embeddings/embed.ts +20 -2
- package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
- package/src/persistence/embeddings/embedding-gemini.ts +39 -1
- package/src/persistence/embeddings/embedding-local.ts +6 -0
- package/src/persistence/embeddings/qdrant-manager.ts +5 -0
- package/src/persistence/job-utils.ts +10 -0
- package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
- package/src/persistence/migrations/384-add-document-revision.ts +21 -0
- package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
- package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
- package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
- package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
- package/src/persistence/schema/conversations.ts +1 -0
- package/src/persistence/schema/documents.ts +20 -0
- package/src/persistence/steps.ts +6 -0
- package/src/platform/managed-speech.ts +42 -46
- package/src/plugin-api/vision-support.test.ts +21 -21
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +25 -0
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/fireworks/client.test.ts +168 -0
- package/src/providers/fireworks/client.ts +24 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/ionet/client.test.ts +143 -0
- package/src/providers/ionet/client.ts +37 -0
- package/src/providers/model-catalog.ts +141 -84
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +33 -2
- package/src/providers/retry.ts +56 -4
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +10 -2
- package/src/providers/unparseable-tool-args.ts +29 -5
- package/src/providers/usage-tracking.ts +5 -3
- package/src/runtime/assistant-event-hub.ts +23 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
- package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +2 -0
- package/src/runtime/channel-retry-sweep.ts +1 -2
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
- package/src/runtime/migrations/vbundle-builder.ts +19 -3
- package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
- package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
- package/src/runtime/migrations/vbundle-validator.ts +54 -1
- package/src/runtime/pending-interactions.ts +20 -2
- package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
- package/src/runtime/routes/contact-routes.ts +2 -2
- package/src/runtime/routes/conversation-list-routes.ts +10 -2
- package/src/runtime/routes/conversation-query-routes.ts +48 -10
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/documents-routes.ts +185 -47
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -0
- package/src/runtime/routes/host-bash-routes.ts +1 -0
- package/src/runtime/routes/host-browser-routes.ts +15 -0
- package/src/runtime/routes/host-cu-routes.ts +4 -1
- package/src/runtime/routes/host-file-routes.ts +1 -0
- package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
- package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
- package/src/runtime/routes/host-transfer-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/integrations/a2a.ts +4 -1
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/notification-routes.ts +1 -2
- package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
- package/src/runtime/routes/user-route-dispatcher.ts +5 -1
- package/src/runtime/verification-templates.ts +1 -2
- package/src/sequence/analytics.ts +0 -4
- package/src/sequence/engine.ts +1 -11
- package/src/sequence/guardrails.ts +1 -6
- package/src/sequence/importer.ts +0 -8
- package/src/sequence/store.ts +0 -10
- package/src/signals/user-message.ts +0 -6
- package/src/skills/catalog-install.ts +52 -23
- package/src/tools/browser/browser-execution.ts +20 -23
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
- package/src/tools/document/document-tool.ts +13 -11
- package/src/tools/executor.ts +1 -2
- package/src/tools/host-terminal/host-shell.ts +4 -2
- package/src/tools/network/__tests__/web-search.test.ts +105 -0
- package/src/tools/network/web-fetch.ts +2 -4
- package/src/tools/network/web-search.ts +239 -12
- package/src/tools/shared/filesystem/format-diff.ts +1 -2
- package/src/tools/shared/shell-output.test.ts +42 -0
- package/src/tools/shared/shell-output.ts +27 -4
- package/src/tools/skills/execute.ts +10 -7
- package/src/tools/terminal/shell.ts +3 -3
- package/src/tools/tool-approval-handler.ts +9 -1
- package/src/tools/ui-surface/visual-validation.ts +1 -2
- package/src/usage/attribution.ts +18 -1
- package/src/util/__tests__/oom-priority.test.ts +55 -0
- package/src/util/__tests__/worker-memory.test.ts +16 -0
- package/src/util/debounce.ts +0 -4
- package/src/util/oom-priority.ts +79 -0
- package/src/util/platform.ts +0 -2
- package/src/util/plugin-manifest.ts +5 -7
- package/src/util/pricing.ts +25 -0
- package/src/util/worker-memory.ts +9 -3
- package/src/util/worker-process.ts +8 -0
- package/src/workspace/byok-default-profile-ensure.ts +11 -0
- package/src/workspace/hatched-date.ts +11 -4
- package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
- package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -46,6 +46,10 @@ const FIRECRAWL_API_URL = "https://api.firecrawl.dev/v2/search";
|
|
|
46
46
|
const FASTCRW_SEARCH_PATH = "/v1/search";
|
|
47
47
|
const TINYFISH_DEFAULT_SEARCH_API_BASE = "https://api.search.tinyfish.ai";
|
|
48
48
|
const EXA_API_URL = "https://api.exa.ai/search";
|
|
49
|
+
const SERPLY_API_URL = "https://api.serply.io/v1/search";
|
|
50
|
+
// Serply returns at most 10 organic results per call; larger counts are
|
|
51
|
+
// clamped on request and trimmed on response.
|
|
52
|
+
const SERPLY_MAX_RESULTS = 10;
|
|
49
53
|
// Keenable is keyless by default: the public path needs no key (rate-limited);
|
|
50
54
|
// a key switches to the authenticated path and lifts the cap.
|
|
51
55
|
const KEENABLE_API_BASE_URL = "https://api.keenable.ai";
|
|
@@ -61,7 +65,8 @@ type WebSearchProvider =
|
|
|
61
65
|
| "fastcrw"
|
|
62
66
|
| "searxng"
|
|
63
67
|
| "tinyfish"
|
|
64
|
-
| "exa"
|
|
68
|
+
| "exa"
|
|
69
|
+
| "serply";
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* Arguments passed to every {@link WebSearchAdapter}. The full superset is
|
|
@@ -213,6 +218,19 @@ interface ExaSearchResponse {
|
|
|
213
218
|
results?: ExaSearchResult[];
|
|
214
219
|
}
|
|
215
220
|
|
|
221
|
+
interface SerplySearchResult {
|
|
222
|
+
title?: string | null;
|
|
223
|
+
link?: string;
|
|
224
|
+
description?: string | null;
|
|
225
|
+
position?: number;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface SerplySearchResponse {
|
|
229
|
+
query?: string;
|
|
230
|
+
total?: number;
|
|
231
|
+
results?: SerplySearchResult[];
|
|
232
|
+
}
|
|
233
|
+
|
|
216
234
|
const SEARXNG_MISSING_INSTANCE_MESSAGE =
|
|
217
235
|
"SearXNG needs an instance URL. Set API Base in Settings under Web Search.";
|
|
218
236
|
const SEARXNG_JSON_DISABLED_MESSAGE =
|
|
@@ -236,8 +254,11 @@ function getWebSearchProvider(): WebSearchProvider {
|
|
|
236
254
|
async function getApiKey(
|
|
237
255
|
provider: WebSearchProvider,
|
|
238
256
|
): Promise<string | undefined> {
|
|
239
|
-
|
|
240
|
-
|
|
257
|
+
return (
|
|
258
|
+
(await getProviderKeyAsync(
|
|
259
|
+
WEB_SEARCH_ADAPTERS[provider].providerKeyName,
|
|
260
|
+
)) ?? undefined
|
|
261
|
+
);
|
|
241
262
|
}
|
|
242
263
|
|
|
243
264
|
function fallbackProvidersFor(
|
|
@@ -810,6 +831,81 @@ function exaStartPublishedDateForFreshness(
|
|
|
810
831
|
return new Date(now - days * 24 * 60 * 60 * 1000).toISOString();
|
|
811
832
|
}
|
|
812
833
|
|
|
834
|
+
function formatSerplyResults(
|
|
835
|
+
data: SerplySearchResponse,
|
|
836
|
+
query: string,
|
|
837
|
+
): string {
|
|
838
|
+
const results = data.results ?? [];
|
|
839
|
+
if (results.length === 0) {
|
|
840
|
+
return `No results found for "${query}".`;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const lines: string[] = [`Web search results for "${query}":\n`];
|
|
844
|
+
for (let i = 0; i < results.length; i++) {
|
|
845
|
+
const result = results[i];
|
|
846
|
+
const title =
|
|
847
|
+
result.title?.trim() || result.link?.trim() || "Untitled result";
|
|
848
|
+
lines.push(`${i + 1}. ${title}`);
|
|
849
|
+
if (result.link) {
|
|
850
|
+
lines.push(` URL: ${result.link}`);
|
|
851
|
+
}
|
|
852
|
+
const snippet = result.description?.trim();
|
|
853
|
+
if (snippet) {
|
|
854
|
+
lines.push(` ${snippet}`);
|
|
855
|
+
}
|
|
856
|
+
lines.push("");
|
|
857
|
+
}
|
|
858
|
+
return lines.join("\n");
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function buildSerplyMetadata(
|
|
862
|
+
data: SerplySearchResponse,
|
|
863
|
+
query: string,
|
|
864
|
+
durationMs: number,
|
|
865
|
+
): WebSearchMetadata {
|
|
866
|
+
const results = data.results ?? [];
|
|
867
|
+
const items: WebSearchResultItem[] = results.map((result, index) => {
|
|
868
|
+
const url = result.link ?? "";
|
|
869
|
+
const domain = extractDomain(url);
|
|
870
|
+
return {
|
|
871
|
+
rank: index + 1,
|
|
872
|
+
title: result.title?.trim() || url.trim() || "Untitled result",
|
|
873
|
+
url,
|
|
874
|
+
domain,
|
|
875
|
+
faviconUrl: faviconUrlForDomain(domain),
|
|
876
|
+
snippet: result.description?.trim() || undefined,
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
return {
|
|
880
|
+
query,
|
|
881
|
+
provider: "serply",
|
|
882
|
+
resultCount: items.length,
|
|
883
|
+
durationMs,
|
|
884
|
+
results: items,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Serply passes Google's `tbs` time filter through unchanged, so the tool's
|
|
890
|
+
* freshness buckets map onto the `qdr:` values Google understands.
|
|
891
|
+
*/
|
|
892
|
+
function serplyTbsForFreshness(
|
|
893
|
+
freshness: string | undefined,
|
|
894
|
+
): string | undefined {
|
|
895
|
+
switch (freshness) {
|
|
896
|
+
case "pd":
|
|
897
|
+
return "qdr:d";
|
|
898
|
+
case "pw":
|
|
899
|
+
return "qdr:w";
|
|
900
|
+
case "pm":
|
|
901
|
+
return "qdr:m";
|
|
902
|
+
case "py":
|
|
903
|
+
return "qdr:y";
|
|
904
|
+
default:
|
|
905
|
+
return undefined;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
813
909
|
function tinyfishRecencyMinutesForFreshness(
|
|
814
910
|
freshness: string | undefined,
|
|
815
911
|
): number | undefined {
|
|
@@ -1517,9 +1613,8 @@ function executeFastcrwSearch(
|
|
|
1517
1613
|
apiKey: string,
|
|
1518
1614
|
signal?: AbortSignal,
|
|
1519
1615
|
): Promise<ToolExecutionResult> {
|
|
1520
|
-
const apiBase = getConfig().services["web-search"]?.apiBase;
|
|
1521
1616
|
const endpoint = resolveProviderApiUrl(
|
|
1522
|
-
apiBase,
|
|
1617
|
+
getConfig().services["web-search"]?.apiBase,
|
|
1523
1618
|
FASTCRW_SEARCH_PATH,
|
|
1524
1619
|
FASTCRW_DEFAULT_API_BASE,
|
|
1525
1620
|
);
|
|
@@ -1779,9 +1874,8 @@ async function executeTinyfishSearch(
|
|
|
1779
1874
|
signal?: AbortSignal,
|
|
1780
1875
|
): Promise<ToolExecutionResult> {
|
|
1781
1876
|
const startedAt = Date.now();
|
|
1782
|
-
const apiBase = getConfig().services["web-search"]?.apiBase;
|
|
1783
1877
|
const endpoint = resolveProviderApiUrl(
|
|
1784
|
-
apiBase,
|
|
1878
|
+
getConfig().services["web-search"]?.apiBase,
|
|
1785
1879
|
"/",
|
|
1786
1880
|
TINYFISH_DEFAULT_SEARCH_API_BASE,
|
|
1787
1881
|
);
|
|
@@ -2023,6 +2117,128 @@ async function executeExaSearch(
|
|
|
2023
2117
|
);
|
|
2024
2118
|
}
|
|
2025
2119
|
|
|
2120
|
+
async function executeSerplySearch(
|
|
2121
|
+
query: string,
|
|
2122
|
+
count: number,
|
|
2123
|
+
freshness: string | undefined,
|
|
2124
|
+
apiKey: string,
|
|
2125
|
+
signal?: AbortSignal,
|
|
2126
|
+
): Promise<ToolExecutionResult> {
|
|
2127
|
+
const startedAt = Date.now();
|
|
2128
|
+
const params = new URLSearchParams({
|
|
2129
|
+
q: query,
|
|
2130
|
+
num: String(Math.min(count, SERPLY_MAX_RESULTS)),
|
|
2131
|
+
});
|
|
2132
|
+
const tbs = serplyTbsForFreshness(freshness);
|
|
2133
|
+
if (tbs !== undefined) {
|
|
2134
|
+
params.set("tbs", tbs);
|
|
2135
|
+
}
|
|
2136
|
+
const url = `${SERPLY_API_URL}?${params.toString()}`;
|
|
2137
|
+
|
|
2138
|
+
const headers = {
|
|
2139
|
+
Accept: "application/json",
|
|
2140
|
+
"X-Api-Key": apiKey.trim(),
|
|
2141
|
+
"User-Agent": "vellum-assistant",
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
|
|
2145
|
+
let response: Response;
|
|
2146
|
+
try {
|
|
2147
|
+
response = await fetch(url, { method: "GET", headers, signal });
|
|
2148
|
+
} catch (err) {
|
|
2149
|
+
return networkFailureResult(query, "serply", startedAt, err, signal);
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
const bodyText = await response.text();
|
|
2153
|
+
if (response.ok) {
|
|
2154
|
+
let data: SerplySearchResponse;
|
|
2155
|
+
try {
|
|
2156
|
+
data = JSON.parse(bodyText) as SerplySearchResponse;
|
|
2157
|
+
} catch {
|
|
2158
|
+
return errorResult(
|
|
2159
|
+
query,
|
|
2160
|
+
"serply",
|
|
2161
|
+
startedAt,
|
|
2162
|
+
"Serply Search returned an invalid JSON payload.",
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
if (data.results && data.results.length > count) {
|
|
2166
|
+
data.results = data.results.slice(0, count);
|
|
2167
|
+
}
|
|
2168
|
+
const durationMs = Date.now() - startedAt;
|
|
2169
|
+
return {
|
|
2170
|
+
content:
|
|
2171
|
+
wrapUntrustedContent(formatSerplyResults(data, query), {
|
|
2172
|
+
source: "search",
|
|
2173
|
+
sourceDetail: "serply",
|
|
2174
|
+
}) + CITATION_INSTRUCTION,
|
|
2175
|
+
isError: false,
|
|
2176
|
+
activityMetadata: {
|
|
2177
|
+
webSearch: buildSerplyMetadata(data, query, durationMs),
|
|
2178
|
+
},
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
if (response.status === 401) {
|
|
2183
|
+
return errorResult(
|
|
2184
|
+
query,
|
|
2185
|
+
"serply",
|
|
2186
|
+
startedAt,
|
|
2187
|
+
"Invalid or expired Serply API key",
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
if (response.status === 402) {
|
|
2191
|
+
return errorResult(
|
|
2192
|
+
query,
|
|
2193
|
+
"serply",
|
|
2194
|
+
startedAt,
|
|
2195
|
+
"Serply account has no remaining credits.",
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
if (response.status === 403) {
|
|
2199
|
+
return errorResult(
|
|
2200
|
+
query,
|
|
2201
|
+
"serply",
|
|
2202
|
+
startedAt,
|
|
2203
|
+
"Serply Search request was forbidden by the upstream service.",
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
|
|
2208
|
+
const delayMs = getHttpRetryDelay(
|
|
2209
|
+
response,
|
|
2210
|
+
attempt,
|
|
2211
|
+
DEFAULT_BASE_DELAY_MS,
|
|
2212
|
+
);
|
|
2213
|
+
log.warn(
|
|
2214
|
+
{ attempt: attempt + 1, delayMs },
|
|
2215
|
+
"Serply Search rate limited, retrying",
|
|
2216
|
+
);
|
|
2217
|
+
await sleep(delayMs);
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
log.warn({ status: response.status }, "Serply Search API error");
|
|
2222
|
+
return backendFailureResult(
|
|
2223
|
+
query,
|
|
2224
|
+
"serply",
|
|
2225
|
+
startedAt,
|
|
2226
|
+
{ statusCode: response.status, error: rawBodyDetail(bodyText) },
|
|
2227
|
+
response.status === 429
|
|
2228
|
+
? "Serply Search rate limit exceeded after retries. Try again shortly."
|
|
2229
|
+
: `Serply Search API returned status ${response.status}`,
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
return backendFailureResult(
|
|
2234
|
+
query,
|
|
2235
|
+
"serply",
|
|
2236
|
+
startedAt,
|
|
2237
|
+
{ statusCode: 429 },
|
|
2238
|
+
"Serply Search rate limit exceeded after retries. Try again shortly.",
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2026
2242
|
// ----------------------------------------------------------------------------
|
|
2027
2243
|
// Adapter registry
|
|
2028
2244
|
//
|
|
@@ -2114,6 +2330,14 @@ const exaSearchAdapter: WebSearchAdapter = {
|
|
|
2114
2330
|
executeExaSearch(query, count, freshness, apiKey, signal),
|
|
2115
2331
|
};
|
|
2116
2332
|
|
|
2333
|
+
const serplySearchAdapter: WebSearchAdapter = {
|
|
2334
|
+
id: "serply",
|
|
2335
|
+
providerKeyName: "serply",
|
|
2336
|
+
fallbackOrder: 10,
|
|
2337
|
+
execute: ({ query, count, freshness, apiKey, signal }) =>
|
|
2338
|
+
executeSerplySearch(query, count, freshness, apiKey, signal),
|
|
2339
|
+
};
|
|
2340
|
+
|
|
2117
2341
|
/**
|
|
2118
2342
|
* All built-in web-search adapters keyed by provider id. The
|
|
2119
2343
|
* `Record<WebSearchProvider, ...>` shape forces TypeScript to flag any
|
|
@@ -2129,6 +2353,7 @@ const WEB_SEARCH_ADAPTERS: Record<WebSearchProvider, WebSearchAdapter> = {
|
|
|
2129
2353
|
searxng: searxngSearchAdapter,
|
|
2130
2354
|
tinyfish: tinyfishSearchAdapter,
|
|
2131
2355
|
exa: exaSearchAdapter,
|
|
2356
|
+
serply: serplySearchAdapter,
|
|
2132
2357
|
};
|
|
2133
2358
|
|
|
2134
2359
|
/**
|
|
@@ -2160,7 +2385,7 @@ export const webSearchTool = {
|
|
|
2160
2385
|
count: {
|
|
2161
2386
|
type: "number",
|
|
2162
2387
|
description:
|
|
2163
|
-
"Number of results to return (1-20, default 10). Used with Brave, Tavily, Firecrawl, Keenable, fastCRW, SearXNG, TinyFish, and
|
|
2388
|
+
"Number of results to return (1-20, default 10). Used with Brave, Tavily, Firecrawl, Keenable, fastCRW, SearXNG, TinyFish, Exa, and Serply providers.",
|
|
2164
2389
|
},
|
|
2165
2390
|
offset: {
|
|
2166
2391
|
type: "number",
|
|
@@ -2170,7 +2395,7 @@ export const webSearchTool = {
|
|
|
2170
2395
|
freshness: {
|
|
2171
2396
|
type: "string",
|
|
2172
2397
|
description:
|
|
2173
|
-
'Filter by recency: "pd" (past day), "pw" (past week), "pm" (past month), "py" (past year). Used with Brave, Tavily, Firecrawl, Keenable, fastCRW, SearXNG, TinyFish, and
|
|
2398
|
+
'Filter by recency: "pd" (past day), "pw" (past week), "pm" (past month), "py" (past year). Used with Brave, Tavily, Firecrawl, Keenable, fastCRW, SearXNG, TinyFish, Exa, and Serply providers. SearXNG maps day/month/year and omits week.',
|
|
2174
2399
|
},
|
|
2175
2400
|
},
|
|
2176
2401
|
required: ["query"],
|
|
@@ -2274,8 +2499,10 @@ export const webSearchTool = {
|
|
|
2274
2499
|
// `mode: "managed"` did for installs that never configured a search
|
|
2275
2500
|
// key. Read the configured provider off config — the local `provider`
|
|
2276
2501
|
// has already been coerced to "perplexity".
|
|
2277
|
-
|
|
2278
|
-
|
|
2502
|
+
if (
|
|
2503
|
+
getConfig().services["web-search"].provider ===
|
|
2504
|
+
"inference-provider-native"
|
|
2505
|
+
) {
|
|
2279
2506
|
const { managedSearchAvailable } =
|
|
2280
2507
|
await import("./managed-search-proxy.js");
|
|
2281
2508
|
if (await managedSearchAvailable()) {
|
|
@@ -2286,7 +2513,7 @@ export const webSearchTool = {
|
|
|
2286
2513
|
query,
|
|
2287
2514
|
provider,
|
|
2288
2515
|
startedAt,
|
|
2289
|
-
"No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, `keys set tavily <key>`, `keys set firecrawl <key>`, or `keys set fastcrw <key>`, or `keys set tinyfish <key>`, or `keys set exa <key>`, or configure it from the Settings page under API Keys. Or switch the web-search provider to Keenable, which works without a key, or SearXNG with your instance URL.",
|
|
2516
|
+
"No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, `keys set tavily <key>`, `keys set firecrawl <key>`, or `keys set fastcrw <key>`, or `keys set tinyfish <key>`, or `keys set exa <key>`, or `keys set serply <key>`, or configure it from the Settings page under API Keys. Or switch the web-search provider to Keenable, which works without a key, or SearXNG with your instance URL.",
|
|
2290
2517
|
);
|
|
2291
2518
|
}
|
|
2292
2519
|
}
|
|
@@ -23,6 +23,5 @@ export function formatWriteSummary(
|
|
|
23
23
|
if (isNewFile) {
|
|
24
24
|
return `(new file, ${newLineCount} line${newLineCount !== 1 ? "s" : ""})`;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
return `(${oldLineCount} → ${newLineCount} lines)`;
|
|
26
|
+
return `(${oldContent.split("\n").length} → ${newLineCount} lines)`;
|
|
28
27
|
}
|
|
@@ -112,3 +112,45 @@ describe("formatShellOutput truncation", () => {
|
|
|
112
112
|
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
describe("formatShellOutput kill reporting", () => {
|
|
117
|
+
test("names the signal and hints at memory for a SIGKILL", () => {
|
|
118
|
+
const result = formatShellOutput("", "", null, false, 120, {
|
|
119
|
+
signal: "SIGKILL",
|
|
120
|
+
});
|
|
121
|
+
expect(result.isError).toBe(true);
|
|
122
|
+
expect(result.content).toContain('<command_killed signal="SIGKILL" />');
|
|
123
|
+
expect(result.content).toContain("out of memory");
|
|
124
|
+
expect(result.content).not.toContain('code="null"');
|
|
125
|
+
expect(result.status).toBe('<command_killed signal="SIGKILL" />');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("keeps the output produced before the kill", () => {
|
|
129
|
+
const result = formatShellOutput("partial", "", null, false, 120, {
|
|
130
|
+
signal: "SIGTERM",
|
|
131
|
+
});
|
|
132
|
+
expect(result.content.startsWith("partial\n")).toBe(true);
|
|
133
|
+
expect(result.content).toContain("killed by SIGTERM");
|
|
134
|
+
expect(result.content).not.toContain("out of memory");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("does not infer a kill from a 128 + n exit code", () => {
|
|
138
|
+
const result = formatShellOutput("", "Killed", 137, false, 120);
|
|
139
|
+
expect(result.content).toBe("Killed");
|
|
140
|
+
expect(result.status).toBe('<command_exit code="137" />');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("leaves a timeout framed as a timeout", () => {
|
|
144
|
+
const result = formatShellOutput("", "", null, true, 120, {
|
|
145
|
+
signal: "SIGKILL",
|
|
146
|
+
});
|
|
147
|
+
expect(result.content).toContain("<command_timeout");
|
|
148
|
+
expect(result.content).not.toContain("command_killed");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("leaves an ordinary failure unchanged", () => {
|
|
152
|
+
const result = formatShellOutput("", "boom", 1, false, 120);
|
|
153
|
+
expect(result.content).toBe("boom");
|
|
154
|
+
expect(result.status).toBe('<command_exit code="1" />');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -13,6 +13,15 @@ export interface ShellOutputResult {
|
|
|
13
13
|
isError: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function describeKill(signal: NodeJS.Signals): string {
|
|
17
|
+
const tag = `<command_killed signal="${signal}" />`;
|
|
18
|
+
const hint =
|
|
19
|
+
signal === "SIGKILL"
|
|
20
|
+
? " A SIGKILL the command did not send itself usually means the system ran out of memory."
|
|
21
|
+
: "";
|
|
22
|
+
return `${tag}\nCommand was killed by ${signal} before it finished.${hint}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
type StdioStream = "stdout" | "stderr";
|
|
17
26
|
|
|
18
27
|
type DataListener = (data: Buffer | string) => void;
|
|
@@ -34,7 +43,11 @@ export function formatShellOutput(
|
|
|
34
43
|
code: number | null,
|
|
35
44
|
timedOut: boolean,
|
|
36
45
|
timeoutSec: number,
|
|
37
|
-
options?: {
|
|
46
|
+
options?: {
|
|
47
|
+
truncated?: boolean;
|
|
48
|
+
started?: boolean;
|
|
49
|
+
signal?: NodeJS.Signals | null;
|
|
50
|
+
},
|
|
38
51
|
): ShellOutputResult {
|
|
39
52
|
if (options?.started === false) {
|
|
40
53
|
return {
|
|
@@ -66,6 +79,12 @@ export function formatShellOutput(
|
|
|
66
79
|
statusParts.push(OUTPUT_TRUNCATED_TAG);
|
|
67
80
|
}
|
|
68
81
|
|
|
82
|
+
const killSignal = timedOut ? null : (options?.signal ?? null);
|
|
83
|
+
if (killSignal) {
|
|
84
|
+
output += (output ? "\n" : "") + describeKill(killSignal);
|
|
85
|
+
statusParts.push(`<command_killed signal="${killSignal}" />`);
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
if (!output.trim()) {
|
|
70
89
|
if (code === 0) {
|
|
71
90
|
output = "<command_completed />";
|
|
@@ -74,7 +93,7 @@ export function formatShellOutput(
|
|
|
74
93
|
output = `${exitTag}\nCommand failed with exit code ${code}. No stdout or stderr output was produced.`;
|
|
75
94
|
statusParts.push(exitTag);
|
|
76
95
|
}
|
|
77
|
-
} else if (code !== 0 && !timedOut) {
|
|
96
|
+
} else if (code !== 0 && !timedOut && !killSignal) {
|
|
78
97
|
statusParts.push(`<command_exit code="${code}" />`);
|
|
79
98
|
}
|
|
80
99
|
|
|
@@ -148,7 +167,7 @@ export class BoundedStdioCollector {
|
|
|
148
167
|
code: number | null,
|
|
149
168
|
timedOut: boolean,
|
|
150
169
|
timeoutSec: number,
|
|
151
|
-
options?: { started?: boolean },
|
|
170
|
+
options?: { started?: boolean; signal?: NodeJS.Signals | null },
|
|
152
171
|
): ShellOutputResult {
|
|
153
172
|
return formatShellOutput(
|
|
154
173
|
Buffer.concat(this.stdoutParts).toString(),
|
|
@@ -156,7 +175,11 @@ export class BoundedStdioCollector {
|
|
|
156
175
|
code,
|
|
157
176
|
timedOut,
|
|
158
177
|
timeoutSec,
|
|
159
|
-
{
|
|
178
|
+
{
|
|
179
|
+
truncated: this.truncated,
|
|
180
|
+
started: options?.started,
|
|
181
|
+
signal: options?.signal,
|
|
182
|
+
},
|
|
160
183
|
);
|
|
161
184
|
}
|
|
162
185
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
1
3
|
import { RiskLevel } from "../../permissions/types.js";
|
|
2
4
|
import { isUnparseableToolArgs } from "../../providers/unparseable-tool-args.js";
|
|
3
5
|
import { declareDaemonActivityField } from "../schema-transforms.js";
|
|
@@ -9,6 +11,9 @@ import type {
|
|
|
9
11
|
|
|
10
12
|
/** Envelope keys consumed by `skill_execute` itself, never inner-tool params. */
|
|
11
13
|
const SKILL_EXECUTE_ENVELOPE_KEYS = new Set(["tool", "input", "activity"]);
|
|
14
|
+
const recoveredSkillExecuteEnvelopeSchema = z
|
|
15
|
+
.object({ tool: z.string() })
|
|
16
|
+
.passthrough();
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* Recover a `skill_execute` envelope that the provider layer wrapped under the
|
|
@@ -30,13 +35,11 @@ export function recoverSkillExecuteEnvelope(
|
|
|
30
35
|
return envelope;
|
|
31
36
|
}
|
|
32
37
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
) {
|
|
39
|
-
return parsed as Record<string, unknown>;
|
|
38
|
+
const recovered = recoveredSkillExecuteEnvelopeSchema.safeParse(
|
|
39
|
+
JSON.parse(envelope._raw),
|
|
40
|
+
);
|
|
41
|
+
if (recovered.success) {
|
|
42
|
+
return recovered.data;
|
|
40
43
|
}
|
|
41
44
|
} catch {
|
|
42
45
|
// Genuinely malformed/truncated — leave wrapped for the retryable error.
|
|
@@ -374,6 +374,7 @@ export const shellTool = {
|
|
|
374
374
|
|
|
375
375
|
const fmtResult = collector.format(code, timedOut, timeoutSec, {
|
|
376
376
|
started: launch.didStart(),
|
|
377
|
+
signal: aborted ? null : signal,
|
|
377
378
|
});
|
|
378
379
|
|
|
379
380
|
const status: BackgroundToolCompletedEvent["status"] = aborted
|
|
@@ -531,7 +532,7 @@ export const shellTool = {
|
|
|
531
532
|
// -----------------------------------------------------------------------
|
|
532
533
|
// Foreground mode: await the process and return its output.
|
|
533
534
|
// -----------------------------------------------------------------------
|
|
534
|
-
|
|
535
|
+
return await new Promise<ToolExecutionResult>((resolve) => {
|
|
535
536
|
let timedOut = false;
|
|
536
537
|
const startedAt = Date.now();
|
|
537
538
|
|
|
@@ -585,6 +586,7 @@ export const shellTool = {
|
|
|
585
586
|
|
|
586
587
|
const fmtResult = collector.format(code, timedOut, timeoutSec, {
|
|
587
588
|
started: launch.didStart(),
|
|
589
|
+
signal: context.signal?.aborted ? null : signal,
|
|
588
590
|
});
|
|
589
591
|
|
|
590
592
|
resolve({
|
|
@@ -620,8 +622,6 @@ export const shellTool = {
|
|
|
620
622
|
});
|
|
621
623
|
});
|
|
622
624
|
});
|
|
623
|
-
|
|
624
|
-
return result;
|
|
625
625
|
},
|
|
626
626
|
} satisfies ToolDefinition;
|
|
627
627
|
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
isWorkspaceWriteTool,
|
|
28
28
|
} from "../permissions/workspace-policy.js";
|
|
29
29
|
import {
|
|
30
|
+
boundUnparseableToolArgs,
|
|
30
31
|
isUnparseableToolArgs,
|
|
31
32
|
unparseableToolArgsMessage,
|
|
32
33
|
} from "../providers/unparseable-tool-args.js";
|
|
@@ -797,7 +798,14 @@ export class ToolApprovalHandler {
|
|
|
797
798
|
// mangled. Fail loudly instead so the model retries.
|
|
798
799
|
if (isUnparseableToolArgs(input)) {
|
|
799
800
|
const msg = unparseableToolArgsMessage(name, input._raw);
|
|
800
|
-
this.auditGateError(
|
|
801
|
+
this.auditGateError(
|
|
802
|
+
context,
|
|
803
|
+
name,
|
|
804
|
+
boundUnparseableToolArgs(input),
|
|
805
|
+
riskLevel,
|
|
806
|
+
startTime,
|
|
807
|
+
msg,
|
|
808
|
+
);
|
|
801
809
|
return { allowed: false, result: { content: msg, isError: true } };
|
|
802
810
|
}
|
|
803
811
|
|
|
@@ -302,8 +302,7 @@ function collectSurfaceFillOnTextProblems(html: string): string[] {
|
|
|
302
302
|
const offenders: string[] = [];
|
|
303
303
|
for (const rule of html.matchAll(/([^{}<>]{1,120})\{([^{}]*)\}/g)) {
|
|
304
304
|
const selector = rule[1].trim();
|
|
305
|
-
|
|
306
|
-
if (!/fill\s*:\s*var\(--surface-/.test(body)) {
|
|
305
|
+
if (!/fill\s*:\s*var\(--surface-/.test(rule[2])) {
|
|
307
306
|
continue;
|
|
308
307
|
}
|
|
309
308
|
if (SHAPE_SCOPED_SELECTOR_PATTERN.test(selector)) {
|
package/src/usage/attribution.ts
CHANGED
|
@@ -10,11 +10,15 @@ import { safeStringSlice } from "../util/unicode.js";
|
|
|
10
10
|
|
|
11
11
|
const MAX_METADATA_VALUE_LENGTH = 128;
|
|
12
12
|
|
|
13
|
+
/** `profileSource` reported for a turn whose profile the Auto router chose. */
|
|
14
|
+
export const AUTO_PROFILE_SOURCE = "auto";
|
|
15
|
+
|
|
13
16
|
export type UsageAttributionProfileSource =
|
|
14
17
|
| "call_site"
|
|
15
18
|
| "conversation"
|
|
16
19
|
| "active"
|
|
17
20
|
| "default"
|
|
21
|
+
| typeof AUTO_PROFILE_SOURCE
|
|
18
22
|
| "unknown";
|
|
19
23
|
|
|
20
24
|
export interface UsageAttributionInput {
|
|
@@ -27,6 +31,12 @@ export interface UsageAttributionInput {
|
|
|
27
31
|
*/
|
|
28
32
|
profileResolutionCallSite?: LLMCallSite | null;
|
|
29
33
|
overrideProfile?: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Who chose `overrideProfile`. `"auto"` marks a profile the Auto profile's
|
|
36
|
+
* router picked for the turn, so usage rows can tell an Auto pick from a
|
|
37
|
+
* user pin that landed on the same profile. Absent for a user pin.
|
|
38
|
+
*/
|
|
39
|
+
overrideProfileOrigin?: "auto" | null;
|
|
30
40
|
/**
|
|
31
41
|
* Mirrors `ResolveCallSiteOpts.forceOverrideProfile`: the override profile
|
|
32
42
|
* was floated above the call-site layers for this request, so attribution
|
|
@@ -227,9 +237,16 @@ function appliedProfileFromWinnerSelection(
|
|
|
227
237
|
call_site: "call_site",
|
|
228
238
|
default: "default",
|
|
229
239
|
} as const;
|
|
240
|
+
// An override the Auto router chose is reported as its own source: the
|
|
241
|
+
// applied profile is the routed arm, so without this a routed turn would
|
|
242
|
+
// be indistinguishable from a user who pinned that arm.
|
|
243
|
+
const profileSource =
|
|
244
|
+
selection.source === "override" && input.overrideProfileOrigin === "auto"
|
|
245
|
+
? AUTO_PROFILE_SOURCE
|
|
246
|
+
: sourceBySelection[selection.source];
|
|
230
247
|
return {
|
|
231
248
|
appliedProfile: selection.profileName,
|
|
232
|
-
profileSource
|
|
249
|
+
profileSource,
|
|
233
250
|
};
|
|
234
251
|
}
|
|
235
252
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import { setOomScoreAdj, withOomScoreAdj } from "../oom-priority.js";
|
|
7
|
+
|
|
8
|
+
function fakeProc(pid: number): string {
|
|
9
|
+
const procRoot = mkdtempSync(join(tmpdir(), "oom-priority-"));
|
|
10
|
+
mkdirSync(join(procRoot, String(pid)));
|
|
11
|
+
return procRoot;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("setOomScoreAdj", () => {
|
|
15
|
+
test("writes the value for a pid on Linux", () => {
|
|
16
|
+
const procRoot = fakeProc(42);
|
|
17
|
+
expect(setOomScoreAdj(1000, 42, { procRoot, platform: "linux" })).toBe(
|
|
18
|
+
true,
|
|
19
|
+
);
|
|
20
|
+
expect(readFileSync(join(procRoot, "42", "oom_score_adj"), "utf-8")).toBe(
|
|
21
|
+
"1000",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("is a no-op off Linux", () => {
|
|
26
|
+
const procRoot = fakeProc(42);
|
|
27
|
+
expect(setOomScoreAdj(1000, 42, { procRoot, platform: "darwin" })).toBe(
|
|
28
|
+
false,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("reports failure for a pid that is gone", () => {
|
|
33
|
+
const procRoot = fakeProc(42);
|
|
34
|
+
expect(setOomScoreAdj(0, 43, { procRoot, platform: "linux" })).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("withOomScoreAdj", () => {
|
|
39
|
+
test("execs the command through a shell that raises its own score on Linux", () => {
|
|
40
|
+
expect(withOomScoreAdj(["chrome", "--flag"], 1000, "linux")).toEqual([
|
|
41
|
+
"sh",
|
|
42
|
+
"-c",
|
|
43
|
+
'echo 1000 2>/dev/null >/proc/self/oom_score_adj; exec "$0" "$@"',
|
|
44
|
+
"chrome",
|
|
45
|
+
"--flag",
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("returns the command unchanged off Linux", () => {
|
|
50
|
+
expect(withOomScoreAdj(["chrome", "--flag"], 1000, "darwin")).toEqual([
|
|
51
|
+
"chrome",
|
|
52
|
+
"--flag",
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|