@vellumai/assistant 0.11.10 → 0.11.11-staging.1
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/AGENTS.md +1 -1
- 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__/plan-credit.test.ts +51 -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/plan-credit.ts +66 -0
- 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__/plan-credit.test.ts +51 -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/plan-credit.ts +66 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/openapi.yaml +152 -55
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +60 -0
- package/src/__tests__/config-get-vision-flag.test.ts +36 -0
- package/src/__tests__/conversation-error.test.ts +38 -0
- package/src/__tests__/copy-composer-tc-templates.test.ts +2 -2
- package/src/__tests__/guardian-action-service-scope.test.ts +53 -0
- package/src/__tests__/headless-browser-mode.test.ts +63 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +118 -27
- package/src/__tests__/host-app-control-proxy.test.ts +2 -2
- package/src/__tests__/host-app-control-routes.test.ts +41 -0
- package/src/__tests__/host-bash-routes.test.ts +30 -1
- package/src/__tests__/host-browser-routes.test.ts +34 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +29 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +29 -0
- package/src/__tests__/host-proxy-base.test.ts +1 -0
- package/src/__tests__/host-transfer-routes-targeted.test.ts +86 -0
- package/src/__tests__/llm-schema.test.ts +32 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +2 -0
- package/src/__tests__/memory-config-file-parity.test.ts +18 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +15 -0
- package/src/__tests__/messaging-send-tool.test.ts +159 -13
- package/src/__tests__/messaging-shared.test.ts +19 -0
- package/src/__tests__/notification-events-source-context-probe.test.ts +211 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +40 -6
- package/src/__tests__/outlook-messaging-provider.test.ts +9 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +2 -2
- package/src/__tests__/schedule-routes.test.ts +30 -1
- package/src/__tests__/scheduler-result-notification.test.ts +209 -0
- package/src/__tests__/skills-install-extract.test.ts +26 -0
- package/src/__tests__/skills-install-staging.test.ts +11 -2
- package/src/api/constants/personality-sliders.test.ts +21 -0
- package/src/api/constants/personality-sliders.ts +64 -0
- package/src/api/index.ts +8 -0
- package/src/archive/ustar-size.test.ts +32 -0
- package/src/archive/ustar-size.ts +23 -0
- package/src/background-wake/next-wake.test.ts +13 -0
- package/src/background-wake/next-wake.ts +4 -2
- package/src/calls/voice-session-bridge.ts +26 -0
- package/src/cli/__tests__/catalog-search-help.test.ts +1 -0
- package/src/cli/commands/__tests__/plugins-search.test.ts +81 -0
- package/src/cli/commands/config.help.ts +3 -1
- package/src/cli/commands/memory/worker.ts +1 -1
- package/src/cli/commands/platform/__tests__/credits-format.test.ts +94 -5
- package/src/cli/commands/platform/__tests__/credits.test.ts +10 -0
- package/src/cli/commands/platform/credits-format.ts +57 -0
- package/src/cli/commands/platform/index.help.ts +19 -3
- package/src/cli/commands/plugins.help.ts +4 -1
- package/src/cli/commands/plugins.ts +2 -1
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-platform.ts +8 -10
- package/src/config/bundled-skills/messaging/SKILL.md +2 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -4
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +85 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +9 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +87 -20
- package/src/config/bundled-skills/screen-annotation/TOOLS.json +21 -5
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +67 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/input-modalities.test.ts +71 -0
- package/src/config/input-modalities.ts +95 -0
- package/src/config/schemas/heartbeat.ts +3 -3
- package/src/config/schemas/llm.ts +13 -3
- package/src/config/schemas/memory-storage.test.ts +39 -0
- package/src/config/schemas/memory-storage.ts +21 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/daemon/__tests__/embedding-reconcile.test.ts +77 -0
- package/src/daemon/__tests__/lifecycle-embedding-reconcile.test.ts +3 -0
- package/src/daemon/conversation-error.ts +20 -7
- package/src/daemon/embedding-reconcile.ts +64 -1
- package/src/daemon/handlers/config-embeddings.ts +31 -0
- package/src/daemon/host-app-control-proxy.ts +2 -0
- package/src/daemon/host-bash-proxy.ts +1 -0
- package/src/daemon/host-cu-proxy.ts +6 -6
- package/src/daemon/host-file-proxy.ts +6 -6
- package/src/daemon/host-proxy-base.ts +10 -9
- package/src/daemon/host-transfer-proxy.ts +21 -24
- package/src/daemon/mcp-reload-service.ts +6 -0
- package/src/daemon/providers-setup.ts +5 -26
- package/src/heartbeat/heartbeat-service.ts +72 -13
- package/src/live-voice/__tests__/live-voice-photo.test.ts +72 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +81 -0
- package/src/live-voice/live-voice-photo.ts +68 -4
- package/src/live-voice/live-voice-session.ts +36 -2
- package/src/live-voice/protocol.ts +98 -1
- package/src/mcp/__tests__/reload-signal-emission.test.ts +54 -0
- package/src/mcp/__tests__/reload-signal.test.ts +57 -0
- package/src/mcp/__tests__/startup.test.ts +150 -0
- package/src/mcp/reload-signal.ts +50 -0
- package/src/mcp/startup.ts +101 -0
- package/src/messaging/providers/outlook/adapter.ts +2 -13
- package/src/messaging/providers/outlook/client.ts +13 -1
- package/src/messaging/providers/outlook/types.ts +3 -0
- package/src/notifications/__tests__/decision-engine.test.ts +79 -6
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +27 -0
- package/src/notifications/__tests__/schedule-result-producer.test.ts +397 -0
- package/src/notifications/copy-composer.ts +33 -11
- package/src/notifications/decision-engine.ts +48 -11
- package/src/notifications/events-store.ts +68 -2
- package/src/notifications/home-feed-side-effect.ts +15 -3
- package/src/notifications/schedule-result-producer.ts +275 -0
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/seed-providers.ts +3 -2
- package/src/persistence/conversation-crud.ts +38 -0
- package/src/persistence/embeddings/embedding-backend.test.ts +162 -0
- package/src/persistence/embeddings/embedding-backend.ts +148 -11
- package/src/persistence/embeddings/embedding-openai.test.ts +113 -0
- package/src/persistence/embeddings/embedding-openai.ts +55 -4
- package/src/persistence/embeddings/embedding-types.test.ts +24 -0
- package/src/persistence/embeddings/embedding-types.ts +25 -1
- package/src/persistence/job-utils.ts +6 -2
- package/src/plugin-api/index.ts +2 -1
- package/src/plugin-api/vision-support.test.ts +81 -0
- package/src/plugin-api/vision-support.ts +57 -10
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +8 -0
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +1 -0
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +11 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +20 -0
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +3 -1
- package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +82 -0
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +9 -27
- package/src/providers/__tests__/provider-secret-catalog.test.ts +4 -0
- package/src/providers/__tests__/xml-tool-call-salvage.test.ts +172 -0
- package/src/providers/anthropic/client.ts +9 -4
- package/src/providers/inline-audio-support.test.ts +34 -0
- package/src/providers/inline-audio-support.ts +27 -0
- package/src/providers/model-catalog.ts +22 -2
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +50 -0
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +51 -0
- package/src/providers/openai/__tests__/chat-completions-provider-xml-salvage.test.ts +291 -0
- package/src/providers/openai/api-error-normalization.ts +18 -3
- package/src/providers/openai/chat-completions-provider.ts +113 -13
- package/src/providers/openai/responses-provider.ts +10 -8
- package/src/providers/provider-secret-catalog.ts +11 -1
- package/src/providers/vellum/client.ts +14 -1
- package/src/providers/vellum/personality-directions.test.ts +163 -0
- package/src/providers/vellum/personality-directions.ts +120 -0
- package/src/providers/xml-tool-call-salvage.ts +212 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +42 -4
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -6
- package/src/runtime/auth/__tests__/route-policy.test.ts +65 -0
- package/src/runtime/auth/same-actor.ts +29 -5
- package/src/runtime/background-job-runner.ts +38 -29
- package/src/runtime/guardian-action-service.ts +20 -1
- package/src/runtime/migrations/vbundle-validator.ts +3 -3
- package/src/runtime/pending-interactions.ts +4 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +24 -0
- package/src/runtime/routes/__tests__/platform-credits-routes.test.ts +99 -5
- package/src/runtime/routes/conversation-query-routes.ts +53 -11
- package/src/runtime/routes/document-comments-routes.ts +8 -2
- package/src/runtime/routes/guardian-action-routes.ts +7 -0
- package/src/runtime/routes/heartbeat-routes.ts +41 -53
- package/src/runtime/routes/host-app-control-routes.ts +10 -36
- package/src/runtime/routes/host-bash-routes.ts +11 -45
- package/src/runtime/routes/host-browser-routes.ts +24 -0
- package/src/runtime/routes/host-cu-routes.ts +11 -45
- package/src/runtime/routes/host-file-routes.ts +11 -44
- package/src/runtime/routes/host-proxy-result-binding.ts +57 -0
- package/src/runtime/routes/host-transfer-routes.ts +33 -25
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/integrations/a2a.ts +12 -3
- package/src/runtime/routes/integrations/vercel.ts +13 -3
- package/src/runtime/routes/platform-routes.ts +121 -24
- package/src/runtime/routes/schedule-routes.ts +15 -1
- package/src/schedule/__tests__/schedule-timezone.test.ts +21 -3
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +88 -0
- package/src/schedule/schedule-timezone.ts +25 -0
- package/src/schedule/scheduler.ts +19 -0
- package/src/schedule/worker.ts +119 -2
- package/src/skills/catalog-install.ts +40 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +3 -0
- package/src/tools/browser/browser-execution.ts +117 -53
- package/src/tools/browser/browser-status-constants.ts +6 -6
- package/src/tools/capability-offer.test.ts +87 -0
- package/src/tools/capability-offer.ts +93 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +17 -0
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/util/provider-error-patterns.ts +34 -0
- package/src/watcher/__tests__/engine.test.ts +5 -2
- package/src/watcher/engine.ts +7 -4
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +0 -487
- package/src/runtime/routes/sight-frame-routes.ts +0 -136
|
@@ -35,6 +35,7 @@ mock.module("../runtime/pending-interactions.js", () => ({
|
|
|
35
35
|
|
|
36
36
|
// Per-test controls for the proxy stub
|
|
37
37
|
let stubTargetClientId: string | null = null;
|
|
38
|
+
let stubTargetActorPrincipalId: string | undefined;
|
|
38
39
|
const getTransferContentCalls: string[] = [];
|
|
39
40
|
const receiveTransferContentCalls: string[] = [];
|
|
40
41
|
const resolveTransferResultCalls: string[] = [];
|
|
@@ -50,6 +51,9 @@ mock.module("../daemon/host-transfer-proxy.js", () => ({
|
|
|
50
51
|
return stubTargetClientId;
|
|
51
52
|
},
|
|
52
53
|
getTargetActorPrincipalIdForTransfer(_transferId: string) {
|
|
54
|
+
if (stubTargetActorPrincipalId !== undefined) {
|
|
55
|
+
return stubTargetActorPrincipalId;
|
|
56
|
+
}
|
|
53
57
|
return stubTargetClientId
|
|
54
58
|
? clientActors.get(stubTargetClientId)
|
|
55
59
|
: undefined;
|
|
@@ -139,6 +143,7 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
139
143
|
beforeEach(() => {
|
|
140
144
|
pendingStore.clear();
|
|
141
145
|
stubTargetClientId = null;
|
|
146
|
+
stubTargetActorPrincipalId = undefined;
|
|
142
147
|
getTransferContentCalls.length = 0;
|
|
143
148
|
clientActors.clear();
|
|
144
149
|
});
|
|
@@ -236,6 +241,30 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
236
241
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
237
242
|
expect(getTransferContentCalls).toContain(TEST_TRANSFER_ID);
|
|
238
243
|
});
|
|
244
|
+
|
|
245
|
+
test("rejects a different actor when the transfer recorded a source actor", () => {
|
|
246
|
+
stubTargetClientId = null;
|
|
247
|
+
stubTargetActorPrincipalId = "principal-owner";
|
|
248
|
+
expect(() =>
|
|
249
|
+
handleTransferContentGet({
|
|
250
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
251
|
+
headers: { "x-vellum-actor-principal-id": "principal-attacker" },
|
|
252
|
+
}),
|
|
253
|
+
).toThrow(ForbiddenError);
|
|
254
|
+
expect(getTransferContentCalls).toHaveLength(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("accepts the recorded source actor on an untargeted transfer", async () => {
|
|
258
|
+
stubTargetClientId = null;
|
|
259
|
+
stubTargetActorPrincipalId = "principal-owner";
|
|
260
|
+
const result = await handleTransferContentGet({
|
|
261
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
262
|
+
headers: { "x-vellum-actor-principal-id": "principal-owner" },
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
266
|
+
expect(getTransferContentCalls).toContain(TEST_TRANSFER_ID);
|
|
267
|
+
});
|
|
239
268
|
});
|
|
240
269
|
|
|
241
270
|
// ── 5. Same-user actor binding (defense-in-depth) ─────────────────────────
|
|
@@ -289,6 +318,7 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
289
318
|
beforeEach(() => {
|
|
290
319
|
pendingStore.clear();
|
|
291
320
|
stubTargetClientId = null;
|
|
321
|
+
stubTargetActorPrincipalId = undefined;
|
|
292
322
|
receiveTransferContentCalls.length = 0;
|
|
293
323
|
clientActors.clear();
|
|
294
324
|
});
|
|
@@ -408,6 +438,38 @@ describe("handleTransferContentPut — Phase 3 targetClientId guard", () => {
|
|
|
408
438
|
expect(result).toEqual({ accepted: true });
|
|
409
439
|
expect(receiveTransferContentCalls).toContain(TEST_TRANSFER_ID);
|
|
410
440
|
});
|
|
441
|
+
|
|
442
|
+
test("rejects a different actor when the transfer recorded a source actor", async () => {
|
|
443
|
+
stubTargetClientId = null;
|
|
444
|
+
stubTargetActorPrincipalId = "principal-owner";
|
|
445
|
+
await expect(
|
|
446
|
+
handleTransferContentPut({
|
|
447
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
448
|
+
headers: {
|
|
449
|
+
"x-vellum-actor-principal-id": "principal-attacker",
|
|
450
|
+
"x-transfer-sha256": "abc",
|
|
451
|
+
},
|
|
452
|
+
rawBody: new Uint8Array(Buffer.from("data")),
|
|
453
|
+
}),
|
|
454
|
+
).rejects.toBeInstanceOf(ForbiddenError);
|
|
455
|
+
expect(receiveTransferContentCalls).toHaveLength(0);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("accepts the recorded source actor on an untargeted transfer", async () => {
|
|
459
|
+
stubTargetClientId = null;
|
|
460
|
+
stubTargetActorPrincipalId = "principal-owner";
|
|
461
|
+
const result = await handleTransferContentPut({
|
|
462
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
463
|
+
headers: {
|
|
464
|
+
"x-vellum-actor-principal-id": "principal-owner",
|
|
465
|
+
"x-transfer-sha256": "abc",
|
|
466
|
+
},
|
|
467
|
+
rawBody: new Uint8Array(Buffer.from("data")),
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect(result).toEqual({ accepted: true });
|
|
471
|
+
expect(receiveTransferContentCalls).toContain(TEST_TRANSFER_ID);
|
|
472
|
+
});
|
|
411
473
|
});
|
|
412
474
|
|
|
413
475
|
// ── 5. Same-user actor binding (defense-in-depth) ─────────────────────────
|
|
@@ -469,6 +531,7 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
469
531
|
beforeEach(() => {
|
|
470
532
|
pendingStore.clear();
|
|
471
533
|
stubTargetClientId = null;
|
|
534
|
+
stubTargetActorPrincipalId = undefined;
|
|
472
535
|
resolveTransferResultCalls.length = 0;
|
|
473
536
|
clientActors.clear();
|
|
474
537
|
});
|
|
@@ -599,6 +662,29 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
599
662
|
|
|
600
663
|
expect(result).toEqual({ accepted: true });
|
|
601
664
|
});
|
|
665
|
+
|
|
666
|
+
test("rejects a different actor when the pending request recorded a source actor", () => {
|
|
667
|
+
registerPending({ targetActorPrincipalId: "principal-owner" });
|
|
668
|
+
expect(() =>
|
|
669
|
+
handleTransferResult({
|
|
670
|
+
body: resultBody(),
|
|
671
|
+
headers: { "x-vellum-actor-principal-id": "principal-attacker" },
|
|
672
|
+
}),
|
|
673
|
+
).toThrow(ForbiddenError);
|
|
674
|
+
expect(resolveTransferResultCalls).toHaveLength(0);
|
|
675
|
+
expect(pendingStore.has(TEST_REQUEST_ID)).toBe(true);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test("accepts the recorded source actor on an untargeted pending request", async () => {
|
|
679
|
+
registerPending({ targetActorPrincipalId: "principal-owner" });
|
|
680
|
+
const result = await handleTransferResult({
|
|
681
|
+
body: resultBody(),
|
|
682
|
+
headers: { "x-vellum-actor-principal-id": "principal-owner" },
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
expect(result).toEqual({ accepted: true });
|
|
686
|
+
expect(resolveTransferResultCalls).toContain(TEST_REQUEST_ID);
|
|
687
|
+
});
|
|
602
688
|
});
|
|
603
689
|
|
|
604
690
|
// ── 5. Same-user actor binding (defense-in-depth) ─────────────────────────
|
|
@@ -358,4 +358,36 @@ describe("LLMSchema", () => {
|
|
|
358
358
|
});
|
|
359
359
|
expect(result.success).toBe(true);
|
|
360
360
|
});
|
|
361
|
+
|
|
362
|
+
test("a mix profile with inputModalities is rejected", () => {
|
|
363
|
+
const result = LLMSchema.safeParse({
|
|
364
|
+
profiles: {
|
|
365
|
+
combo: {
|
|
366
|
+
mix: [
|
|
367
|
+
{ profile: "fast", weight: 1 },
|
|
368
|
+
{ profile: "slow", weight: 1 },
|
|
369
|
+
],
|
|
370
|
+
inputModalities: { image: { enabled: true, supported: true } },
|
|
371
|
+
},
|
|
372
|
+
fast: { provider: "anthropic", model: "claude-haiku-4-5" },
|
|
373
|
+
slow: { provider: "anthropic", model: "claude-opus-4-6" },
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
expect(result.success).toBe(false);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("a custom profile may persist inputModalities", () => {
|
|
380
|
+
const parsed = LLMSchema.parse({
|
|
381
|
+
profiles: {
|
|
382
|
+
gateway: {
|
|
383
|
+
provider: "openai-compatible",
|
|
384
|
+
model: "qwen2.5-vl",
|
|
385
|
+
inputModalities: { image: { enabled: true, supported: true } },
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
expect(parsed.profiles.gateway?.inputModalities).toEqual({
|
|
390
|
+
image: { enabled: true, supported: true },
|
|
391
|
+
});
|
|
392
|
+
});
|
|
361
393
|
});
|
|
@@ -82,6 +82,8 @@ mock.module("../persistence/embeddings/embedding-backend.js", () => ({
|
|
|
82
82
|
vectors: [],
|
|
83
83
|
}),
|
|
84
84
|
geminiCacheExtras: () => [],
|
|
85
|
+
customCacheExtras: () => [],
|
|
86
|
+
durableEmbeddingCacheExtras: () => [],
|
|
85
87
|
generateSparseEmbedding: () => ({ indices: [], values: [] }),
|
|
86
88
|
getMemoryBackendStatus: async () => ({
|
|
87
89
|
enabled: false,
|
|
@@ -101,6 +101,24 @@ describe("memory config file parity", () => {
|
|
|
101
101
|
expect(memory.embeddings.provider).toBe("openai");
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test("custom embedding endpoint knobs are visible and identical", () => {
|
|
105
|
+
writeConfig({
|
|
106
|
+
memory: {
|
|
107
|
+
embeddings: {
|
|
108
|
+
provider: "custom",
|
|
109
|
+
baseUrl: "http://127.0.0.1:4000/v1",
|
|
110
|
+
customModel: "text-embedding-3-small",
|
|
111
|
+
customDimensions: 1024,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const memory = expectParity();
|
|
116
|
+
expect(memory.embeddings.provider).toBe("custom");
|
|
117
|
+
expect(memory.embeddings.baseUrl).toBe("http://127.0.0.1:4000/v1");
|
|
118
|
+
expect(memory.embeddings.customModel).toBe("text-embedding-3-small");
|
|
119
|
+
expect(memory.embeddings.customDimensions).toBe(1024);
|
|
120
|
+
});
|
|
121
|
+
|
|
104
122
|
test("cross-field invariant: segmentation overlap >= target falls back like the loader", () => {
|
|
105
123
|
writeConfig({
|
|
106
124
|
memory: {
|
|
@@ -202,6 +202,21 @@ beforeEach(() => {
|
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
describe("user-prompt-submit hook (memory retrieval)", () => {
|
|
205
|
+
test("does not run on a memory-retrospective wake", async () => {
|
|
206
|
+
const { memory, prepareMemoryMock, recordPkbQueryVectorsMock } =
|
|
207
|
+
makeFakeGraphMemory();
|
|
208
|
+
const conversation = installConversation(memory, { trusted: true });
|
|
209
|
+
conversation.currentCallSite = "memoryRetrospective";
|
|
210
|
+
const ctx = makeHookCtx({ conversationId: "conv-retro" });
|
|
211
|
+
|
|
212
|
+
await userPromptSubmitMemoryRetrieval(ctx);
|
|
213
|
+
|
|
214
|
+
expect(prepareMemoryMock).not.toHaveBeenCalled();
|
|
215
|
+
expect(recordPkbQueryVectorsMock).not.toHaveBeenCalled();
|
|
216
|
+
expect(applyRuntimeInjectionsMock).not.toHaveBeenCalled();
|
|
217
|
+
expect(updateMessageMetadataMock).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
205
220
|
test("voice front door skips serial legacy retrieval", async () => {
|
|
206
221
|
const { memory, prepareMemoryMock, recordPkbQueryVectorsMock } =
|
|
207
222
|
makeFakeGraphMemory();
|
|
@@ -44,14 +44,17 @@ const telegramProvider = makeProvider("telegram", "Telegram");
|
|
|
44
44
|
const outlookProvider = makeProvider("outlook", "Outlook");
|
|
45
45
|
let provider: MessagingProvider = phoneProvider;
|
|
46
46
|
|
|
47
|
+
let connection: OAuthConnection | undefined = undefined;
|
|
48
|
+
|
|
47
49
|
mock.module("../config/bundled-skills/messaging/tools/shared.js", () => ({
|
|
48
50
|
resolveProvider: () => provider,
|
|
49
|
-
getProviderConnection: () =>
|
|
51
|
+
getProviderConnection: () => connection,
|
|
50
52
|
ok: (content: string) => ({ content, isError: false }),
|
|
51
53
|
err: (content: string) => ({ content, isError: true }),
|
|
52
54
|
extractHeader: () => "",
|
|
53
55
|
parseAddressList: () => [],
|
|
54
56
|
extractEmail: (a: string) => a.toLowerCase(),
|
|
57
|
+
isMailboxAddress: (value: string) => value.includes("@"),
|
|
55
58
|
}));
|
|
56
59
|
|
|
57
60
|
// ── Sent-post record dependency mocks ──
|
|
@@ -75,6 +78,38 @@ const recordDeliveredChannelPostMock = mock(
|
|
|
75
78
|
async (_post: Record<string, unknown>) => ({ messageId: "row-1" }),
|
|
76
79
|
);
|
|
77
80
|
|
|
81
|
+
const mockOutlookCreateDraft = mock(
|
|
82
|
+
async (_conn: OAuthConnection, _draft: Record<string, unknown>) => ({
|
|
83
|
+
id: "outlook-draft-1",
|
|
84
|
+
conversationId: "conv-draft",
|
|
85
|
+
subject: "Docs",
|
|
86
|
+
webLink: "https://outlook.office.com/mail/drafts/id/outlook-draft-1",
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const mockOutlookCreateReplyDraft = mock(
|
|
91
|
+
async (_conn: OAuthConnection, _messageId: string, _comment?: string) => ({
|
|
92
|
+
id: "outlook-reply-draft-1",
|
|
93
|
+
conversationId: "conv-reply",
|
|
94
|
+
subject: "Re: Hello",
|
|
95
|
+
webLink: "https://outlook.office.com/mail/drafts/id/outlook-reply-draft-1",
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
mock.module("../messaging/providers/outlook/client.js", () => ({
|
|
100
|
+
createDraft: mockOutlookCreateDraft,
|
|
101
|
+
createReplyDraft: mockOutlookCreateReplyDraft,
|
|
102
|
+
toOutlookFileAttachments: (
|
|
103
|
+
attachments: Array<{ filename: string; mimeType: string; data: Buffer }>,
|
|
104
|
+
) =>
|
|
105
|
+
attachments.map((att) => ({
|
|
106
|
+
"@odata.type": "#microsoft.graph.fileAttachment",
|
|
107
|
+
name: att.filename,
|
|
108
|
+
contentType: att.mimeType,
|
|
109
|
+
contentBytes: att.data.toString("base64"),
|
|
110
|
+
})),
|
|
111
|
+
}));
|
|
112
|
+
|
|
78
113
|
mock.module("../persistence/conversation-crud.js", () => ({
|
|
79
114
|
setConversationProcessingStartedAt: () => {},
|
|
80
115
|
isConversationProcessing: () => false,
|
|
@@ -99,7 +134,10 @@ import { run } from "../config/bundled-skills/messaging/tools/messaging-send.js"
|
|
|
99
134
|
describe("messaging-send tool", () => {
|
|
100
135
|
beforeEach(() => {
|
|
101
136
|
provider = phoneProvider;
|
|
137
|
+
connection = undefined;
|
|
102
138
|
sendMessageMock.mockClear();
|
|
139
|
+
mockOutlookCreateDraft.mockClear();
|
|
140
|
+
mockOutlookCreateReplyDraft.mockClear();
|
|
103
141
|
getConversationMock.mockClear();
|
|
104
142
|
syncMessageToDiskMock.mockClear();
|
|
105
143
|
resolveProactiveHomeConversationMock.mockClear();
|
|
@@ -195,11 +233,15 @@ describe("messaging-send tool", () => {
|
|
|
195
233
|
expect(sendMessageMock).not.toHaveBeenCalled();
|
|
196
234
|
});
|
|
197
235
|
|
|
198
|
-
test("reads and forwards attachments
|
|
236
|
+
test("reads and forwards attachments onto an Outlook draft", async () => {
|
|
199
237
|
const dir = mkdtempSync(join(tmpdir(), "msg-send-att-"));
|
|
200
238
|
const filePath = join(dir, "report.pdf");
|
|
201
239
|
writeFileSync(filePath, "pdf-bytes");
|
|
202
240
|
provider = outlookProvider;
|
|
241
|
+
connection = {
|
|
242
|
+
id: "outlook-conn-1",
|
|
243
|
+
provider: "outlook",
|
|
244
|
+
} as OAuthConnection;
|
|
203
245
|
|
|
204
246
|
try {
|
|
205
247
|
const result = await run(
|
|
@@ -219,17 +261,21 @@ describe("messaging-send tool", () => {
|
|
|
219
261
|
);
|
|
220
262
|
|
|
221
263
|
expect(result.isError).toBe(false);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
expect(result.content).toContain("Outlook draft created");
|
|
265
|
+
expect(result.content).toContain("Draft ID: outlook-draft-1");
|
|
266
|
+
expect(sendMessageMock).not.toHaveBeenCalled();
|
|
267
|
+
expect(mockOutlookCreateDraft).toHaveBeenCalledTimes(1);
|
|
268
|
+
expect(mockOutlookCreateDraft).toHaveBeenCalledWith(
|
|
269
|
+
connection,
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
subject: "Docs",
|
|
272
|
+
body: { contentType: "text", content: "see attached" },
|
|
273
|
+
toRecipients: [
|
|
274
|
+
{ emailAddress: { address: "user@example.com" } },
|
|
275
|
+
],
|
|
276
|
+
attachments: [expect.objectContaining({ name: "report.pdf" })],
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
233
279
|
} finally {
|
|
234
280
|
rmSync(dir, { recursive: true, force: true });
|
|
235
281
|
}
|
|
@@ -305,6 +351,10 @@ describe("messaging-send tool", () => {
|
|
|
305
351
|
|
|
306
352
|
test("records nothing for a provider that is not a channel", async () => {
|
|
307
353
|
provider = outlookProvider;
|
|
354
|
+
connection = {
|
|
355
|
+
id: "outlook-conn-1",
|
|
356
|
+
provider: "outlook",
|
|
357
|
+
} as OAuthConnection;
|
|
308
358
|
|
|
309
359
|
await run(
|
|
310
360
|
{
|
|
@@ -324,6 +374,102 @@ describe("messaging-send tool", () => {
|
|
|
324
374
|
expect(recordDeliveredChannelPostMock).not.toHaveBeenCalled();
|
|
325
375
|
});
|
|
326
376
|
|
|
377
|
+
test("creates an Outlook draft without a To address when conversation_id is not an email", async () => {
|
|
378
|
+
provider = outlookProvider;
|
|
379
|
+
connection = {
|
|
380
|
+
id: "outlook-conn-1",
|
|
381
|
+
provider: "outlook",
|
|
382
|
+
} as OAuthConnection;
|
|
383
|
+
|
|
384
|
+
const result = await run(
|
|
385
|
+
{
|
|
386
|
+
platform: "outlook",
|
|
387
|
+
conversation_id: "drafts",
|
|
388
|
+
text: "Hey team, here is the update.",
|
|
389
|
+
subject: "Team update",
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
workingDir: "/tmp",
|
|
393
|
+
conversationId: "conv-1",
|
|
394
|
+
assistantId: "ast-alpha",
|
|
395
|
+
trustClass: "guardian" as const,
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
expect(result.isError).toBe(false);
|
|
400
|
+
expect(result.content).toContain("Outlook draft created");
|
|
401
|
+
expect(result.content).toContain("No recipient set");
|
|
402
|
+
expect(result.content).toContain("Open it:");
|
|
403
|
+
expect(sendMessageMock).not.toHaveBeenCalled();
|
|
404
|
+
expect(mockOutlookCreateDraft).toHaveBeenCalledWith(
|
|
405
|
+
connection,
|
|
406
|
+
expect.objectContaining({
|
|
407
|
+
subject: "Team update",
|
|
408
|
+
body: {
|
|
409
|
+
contentType: "text",
|
|
410
|
+
content: "Hey team, here is the update.",
|
|
411
|
+
},
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
const draftArg = mockOutlookCreateDraft.mock.calls[0][1];
|
|
415
|
+
expect(draftArg.toRecipients).toBeUndefined();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("creates an Outlook reply draft instead of sending when in_reply_to is set", async () => {
|
|
419
|
+
provider = outlookProvider;
|
|
420
|
+
connection = {
|
|
421
|
+
id: "outlook-conn-1",
|
|
422
|
+
provider: "outlook",
|
|
423
|
+
} as OAuthConnection;
|
|
424
|
+
|
|
425
|
+
const result = await run(
|
|
426
|
+
{
|
|
427
|
+
platform: "outlook",
|
|
428
|
+
conversation_id: "user@example.com",
|
|
429
|
+
text: "Thanks, that works.",
|
|
430
|
+
in_reply_to: "AAMk-original",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
workingDir: "/tmp",
|
|
434
|
+
conversationId: "conv-1",
|
|
435
|
+
assistantId: "ast-alpha",
|
|
436
|
+
trustClass: "guardian" as const,
|
|
437
|
+
},
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(result.isError).toBe(false);
|
|
441
|
+
expect(mockOutlookCreateReplyDraft).toHaveBeenCalledWith(
|
|
442
|
+
connection,
|
|
443
|
+
"AAMk-original",
|
|
444
|
+
"Thanks, that works.",
|
|
445
|
+
);
|
|
446
|
+
expect(sendMessageMock).not.toHaveBeenCalled();
|
|
447
|
+
expect(result.content).toContain("outlook-reply-draft-1");
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("errors when Outlook is not connected", async () => {
|
|
451
|
+
provider = outlookProvider;
|
|
452
|
+
connection = undefined;
|
|
453
|
+
|
|
454
|
+
const result = await run(
|
|
455
|
+
{
|
|
456
|
+
platform: "outlook",
|
|
457
|
+
conversation_id: "user@example.com",
|
|
458
|
+
text: "hello",
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
workingDir: "/tmp",
|
|
462
|
+
conversationId: "conv-1",
|
|
463
|
+
assistantId: "ast-alpha",
|
|
464
|
+
trustClass: "guardian" as const,
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(result.isError).toBe(true);
|
|
469
|
+
expect(result.content).toContain("OAuth connection");
|
|
470
|
+
expect(mockOutlookCreateDraft).not.toHaveBeenCalled();
|
|
471
|
+
});
|
|
472
|
+
|
|
327
473
|
test("a record failure does not fail the send", async () => {
|
|
328
474
|
provider = telegramProvider;
|
|
329
475
|
getConversationMock.mockImplementation(() => ({
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { isMailboxAddress } from "../config/bundled-skills/messaging/tools/shared.js";
|
|
4
|
+
|
|
5
|
+
describe("isMailboxAddress", () => {
|
|
6
|
+
test("accepts a bare email", () => {
|
|
7
|
+
expect(isMailboxAddress("user@example.com")).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("accepts a display-name address", () => {
|
|
11
|
+
expect(isMailboxAddress("Alice <alice@example.org>")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("rejects placeholders used when the recipient is unknown", () => {
|
|
15
|
+
expect(isMailboxAddress("drafts")).toBe(false);
|
|
16
|
+
expect(isMailboxAddress("outlook")).toBe(false);
|
|
17
|
+
expect(isMailboxAddress("")).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|