@vellumai/assistant 0.4.29 → 0.4.31
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 +39 -37
- package/Dockerfile +14 -8
- package/README.md +7 -8
- package/docs/architecture/memory.md +28 -29
- package/docs/runbook-trusted-contacts.md +76 -43
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
- package/scripts/test.sh +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
- package/src/__tests__/actor-token-service.test.ts +4 -3
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +44 -44
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +15 -5
- package/src/__tests__/channel-reply-delivery.test.ts +31 -0
- package/src/__tests__/config-schema.test.ts +0 -9
- package/src/__tests__/conflict-policy.test.ts +76 -0
- package/src/__tests__/conflict-store.test.ts +14 -20
- package/src/__tests__/contacts-tools.test.ts +8 -61
- package/src/__tests__/contradiction-checker.test.ts +5 -1
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +40 -15
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
- package/src/__tests__/integrations-cli.test.ts +3 -27
- package/src/__tests__/intent-routing.test.ts +3 -0
- package/src/__tests__/invite-redemption-service.test.ts +1 -1
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
- package/src/__tests__/ipc-snapshot.test.ts +4 -31
- package/src/__tests__/memory-lifecycle-e2e.test.ts +11 -10
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/registry.test.ts +0 -10
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
- package/src/__tests__/session-agent-loop.test.ts +0 -2
- package/src/__tests__/session-conflict-gate.test.ts +243 -388
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/session-profile-injection.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +2 -3
- package/src/__tests__/session-skill-tools.test.ts +0 -49
- package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +3 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
- package/src/__tests__/trusted-contact-verification.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +1 -1
- package/src/amazon/client.ts +7 -24
- package/src/approvals/guardian-decision-primitive.ts +11 -7
- package/src/approvals/guardian-request-resolvers.ts +5 -3
- package/src/calls/relay-server.ts +44 -11
- package/src/channels/config.ts +1 -1
- package/src/cli/integrations.ts +10 -66
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/contacts/SKILL.md +49 -53
- package/src/config/bundled-skills/contacts/TOOLS.json +26 -22
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +40 -62
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +17 -43
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +18 -57
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
- package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
- package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +89 -2
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/config/memory-schema.ts +0 -10
- package/src/config/system-prompt.ts +6 -0
- package/src/contacts/contact-store.ts +221 -56
- package/src/contacts/contacts-write.ts +14 -3
- package/src/contacts/types.ts +35 -4
- package/src/daemon/assistant-attachments.ts +23 -3
- package/src/daemon/guardian-verification-intent.ts +7 -4
- package/src/daemon/handlers/apps.ts +1 -2
- package/src/daemon/handlers/config-heartbeat.ts +1 -2
- package/src/daemon/handlers/config-inbox.ts +16 -134
- package/src/daemon/handlers/contacts.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +21 -88
- package/src/daemon/handlers/sessions.ts +2 -2
- package/src/daemon/ipc-contract/apps.ts +0 -1
- package/src/daemon/ipc-contract/contacts.ts +2 -2
- package/src/daemon/ipc-contract/inbox.ts +7 -66
- package/src/daemon/ipc-contract/sessions.ts +1 -0
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +14 -2
- package/src/daemon/session-agent-loop-handlers.ts +9 -0
- package/src/daemon/session-agent-loop.ts +2 -45
- package/src/daemon/session-attachments.ts +5 -1
- package/src/daemon/session-conflict-gate.ts +21 -82
- package/src/daemon/session-error.ts +18 -0
- package/src/daemon/session-lifecycle.ts +4 -5
- package/src/daemon/session-media-retry.ts +15 -1
- package/src/daemon/session-memory.ts +7 -52
- package/src/daemon/session-process.ts +3 -1
- package/src/daemon/session-runtime-assembly.ts +18 -35
- package/src/daemon/session-surfaces.ts +0 -1
- package/src/daemon/session-tool-setup.ts +7 -4
- package/src/events/domain-events.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +5 -1
- package/src/home-base/prebuilt/seed.ts +0 -1
- package/src/influencer/client.ts +7 -24
- package/src/media/gemini-image-service.ts +48 -3
- package/src/memory/app-store.ts +0 -4
- package/src/memory/conflict-intent.ts +3 -6
- package/src/memory/conflict-policy.ts +34 -0
- package/src/memory/conflict-store.ts +10 -18
- package/src/memory/contradiction-checker.ts +2 -2
- package/src/memory/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +8 -0
- package/src/memory/job-handlers/conflict.ts +0 -7
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +51 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/schema.ts +12 -17
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/index.ts +0 -1
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/messaging/types.ts +0 -38
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/destination-resolver.ts +42 -1
- package/src/notifications/emit-signal.ts +17 -1
- package/src/oauth/provider-profiles.ts +22 -0
- package/src/providers/anthropic/client.ts +3 -0
- package/src/providers/openai/client.ts +3 -0
- package/src/providers/retry.ts +9 -1
- package/src/runtime/actor-trust-resolver.ts +8 -0
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +4 -8
- package/src/runtime/channel-approval-types.ts +18 -0
- package/src/runtime/channel-approvals.ts +8 -0
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +62 -3
- package/src/runtime/gateway-client.ts +36 -2
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-service.ts +128 -0
- package/src/runtime/guardian-outbound-actions.ts +3 -3
- package/src/runtime/guardian-reply-router.ts +4 -4
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +29 -46
- package/src/runtime/invite-redemption-service.ts +1 -1
- package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/routes/approval-routes.ts +1 -40
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
- package/src/runtime/routes/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +494 -47
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/global-search-routes.ts +2 -2
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +78 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -1
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +227 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- package/src/runtime/routes/migration-routes.ts +17 -17
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/schedule/scheduler.ts +11 -2
- package/src/tools/apps/executors.ts +16 -15
- package/src/tools/calls/call-end.ts +1 -1
- package/src/tools/computer-use/definitions.ts +16 -0
- package/src/tools/credentials/vault.ts +86 -2
- package/src/tools/network/script-proxy/session-manager.ts +28 -3
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/terminal/shell.ts +15 -5
- package/src/tools/tool-approval-handler.ts +48 -4
- package/src/tools/types.ts +38 -1
- package/src/util/errors.ts +5 -1
- package/src/util/retry.ts +21 -0
- package/src/watcher/providers/slack.ts +33 -3
- package/src/workspace/git-service.ts +6 -4
- package/src/__tests__/get-weather.test.ts +0 -393
- package/src/__tests__/weather-skill-regression.test.ts +0 -276
- package/src/autonomy/autonomy-resolver.ts +0 -62
- package/src/autonomy/autonomy-store.ts +0 -138
- package/src/autonomy/disposition-mapper.ts +0 -31
- package/src/autonomy/index.ts +0 -11
- package/src/autonomy/types.ts +0 -43
- package/src/config/bundled-skills/weather/SKILL.md +0 -38
- package/src/config/bundled-skills/weather/TOOLS.json +0 -32
- package/src/config/bundled-skills/weather/icon.svg +0 -24
- package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
- package/src/messaging/triage-engine.ts +0 -344
- package/src/tools/weather/service.ts +0 -712
- /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
getPendingConflictByPair,
|
|
34
34
|
listPendingConflictDetails,
|
|
35
35
|
listPendingConflicts,
|
|
36
|
-
markConflictAsked,
|
|
37
36
|
resolveConflict,
|
|
38
37
|
} from "../memory/conflict-store.js";
|
|
39
38
|
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
@@ -60,6 +59,10 @@ function resetTables() {
|
|
|
60
59
|
function insertItemPair(
|
|
61
60
|
suffix: string,
|
|
62
61
|
scopeId = "default",
|
|
62
|
+
opts?: {
|
|
63
|
+
existingVerificationState?: string;
|
|
64
|
+
candidateVerificationState?: string;
|
|
65
|
+
},
|
|
63
66
|
): { existingItemId: string; candidateItemId: string } {
|
|
64
67
|
const db = getDb();
|
|
65
68
|
const now = Date.now();
|
|
@@ -76,7 +79,8 @@ function insertItemPair(
|
|
|
76
79
|
confidence: 0.8,
|
|
77
80
|
importance: 0.5,
|
|
78
81
|
fingerprint: `fp-existing-${suffix}`,
|
|
79
|
-
verificationState:
|
|
82
|
+
verificationState:
|
|
83
|
+
opts?.existingVerificationState ?? "assistant_inferred",
|
|
80
84
|
scopeId,
|
|
81
85
|
firstSeenAt: now,
|
|
82
86
|
lastSeenAt: now,
|
|
@@ -90,7 +94,8 @@ function insertItemPair(
|
|
|
90
94
|
confidence: 0.8,
|
|
91
95
|
importance: 0.5,
|
|
92
96
|
fingerprint: `fp-candidate-${suffix}`,
|
|
93
|
-
verificationState:
|
|
97
|
+
verificationState:
|
|
98
|
+
opts?.candidateVerificationState ?? "assistant_inferred",
|
|
94
99
|
scopeId,
|
|
95
100
|
firstSeenAt: now,
|
|
96
101
|
lastSeenAt: now,
|
|
@@ -218,24 +223,11 @@ describe("conflict-store", () => {
|
|
|
218
223
|
expect(pendingDefault[0].status).toBe("pending_clarification");
|
|
219
224
|
});
|
|
220
225
|
|
|
221
|
-
test("
|
|
222
|
-
const pair = insertItemPair("
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
existingItemId: pair.existingItemId,
|
|
226
|
-
candidateItemId: pair.candidateItemId,
|
|
227
|
-
relationship: "ambiguous_contradiction",
|
|
226
|
+
test("listPendingConflictDetails joins current statements and verification states", () => {
|
|
227
|
+
const pair = insertItemPair("details", "workspace-a", {
|
|
228
|
+
existingVerificationState: "user_confirmed",
|
|
229
|
+
candidateVerificationState: "assistant_inferred",
|
|
228
230
|
});
|
|
229
|
-
|
|
230
|
-
const askedAt = 1_734_000_000_000;
|
|
231
|
-
expect(markConflictAsked(conflict.id, askedAt)).toBe(true);
|
|
232
|
-
const updated = getConflictById(conflict.id);
|
|
233
|
-
expect(updated?.lastAskedAt).toBe(askedAt);
|
|
234
|
-
expect(updated?.updatedAt).toBe(askedAt);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("listPendingConflictDetails joins current statements", () => {
|
|
238
|
-
const pair = insertItemPair("details", "workspace-a");
|
|
239
231
|
createOrUpdatePendingConflict({
|
|
240
232
|
scopeId: "workspace-a",
|
|
241
233
|
existingItemId: pair.existingItemId,
|
|
@@ -250,6 +242,8 @@ describe("conflict-store", () => {
|
|
|
250
242
|
expect(details[0].candidateStatement).toBe("Candidate statement details");
|
|
251
243
|
expect(details[0].existingKind).toBe("fact");
|
|
252
244
|
expect(details[0].candidateKind).toBe("fact");
|
|
245
|
+
expect(details[0].existingVerificationState).toBe("user_confirmed");
|
|
246
|
+
expect(details[0].candidateVerificationState).toBe("assistant_inferred");
|
|
253
247
|
});
|
|
254
248
|
|
|
255
249
|
test("applyConflictResolution keeps candidate and resolves conflict row", () => {
|
|
@@ -140,17 +140,14 @@ describe("contact_upsert tool", () => {
|
|
|
140
140
|
expect(result.isError).toBe(false);
|
|
141
141
|
expect(result.content).toContain("Created contact");
|
|
142
142
|
expect(result.content).toContain("Alice");
|
|
143
|
-
expect(result.content).toContain("Importance: 0.50");
|
|
144
143
|
});
|
|
145
144
|
|
|
146
145
|
test("creates a contact with all fields", async () => {
|
|
147
146
|
const result = await executeContactUpsert(
|
|
148
147
|
{
|
|
149
148
|
display_name: "Bob",
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
response_expectation: "within_hours",
|
|
153
|
-
preferred_tone: "professional",
|
|
149
|
+
notes:
|
|
150
|
+
"Colleague at Acme Corp, prefers professional tone, responds within hours",
|
|
154
151
|
channels: [
|
|
155
152
|
{ type: "email", address: "bob@example.com", is_primary: true },
|
|
156
153
|
{ type: "slack", address: "@bob" },
|
|
@@ -161,10 +158,7 @@ describe("contact_upsert tool", () => {
|
|
|
161
158
|
|
|
162
159
|
expect(result.isError).toBe(false);
|
|
163
160
|
expect(result.content).toContain("Bob");
|
|
164
|
-
expect(result.content).toContain("
|
|
165
|
-
expect(result.content).toContain("0.80");
|
|
166
|
-
expect(result.content).toContain("within_hours");
|
|
167
|
-
expect(result.content).toContain("professional");
|
|
161
|
+
expect(result.content).toContain("Notes: Colleague at Acme Corp");
|
|
168
162
|
expect(result.content).toContain("email: bob@example.com");
|
|
169
163
|
expect(result.content).toContain("slack: @bob");
|
|
170
164
|
});
|
|
@@ -185,7 +179,7 @@ describe("contact_upsert tool", () => {
|
|
|
185
179
|
{
|
|
186
180
|
id: contactId,
|
|
187
181
|
display_name: "Charlie Updated",
|
|
188
|
-
|
|
182
|
+
notes: "Updated notes for Charlie",
|
|
189
183
|
},
|
|
190
184
|
ctx,
|
|
191
185
|
);
|
|
@@ -193,7 +187,7 @@ describe("contact_upsert tool", () => {
|
|
|
193
187
|
expect(updateResult.isError).toBe(false);
|
|
194
188
|
expect(updateResult.content).toContain("Updated contact");
|
|
195
189
|
expect(updateResult.content).toContain("Charlie Updated");
|
|
196
|
-
expect(updateResult.content).toContain("
|
|
190
|
+
expect(updateResult.content).toContain("Notes: Updated notes for Charlie");
|
|
197
191
|
});
|
|
198
192
|
|
|
199
193
|
test("auto-matches by channel address on create", async () => {
|
|
@@ -239,36 +233,6 @@ describe("contact_upsert tool", () => {
|
|
|
239
233
|
expect(result.isError).toBe(true);
|
|
240
234
|
expect(result.content).toContain("display_name is required");
|
|
241
235
|
});
|
|
242
|
-
|
|
243
|
-
test("rejects importance out of range", async () => {
|
|
244
|
-
const result = await executeContactUpsert(
|
|
245
|
-
{
|
|
246
|
-
display_name: "Test",
|
|
247
|
-
importance: 1.5,
|
|
248
|
-
},
|
|
249
|
-
ctx,
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
expect(result.isError).toBe(true);
|
|
253
|
-
expect(result.content).toContain(
|
|
254
|
-
"importance must be a number between 0 and 1",
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("rejects negative importance", async () => {
|
|
259
|
-
const result = await executeContactUpsert(
|
|
260
|
-
{
|
|
261
|
-
display_name: "Test",
|
|
262
|
-
importance: -0.1,
|
|
263
|
-
},
|
|
264
|
-
ctx,
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
expect(result.isError).toBe(true);
|
|
268
|
-
expect(result.content).toContain(
|
|
269
|
-
"importance must be a number between 0 and 1",
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
236
|
});
|
|
273
237
|
|
|
274
238
|
// ── contact_search ──────────────────────────────────────────────────
|
|
@@ -305,23 +269,6 @@ describe("contact_search tool", () => {
|
|
|
305
269
|
expect(result.content).toContain("Charlie");
|
|
306
270
|
});
|
|
307
271
|
|
|
308
|
-
test("searches by relationship", async () => {
|
|
309
|
-
await executeContactUpsert(
|
|
310
|
-
{ display_name: "Diana", relationship: "friend" },
|
|
311
|
-
ctx,
|
|
312
|
-
);
|
|
313
|
-
await executeContactUpsert(
|
|
314
|
-
{ display_name: "Eve", relationship: "colleague" },
|
|
315
|
-
ctx,
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
const result = await executeContactSearch({ relationship: "friend" }, ctx);
|
|
319
|
-
|
|
320
|
-
expect(result.isError).toBe(false);
|
|
321
|
-
expect(result.content).toContain("Diana");
|
|
322
|
-
expect(result.content).not.toContain("Eve");
|
|
323
|
-
});
|
|
324
|
-
|
|
325
272
|
test("returns no results message when nothing matches", async () => {
|
|
326
273
|
await executeContactUpsert({ display_name: "Existing" }, ctx);
|
|
327
274
|
|
|
@@ -380,7 +327,7 @@ describe("contact_merge tool", () => {
|
|
|
380
327
|
const r1 = await executeContactUpsert(
|
|
381
328
|
{
|
|
382
329
|
display_name: "Alice (Email)",
|
|
383
|
-
|
|
330
|
+
notes: "Prefers email",
|
|
384
331
|
channels: [{ type: "email", address: "alice@example.com" }],
|
|
385
332
|
},
|
|
386
333
|
ctx,
|
|
@@ -388,7 +335,7 @@ describe("contact_merge tool", () => {
|
|
|
388
335
|
const r2 = await executeContactUpsert(
|
|
389
336
|
{
|
|
390
337
|
display_name: "Alice (Slack)",
|
|
391
|
-
|
|
338
|
+
notes: "Active on Slack",
|
|
392
339
|
channels: [{ type: "slack", address: "@alice" }],
|
|
393
340
|
},
|
|
394
341
|
ctx,
|
|
@@ -407,7 +354,7 @@ describe("contact_merge tool", () => {
|
|
|
407
354
|
|
|
408
355
|
expect(result.isError).toBe(false);
|
|
409
356
|
expect(result.content).toContain("Merged");
|
|
410
|
-
expect(result.content).toContain("
|
|
357
|
+
expect(result.content).toContain("Notes: Prefers email\nActive on Slack"); // concatenated notes
|
|
411
358
|
expect(result.content).toContain("email: alice@example.com");
|
|
412
359
|
expect(result.content).toContain("slack: @alice");
|
|
413
360
|
|
|
@@ -201,7 +201,11 @@ describe("checkContradictions", () => {
|
|
|
201
201
|
expect(conflicts[0].existingItemId).toBe("item-existing-ambiguous");
|
|
202
202
|
expect(conflicts[0].candidateItemId).toBe("item-candidate-ambiguous");
|
|
203
203
|
expect(conflicts[0].relationship).toBe("ambiguous_contradiction");
|
|
204
|
-
expect(conflicts[0].clarificationQuestion).toContain(
|
|
204
|
+
expect(conflicts[0].clarificationQuestion).toContain("Pending conflict:");
|
|
205
|
+
expect(conflicts[0].clarificationQuestion).not.toContain(
|
|
206
|
+
"I have conflicting notes",
|
|
207
|
+
);
|
|
208
|
+
expect(conflicts[0].clarificationQuestion).not.toContain(
|
|
205
209
|
"Which one is correct?",
|
|
206
210
|
);
|
|
207
211
|
});
|
|
@@ -46,6 +46,15 @@ mock.module("../util/logger.js", () => ({
|
|
|
46
46
|
truncateForLog: (v: string) => v,
|
|
47
47
|
}));
|
|
48
48
|
|
|
49
|
+
mock.module("../config/loader.js", () => ({
|
|
50
|
+
getConfig: () => ({
|
|
51
|
+
sandbox: { enabled: false, backend: "native" },
|
|
52
|
+
assistantFeatureFlagValues: {
|
|
53
|
+
"feature_flags.browser.enabled": true,
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
}));
|
|
57
|
+
|
|
49
58
|
const { buildSystemPrompt } = await import("../config/system-prompt.js");
|
|
50
59
|
|
|
51
60
|
describe("Dynamic Skill Authoring Workflow prompt section", () => {
|
|
@@ -32,6 +32,7 @@ const ALLOWLIST = new Set([
|
|
|
32
32
|
|
|
33
33
|
// --- Documentation and comments that mention the port for explanatory purposes ---
|
|
34
34
|
"AGENTS.md", // documents the gateway-only rule itself
|
|
35
|
+
"assistant/docs/runbook-trusted-contacts.md", // operator runbook targeting runtime-only /v1/contacts endpoints
|
|
35
36
|
"assistant/src/runtime/middleware/twilio-validation.ts", // comment explaining proxy URL rewriting
|
|
36
37
|
]);
|
|
37
38
|
|
|
@@ -137,8 +137,8 @@ describe("generateImage", () => {
|
|
|
137
137
|
.contents as Array<Record<string, unknown>>;
|
|
138
138
|
const parts = contents[0].parts as Array<Record<string, unknown>>;
|
|
139
139
|
|
|
140
|
-
// First part is the text prompt
|
|
141
|
-
expect(parts[0]
|
|
140
|
+
// First part is the text prompt (with appended title instruction)
|
|
141
|
+
expect((parts[0] as { text: string }).text).toContain("remove background");
|
|
142
142
|
// Second part is the source image
|
|
143
143
|
expect(parts[1]).toEqual({
|
|
144
144
|
inlineData: { mimeType: "image/jpeg", data: "srcdata" },
|
|
@@ -71,7 +71,8 @@ const TEST_PRINCIPAL_ID = "test-principal-id";
|
|
|
71
71
|
|
|
72
72
|
function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
73
73
|
return {
|
|
74
|
-
|
|
74
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
75
|
+
actorExternalUserId: "guardian-1",
|
|
75
76
|
channel: "telegram",
|
|
76
77
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
77
78
|
...overrides,
|
|
@@ -80,7 +81,8 @@ function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
|
80
81
|
|
|
81
82
|
function trustedActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
82
83
|
return {
|
|
83
|
-
|
|
84
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
85
|
+
actorExternalUserId: undefined,
|
|
84
86
|
channel: "desktop",
|
|
85
87
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
86
88
|
...overrides,
|
|
@@ -254,7 +256,7 @@ describe("applyCanonicalGuardianDecision", () => {
|
|
|
254
256
|
|
|
255
257
|
expect(result.applied).toBe(true);
|
|
256
258
|
if (!result.applied) return;
|
|
257
|
-
// No grant minted because trusted actor has no
|
|
259
|
+
// No grant minted because trusted actor has no actorExternalUserId
|
|
258
260
|
expect(result.grantMinted).toBe(false);
|
|
259
261
|
});
|
|
260
262
|
|
|
@@ -189,9 +189,9 @@ describe("guardian grant minting on tool-approval decisions", () => {
|
|
|
189
189
|
|
|
190
190
|
beforeEach(() => {
|
|
191
191
|
resetTables();
|
|
192
|
-
deliverSpy = spyOn(gatewayClient, "deliverChannelReply").mockResolvedValue(
|
|
193
|
-
|
|
194
|
-
);
|
|
192
|
+
deliverSpy = spyOn(gatewayClient, "deliverChannelReply").mockResolvedValue({
|
|
193
|
+
ok: true,
|
|
194
|
+
});
|
|
195
195
|
composeSpy = spyOn(
|
|
196
196
|
approvalMessageComposer,
|
|
197
197
|
"composeApprovalMessageGenerative",
|
|
@@ -593,9 +593,9 @@ describe("approval interception trust-class regression coverage", () => {
|
|
|
593
593
|
|
|
594
594
|
beforeEach(() => {
|
|
595
595
|
resetTables();
|
|
596
|
-
deliverSpy = spyOn(gatewayClient, "deliverChannelReply").mockResolvedValue(
|
|
597
|
-
|
|
598
|
-
);
|
|
596
|
+
deliverSpy = spyOn(gatewayClient, "deliverChannelReply").mockResolvedValue({
|
|
597
|
+
ok: true,
|
|
598
|
+
});
|
|
599
599
|
composeSpy = spyOn(
|
|
600
600
|
approvalMessageComposer,
|
|
601
601
|
"composeApprovalMessageGenerative",
|
|
@@ -94,7 +94,8 @@ const TEST_PRINCIPAL_ID = "test-principal-id";
|
|
|
94
94
|
|
|
95
95
|
function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
96
96
|
return {
|
|
97
|
-
|
|
97
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
98
|
+
actorExternalUserId: "guardian-1",
|
|
98
99
|
channel: "telegram",
|
|
99
100
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
100
101
|
...overrides,
|
|
@@ -103,7 +104,8 @@ function guardianActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
|
103
104
|
|
|
104
105
|
function trustedActor(overrides: Partial<ActorContext> = {}): ActorContext {
|
|
105
106
|
return {
|
|
106
|
-
|
|
107
|
+
actorPrincipalId: TEST_PRINCIPAL_ID,
|
|
108
|
+
actorExternalUserId: undefined,
|
|
107
109
|
channel: "desktop",
|
|
108
110
|
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
109
111
|
...overrides,
|
|
@@ -169,22 +171,45 @@ function registerPendingToolApprovalInteraction(
|
|
|
169
171
|
describe("routing invariant: all decision paths reference applyCanonicalGuardianDecision", () => {
|
|
170
172
|
const srcRoot = resolve(__dirname, "..");
|
|
171
173
|
|
|
172
|
-
// The files that constitute decision entrypoints. Each must
|
|
173
|
-
// `applyCanonicalGuardianDecision`
|
|
174
|
-
|
|
174
|
+
// The files that constitute decision entrypoints. Each must reference
|
|
175
|
+
// `applyCanonicalGuardianDecision` (directly) or `processGuardianDecision`
|
|
176
|
+
// (shared wrapper that calls applyCanonicalGuardianDecision internally).
|
|
177
|
+
const DECISION_ENTRYPOINTS: Array<{
|
|
178
|
+
path: string;
|
|
179
|
+
symbols: string[];
|
|
180
|
+
}> = [
|
|
175
181
|
// Inbound channel router (Telegram/SMS/WhatsApp)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
{
|
|
183
|
+
path: "runtime/guardian-reply-router.ts",
|
|
184
|
+
symbols: ["applyCanonicalGuardianDecision"],
|
|
185
|
+
},
|
|
186
|
+
// HTTP API route handler (desktop and API clients) — uses processGuardianDecision
|
|
187
|
+
// which is a shared wrapper around applyCanonicalGuardianDecision
|
|
188
|
+
{
|
|
189
|
+
path: "runtime/routes/guardian-action-routes.ts",
|
|
190
|
+
symbols: ["processGuardianDecision"],
|
|
191
|
+
},
|
|
192
|
+
// IPC handler (desktop socket clients) — uses processGuardianDecision
|
|
193
|
+
// which is a shared wrapper around applyCanonicalGuardianDecision
|
|
194
|
+
{
|
|
195
|
+
path: "daemon/handlers/guardian-actions.ts",
|
|
196
|
+
symbols: ["processGuardianDecision"],
|
|
197
|
+
},
|
|
198
|
+
// Shared service where processGuardianDecision is defined — must route
|
|
199
|
+
// through the canonical primitive to complete the chain:
|
|
200
|
+
// entrypoint → processGuardianDecision → applyCanonicalGuardianDecision
|
|
201
|
+
{
|
|
202
|
+
path: "runtime/guardian-action-service.ts",
|
|
203
|
+
symbols: ["applyCanonicalGuardianDecision"],
|
|
204
|
+
},
|
|
181
205
|
];
|
|
182
206
|
|
|
183
|
-
for (const relPath of DECISION_ENTRYPOINTS) {
|
|
184
|
-
test(`${relPath} imports
|
|
207
|
+
for (const { path: relPath, symbols } of DECISION_ENTRYPOINTS) {
|
|
208
|
+
test(`${relPath} imports ${symbols.join(" or ")}`, () => {
|
|
185
209
|
const fullPath = join(srcRoot, relPath);
|
|
186
210
|
const source = readFileSync(fullPath, "utf-8");
|
|
187
|
-
|
|
211
|
+
const found = symbols.some((s) => source.includes(s));
|
|
212
|
+
expect(found).toBe(true);
|
|
188
213
|
});
|
|
189
214
|
}
|
|
190
215
|
|
|
@@ -1189,13 +1214,13 @@ describe("routing invariant: destination hints do not bypass tool_approval princ
|
|
|
1189
1214
|
});
|
|
1190
1215
|
|
|
1191
1216
|
// No pendingRequestIds passed — identity-based fallback uses
|
|
1192
|
-
// actor.
|
|
1217
|
+
// actor.actorExternalUserId which does not match any request's
|
|
1193
1218
|
// guardianExternalUserId (since it's null).
|
|
1194
1219
|
const result = await routeGuardianReply(
|
|
1195
1220
|
replyCtx({
|
|
1196
1221
|
messageText: "approve",
|
|
1197
1222
|
channel: "telegram",
|
|
1198
|
-
actor: guardianActor({
|
|
1223
|
+
actor: guardianActor({ actorExternalUserId: "guardian-tg-user" }),
|
|
1199
1224
|
conversationId: "conv-guardian-chat",
|
|
1200
1225
|
// pendingRequestIds: undefined — no delivery hints
|
|
1201
1226
|
approvalConversationGenerator: undefined,
|
|
@@ -107,14 +107,12 @@ describe("guardian-verify-setup skill — voice auto-followup", () => {
|
|
|
107
107
|
?.split("## Step 6")[0] ?? "";
|
|
108
108
|
// Must mention rebind guard concept
|
|
109
109
|
expect(pollingSection).toContain("Rebind guard");
|
|
110
|
-
// Must instruct not to trust
|
|
110
|
+
// Must instruct not to trust bound: true alone in a rebind flow
|
|
111
111
|
expect(pollingSection).toContain(
|
|
112
|
-
"do NOT treat
|
|
112
|
+
"do NOT treat `bound: true` alone as success",
|
|
113
113
|
);
|
|
114
|
-
// Must reference
|
|
115
|
-
expect(pollingSection).toContain("
|
|
116
|
-
// Must have a fallback for when bound_at is unavailable
|
|
117
|
-
expect(pollingSection).toContain("second poll onward");
|
|
114
|
+
// Must reference verificationSessionId as the mechanism to detect fresh binding
|
|
115
|
+
expect(pollingSection).toContain("verificationSessionId");
|
|
118
116
|
// Must clarify non-rebind flows are unaffected
|
|
119
117
|
expect(pollingSection).toContain("Non-rebind flows");
|
|
120
118
|
});
|
|
@@ -90,7 +90,7 @@ mock.module("../runtime/approval-message-composer.js", () => ({
|
|
|
90
90
|
import { findContactChannel } from "../contacts/contact-store.js";
|
|
91
91
|
import { upsertMember } from "../contacts/contacts-write.js";
|
|
92
92
|
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
93
|
-
import { createInvite, revokeInvite } from "../memory/
|
|
93
|
+
import { createInvite, revokeInvite } from "../memory/invite-store.js";
|
|
94
94
|
import { handleChannelInbound } from "../runtime/routes/channel-routes.js";
|
|
95
95
|
|
|
96
96
|
initializeDb();
|
|
@@ -108,7 +108,6 @@ describe("vellum integrations CLI", () => {
|
|
|
108
108
|
|
|
109
109
|
afterEach(() => {
|
|
110
110
|
delete process.env.GATEWAY_AUTH_TOKEN;
|
|
111
|
-
delete process.env.INTERNAL_GATEWAY_BASE_URL;
|
|
112
111
|
process.exitCode = 0;
|
|
113
112
|
});
|
|
114
113
|
|
|
@@ -141,8 +140,8 @@ describe("vellum integrations CLI", () => {
|
|
|
141
140
|
expect(mintCalls).toBe(1);
|
|
142
141
|
});
|
|
143
142
|
|
|
144
|
-
test("
|
|
145
|
-
|
|
143
|
+
test("uses configured gateway base for requests", async () => {
|
|
144
|
+
gatewayBase = "http://gateway.internal:9900";
|
|
146
145
|
const result = await runCli(["--json", "twilio", "config"], {
|
|
147
146
|
success: true,
|
|
148
147
|
});
|
|
@@ -163,28 +162,6 @@ describe("vellum integrations CLI", () => {
|
|
|
163
162
|
);
|
|
164
163
|
});
|
|
165
164
|
|
|
166
|
-
test("passes filters for ingress members (now calls /v1/contacts)", async () => {
|
|
167
|
-
const result = await runCli(
|
|
168
|
-
["--json", "ingress", "members", "--role", "contact", "--limit", "20"],
|
|
169
|
-
{ ok: true, contacts: [] },
|
|
170
|
-
);
|
|
171
|
-
expect(result.exitCode).toBe(0);
|
|
172
|
-
expect(result.fetchCalls[0]?.url).toBe(
|
|
173
|
-
"http://gateway.test/v1/contacts?role=contact&limit=20",
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("ingress members defaults role to contact", async () => {
|
|
178
|
-
const result = await runCli(["--json", "ingress", "members"], {
|
|
179
|
-
ok: true,
|
|
180
|
-
contacts: [],
|
|
181
|
-
});
|
|
182
|
-
expect(result.exitCode).toBe(0);
|
|
183
|
-
expect(result.fetchCalls[0]?.url).toBe(
|
|
184
|
-
"http://gateway.test/v1/contacts?role=contact",
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
165
|
test("reads ingress config without gateway fetch", async () => {
|
|
189
166
|
rawConfig = {
|
|
190
167
|
ingress: {
|
|
@@ -192,7 +169,6 @@ describe("vellum integrations CLI", () => {
|
|
|
192
169
|
publicBaseUrl: "https://public.example.com",
|
|
193
170
|
},
|
|
194
171
|
};
|
|
195
|
-
process.env.INTERNAL_GATEWAY_BASE_URL = "http://gateway.internal:9900/";
|
|
196
172
|
const result = await runCli(["--json", "ingress", "config"], { ok: true });
|
|
197
173
|
|
|
198
174
|
expect(result.exitCode).toBe(0);
|
|
@@ -201,7 +177,7 @@ describe("vellum integrations CLI", () => {
|
|
|
201
177
|
success: true,
|
|
202
178
|
enabled: true,
|
|
203
179
|
publicBaseUrl: "https://public.example.com",
|
|
204
|
-
localGatewayTarget: "http://gateway.
|
|
180
|
+
localGatewayTarget: "http://gateway.test",
|
|
205
181
|
});
|
|
206
182
|
});
|
|
207
183
|
|
|
@@ -29,7 +29,7 @@ import { getSqlite, initializeDb, resetDb } from "../memory/db.js";
|
|
|
29
29
|
import {
|
|
30
30
|
createInvite,
|
|
31
31
|
revokeInvite as revokeStoreFn,
|
|
32
|
-
} from "../memory/
|
|
32
|
+
} from "../memory/invite-store.js";
|
|
33
33
|
import {
|
|
34
34
|
type InviteRedemptionOutcome,
|
|
35
35
|
redeemInvite,
|