@vellumai/assistant 0.4.49 → 0.4.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
routeConnection,
|
|
20
20
|
stripQueryString,
|
|
21
21
|
} from "../../../outbound-proxy/index.js";
|
|
22
|
-
import {
|
|
22
|
+
import { getSecureKeyAsync } from "../../../security/secure-keys.js";
|
|
23
23
|
import { getLogger } from "../../../util/logger.js";
|
|
24
24
|
import { silentlyWithLog } from "../../../util/silently.js";
|
|
25
25
|
import {
|
|
@@ -97,10 +97,10 @@ const acquireLocks = new Map<string, Promise<ProxySession>>();
|
|
|
97
97
|
* Handles optional composition with a second credential and value transforms.
|
|
98
98
|
* Returns null if any referenced credential cannot be resolved.
|
|
99
99
|
*/
|
|
100
|
-
function buildInjectedValue(
|
|
100
|
+
async function buildInjectedValue(
|
|
101
101
|
tpl: CredentialInjectionTemplate,
|
|
102
102
|
primaryValue: string,
|
|
103
|
-
): string | null {
|
|
103
|
+
): Promise<string | null> {
|
|
104
104
|
let value = primaryValue;
|
|
105
105
|
|
|
106
106
|
if (tpl.composeWith) {
|
|
@@ -109,7 +109,7 @@ function buildInjectedValue(
|
|
|
109
109
|
tpl.composeWith.field,
|
|
110
110
|
);
|
|
111
111
|
if (!composed) return null;
|
|
112
|
-
const composedValue =
|
|
112
|
+
const composedValue = await getSecureKeyAsync(composed.storageKey);
|
|
113
113
|
if (!composedValue) return null;
|
|
114
114
|
value = `${value}${tpl.composeWith.separator}${composedValue}`;
|
|
115
115
|
}
|
|
@@ -311,10 +311,10 @@ export async function startSession(
|
|
|
311
311
|
if (tpl.injectionType === "header" && tpl.headerName) {
|
|
312
312
|
const resolved = resolveById(credId);
|
|
313
313
|
if (!resolved) return req.headers;
|
|
314
|
-
const value =
|
|
314
|
+
const value = await getSecureKeyAsync(resolved.storageKey);
|
|
315
315
|
if (!value) return req.headers;
|
|
316
316
|
|
|
317
|
-
const headerValue = buildInjectedValue(tpl, value);
|
|
317
|
+
const headerValue = await buildInjectedValue(tpl, value);
|
|
318
318
|
if (!headerValue) {
|
|
319
319
|
log.warn(
|
|
320
320
|
{ host: req.hostname, credentialId: credId },
|
|
@@ -399,11 +399,11 @@ export async function startSession(
|
|
|
399
399
|
const { credentialId, template } = decision;
|
|
400
400
|
const resolved = resolveById(credentialId);
|
|
401
401
|
if (!resolved) return {};
|
|
402
|
-
const value =
|
|
402
|
+
const value = await getSecureKeyAsync(resolved.storageKey);
|
|
403
403
|
if (!value) return {};
|
|
404
404
|
|
|
405
405
|
if (template.injectionType === "header" && template.headerName) {
|
|
406
|
-
const headerValue = buildInjectedValue(template, value);
|
|
406
|
+
const headerValue = await buildInjectedValue(template, value);
|
|
407
407
|
if (!headerValue) {
|
|
408
408
|
log.warn(
|
|
409
409
|
{ hostname, credentialId },
|
|
@@ -22,8 +22,15 @@ const VALID_ROUTING_INTENTS: RoutingIntent[] = [
|
|
|
22
22
|
|
|
23
23
|
export async function executeScheduleCreate(
|
|
24
24
|
input: Record<string, unknown>,
|
|
25
|
-
|
|
25
|
+
context: ToolContext,
|
|
26
26
|
): Promise<ToolExecutionResult> {
|
|
27
|
+
if (context.trustClass !== "guardian") {
|
|
28
|
+
return {
|
|
29
|
+
content:
|
|
30
|
+
"Error: schedule_create is restricted to guardian actors because schedules execute with elevated privileges.",
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
27
34
|
const name = input.name as string;
|
|
28
35
|
const timezone = (input.timezone as string) ?? null;
|
|
29
36
|
const message = input.message as string;
|
|
@@ -108,7 +115,7 @@ export async function executeScheduleCreate(
|
|
|
108
115
|
});
|
|
109
116
|
|
|
110
117
|
const fireDate = formatLocalDate(job.nextRunAt);
|
|
111
|
-
const integrations = formatIntegrationSummary();
|
|
118
|
+
const integrations = await formatIntegrationSummary();
|
|
112
119
|
return {
|
|
113
120
|
content: [
|
|
114
121
|
`One-shot schedule created successfully.`,
|
|
@@ -190,7 +197,7 @@ export async function executeScheduleCreate(
|
|
|
190
197
|
: describeCronExpression(job.cronExpression);
|
|
191
198
|
|
|
192
199
|
const nextRunDate = formatLocalDate(job.nextRunAt);
|
|
193
|
-
const integrations = formatIntegrationSummary();
|
|
200
|
+
const integrations = await formatIntegrationSummary();
|
|
194
201
|
return {
|
|
195
202
|
content: [
|
|
196
203
|
`Recurring schedule created successfully.`,
|
|
@@ -25,8 +25,15 @@ const VALID_ROUTING_INTENTS: RoutingIntent[] = [
|
|
|
25
25
|
|
|
26
26
|
export async function executeScheduleUpdate(
|
|
27
27
|
input: Record<string, unknown>,
|
|
28
|
-
|
|
28
|
+
context: ToolContext,
|
|
29
29
|
): Promise<ToolExecutionResult> {
|
|
30
|
+
if (context.trustClass !== "guardian") {
|
|
31
|
+
return {
|
|
32
|
+
content:
|
|
33
|
+
"Error: schedule_update is restricted to guardian actors because schedules execute with elevated privileges.",
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
30
37
|
const jobId = input.job_id as string;
|
|
31
38
|
if (!jobId || typeof jobId !== "string") {
|
|
32
39
|
return { content: "Error: job_id is required", isError: true };
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -8,7 +8,10 @@ import type { SkillSummary, SkillToolManifest } from "../../config/skills.js";
|
|
|
8
8
|
import { loadSkillBySelector, loadSkillCatalog } from "../../config/skills.js";
|
|
9
9
|
import { RiskLevel } from "../../permissions/types.js";
|
|
10
10
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
autoInstallFromCatalog,
|
|
13
|
+
resolveCatalog,
|
|
14
|
+
} from "../../skills/catalog-install.js";
|
|
12
15
|
import {
|
|
13
16
|
collectAllMissing,
|
|
14
17
|
indexCatalogById,
|
|
@@ -191,15 +194,35 @@ export class SkillLoadTool implements Tool {
|
|
|
191
194
|
catalogIndex = indexCatalogById(catalog);
|
|
192
195
|
|
|
193
196
|
// Auto-install missing includes before validation (max 5 rounds for transitive deps)
|
|
197
|
+
// Defer catalog resolution until we confirm there are missing includes,
|
|
198
|
+
// then cache the result to avoid redundant network requests per dependency.
|
|
199
|
+
let remoteCatalog: Awaited<ReturnType<typeof resolveCatalog>> | undefined;
|
|
200
|
+
|
|
194
201
|
const MAX_INSTALL_ROUNDS = 5;
|
|
195
202
|
for (let round = 0; round < MAX_INSTALL_ROUNDS; round++) {
|
|
196
203
|
const missing = collectAllMissing(skill.id, catalogIndex);
|
|
197
204
|
if (missing.size === 0) break;
|
|
198
205
|
|
|
206
|
+
// Lazily resolve catalog on first round with missing includes
|
|
207
|
+
if (!remoteCatalog) {
|
|
208
|
+
try {
|
|
209
|
+
remoteCatalog = await resolveCatalog([...missing][0]);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
log.warn(
|
|
212
|
+
{ err, skillId: skill.id },
|
|
213
|
+
"Failed to resolve catalog for include auto-install",
|
|
214
|
+
);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
199
219
|
let installedAny = false;
|
|
200
220
|
for (const missingId of missing) {
|
|
201
221
|
try {
|
|
202
|
-
const installed = await autoInstallFromCatalog(
|
|
222
|
+
const installed = await autoInstallFromCatalog(
|
|
223
|
+
missingId,
|
|
224
|
+
remoteCatalog,
|
|
225
|
+
);
|
|
203
226
|
if (installed) {
|
|
204
227
|
log.info(
|
|
205
228
|
{ skillId: missingId, parentSkillId: skill.id },
|
|
@@ -28,7 +28,7 @@ export interface WatcherProvider {
|
|
|
28
28
|
id: string;
|
|
29
29
|
/** Human-readable name. */
|
|
30
30
|
displayName: string;
|
|
31
|
-
/** Credential service required (e.g. 'integration:
|
|
31
|
+
/** Credential service required (e.g. 'integration:google'). */
|
|
32
32
|
requiredCredentialService: string;
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -130,7 +130,7 @@ export const githubProvider: WatcherProvider = {
|
|
|
130
130
|
_config: Record<string, unknown>,
|
|
131
131
|
_watcherKey: string,
|
|
132
132
|
): Promise<FetchResult> {
|
|
133
|
-
const connection = resolveOAuthConnection(credentialService);
|
|
133
|
+
const connection = await resolveOAuthConnection(credentialService);
|
|
134
134
|
const since = watermark ?? new Date().toISOString();
|
|
135
135
|
const items: WatcherItem[] = [];
|
|
136
136
|
let page = 1;
|
|
@@ -115,10 +115,10 @@ class HistoryExpiredError extends Error {
|
|
|
115
115
|
export const gmailProvider: WatcherProvider = {
|
|
116
116
|
id: "gmail",
|
|
117
117
|
displayName: "Gmail",
|
|
118
|
-
requiredCredentialService: "integration:
|
|
118
|
+
requiredCredentialService: "integration:google",
|
|
119
119
|
|
|
120
120
|
async getInitialWatermark(credentialService: string): Promise<string> {
|
|
121
|
-
const connection = resolveOAuthConnection(credentialService);
|
|
121
|
+
const connection = await resolveOAuthConnection(credentialService);
|
|
122
122
|
const profile = await getProfile(connection);
|
|
123
123
|
if (!profile.historyId) {
|
|
124
124
|
throw new Error("Gmail profile did not return a historyId");
|
|
@@ -132,7 +132,7 @@ export const gmailProvider: WatcherProvider = {
|
|
|
132
132
|
_config: Record<string, unknown>,
|
|
133
133
|
_watcherKey: string,
|
|
134
134
|
): Promise<FetchResult> {
|
|
135
|
-
const connection = resolveOAuthConnection(credentialService);
|
|
135
|
+
const connection = await resolveOAuthConnection(credentialService);
|
|
136
136
|
|
|
137
137
|
if (!watermark) {
|
|
138
138
|
// No watermark — get initial position, return no items
|
|
@@ -25,7 +25,7 @@ import type {
|
|
|
25
25
|
const log = getLogger("watcher:google-calendar");
|
|
26
26
|
|
|
27
27
|
/** The credential service — calendar shares OAuth tokens with Gmail. */
|
|
28
|
-
const CREDENTIAL_SERVICE = "integration:
|
|
28
|
+
const CREDENTIAL_SERVICE = "integration:google";
|
|
29
29
|
|
|
30
30
|
function eventToItem(event: CalendarEvent, eventType: string): WatcherItem {
|
|
31
31
|
const start = event.start?.dateTime ?? event.start?.date ?? "";
|
|
@@ -125,7 +125,7 @@ export const googleCalendarProvider: WatcherProvider = {
|
|
|
125
125
|
requiredCredentialService: CREDENTIAL_SERVICE,
|
|
126
126
|
|
|
127
127
|
async getInitialWatermark(credentialService: string): Promise<string> {
|
|
128
|
-
const connection = resolveOAuthConnection(credentialService);
|
|
128
|
+
const connection = await resolveOAuthConnection(credentialService);
|
|
129
129
|
|
|
130
130
|
// Do a full sync with a narrow window to get the initial syncToken.
|
|
131
131
|
// The API may paginate even for small result sets, so follow nextPageToken
|
|
@@ -157,7 +157,7 @@ export const googleCalendarProvider: WatcherProvider = {
|
|
|
157
157
|
_config: Record<string, unknown>,
|
|
158
158
|
_watcherKey: string,
|
|
159
159
|
): Promise<FetchResult> {
|
|
160
|
-
const connection = resolveOAuthConnection(credentialService);
|
|
160
|
+
const connection = await resolveOAuthConnection(credentialService);
|
|
161
161
|
|
|
162
162
|
if (!watermark) {
|
|
163
163
|
// No watermark — paginate through to get the initial syncToken, return no items
|
|
@@ -512,7 +512,7 @@ export const linearProvider: WatcherProvider = {
|
|
|
512
512
|
_config: Record<string, unknown>,
|
|
513
513
|
watcherKey: string,
|
|
514
514
|
): Promise<FetchResult> {
|
|
515
|
-
const connection = resolveOAuthConnection(credentialService);
|
|
515
|
+
const connection = await resolveOAuthConnection(credentialService);
|
|
516
516
|
const since = watermark ?? new Date().toISOString();
|
|
517
517
|
|
|
518
518
|
// Resolve the authenticated viewer's ID once per poll for the assigned-issues query
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
let llmCallCount = 0;
|
|
4
|
-
let llmDelayMs = 0;
|
|
5
|
-
let llmResolution:
|
|
6
|
-
| "keep_existing"
|
|
7
|
-
| "keep_candidate"
|
|
8
|
-
| "merge"
|
|
9
|
-
| "still_unclear" = "still_unclear";
|
|
10
|
-
let llmResolvedStatement = "";
|
|
11
|
-
let llmExplanation = "Unclear response from user.";
|
|
12
|
-
|
|
13
|
-
mock.module("../providers/provider-send-message.js", () => ({
|
|
14
|
-
getConfiguredProvider: () => ({
|
|
15
|
-
sendMessage: async (
|
|
16
|
-
_messages: unknown,
|
|
17
|
-
_tools: unknown,
|
|
18
|
-
_system: unknown,
|
|
19
|
-
opts?: { signal?: AbortSignal },
|
|
20
|
-
) => {
|
|
21
|
-
llmCallCount += 1;
|
|
22
|
-
if (llmDelayMs > 0) {
|
|
23
|
-
await new Promise((resolve, reject) => {
|
|
24
|
-
const timer = setTimeout(resolve, llmDelayMs);
|
|
25
|
-
opts?.signal?.addEventListener("abort", () => {
|
|
26
|
-
clearTimeout(timer);
|
|
27
|
-
reject(new Error("Request was aborted."));
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
content: [
|
|
33
|
-
{
|
|
34
|
-
type: "tool_use" as const,
|
|
35
|
-
id: "test-tool-use-id",
|
|
36
|
-
name: "resolve_conflict",
|
|
37
|
-
input: {
|
|
38
|
-
resolution: llmResolution,
|
|
39
|
-
resolved_statement: llmResolvedStatement,
|
|
40
|
-
explanation: llmExplanation,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
model: "claude-haiku-4-5-20251001",
|
|
45
|
-
stopReason: "tool_use",
|
|
46
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
47
|
-
};
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
createTimeout: (ms: number) => {
|
|
51
|
-
const controller = new AbortController();
|
|
52
|
-
const timer = setTimeout(() => controller.abort(), ms);
|
|
53
|
-
return {
|
|
54
|
-
signal: controller.signal,
|
|
55
|
-
cleanup: () => clearTimeout(timer),
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
extractToolUse: (response: { content: Array<{ type: string }> }) => {
|
|
59
|
-
return response.content.find(
|
|
60
|
-
(b: { type: string }) => b.type === "tool_use",
|
|
61
|
-
);
|
|
62
|
-
},
|
|
63
|
-
userMessage: (text: string) => ({
|
|
64
|
-
role: "user",
|
|
65
|
-
content: [{ type: "text", text }],
|
|
66
|
-
}),
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
mock.module("../config/loader.js", () => ({
|
|
70
|
-
getConfig: () => ({
|
|
71
|
-
ui: {},
|
|
72
|
-
|
|
73
|
-
apiKeys: {
|
|
74
|
-
anthropic: "test-key",
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
}));
|
|
78
|
-
|
|
79
|
-
import { resolveConflictClarification } from "../memory/clarification-resolver.js";
|
|
80
|
-
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
llmCallCount = 0;
|
|
83
|
-
llmDelayMs = 0;
|
|
84
|
-
llmResolution = "still_unclear";
|
|
85
|
-
llmResolvedStatement = "";
|
|
86
|
-
llmExplanation = "Unclear response from user.";
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("resolveConflictClarification", () => {
|
|
90
|
-
test("returns keep_existing from deterministic heuristic", async () => {
|
|
91
|
-
const result = await resolveConflictClarification({
|
|
92
|
-
existingStatement: "Use React for frontend work.",
|
|
93
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
94
|
-
userMessage: "Keep the old React preference.",
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
expect(result.resolution).toBe("keep_existing");
|
|
98
|
-
expect(result.strategy).toBe("heuristic");
|
|
99
|
-
expect(llmCallCount).toBe(0);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("returns keep_candidate from deterministic heuristic", async () => {
|
|
103
|
-
const result = await resolveConflictClarification({
|
|
104
|
-
existingStatement: "Use React for frontend work.",
|
|
105
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
106
|
-
userMessage: "Use the new Vue note going forward.",
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(result.resolution).toBe("keep_candidate");
|
|
110
|
-
expect(result.strategy).toBe("heuristic");
|
|
111
|
-
expect(llmCallCount).toBe(0);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("returns merge from deterministic heuristic", async () => {
|
|
115
|
-
const result = await resolveConflictClarification({
|
|
116
|
-
existingStatement: "React is preferred for dashboards.",
|
|
117
|
-
candidateStatement: "Vue is preferred for marketing pages.",
|
|
118
|
-
userMessage:
|
|
119
|
-
"Both are true: React for dashboards and Vue for marketing pages.",
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
expect(result.resolution).toBe("merge");
|
|
123
|
-
expect(result.strategy).toBe("heuristic");
|
|
124
|
-
expect(result.resolvedStatement).toContain("Both are true");
|
|
125
|
-
expect(llmCallCount).toBe(0);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("uses LLM fallback when heuristics are inconclusive", async () => {
|
|
129
|
-
llmResolution = "still_unclear";
|
|
130
|
-
llmExplanation = "The user message does not pick a side.";
|
|
131
|
-
|
|
132
|
-
const result = await resolveConflictClarification({
|
|
133
|
-
existingStatement: "Use React for frontend work.",
|
|
134
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
135
|
-
userMessage: "Not sure yet.",
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(result.resolution).toBe("still_unclear");
|
|
139
|
-
expect(result.strategy).toBe("llm");
|
|
140
|
-
expect(llmCallCount).toBe(1);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("does not match cue substrings inside unrelated words", async () => {
|
|
144
|
-
llmResolution = "keep_candidate";
|
|
145
|
-
llmExplanation = "User wants Vue.";
|
|
146
|
-
|
|
147
|
-
// "told" contains "old" as a substring but not as a whole word
|
|
148
|
-
const result = await resolveConflictClarification({
|
|
149
|
-
existingStatement: "Use React for frontend work.",
|
|
150
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
151
|
-
userMessage: "I told you, use Vue.",
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(result.resolution).toBe("keep_candidate");
|
|
155
|
-
expect(result.strategy).toBe("llm");
|
|
156
|
-
expect(llmCallCount).toBe(1);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("delegates to LLM when multiple cue categories match", async () => {
|
|
160
|
-
llmResolution = "keep_existing";
|
|
161
|
-
llmExplanation = "User wants the old one.";
|
|
162
|
-
|
|
163
|
-
// "either" is a merge cue, "old" is an existing cue — ambiguous
|
|
164
|
-
const result = await resolveConflictClarification({
|
|
165
|
-
existingStatement: "Use React for frontend work.",
|
|
166
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
167
|
-
userMessage: "I don't want either, keep the old one.",
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
expect(result.resolution).toBe("keep_existing");
|
|
171
|
-
expect(result.strategy).toBe("llm");
|
|
172
|
-
expect(llmCallCount).toBe(1);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test("enforces timeout bound on LLM fallback", async () => {
|
|
176
|
-
llmResolution = "keep_candidate";
|
|
177
|
-
llmExplanation = "Prefer the newer statement.";
|
|
178
|
-
llmDelayMs = 50;
|
|
179
|
-
|
|
180
|
-
const result = await resolveConflictClarification(
|
|
181
|
-
{
|
|
182
|
-
existingStatement: "Use React for frontend work.",
|
|
183
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
184
|
-
userMessage: "I cannot decide right now.",
|
|
185
|
-
},
|
|
186
|
-
{ timeoutMs: 5 },
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
expect(result.resolution).toBe("still_unclear");
|
|
190
|
-
expect(result.strategy).toBe("llm_timeout");
|
|
191
|
-
expect(llmCallCount).toBe(1);
|
|
192
|
-
});
|
|
193
|
-
});
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
areStatementsCoherent,
|
|
5
|
-
computeConflictRelevance,
|
|
6
|
-
overlapRatio as _overlapRatio,
|
|
7
|
-
tokenizeForConflictRelevance as _tokenizeForConflictRelevance,
|
|
8
|
-
} from "../memory/conflict-intent.js";
|
|
9
|
-
|
|
10
|
-
describe("tokenizeForConflictRelevance hardening", () => {
|
|
11
|
-
test("excludes numeric-only tokens from relevance", () => {
|
|
12
|
-
const relevance = computeConflictRelevance("Check PR 5526", {
|
|
13
|
-
existingStatement: "Track PR 5525 for review.",
|
|
14
|
-
candidateStatement: "Track PR 5526 for review.",
|
|
15
|
-
});
|
|
16
|
-
// Numeric tokens "5526" and "5525" should be excluded, so overlap is minimal
|
|
17
|
-
expect(relevance).toBeLessThan(0.5);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("excludes URL boilerplate tokens from relevance", () => {
|
|
21
|
-
const relevance = computeConflictRelevance(
|
|
22
|
-
"Check https://github.com/org/repo/pull/123",
|
|
23
|
-
{
|
|
24
|
-
existingStatement: "Review https://github.com/org/repo/pull/456",
|
|
25
|
-
candidateStatement: "Review https://github.com/org/repo/pull/789",
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
// URL tokens like "https", "github", "pull" should be excluded;
|
|
29
|
-
// only real content tokens like "repo" remain, keeping relevance low
|
|
30
|
-
expect(relevance).toBeLessThanOrEqual(0.5);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("URL-embedded tracking tokens are stripped, standalone usage preserved", () => {
|
|
34
|
-
// URLs containing "issue", "pull", etc. are stripped entirely before tokenizing
|
|
35
|
-
const urlRelevance = computeConflictRelevance(
|
|
36
|
-
"Check https://github.com/org/repo/issues/42",
|
|
37
|
-
{
|
|
38
|
-
existingStatement: "Review https://github.com/org/repo/issues/10",
|
|
39
|
-
candidateStatement: "Review https://github.com/org/repo/issues/11",
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
expect(urlRelevance).toBeLessThanOrEqual(0.5);
|
|
43
|
-
|
|
44
|
-
// Standalone "issue" is preserved as a meaningful token
|
|
45
|
-
const standaloneRelevance = computeConflictRelevance(
|
|
46
|
-
"should I file an issue?",
|
|
47
|
-
{
|
|
48
|
-
existingStatement: "File an issue when bugs are found.",
|
|
49
|
-
candidateStatement: "Skip filing an issue for minor bugs.",
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
expect(standaloneRelevance).toBeGreaterThan(0);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("strips scheme-less bare domain URLs from relevance", () => {
|
|
56
|
-
const relevance = computeConflictRelevance(
|
|
57
|
-
"Check github.com/org/repo/pull/123",
|
|
58
|
-
{
|
|
59
|
-
existingStatement: "Review gitlab.com/org/repo/issues/456",
|
|
60
|
-
candidateStatement: "Review github.com/org/repo/pull/789",
|
|
61
|
-
},
|
|
62
|
-
);
|
|
63
|
-
// Bare URLs should be stripped entirely; tokens like "pull", "issues"
|
|
64
|
-
// embedded in paths must not contribute to overlap
|
|
65
|
-
expect(relevance).toBeLessThanOrEqual(0.5);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("preserves dotted identifiers that look like file paths", () => {
|
|
69
|
-
const relevance = computeConflictRelevance("Use index.ts/runtime parser", {
|
|
70
|
-
existingStatement: "Keep index.ts/runtime approach.",
|
|
71
|
-
candidateStatement: "Switch to config.ts/runtime approach.",
|
|
72
|
-
});
|
|
73
|
-
// File-like identifiers should NOT be stripped as URLs
|
|
74
|
-
expect(relevance).toBeGreaterThan(0);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("still computes meaningful relevance for real content tokens", () => {
|
|
78
|
-
const relevance = computeConflictRelevance(
|
|
79
|
-
"Should I use React for frontend?",
|
|
80
|
-
{
|
|
81
|
-
existingStatement: "Use React for frontend work.",
|
|
82
|
-
candidateStatement: "Use Vue for frontend work.",
|
|
83
|
-
},
|
|
84
|
-
);
|
|
85
|
-
// Real content tokens like "react", "frontend" should still match
|
|
86
|
-
expect(relevance).toBeGreaterThan(0);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe("statement coherence (areStatementsCoherent)", () => {
|
|
91
|
-
test("unrelated statements are incoherent", () => {
|
|
92
|
-
expect(
|
|
93
|
-
areStatementsCoherent(
|
|
94
|
-
"The default model for the summarize CLI is google/gemini-3-flash-preview.",
|
|
95
|
-
"User's favorite color is blue.",
|
|
96
|
-
),
|
|
97
|
-
).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("related statements are coherent", () => {
|
|
101
|
-
expect(
|
|
102
|
-
areStatementsCoherent(
|
|
103
|
-
"User's favorite color is blue.",
|
|
104
|
-
"User's favorite color is green.",
|
|
105
|
-
),
|
|
106
|
-
).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("topically similar preferences are coherent", () => {
|
|
110
|
-
expect(
|
|
111
|
-
areStatementsCoherent(
|
|
112
|
-
"Use React for frontend work.",
|
|
113
|
-
"Use Vue for frontend work.",
|
|
114
|
-
),
|
|
115
|
-
).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("completely disjoint technical topics are incoherent", () => {
|
|
119
|
-
expect(
|
|
120
|
-
areStatementsCoherent(
|
|
121
|
-
"Always use PostgreSQL for database storage.",
|
|
122
|
-
"The preferred terminal font is JetBrains Mono.",
|
|
123
|
-
),
|
|
124
|
-
).toBe(false);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("short technical terms (3 chars) are preserved for coherence", () => {
|
|
128
|
-
// "vim" and "css" are 3 chars — should not be filtered
|
|
129
|
-
expect(
|
|
130
|
-
areStatementsCoherent(
|
|
131
|
-
"Use Vim for editing.",
|
|
132
|
-
"Use Emacs instead of Vim.",
|
|
133
|
-
),
|
|
134
|
-
).toBe(true);
|
|
135
|
-
|
|
136
|
-
expect(
|
|
137
|
-
areStatementsCoherent(
|
|
138
|
-
"Use CSS grid for layouts.",
|
|
139
|
-
"Use CSS flexbox for layouts.",
|
|
140
|
-
),
|
|
141
|
-
).toBe(true);
|
|
142
|
-
|
|
143
|
-
expect(
|
|
144
|
-
areStatementsCoherent(
|
|
145
|
-
"Use npm for installs.",
|
|
146
|
-
"Use npm with --legacy-peer-deps.",
|
|
147
|
-
),
|
|
148
|
-
).toBe(true);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("short terms with no shared context are still incoherent", () => {
|
|
152
|
-
// No shared tokens at all — completely different topics
|
|
153
|
-
expect(
|
|
154
|
-
areStatementsCoherent(
|
|
155
|
-
"Vim is the preferred editor.",
|
|
156
|
-
"CSS grid handles page layouts.",
|
|
157
|
-
),
|
|
158
|
-
).toBe(false);
|
|
159
|
-
});
|
|
160
|
-
});
|