@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
|
@@ -13,8 +13,6 @@ import {
|
|
|
13
13
|
} from "./store.js";
|
|
14
14
|
import type { Sequence } from "./types.js";
|
|
15
15
|
|
|
16
|
-
// ── Event tracking ──────────────────────────────────────────────────
|
|
17
|
-
|
|
18
16
|
type SequenceEventType =
|
|
19
17
|
| "send"
|
|
20
18
|
| "reply"
|
|
@@ -72,8 +70,6 @@ function getRecentEvents(limit = 20): SequenceEvent[] {
|
|
|
72
70
|
return eventLog.slice(-limit).reverse();
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
// ── Metrics ─────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
73
|
interface SequenceMetrics {
|
|
78
74
|
sequenceId: string;
|
|
79
75
|
sequenceName: string;
|
package/src/sequence/engine.ts
CHANGED
|
@@ -105,8 +105,7 @@ async function processEnrollment(
|
|
|
105
105
|
},
|
|
106
106
|
"Sequence not active, skipping",
|
|
107
107
|
);
|
|
108
|
-
|
|
109
|
-
advanceEnrollmentToCurrentStep(enrollment, sequence);
|
|
108
|
+
rescheduleEnrollment(enrollment.id, Date.now() + 60_000);
|
|
110
109
|
return;
|
|
111
110
|
}
|
|
112
111
|
|
|
@@ -302,15 +301,6 @@ function buildStepPrompt(
|
|
|
302
301
|
return parts.join("\n");
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
/** Re-schedule the enrollment for the current step (used when sequence is paused). */
|
|
306
|
-
function advanceEnrollmentToCurrentStep(
|
|
307
|
-
enrollment: SequenceEnrollment,
|
|
308
|
-
_sequence: Sequence,
|
|
309
|
-
): void {
|
|
310
|
-
// Re-schedule 60 seconds from now so it gets picked up after the sequence resumes
|
|
311
|
-
rescheduleEnrollment(enrollment.id, Date.now() + 60_000);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
304
|
/**
|
|
315
305
|
* Scan conversation messages for an email thread ID returned by tool
|
|
316
306
|
* invocations. Looks for common patterns like "thread_id": "..." or
|
|
@@ -12,8 +12,6 @@ import type { SequenceEnrollment } from "./types.js";
|
|
|
12
12
|
|
|
13
13
|
const log = getLogger("sequence:guardrails");
|
|
14
14
|
|
|
15
|
-
// ── Defaults ────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
15
|
export interface GuardrailConfig {
|
|
18
16
|
dailySendCap: number;
|
|
19
17
|
perSequenceHourlyRate: number;
|
|
@@ -45,13 +43,12 @@ export function setGuardrailConfig(
|
|
|
45
43
|
return { ...config };
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
// ── Send tracking (in-memory, resets on daemon restart) ─────────────
|
|
49
|
-
|
|
50
46
|
interface SendRecord {
|
|
51
47
|
sequenceId: string;
|
|
52
48
|
timestamp: number;
|
|
53
49
|
}
|
|
54
50
|
|
|
51
|
+
// In-memory; resets on assistant restart.
|
|
55
52
|
const sendLog: SendRecord[] = [];
|
|
56
53
|
|
|
57
54
|
export function recordSend(sequenceId: string): void {
|
|
@@ -72,8 +69,6 @@ function getSendsThisHour(sequenceId: string): number {
|
|
|
72
69
|
).length;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
// ── Guardrail checks ────────────────────────────────────────────────
|
|
76
|
-
|
|
77
72
|
export type GuardrailResult =
|
|
78
73
|
| { ok: true }
|
|
79
74
|
| { ok: false; reason: string; guardrail: string };
|
package/src/sequence/importer.ts
CHANGED
|
@@ -17,8 +17,6 @@ import { enrollContact, getSequence } from "./store.js";
|
|
|
17
17
|
|
|
18
18
|
const log = getLogger("sequence:importer");
|
|
19
19
|
|
|
20
|
-
// ── Types ───────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
20
|
export interface ParsedContact {
|
|
23
21
|
email: string;
|
|
24
22
|
name?: string;
|
|
@@ -37,16 +35,12 @@ export interface EnrollResult {
|
|
|
37
35
|
failed: Array<{ email: string; reason: string }>;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
// ── Email validation ────────────────────────────────────────────────
|
|
41
|
-
|
|
42
38
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
43
39
|
|
|
44
40
|
function isValidEmail(email: string): boolean {
|
|
45
41
|
return EMAIL_RE.test(email);
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
// ── Column detection ────────────────────────────────────────────────
|
|
49
|
-
|
|
50
44
|
// Alias lists are pre-normalized (same transform as normalizeHeader) so lookups
|
|
51
45
|
// match regardless of the original casing, spacing, or punctuation in the file.
|
|
52
46
|
const EMAIL_HEADERS = [
|
|
@@ -101,8 +95,6 @@ function detectColumns(headers: string[]): {
|
|
|
101
95
|
return { emailIdx, nameIdx, firstNameIdx, lastNameIdx, contextIdxs };
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
// ── CSV parsing ─────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
98
|
function detectDelimiter(firstLine: string): string {
|
|
107
99
|
if (firstLine.includes("\t")) {
|
|
108
100
|
return "\t";
|
package/src/sequence/store.ts
CHANGED
|
@@ -33,8 +33,6 @@ import type {
|
|
|
33
33
|
UpdateSequenceInput,
|
|
34
34
|
} from "./types.js";
|
|
35
35
|
|
|
36
|
-
// ── Row Mappers ─────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
36
|
const parseSequenceRow = createRowMapper<
|
|
39
37
|
typeof sequences.$inferSelect,
|
|
40
38
|
Sequence
|
|
@@ -70,8 +68,6 @@ const parseEnrollmentRow = createRowMapper<
|
|
|
70
68
|
updatedAt: "updatedAt",
|
|
71
69
|
});
|
|
72
70
|
|
|
73
|
-
// ── Sequence CRUD ───────────────────────────────────────────────────
|
|
74
|
-
|
|
75
71
|
export function createSequence(input: CreateSequenceInput): Sequence {
|
|
76
72
|
const db = getDb();
|
|
77
73
|
const now = Date.now();
|
|
@@ -156,8 +152,6 @@ export function deleteSequence(id: string): void {
|
|
|
156
152
|
db.delete(sequences).where(eq(sequences.id, id)).run();
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
// ── Enrollment CRUD ─────────────────────────────────────────────────
|
|
160
|
-
|
|
161
155
|
export function enrollContact(input: EnrollContactInput): SequenceEnrollment {
|
|
162
156
|
const db = getDb();
|
|
163
157
|
const now = Date.now();
|
|
@@ -265,8 +259,6 @@ export function claimDueEnrollments(
|
|
|
265
259
|
|
|
266
260
|
const claimed: SequenceEnrollment[] = [];
|
|
267
261
|
for (const row of candidates) {
|
|
268
|
-
// Optimistic lock: set status to 'active' (no-op value-wise) but bump updatedAt
|
|
269
|
-
// The WHERE ensures only one claimer succeeds
|
|
270
262
|
// Optimistic lock: null out nextStepAt to prevent the row from being
|
|
271
263
|
// picked up by a concurrent claim. The WHERE on the old nextStepAt value
|
|
272
264
|
// ensures only one claimer wins (same pattern as reminders changing
|
|
@@ -358,8 +350,6 @@ export function resumeEnrollment(id: string): void {
|
|
|
358
350
|
.run();
|
|
359
351
|
}
|
|
360
352
|
|
|
361
|
-
// ── Query Helpers ───────────────────────────────────────────────────
|
|
362
|
-
|
|
363
353
|
export function findActiveEnrollmentsByEmail(
|
|
364
354
|
email: string,
|
|
365
355
|
): SequenceEnrollment[] {
|
|
@@ -43,8 +43,6 @@ import { getSignalsDir } from "../util/platform.js";
|
|
|
43
43
|
|
|
44
44
|
const log = getLogger("signal:user-message");
|
|
45
45
|
|
|
46
|
-
// ── Attachment descriptor ───────────────────────────────────────────
|
|
47
|
-
|
|
48
46
|
/** A file-backed attachment included in a signal payload. */
|
|
49
47
|
export interface SignalAttachment {
|
|
50
48
|
/** Absolute path to the file on disk. */
|
|
@@ -55,8 +53,6 @@ export interface SignalAttachment {
|
|
|
55
53
|
mimeType: string;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
// ── Dispatch helper ──────────────────────────────────────────────────
|
|
59
|
-
|
|
60
56
|
async function dispatchUserMessage(params: {
|
|
61
57
|
conversationKey: string;
|
|
62
58
|
content: string;
|
|
@@ -279,8 +275,6 @@ async function dispatchUserMessage(params: {
|
|
|
279
275
|
return { accepted: true };
|
|
280
276
|
}
|
|
281
277
|
|
|
282
|
-
// ── Signal handler ───────────────────────────────────────────────────
|
|
283
|
-
|
|
284
278
|
/**
|
|
285
279
|
* Read a `signals/user-message.<requestId>` file and dispatch the message
|
|
286
280
|
* through the daemon's send pipeline. Writes
|
|
@@ -14,6 +14,8 @@ import { homedir } from "node:os";
|
|
|
14
14
|
import { dirname, join, posix, resolve, sep } from "node:path";
|
|
15
15
|
import { gunzipSync } from "node:zlib";
|
|
16
16
|
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
17
19
|
import {
|
|
18
20
|
MALFORMED_USTAR_SIZE,
|
|
19
21
|
parseUstarSizeField,
|
|
@@ -27,6 +29,7 @@ import { computeSkillHash, writeInstallMeta } from "./install-meta.js";
|
|
|
27
29
|
import {
|
|
28
30
|
isSkillCompatibleWithPlatform,
|
|
29
31
|
normalizeSkillPlatforms,
|
|
32
|
+
SKILL_PLATFORM_VALUES,
|
|
30
33
|
type SkillPlatform,
|
|
31
34
|
skillPlatformUnavailableMessage,
|
|
32
35
|
} from "./platform-compatibility.js";
|
|
@@ -142,21 +145,46 @@ export function getRepoSkillsDir(): string | undefined {
|
|
|
142
145
|
* `metadata.vellum.*` shape used by the local `catalog.json`. Both shapes are
|
|
143
146
|
* accepted here so the daemon has a single canonical representation downstream.
|
|
144
147
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
const CatalogPlatformsSchema = z
|
|
149
|
+
.array(z.enum(SKILL_PLATFORM_VALUES).optional().catch(undefined))
|
|
150
|
+
.transform((platforms) =>
|
|
151
|
+
platforms.filter((platform): platform is SkillPlatform => platform != null),
|
|
152
|
+
);
|
|
153
|
+
const CatalogVellumMetadataSchema = z
|
|
154
|
+
.object({
|
|
155
|
+
"display-name": z.string().optional().catch(undefined),
|
|
156
|
+
"activation-hints": z.array(z.string()).optional().catch(undefined),
|
|
157
|
+
"avoid-when": z.array(z.string()).optional().catch(undefined),
|
|
158
|
+
"feature-flag": z.string().optional().catch(undefined),
|
|
159
|
+
category: z.string().optional().catch(undefined),
|
|
160
|
+
platforms: CatalogPlatformsSchema.optional().catch(undefined),
|
|
161
|
+
})
|
|
162
|
+
.passthrough();
|
|
163
|
+
const CatalogMetadataSchema = z
|
|
164
|
+
.object({
|
|
165
|
+
icon: z.string().optional().catch(undefined),
|
|
166
|
+
emoji: z.string().optional().catch(undefined),
|
|
167
|
+
vellum: CatalogVellumMetadataSchema.optional().catch(undefined),
|
|
168
|
+
})
|
|
169
|
+
.passthrough();
|
|
170
|
+
const RawCatalogEntrySchema = z.object({
|
|
171
|
+
id: z.string().optional().catch(undefined),
|
|
172
|
+
name: z.string().optional().catch(undefined),
|
|
173
|
+
description: z.string().optional().catch(undefined),
|
|
174
|
+
icon: z.string().optional().catch(undefined),
|
|
175
|
+
emoji: z.string().optional().catch(undefined),
|
|
176
|
+
includes: z.array(z.string()).optional().catch(undefined),
|
|
177
|
+
version: z.string().optional().catch(undefined),
|
|
178
|
+
updatedAt: z.string().optional().catch(undefined),
|
|
179
|
+
display_name: z.string().optional().catch(undefined),
|
|
180
|
+
category: z.string().optional().catch(undefined),
|
|
181
|
+
platforms: CatalogPlatformsSchema.optional().catch(undefined),
|
|
182
|
+
updated_at: z.string().optional().catch(undefined),
|
|
183
|
+
metadata: CatalogMetadataSchema.optional().catch(undefined),
|
|
184
|
+
});
|
|
185
|
+
const CatalogManifestSchema = z.object({
|
|
186
|
+
skills: z.array(z.unknown()),
|
|
187
|
+
});
|
|
160
188
|
|
|
161
189
|
function asStr(value: unknown): string | undefined {
|
|
162
190
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
@@ -172,10 +200,11 @@ function asStr(value: unknown): string | undefined {
|
|
|
172
200
|
* so a future API change to the nested shape keeps working.
|
|
173
201
|
*/
|
|
174
202
|
function normalizeCatalogEntry(raw: unknown): CatalogSkill | null {
|
|
175
|
-
|
|
203
|
+
const parsed = RawCatalogEntrySchema.safeParse(raw);
|
|
204
|
+
if (!parsed.success) {
|
|
176
205
|
return null;
|
|
177
206
|
}
|
|
178
|
-
const entry =
|
|
207
|
+
const entry = parsed.data;
|
|
179
208
|
|
|
180
209
|
const id = asStr(entry.id);
|
|
181
210
|
if (!id) {
|
|
@@ -227,11 +256,11 @@ export async function fetchCatalog(): Promise<CatalogSkill[]> {
|
|
|
227
256
|
);
|
|
228
257
|
}
|
|
229
258
|
|
|
230
|
-
const manifest = (await response.json())
|
|
231
|
-
if (!
|
|
259
|
+
const manifest = CatalogManifestSchema.safeParse(await response.json());
|
|
260
|
+
if (!manifest.success) {
|
|
232
261
|
throw new Error("Platform catalog has invalid skills array");
|
|
233
262
|
}
|
|
234
|
-
return manifest.skills
|
|
263
|
+
return manifest.data.skills
|
|
235
264
|
.map((s) => normalizeCatalogEntry(s))
|
|
236
265
|
.filter((s): s is CatalogSkill => s !== null);
|
|
237
266
|
}
|
|
@@ -239,11 +268,11 @@ export async function fetchCatalog(): Promise<CatalogSkill[]> {
|
|
|
239
268
|
export function readLocalCatalog(repoSkillsDir: string): CatalogSkill[] {
|
|
240
269
|
try {
|
|
241
270
|
const raw = readFileSync(join(repoSkillsDir, "catalog.json"), "utf-8");
|
|
242
|
-
const manifest = JSON.parse(raw)
|
|
243
|
-
if (!
|
|
271
|
+
const manifest = CatalogManifestSchema.safeParse(JSON.parse(raw));
|
|
272
|
+
if (!manifest.success) {
|
|
244
273
|
return [];
|
|
245
274
|
}
|
|
246
|
-
return manifest.skills
|
|
275
|
+
return manifest.data.skills
|
|
247
276
|
.map((s) => normalizeCatalogEntry(s))
|
|
248
277
|
.filter((s): s is CatalogSkill => s !== null);
|
|
249
278
|
} catch {
|
|
@@ -287,8 +287,7 @@ export const EXTRACT_LINKS_EXPRESSION = `
|
|
|
287
287
|
export function parseBrowserMode(
|
|
288
288
|
input: Record<string, unknown>,
|
|
289
289
|
): { ok: true; mode: BrowserMode } | { ok: false; error: string } {
|
|
290
|
-
const
|
|
291
|
-
const result = normalizeBrowserMode(raw);
|
|
290
|
+
const result = normalizeBrowserMode(input.browser_mode);
|
|
292
291
|
if ("error" in result) {
|
|
293
292
|
return { ok: false, error: `Error: ${result.error}` };
|
|
294
293
|
}
|
|
@@ -900,7 +899,7 @@ export async function executeBrowserNavigate(
|
|
|
900
899
|
// authoritative — otherwise a pin from an earlier navigate would
|
|
901
900
|
// still capture the command and route it to the dedicated tab.
|
|
902
901
|
clearPinnedTab(context.conversationId, targetClientId);
|
|
903
|
-
cdp.setCdpSessionId?.(
|
|
902
|
+
cdp.setCdpSessionId?.("active");
|
|
904
903
|
} else if (cdp.kind === "extension") {
|
|
905
904
|
const alreadyPinned =
|
|
906
905
|
getPinnedTab(context.conversationId, targetClientId) !== undefined;
|
|
@@ -921,24 +920,20 @@ export async function executeBrowserNavigate(
|
|
|
921
920
|
? result.clientId
|
|
922
921
|
: undefined;
|
|
923
922
|
if (!tabId) {
|
|
924
|
-
// Malformed createTab response (no tabId). We're nominally falling
|
|
925
|
-
// back to active-tab routing — but the live `cdp` instance was
|
|
926
|
-
// already constructed with whatever pin was in scope for this
|
|
927
|
-
// conversation, AND the pin store still holds it for future
|
|
928
|
-
// client construction. Clear both: the pin store (so the next
|
|
929
|
-
// executeBrowserNavigate builds a clean client) AND the current
|
|
930
|
-
// cdp instance's session (so the Page.navigate that runs in a
|
|
931
|
-
// few lines targets the active tab rather than the stale pin).
|
|
932
|
-
// Without the setCdpSessionId(undefined) call, the warn message
|
|
933
|
-
// is a lie: navigation would still route to the dead tab via the
|
|
934
|
-
// already-injected cdpSessionId and likely fail with
|
|
935
|
-
// cdp_session_not_found.
|
|
936
923
|
clearPinnedTab(context.conversationId, targetClientId);
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
924
|
+
try {
|
|
925
|
+
cdp.dispose();
|
|
926
|
+
} catch (disposeErr) {
|
|
927
|
+
log.warn(
|
|
928
|
+
{ conversationId: context.conversationId, err: disposeErr },
|
|
929
|
+
"Failed to dispose CDP client after Vellum.createTab returned no tabId",
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
content:
|
|
934
|
+
"Error: Failed to open a new tab for navigation: Vellum.createTab returned no tabId. Pass --use-active-tab to navigate the currently-active tab instead.",
|
|
935
|
+
isError: true,
|
|
936
|
+
};
|
|
942
937
|
} else {
|
|
943
938
|
cdp.setCdpSessionId?.(tabId);
|
|
944
939
|
setPinnedTab(context.conversationId, tabId, clientId);
|
|
@@ -2672,9 +2667,11 @@ async function checkExtensionModeStatus(
|
|
|
2672
2667
|
context: ToolContext,
|
|
2673
2668
|
autoCandidate: boolean,
|
|
2674
2669
|
): Promise<BrowserStatusModeResult> {
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2670
|
+
if (
|
|
2671
|
+
!HostBrowserProxy.instance.hasExtensionClient(
|
|
2672
|
+
context.sourceActorPrincipalId,
|
|
2673
|
+
)
|
|
2674
|
+
) {
|
|
2678
2675
|
return {
|
|
2679
2676
|
mode: BROWSER_STATUS_MODE.EXTENSION,
|
|
2680
2677
|
available: false,
|
|
@@ -465,7 +465,7 @@ function createTransport(ws: WsLike): CdpWsTransport {
|
|
|
465
465
|
ws.addEventListener("close", handleClose);
|
|
466
466
|
ws.addEventListener("error", handleError);
|
|
467
467
|
|
|
468
|
-
|
|
468
|
+
return {
|
|
469
469
|
get closed() {
|
|
470
470
|
return disposed || closed;
|
|
471
471
|
},
|
|
@@ -607,6 +607,4 @@ function createTransport(ws: WsLike): CdpWsTransport {
|
|
|
607
607
|
}
|
|
608
608
|
},
|
|
609
609
|
};
|
|
610
|
-
|
|
611
|
-
return transport;
|
|
612
610
|
}
|
|
@@ -158,6 +158,7 @@ export function executeDocumentOpen(
|
|
|
158
158
|
surfaceId: doc.surfaceId,
|
|
159
159
|
title: doc.title,
|
|
160
160
|
initialContent: doc.content,
|
|
161
|
+
revision: doc.revision,
|
|
161
162
|
});
|
|
162
163
|
|
|
163
164
|
context.sendToClient({
|
|
@@ -244,6 +245,7 @@ function maybeReuseEmptyDocument(
|
|
|
244
245
|
surfaceId,
|
|
245
246
|
markdown: initialContent,
|
|
246
247
|
mode: "replace",
|
|
248
|
+
revision: update.revision,
|
|
247
249
|
});
|
|
248
250
|
|
|
249
251
|
context.sendToClient({
|
|
@@ -304,7 +306,7 @@ export function executeDocumentCreate(
|
|
|
304
306
|
isError: true,
|
|
305
307
|
};
|
|
306
308
|
}
|
|
307
|
-
const { surfaceId } = created;
|
|
309
|
+
const { surfaceId, revision } = created;
|
|
308
310
|
|
|
309
311
|
// Send document_editor_show message to open the built-in RTE
|
|
310
312
|
if (context.sendToClient) {
|
|
@@ -314,6 +316,7 @@ export function executeDocumentCreate(
|
|
|
314
316
|
surfaceId,
|
|
315
317
|
title,
|
|
316
318
|
initialContent,
|
|
319
|
+
revision,
|
|
317
320
|
});
|
|
318
321
|
|
|
319
322
|
context.sendToClient({
|
|
@@ -441,6 +444,7 @@ export function executeDocumentUpdate(
|
|
|
441
444
|
surfaceId,
|
|
442
445
|
markdown: applied,
|
|
443
446
|
mode,
|
|
447
|
+
revision: result.revision,
|
|
444
448
|
});
|
|
445
449
|
|
|
446
450
|
return {
|
|
@@ -684,16 +688,14 @@ export function executeDocumentReplaceText(
|
|
|
684
688
|
}
|
|
685
689
|
|
|
686
690
|
if (context.sendToClient && result.content_changed) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
context.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
});
|
|
696
|
-
}
|
|
691
|
+
context.sendToClient({
|
|
692
|
+
type: "document_editor_update",
|
|
693
|
+
conversationId: context.conversationId,
|
|
694
|
+
surfaceId,
|
|
695
|
+
markdown: result.content,
|
|
696
|
+
mode: "replace",
|
|
697
|
+
revision: result.revision,
|
|
698
|
+
});
|
|
697
699
|
}
|
|
698
700
|
|
|
699
701
|
return {
|
package/src/tools/executor.ts
CHANGED
|
@@ -482,8 +482,7 @@ export function computePerToolTimeoutMs(
|
|
|
482
482
|
const { questionResponseTimeoutSec } = getConfig().timeouts;
|
|
483
483
|
return (questionResponseTimeoutSec + 5) * 1000;
|
|
484
484
|
}
|
|
485
|
-
|
|
486
|
-
return safeTimeoutMs(rawTimeoutSec);
|
|
485
|
+
return safeTimeoutMs(getConfig().timeouts.toolExecutionTimeoutSec);
|
|
487
486
|
}
|
|
488
487
|
|
|
489
488
|
/**
|
|
@@ -486,7 +486,7 @@ export const hostShellTool = {
|
|
|
486
486
|
// Only the first handler to fire should wake the agent.
|
|
487
487
|
let completed = false;
|
|
488
488
|
|
|
489
|
-
child.on("close", (code) => {
|
|
489
|
+
child.on("close", (code, signal) => {
|
|
490
490
|
if (completed) {
|
|
491
491
|
return;
|
|
492
492
|
}
|
|
@@ -494,6 +494,7 @@ export const hostShellTool = {
|
|
|
494
494
|
clearTimeout(timer);
|
|
495
495
|
const result = collector.format(code, timedOut, timeoutSec, {
|
|
496
496
|
started: launch.didStart(),
|
|
497
|
+
signal: aborted ? null : signal,
|
|
497
498
|
});
|
|
498
499
|
// Cancel takes precedence over the SIGKILL-induced error result.
|
|
499
500
|
const status = aborted
|
|
@@ -667,12 +668,13 @@ export const hostShellTool = {
|
|
|
667
668
|
}
|
|
668
669
|
}
|
|
669
670
|
|
|
670
|
-
child.on("close", (code) => {
|
|
671
|
+
child.on("close", (code, signal) => {
|
|
671
672
|
clearTimeout(timer);
|
|
672
673
|
context.signal?.removeEventListener("abort", onAbort);
|
|
673
674
|
|
|
674
675
|
const result = collector.format(code, timedOut, timeoutSec, {
|
|
675
676
|
started: launch.didStart(),
|
|
677
|
+
signal: context.signal?.aborted ? null : signal,
|
|
676
678
|
});
|
|
677
679
|
|
|
678
680
|
resolve({
|
|
@@ -13,6 +13,7 @@ let mockFastcrwSecureKey: string | undefined;
|
|
|
13
13
|
let mockSearxngSecureKey: string | undefined;
|
|
14
14
|
let mockTinyfishSecureKey: string | undefined;
|
|
15
15
|
let mockExaSecureKey: string | undefined;
|
|
16
|
+
let mockSerplySecureKey: string | undefined;
|
|
16
17
|
let mockManagedSearchProxyResult: any;
|
|
17
18
|
let mockManagedSearchAvailable = true;
|
|
18
19
|
let mockManagedSearchProxyCalls: Array<{
|
|
@@ -69,6 +70,9 @@ mock.module("../../../security/secure-keys.js", () => ({
|
|
|
69
70
|
if (provider === "exa") {
|
|
70
71
|
return mockExaSecureKey;
|
|
71
72
|
}
|
|
73
|
+
if (provider === "serply") {
|
|
74
|
+
return mockSerplySecureKey;
|
|
75
|
+
}
|
|
72
76
|
return undefined;
|
|
73
77
|
},
|
|
74
78
|
}));
|
|
@@ -116,6 +120,7 @@ describe("web_search tool", () => {
|
|
|
116
120
|
mockSearxngSecureKey = undefined;
|
|
117
121
|
mockTinyfishSecureKey = undefined;
|
|
118
122
|
mockExaSecureKey = undefined;
|
|
123
|
+
mockSerplySecureKey = undefined;
|
|
119
124
|
mockManagedSearchProxyCalls = [];
|
|
120
125
|
mockManagedSearchAvailable = true;
|
|
121
126
|
mockManagedSearchProxyResult = {
|
|
@@ -1441,6 +1446,106 @@ describe("web_search tool", () => {
|
|
|
1441
1446
|
expect(result.activityMetadata?.webSearch?.provider).toBe("exa");
|
|
1442
1447
|
});
|
|
1443
1448
|
|
|
1449
|
+
// ---- Serply provider ---------------------------------------------------
|
|
1450
|
+
|
|
1451
|
+
test("Serply search sends the query form request and maps freshness", async () => {
|
|
1452
|
+
seedWebSearch("your-own", "serply");
|
|
1453
|
+
mockSerplySecureKey = "serply_test";
|
|
1454
|
+
let capturedUrl = "";
|
|
1455
|
+
let capturedInit: RequestInit | undefined;
|
|
1456
|
+
globalThis.fetch = (async (url: string, init?: RequestInit) => {
|
|
1457
|
+
capturedUrl = url;
|
|
1458
|
+
capturedInit = init;
|
|
1459
|
+
return new Response(
|
|
1460
|
+
JSON.stringify({
|
|
1461
|
+
query: "fresh tools",
|
|
1462
|
+
total: 1,
|
|
1463
|
+
results: [
|
|
1464
|
+
{
|
|
1465
|
+
title: "Serply Result",
|
|
1466
|
+
link: "https://example.com/serply",
|
|
1467
|
+
description: "Fresh from Serply",
|
|
1468
|
+
position: 1,
|
|
1469
|
+
},
|
|
1470
|
+
],
|
|
1471
|
+
}),
|
|
1472
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1473
|
+
);
|
|
1474
|
+
}) as any;
|
|
1475
|
+
|
|
1476
|
+
const result = await execute({
|
|
1477
|
+
query: "fresh tools",
|
|
1478
|
+
freshness: "pw",
|
|
1479
|
+
count: 5,
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
expect(result.isError).toBe(false);
|
|
1483
|
+
const parsed = new URL(capturedUrl);
|
|
1484
|
+
expect(parsed.origin + parsed.pathname).toBe(
|
|
1485
|
+
"https://api.serply.io/v1/search",
|
|
1486
|
+
);
|
|
1487
|
+
expect(parsed.searchParams.get("q")).toBe("fresh tools");
|
|
1488
|
+
expect(parsed.searchParams.get("num")).toBe("5");
|
|
1489
|
+
expect(parsed.searchParams.get("tbs")).toBe("qdr:w");
|
|
1490
|
+
expect(capturedInit?.method).toBe("GET");
|
|
1491
|
+
const headers = new Headers(capturedInit?.headers);
|
|
1492
|
+
expect(headers.get("x-api-key")).toBe("serply_test");
|
|
1493
|
+
expect(headers.get("user-agent")).toBe("vellum-assistant");
|
|
1494
|
+
expect(result.content).toContain("Serply Result");
|
|
1495
|
+
expect(result.content).toContain("Fresh from Serply");
|
|
1496
|
+
expect(result.activityMetadata?.webSearch?.provider).toBe("serply");
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
test("Serply omits tbs without freshness, clamps num, and trims to count", async () => {
|
|
1500
|
+
seedWebSearch("your-own", "serply");
|
|
1501
|
+
mockSerplySecureKey = "serply_test";
|
|
1502
|
+
let capturedUrl = "";
|
|
1503
|
+
globalThis.fetch = (async (url: string) => {
|
|
1504
|
+
capturedUrl = url;
|
|
1505
|
+
return new Response(
|
|
1506
|
+
JSON.stringify({
|
|
1507
|
+
results: [
|
|
1508
|
+
{ title: "A", link: "https://a.example.com", description: "a" },
|
|
1509
|
+
{ title: "B", link: "https://b.example.com", description: "b" },
|
|
1510
|
+
{ title: "C", link: "https://c.example.com", description: "c" },
|
|
1511
|
+
],
|
|
1512
|
+
}),
|
|
1513
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1514
|
+
);
|
|
1515
|
+
}) as any;
|
|
1516
|
+
|
|
1517
|
+
const result = await execute({ query: "test", count: 2 });
|
|
1518
|
+
|
|
1519
|
+
const parsed = new URL(capturedUrl);
|
|
1520
|
+
expect(parsed.searchParams.get("tbs")).toBeNull();
|
|
1521
|
+
expect(parsed.searchParams.get("num")).toBe("2");
|
|
1522
|
+
expect(result.content).toContain("A");
|
|
1523
|
+
expect(result.content).toContain("B");
|
|
1524
|
+
expect(result.content).not.toContain("https://c.example.com");
|
|
1525
|
+
expect(result.activityMetadata?.webSearch?.resultCount).toBe(2);
|
|
1526
|
+
|
|
1527
|
+
const clamped = await execute({ query: "test", count: 20 });
|
|
1528
|
+
expect(new URL(capturedUrl).searchParams.get("num")).toBe("10");
|
|
1529
|
+
expect(clamped.isError).toBe(false);
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
test.each([401, 402, 403])(
|
|
1533
|
+
"Serply handles %d access error",
|
|
1534
|
+
async (status) => {
|
|
1535
|
+
seedWebSearch("your-own", "serply");
|
|
1536
|
+
mockSerplySecureKey = "serply_test";
|
|
1537
|
+
globalThis.fetch = (async () =>
|
|
1538
|
+
new Response(JSON.stringify({ detail: "Invalid API key" }), {
|
|
1539
|
+
status,
|
|
1540
|
+
})) as any;
|
|
1541
|
+
|
|
1542
|
+
const result = await execute({ query: "test" });
|
|
1543
|
+
|
|
1544
|
+
expect(result.isError).toBe(true);
|
|
1545
|
+
expect(result.activityMetadata?.webSearch?.provider).toBe("serply");
|
|
1546
|
+
},
|
|
1547
|
+
);
|
|
1548
|
+
|
|
1444
1549
|
// ---- Provider fallback --------------------------------------------------
|
|
1445
1550
|
|
|
1446
1551
|
test("falls back from perplexity to brave when perplexity has no key", async () => {
|
|
@@ -1530,9 +1530,8 @@ export async function executeFastcrwScrape(
|
|
|
1530
1530
|
input: Record<string, unknown>,
|
|
1531
1531
|
options: { apiKey: string; signal?: AbortSignal },
|
|
1532
1532
|
): Promise<ToolExecutionResult> {
|
|
1533
|
-
const apiBase = getConfig().services["web-fetch"]?.apiBase;
|
|
1534
1533
|
const endpoint = resolveProviderApiUrl(
|
|
1535
|
-
apiBase,
|
|
1534
|
+
getConfig().services["web-fetch"]?.apiBase,
|
|
1536
1535
|
FASTCRW_SCRAPE_PATH,
|
|
1537
1536
|
FASTCRW_DEFAULT_API_BASE,
|
|
1538
1537
|
);
|
|
@@ -1587,9 +1586,8 @@ export async function executeTinyfishFetch(
|
|
|
1587
1586
|
1,
|
|
1588
1587
|
MAX_TIMEOUT_SECONDS,
|
|
1589
1588
|
);
|
|
1590
|
-
const apiBase = getConfig().services["web-fetch"]?.apiBase;
|
|
1591
1589
|
const endpoint = resolveProviderApiUrl(
|
|
1592
|
-
apiBase,
|
|
1590
|
+
getConfig().services["web-fetch"]?.apiBase,
|
|
1593
1591
|
"/",
|
|
1594
1592
|
TINYFISH_DEFAULT_FETCH_API_BASE,
|
|
1595
1593
|
);
|