@vellumai/assistant 0.4.29 → 0.4.30
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/README.md +5 -6
- package/docs/runbook-trusted-contacts.md +79 -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__/commit-message-enrichment-service.test.ts +4 -0
- 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-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +34 -11
- 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__/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__/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__/session-media-retry.test.ts +147 -0
- 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__/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/calls/relay-server.ts +39 -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 +42 -35
- package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +38 -58
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +11 -31
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +19 -37
- 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-skills/weather/TOOLS.json +4 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/contacts/contact-store.ts +195 -4
- package/src/contacts/types.ts +26 -0
- 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-inbox.ts +16 -134
- package/src/daemon/handlers/guardian-actions.ts +20 -87
- package/src/daemon/handlers/sessions.ts +0 -1
- package/src/daemon/ipc-contract/apps.ts +0 -1
- 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 +1 -0
- package/src/daemon/session-attachments.ts +5 -1
- 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-surfaces.ts +0 -1
- package/src/daemon/session-tool-setup.ts +7 -4
- package/src/events/domain-events.ts +2 -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/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +12 -0
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- 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 +127 -0
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +20 -49
- 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/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +196 -28
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +76 -0
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +222 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- 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/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
|
@@ -146,16 +146,20 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
146
146
|
|
|
147
147
|
currentConfig = {
|
|
148
148
|
sandbox: { enabled: false, backend: "native" },
|
|
149
|
-
assistantFeatureFlagValues: {
|
|
149
|
+
assistantFeatureFlagValues: {
|
|
150
|
+
[DECLARED_FLAG_KEY]: false,
|
|
151
|
+
"feature_flags.twitter.enabled": true,
|
|
152
|
+
},
|
|
150
153
|
};
|
|
151
154
|
|
|
152
155
|
const result = buildSystemPrompt();
|
|
153
156
|
|
|
157
|
+
// twitter is explicitly enabled, declared flagged skill is explicitly off
|
|
154
158
|
expect(result).toContain('id="twitter"');
|
|
155
159
|
expect(result).not.toContain(`id="${DECLARED_SKILL_ID}"`);
|
|
156
160
|
});
|
|
157
161
|
|
|
158
|
-
test("
|
|
162
|
+
test("declared skills hidden when no flag overrides set (registry defaults to false)", () => {
|
|
159
163
|
createSkillOnDisk(
|
|
160
164
|
DECLARED_SKILL_ID,
|
|
161
165
|
"Hatch New Assistant",
|
|
@@ -169,8 +173,9 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
169
173
|
|
|
170
174
|
const result = buildSystemPrompt();
|
|
171
175
|
|
|
172
|
-
|
|
173
|
-
expect(result).toContain(
|
|
176
|
+
// Both skills are declared in the registry with defaultEnabled: false
|
|
177
|
+
expect(result).not.toContain(`id="${DECLARED_SKILL_ID}"`);
|
|
178
|
+
expect(result).not.toContain('id="twitter"');
|
|
174
179
|
});
|
|
175
180
|
|
|
176
181
|
test("flagged-off skills hidden when all flags are OFF", () => {
|
|
@@ -227,7 +232,7 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
227
232
|
expect(result).not.toContain('id="browser"');
|
|
228
233
|
});
|
|
229
234
|
|
|
230
|
-
test("
|
|
235
|
+
test("declared flags with no persisted override use registry default", () => {
|
|
231
236
|
createSkillOnDisk("browser", "Browser", "Web browsing automation");
|
|
232
237
|
|
|
233
238
|
currentConfig = {
|
|
@@ -236,7 +241,8 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
236
241
|
|
|
237
242
|
const result = buildSystemPrompt();
|
|
238
243
|
|
|
239
|
-
|
|
244
|
+
// browser is declared in the registry with defaultEnabled: false
|
|
245
|
+
expect(result).not.toContain('id="browser"');
|
|
240
246
|
});
|
|
241
247
|
});
|
|
242
248
|
|
|
@@ -265,10 +271,12 @@ describe("isAssistantFeatureFlagEnabled", () => {
|
|
|
265
271
|
|
|
266
272
|
test("missing persisted value falls back to defaults registry defaultEnabled", () => {
|
|
267
273
|
// No explicit config at all — should fall back to defaults registry
|
|
268
|
-
// which has defaultEnabled:
|
|
274
|
+
// which has defaultEnabled: false for hatch-new-assistant
|
|
269
275
|
const config = {} as any;
|
|
270
276
|
|
|
271
|
-
expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(
|
|
277
|
+
expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(
|
|
278
|
+
false,
|
|
279
|
+
);
|
|
272
280
|
});
|
|
273
281
|
|
|
274
282
|
test("unknown flag defaults to true when no persisted override", () => {
|
|
@@ -302,9 +310,9 @@ describe("legacy isSkillFeatureEnabled backward compat", () => {
|
|
|
302
310
|
expect(isSkillFeatureEnabled(DECLARED_SKILL_ID, config)).toBe(false);
|
|
303
311
|
});
|
|
304
312
|
|
|
305
|
-
test("
|
|
313
|
+
test("disabled when no override set (registry default is false)", () => {
|
|
306
314
|
const config = {} as any;
|
|
307
315
|
|
|
308
|
-
expect(isSkillFeatureEnabled(DECLARED_SKILL_ID, config)).toBe(
|
|
316
|
+
expect(isSkillFeatureEnabled(DECLARED_SKILL_ID, config)).toBe(false);
|
|
309
317
|
});
|
|
310
318
|
});
|
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
* Locks the final invariants from the BROWSER_SKILL plan so that future
|
|
5
5
|
* changes cannot silently regress any of the migration guarantees.
|
|
6
6
|
*/
|
|
7
|
-
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
7
|
+
import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
mock.module("../config/loader.js", () => ({
|
|
10
|
+
getConfig: () => ({
|
|
11
|
+
sandbox: { enabled: false, backend: "native" },
|
|
12
|
+
assistantFeatureFlagValues: {
|
|
13
|
+
"feature_flags.browser.enabled": true,
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
8
17
|
|
|
9
18
|
import {
|
|
10
19
|
projectSkillTools,
|
|
@@ -62,6 +62,7 @@ const GATEWAY_RETRIEVAL_BANLIST: Array<{
|
|
|
62
62
|
bannedSnippets: [
|
|
63
63
|
'curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/members',
|
|
64
64
|
'curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/ingress/invites',
|
|
65
|
+
'curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/contacts/invites',
|
|
65
66
|
'curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/telegram/config',
|
|
66
67
|
],
|
|
67
68
|
},
|
|
@@ -280,7 +280,7 @@ describe("inbound callback metadata triggers decision handling", () => {
|
|
|
280
280
|
const deliverSpy = spyOn(
|
|
281
281
|
gatewayClient,
|
|
282
282
|
"deliverChannelReply",
|
|
283
|
-
).mockResolvedValue(
|
|
283
|
+
).mockResolvedValue({ ok: true });
|
|
284
284
|
|
|
285
285
|
// Establish the conversation to get a conversationId mapping
|
|
286
286
|
const initReq = makeInboundRequest({ content: "init" });
|
|
@@ -321,7 +321,7 @@ describe("inbound callback metadata triggers decision handling", () => {
|
|
|
321
321
|
const deliverSpy = spyOn(
|
|
322
322
|
gatewayClient,
|
|
323
323
|
"deliverChannelReply",
|
|
324
|
-
).mockResolvedValue(
|
|
324
|
+
).mockResolvedValue({ ok: true });
|
|
325
325
|
|
|
326
326
|
const initReq = makeInboundRequest({ content: "init" });
|
|
327
327
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -374,7 +374,7 @@ describe("inbound text matching approval phrases triggers decision handling", ()
|
|
|
374
374
|
const deliverSpy = spyOn(
|
|
375
375
|
gatewayClient,
|
|
376
376
|
"deliverChannelReply",
|
|
377
|
-
).mockResolvedValue(
|
|
377
|
+
).mockResolvedValue({ ok: true });
|
|
378
378
|
|
|
379
379
|
const initReq = makeInboundRequest({ content: "init" });
|
|
380
380
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -407,7 +407,7 @@ describe("inbound text matching approval phrases triggers decision handling", ()
|
|
|
407
407
|
const deliverSpy = spyOn(
|
|
408
408
|
gatewayClient,
|
|
409
409
|
"deliverChannelReply",
|
|
410
|
-
).mockResolvedValue(
|
|
410
|
+
).mockResolvedValue({ ok: true });
|
|
411
411
|
|
|
412
412
|
const initReq = makeInboundRequest({ content: "init" });
|
|
413
413
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -456,7 +456,7 @@ describe("non-decision messages during pending approval (legacy fallback)", () =
|
|
|
456
456
|
const replySpy = spyOn(
|
|
457
457
|
gatewayClient,
|
|
458
458
|
"deliverChannelReply",
|
|
459
|
-
).mockResolvedValue(
|
|
459
|
+
).mockResolvedValue({ ok: true });
|
|
460
460
|
|
|
461
461
|
const initReq = makeInboundRequest({ content: "init" });
|
|
462
462
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -565,7 +565,7 @@ describe("empty content with callbackData bypasses validation", () => {
|
|
|
565
565
|
const deliverSpy = spyOn(
|
|
566
566
|
gatewayClient,
|
|
567
567
|
"deliverChannelReply",
|
|
568
|
-
).mockResolvedValue(
|
|
568
|
+
).mockResolvedValue({ ok: true });
|
|
569
569
|
|
|
570
570
|
const req = makeInboundRequest({
|
|
571
571
|
content: "",
|
|
@@ -603,7 +603,7 @@ describe("empty content with callbackData bypasses validation", () => {
|
|
|
603
603
|
const deliverSpy = spyOn(
|
|
604
604
|
gatewayClient,
|
|
605
605
|
"deliverChannelReply",
|
|
606
|
-
).mockResolvedValue(
|
|
606
|
+
).mockResolvedValue({ ok: true });
|
|
607
607
|
|
|
608
608
|
// Send with no content field at all, just callbackData
|
|
609
609
|
const reqBody = {
|
|
@@ -651,7 +651,7 @@ describe("callback requestId validation", () => {
|
|
|
651
651
|
const deliverSpy = spyOn(
|
|
652
652
|
gatewayClient,
|
|
653
653
|
"deliverChannelReply",
|
|
654
|
-
).mockResolvedValue(
|
|
654
|
+
).mockResolvedValue({ ok: true });
|
|
655
655
|
|
|
656
656
|
const initReq = makeInboundRequest({ content: "init" });
|
|
657
657
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -691,7 +691,7 @@ describe("callback requestId validation", () => {
|
|
|
691
691
|
const deliverSpy = spyOn(
|
|
692
692
|
gatewayClient,
|
|
693
693
|
"deliverChannelReply",
|
|
694
|
-
).mockResolvedValue(
|
|
694
|
+
).mockResolvedValue({ ok: true });
|
|
695
695
|
|
|
696
696
|
const initReq = makeInboundRequest({ content: "init" });
|
|
697
697
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -729,7 +729,7 @@ describe("callback requestId validation", () => {
|
|
|
729
729
|
const deliverSpy = spyOn(
|
|
730
730
|
gatewayClient,
|
|
731
731
|
"deliverChannelReply",
|
|
732
|
-
).mockResolvedValue(
|
|
732
|
+
).mockResolvedValue({ ok: true });
|
|
733
733
|
|
|
734
734
|
const initReq = makeInboundRequest({ content: "init" });
|
|
735
735
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -779,7 +779,7 @@ describe("no immediate reply after approval decision", () => {
|
|
|
779
779
|
const deliverSpy = spyOn(
|
|
780
780
|
gatewayClient,
|
|
781
781
|
"deliverChannelReply",
|
|
782
|
-
).mockResolvedValue(
|
|
782
|
+
).mockResolvedValue({ ok: true });
|
|
783
783
|
|
|
784
784
|
const initReq = makeInboundRequest({ content: "init" });
|
|
785
785
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -818,7 +818,7 @@ describe("no immediate reply after approval decision", () => {
|
|
|
818
818
|
const deliverSpy = spyOn(
|
|
819
819
|
gatewayClient,
|
|
820
820
|
"deliverChannelReply",
|
|
821
|
-
).mockResolvedValue(
|
|
821
|
+
).mockResolvedValue({ ok: true });
|
|
822
822
|
|
|
823
823
|
const initReq = makeInboundRequest({ content: "init" });
|
|
824
824
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -934,7 +934,7 @@ describe("SMS channel approval decisions", () => {
|
|
|
934
934
|
const deliverSpy = spyOn(
|
|
935
935
|
gatewayClient,
|
|
936
936
|
"deliverChannelReply",
|
|
937
|
-
).mockResolvedValue(
|
|
937
|
+
).mockResolvedValue({ ok: true });
|
|
938
938
|
|
|
939
939
|
// Establish the conversation via SMS
|
|
940
940
|
const initReq = makeSmsInboundRequest({ content: "init" });
|
|
@@ -968,7 +968,7 @@ describe("SMS channel approval decisions", () => {
|
|
|
968
968
|
const deliverSpy = spyOn(
|
|
969
969
|
gatewayClient,
|
|
970
970
|
"deliverChannelReply",
|
|
971
|
-
).mockResolvedValue(
|
|
971
|
+
).mockResolvedValue({ ok: true });
|
|
972
972
|
|
|
973
973
|
const initReq = makeSmsInboundRequest({ content: "init" });
|
|
974
974
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -1001,7 +1001,7 @@ describe("SMS channel approval decisions", () => {
|
|
|
1001
1001
|
const deliverSpy = spyOn(
|
|
1002
1002
|
gatewayClient,
|
|
1003
1003
|
"deliverChannelReply",
|
|
1004
|
-
).mockResolvedValue(
|
|
1004
|
+
).mockResolvedValue({ ok: true });
|
|
1005
1005
|
const approvalSpy = spyOn(
|
|
1006
1006
|
gatewayClient,
|
|
1007
1007
|
"deliverApprovalPrompt",
|
|
@@ -1061,7 +1061,7 @@ describe("SMS guardian verify intercept", () => {
|
|
|
1061
1061
|
const deliverSpy = spyOn(
|
|
1062
1062
|
gatewayClient,
|
|
1063
1063
|
"deliverChannelReply",
|
|
1064
|
-
).mockResolvedValue(
|
|
1064
|
+
).mockResolvedValue({ ok: true });
|
|
1065
1065
|
|
|
1066
1066
|
const req = new Request("http://localhost/channels/inbound", {
|
|
1067
1067
|
method: "POST",
|
|
@@ -1105,7 +1105,7 @@ describe("SMS guardian verify intercept", () => {
|
|
|
1105
1105
|
const deliverSpy = spyOn(
|
|
1106
1106
|
gatewayClient,
|
|
1107
1107
|
"deliverChannelReply",
|
|
1108
|
-
).mockResolvedValue(
|
|
1108
|
+
).mockResolvedValue({ ok: true });
|
|
1109
1109
|
|
|
1110
1110
|
const req = new Request("http://localhost/channels/inbound", {
|
|
1111
1111
|
method: "POST",
|
|
@@ -1202,7 +1202,7 @@ describe("guardian decision scoping — multiple pending approvals", () => {
|
|
|
1202
1202
|
const deliverSpy = spyOn(
|
|
1203
1203
|
gatewayClient,
|
|
1204
1204
|
"deliverChannelReply",
|
|
1205
|
-
).mockResolvedValue(
|
|
1205
|
+
).mockResolvedValue({ ok: true });
|
|
1206
1206
|
|
|
1207
1207
|
const olderConvId = "conv-scope-older";
|
|
1208
1208
|
const newerConvId = "conv-scope-newer";
|
|
@@ -1287,7 +1287,7 @@ describe("ambiguous plain-text decision with multiple pending requests", () => {
|
|
|
1287
1287
|
const deliverSpy = spyOn(
|
|
1288
1288
|
gatewayClient,
|
|
1289
1289
|
"deliverChannelReply",
|
|
1290
|
-
).mockResolvedValue(
|
|
1290
|
+
).mockResolvedValue({ ok: true });
|
|
1291
1291
|
|
|
1292
1292
|
const convA = "conv-ambig-a";
|
|
1293
1293
|
const convB = "conv-ambig-b";
|
|
@@ -1376,7 +1376,7 @@ describe("expired guardian approval auto-denies via sweep", () => {
|
|
|
1376
1376
|
const deliverSpy = spyOn(
|
|
1377
1377
|
gatewayClient,
|
|
1378
1378
|
"deliverChannelReply",
|
|
1379
|
-
).mockResolvedValue(
|
|
1379
|
+
).mockResolvedValue({ ok: true });
|
|
1380
1380
|
|
|
1381
1381
|
const convId = "conv-expiry-sweep";
|
|
1382
1382
|
ensureConversation(convId);
|
|
@@ -1440,7 +1440,7 @@ describe("expired guardian approval auto-denies via sweep", () => {
|
|
|
1440
1440
|
const deliverSpy = spyOn(
|
|
1441
1441
|
gatewayClient,
|
|
1442
1442
|
"deliverChannelReply",
|
|
1443
|
-
).mockResolvedValue(
|
|
1443
|
+
).mockResolvedValue({ ok: true });
|
|
1444
1444
|
|
|
1445
1445
|
const convId = "conv-not-expired";
|
|
1446
1446
|
ensureConversation(convId);
|
|
@@ -1515,7 +1515,7 @@ describe("assistant-scoped guardian verification via handleChannelInbound", () =
|
|
|
1515
1515
|
const deliverSpy = spyOn(
|
|
1516
1516
|
gatewayClient,
|
|
1517
1517
|
"deliverChannelReply",
|
|
1518
|
-
).mockResolvedValue(
|
|
1518
|
+
).mockResolvedValue({ ok: true });
|
|
1519
1519
|
|
|
1520
1520
|
const req = makeInboundRequest({
|
|
1521
1521
|
content: secret,
|
|
@@ -1543,7 +1543,7 @@ describe("assistant-scoped guardian verification via handleChannelInbound", () =
|
|
|
1543
1543
|
const deliverSpy = spyOn(
|
|
1544
1544
|
gatewayClient,
|
|
1545
1545
|
"deliverChannelReply",
|
|
1546
|
-
).mockResolvedValue(
|
|
1546
|
+
).mockResolvedValue({ ok: true });
|
|
1547
1547
|
|
|
1548
1548
|
const req = makeInboundRequest({
|
|
1549
1549
|
content: secret,
|
|
@@ -1577,7 +1577,7 @@ describe("assistant-scoped guardian verification via handleChannelInbound", () =
|
|
|
1577
1577
|
const deliverSpy = spyOn(
|
|
1578
1578
|
gatewayClient,
|
|
1579
1579
|
"deliverChannelReply",
|
|
1580
|
-
).mockResolvedValue(
|
|
1580
|
+
).mockResolvedValue({ ok: true });
|
|
1581
1581
|
|
|
1582
1582
|
const req = makeInboundRequest({
|
|
1583
1583
|
content: secret,
|
|
@@ -1677,7 +1677,7 @@ describe("conversational approval engine — standard path", () => {
|
|
|
1677
1677
|
const deliverSpy = spyOn(
|
|
1678
1678
|
gatewayClient,
|
|
1679
1679
|
"deliverChannelReply",
|
|
1680
|
-
).mockResolvedValue(
|
|
1680
|
+
).mockResolvedValue({ ok: true });
|
|
1681
1681
|
|
|
1682
1682
|
const initReq = makeInboundRequest({ content: "init" });
|
|
1683
1683
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -1735,7 +1735,7 @@ describe("conversational approval engine — standard path", () => {
|
|
|
1735
1735
|
const deliverSpy = spyOn(
|
|
1736
1736
|
gatewayClient,
|
|
1737
1737
|
"deliverChannelReply",
|
|
1738
|
-
).mockResolvedValue(
|
|
1738
|
+
).mockResolvedValue({ ok: true });
|
|
1739
1739
|
|
|
1740
1740
|
const initReq = makeInboundRequest({ content: "init" });
|
|
1741
1741
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -1783,7 +1783,7 @@ describe("conversational approval engine — standard path", () => {
|
|
|
1783
1783
|
const deliverSpy = spyOn(
|
|
1784
1784
|
gatewayClient,
|
|
1785
1785
|
"deliverChannelReply",
|
|
1786
|
-
).mockResolvedValue(
|
|
1786
|
+
).mockResolvedValue({ ok: true });
|
|
1787
1787
|
|
|
1788
1788
|
const initReq = makeInboundRequest({ content: "init" });
|
|
1789
1789
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -1830,7 +1830,7 @@ describe("conversational approval engine — standard path", () => {
|
|
|
1830
1830
|
const deliverSpy = spyOn(
|
|
1831
1831
|
gatewayClient,
|
|
1832
1832
|
"deliverChannelReply",
|
|
1833
|
-
).mockResolvedValue(
|
|
1833
|
+
).mockResolvedValue({ ok: true });
|
|
1834
1834
|
|
|
1835
1835
|
const initReq = makeInboundRequest({ content: "init" });
|
|
1836
1836
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -1896,7 +1896,7 @@ describe("guardian conversational approval via conversation engine", () => {
|
|
|
1896
1896
|
const deliverSpy = spyOn(
|
|
1897
1897
|
gatewayClient,
|
|
1898
1898
|
"deliverChannelReply",
|
|
1899
|
-
).mockResolvedValue(
|
|
1899
|
+
).mockResolvedValue({ ok: true });
|
|
1900
1900
|
|
|
1901
1901
|
const convId = "conv-guardian-clarify";
|
|
1902
1902
|
ensureConversation(convId);
|
|
@@ -1978,7 +1978,7 @@ describe("guardian conversational approval via conversation engine", () => {
|
|
|
1978
1978
|
const deliverSpy = spyOn(
|
|
1979
1979
|
gatewayClient,
|
|
1980
1980
|
"deliverChannelReply",
|
|
1981
|
-
).mockResolvedValue(
|
|
1981
|
+
).mockResolvedValue({ ok: true });
|
|
1982
1982
|
|
|
1983
1983
|
const convId = "conv-guardian-nlp";
|
|
1984
1984
|
ensureConversation(convId);
|
|
@@ -2058,7 +2058,7 @@ describe("guardian conversational approval via conversation engine", () => {
|
|
|
2058
2058
|
const deliverSpy = spyOn(
|
|
2059
2059
|
gatewayClient,
|
|
2060
2060
|
"deliverChannelReply",
|
|
2061
|
-
).mockResolvedValue(
|
|
2061
|
+
).mockResolvedValue({ ok: true });
|
|
2062
2062
|
|
|
2063
2063
|
const convId = "conv-guardian-downgrade";
|
|
2064
2064
|
ensureConversation(convId);
|
|
@@ -2113,7 +2113,7 @@ describe("guardian conversational approval via conversation engine", () => {
|
|
|
2113
2113
|
const deliverSpy = spyOn(
|
|
2114
2114
|
gatewayClient,
|
|
2115
2115
|
"deliverChannelReply",
|
|
2116
|
-
).mockResolvedValue(
|
|
2116
|
+
).mockResolvedValue({ ok: true });
|
|
2117
2117
|
|
|
2118
2118
|
const convA = "conv-multi-a";
|
|
2119
2119
|
const convB = "conv-multi-b";
|
|
@@ -2218,7 +2218,7 @@ describe("keep_pending remains conversational — standard path", () => {
|
|
|
2218
2218
|
const deliverSpy = spyOn(
|
|
2219
2219
|
gatewayClient,
|
|
2220
2220
|
"deliverChannelReply",
|
|
2221
|
-
).mockResolvedValue(
|
|
2221
|
+
).mockResolvedValue({ ok: true });
|
|
2222
2222
|
|
|
2223
2223
|
const initReq = makeInboundRequest({ content: "init" });
|
|
2224
2224
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -2277,7 +2277,7 @@ describe("keep_pending remains conversational — guardian path", () => {
|
|
|
2277
2277
|
const deliverSpy = spyOn(
|
|
2278
2278
|
gatewayClient,
|
|
2279
2279
|
"deliverChannelReply",
|
|
2280
|
-
).mockResolvedValue(
|
|
2280
|
+
).mockResolvedValue({ ok: true });
|
|
2281
2281
|
|
|
2282
2282
|
const convId = "conv-gfb-1";
|
|
2283
2283
|
ensureConversation(convId);
|
|
@@ -2367,7 +2367,7 @@ describe("requester cancel of guardian-gated pending request", () => {
|
|
|
2367
2367
|
const deliverSpy = spyOn(
|
|
2368
2368
|
gatewayClient,
|
|
2369
2369
|
"deliverChannelReply",
|
|
2370
|
-
).mockResolvedValue(
|
|
2370
|
+
).mockResolvedValue({ ok: true });
|
|
2371
2371
|
|
|
2372
2372
|
// Create requester conversation
|
|
2373
2373
|
const initReq = makeInboundRequest({
|
|
@@ -2450,7 +2450,7 @@ describe("requester cancel of guardian-gated pending request", () => {
|
|
|
2450
2450
|
const deliverSpy = spyOn(
|
|
2451
2451
|
gatewayClient,
|
|
2452
2452
|
"deliverChannelReply",
|
|
2453
|
-
).mockResolvedValue(
|
|
2453
|
+
).mockResolvedValue({ ok: true });
|
|
2454
2454
|
|
|
2455
2455
|
const initReq = makeInboundRequest({
|
|
2456
2456
|
content: "init",
|
|
@@ -2526,7 +2526,7 @@ describe("requester cancel of guardian-gated pending request", () => {
|
|
|
2526
2526
|
const deliverSpy = spyOn(
|
|
2527
2527
|
gatewayClient,
|
|
2528
2528
|
"deliverChannelReply",
|
|
2529
|
-
).mockResolvedValue(
|
|
2529
|
+
).mockResolvedValue({ ok: true });
|
|
2530
2530
|
|
|
2531
2531
|
const initReq = makeInboundRequest({
|
|
2532
2532
|
content: "init",
|
|
@@ -2599,7 +2599,7 @@ describe("requester cancel of guardian-gated pending request", () => {
|
|
|
2599
2599
|
const deliverSpy = spyOn(
|
|
2600
2600
|
gatewayClient,
|
|
2601
2601
|
"deliverChannelReply",
|
|
2602
|
-
).mockResolvedValue(
|
|
2602
|
+
).mockResolvedValue({ ok: true });
|
|
2603
2603
|
|
|
2604
2604
|
const initReq = makeInboundRequest({
|
|
2605
2605
|
content: "init",
|
|
@@ -2669,7 +2669,7 @@ describe("engine decision race condition — standard path", () => {
|
|
|
2669
2669
|
const deliverSpy = spyOn(
|
|
2670
2670
|
gatewayClient,
|
|
2671
2671
|
"deliverChannelReply",
|
|
2672
|
-
).mockResolvedValue(
|
|
2672
|
+
).mockResolvedValue({ ok: true });
|
|
2673
2673
|
|
|
2674
2674
|
const initReq = makeInboundRequest({ content: "init" });
|
|
2675
2675
|
await handleChannelInbound(initReq, noopProcessMessage);
|
|
@@ -2737,7 +2737,7 @@ describe("engine decision race condition — guardian path", () => {
|
|
|
2737
2737
|
const deliverSpy = spyOn(
|
|
2738
2738
|
gatewayClient,
|
|
2739
2739
|
"deliverChannelReply",
|
|
2740
|
-
).mockResolvedValue(
|
|
2740
|
+
).mockResolvedValue({ ok: true });
|
|
2741
2741
|
|
|
2742
2742
|
const convId = "conv-guardian-race";
|
|
2743
2743
|
ensureConversation(convId);
|
|
@@ -2828,7 +2828,7 @@ describe("non-decision status reply for different channels", () => {
|
|
|
2828
2828
|
const deliverSpy = spyOn(
|
|
2829
2829
|
gatewayClient,
|
|
2830
2830
|
"deliverChannelReply",
|
|
2831
|
-
).mockResolvedValue(
|
|
2831
|
+
).mockResolvedValue({ ok: true });
|
|
2832
2832
|
|
|
2833
2833
|
// Establish the conversation using sms (non-rich channel)
|
|
2834
2834
|
const initReq = makeInboundRequest({
|
|
@@ -2875,7 +2875,7 @@ describe("non-decision status reply for different channels", () => {
|
|
|
2875
2875
|
const replySpy = spyOn(
|
|
2876
2876
|
gatewayClient,
|
|
2877
2877
|
"deliverChannelReply",
|
|
2878
|
-
).mockResolvedValue(
|
|
2878
|
+
).mockResolvedValue({ ok: true });
|
|
2879
2879
|
|
|
2880
2880
|
// Establish the conversation using telegram (rich channel)
|
|
2881
2881
|
const initReq = makeInboundRequest({
|
|
@@ -3318,7 +3318,7 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
|
|
|
3318
3318
|
const deliverSpy = spyOn(
|
|
3319
3319
|
gatewayClient,
|
|
3320
3320
|
"deliverChannelReply",
|
|
3321
|
-
).mockResolvedValue(
|
|
3321
|
+
).mockResolvedValue({ ok: true });
|
|
3322
3322
|
|
|
3323
3323
|
// Create the requester conversation (different user than guardian)
|
|
3324
3324
|
const initReq = makeInboundRequest({
|
|
@@ -3387,7 +3387,7 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
|
|
|
3387
3387
|
const deliverSpy = spyOn(
|
|
3388
3388
|
gatewayClient,
|
|
3389
3389
|
"deliverChannelReply",
|
|
3390
|
-
).mockResolvedValue(
|
|
3390
|
+
).mockResolvedValue({ ok: true });
|
|
3391
3391
|
|
|
3392
3392
|
const initReq = makeInboundRequest({
|
|
3393
3393
|
content: "init",
|
|
@@ -229,6 +229,14 @@ describe("parseCallbackData", () => {
|
|
|
229
229
|
expect(result!.source).toBe("whatsapp_button");
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
+
test("parses slack source channel", () => {
|
|
233
|
+
const result = parseCallbackData("apr:req-789:approve_once", "slack");
|
|
234
|
+
expect(result).not.toBeNull();
|
|
235
|
+
expect(result!.action).toBe("approve_once");
|
|
236
|
+
expect(result!.requestId).toBe("req-789");
|
|
237
|
+
expect(result!.source).toBe("slack_button");
|
|
238
|
+
});
|
|
239
|
+
|
|
232
240
|
test("returns null for unknown action", () => {
|
|
233
241
|
expect(parseCallbackData("apr:req-123:unknown_action")).toBeNull();
|
|
234
242
|
});
|
|
@@ -234,6 +234,41 @@ describe("buildApprovalUIMetadata", () => {
|
|
|
234
234
|
expect(metadata.requestId).toBe("req-abc");
|
|
235
235
|
expect(metadata.actions).toEqual(prompt.actions);
|
|
236
236
|
expect(metadata.plainTextFallback).toBe("Reply yes or no.");
|
|
237
|
+
expect(metadata.permissionDetails).toEqual({
|
|
238
|
+
toolName: "shell",
|
|
239
|
+
riskLevel: "low",
|
|
240
|
+
toolInput: { command: "ls" },
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("includes requesterIdentifier in permissionDetails when provided", () => {
|
|
245
|
+
const prompt: ChannelApprovalPrompt = {
|
|
246
|
+
promptText: "Allow deploy?",
|
|
247
|
+
actions: [
|
|
248
|
+
{ id: "approve_once", label: "Approve once" },
|
|
249
|
+
{ id: "reject", label: "Reject" },
|
|
250
|
+
],
|
|
251
|
+
plainTextFallback: "Reply yes or no.",
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const approvalInfo: PendingApprovalInfo = {
|
|
255
|
+
requestId: "req-guard",
|
|
256
|
+
toolName: "deploy",
|
|
257
|
+
input: { target: "prod" },
|
|
258
|
+
riskLevel: "high",
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const metadata = buildApprovalUIMetadata(
|
|
262
|
+
prompt,
|
|
263
|
+
approvalInfo,
|
|
264
|
+
"alice@example.com",
|
|
265
|
+
);
|
|
266
|
+
expect(metadata.permissionDetails).toEqual({
|
|
267
|
+
toolName: "deploy",
|
|
268
|
+
riskLevel: "high",
|
|
269
|
+
toolInput: { target: "prod" },
|
|
270
|
+
requesterIdentifier: "alice@example.com",
|
|
271
|
+
});
|
|
237
272
|
});
|
|
238
273
|
});
|
|
239
274
|
|
|
@@ -478,8 +513,11 @@ describe("channelSupportsRichApprovalUI", () => {
|
|
|
478
513
|
expect(channelSupportsRichApprovalUI("sms")).toBe(false);
|
|
479
514
|
});
|
|
480
515
|
|
|
516
|
+
test("returns true for slack", () => {
|
|
517
|
+
expect(channelSupportsRichApprovalUI("slack")).toBe(true);
|
|
518
|
+
});
|
|
519
|
+
|
|
481
520
|
test("returns false for unknown channels", () => {
|
|
482
|
-
expect(channelSupportsRichApprovalUI("slack")).toBe(false);
|
|
483
521
|
expect(channelSupportsRichApprovalUI("")).toBe(false);
|
|
484
522
|
});
|
|
485
523
|
});
|
|
@@ -394,7 +394,9 @@ describe("guardian service challenge validation", () => {
|
|
|
394
394
|
);
|
|
395
395
|
|
|
396
396
|
expect(result.success).toBe(true);
|
|
397
|
-
|
|
397
|
+
if (result.success) {
|
|
398
|
+
expect(result.verificationType).toBe("guardian");
|
|
399
|
+
}
|
|
398
400
|
});
|
|
399
401
|
|
|
400
402
|
test("validateAndConsumeChallenge does not create a guardian binding (caller responsibility)", () => {
|
|
@@ -497,7 +499,9 @@ describe("guardian service challenge validation", () => {
|
|
|
497
499
|
);
|
|
498
500
|
|
|
499
501
|
expect(result.success).toBe(true);
|
|
500
|
-
|
|
502
|
+
if (result.success) {
|
|
503
|
+
expect(result.verificationType).toBe("guardian");
|
|
504
|
+
}
|
|
501
505
|
|
|
502
506
|
// validateAndConsumeChallenge no longer creates bindings — that is
|
|
503
507
|
// now handled by the caller (verification-intercept / relay-server).
|
|
@@ -1757,7 +1761,9 @@ describe("voice guardian challenge validation", () => {
|
|
|
1757
1761
|
);
|
|
1758
1762
|
|
|
1759
1763
|
expect(result.success).toBe(true);
|
|
1760
|
-
|
|
1764
|
+
if (result.success) {
|
|
1765
|
+
expect(result.verificationType).toBe("guardian");
|
|
1766
|
+
}
|
|
1761
1767
|
});
|
|
1762
1768
|
|
|
1763
1769
|
test("validateAndConsumeChallenge does not create a guardian binding for voice (caller responsibility)", () => {
|
|
@@ -2492,7 +2498,9 @@ describe("outbound verification sessions", () => {
|
|
|
2492
2498
|
);
|
|
2493
2499
|
|
|
2494
2500
|
expect(result.success).toBe(true);
|
|
2495
|
-
|
|
2501
|
+
if (result.success) {
|
|
2502
|
+
expect(result.verificationType).toBe("guardian");
|
|
2503
|
+
}
|
|
2496
2504
|
});
|
|
2497
2505
|
|
|
2498
2506
|
// ── Session state transitions ──
|
|
@@ -3022,7 +3030,9 @@ describe("outbound SMS verification", () => {
|
|
|
3022
3030
|
);
|
|
3023
3031
|
|
|
3024
3032
|
expect(result.success).toBe(true);
|
|
3025
|
-
|
|
3033
|
+
if (result.success) {
|
|
3034
|
+
expect(result.verificationType).toBe("guardian");
|
|
3035
|
+
}
|
|
3026
3036
|
});
|
|
3027
3037
|
|
|
3028
3038
|
test("inbound SMS from wrong identity + correct code is rejected", () => {
|
|
@@ -56,6 +56,7 @@ mock.module("../runtime/gateway-client.js", () => ({
|
|
|
56
56
|
throw new Error("Simulated delivery failure (502)");
|
|
57
57
|
}
|
|
58
58
|
deliveryCalls.push({ callbackUrl, payload, bearerToken });
|
|
59
|
+
return { ok: true };
|
|
59
60
|
},
|
|
60
61
|
}));
|
|
61
62
|
|
|
@@ -334,6 +335,36 @@ describe("channel-reply-delivery", () => {
|
|
|
334
335
|
expect(deliveryCalls).toHaveLength(0);
|
|
335
336
|
});
|
|
336
337
|
|
|
338
|
+
it("passes ephemeral and user through to each delivery call", async () => {
|
|
339
|
+
await deliverRenderedReplyViaCallback({
|
|
340
|
+
callbackUrl: "http://gateway/deliver/slack",
|
|
341
|
+
chatId: "C123",
|
|
342
|
+
textSegments: ["Part 1.", "Part 2."],
|
|
343
|
+
interSegmentDelayMs: 0,
|
|
344
|
+
ephemeral: true,
|
|
345
|
+
user: "U456",
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
expect(deliveryCalls).toHaveLength(2);
|
|
349
|
+
expect(deliveryCalls[0].payload.ephemeral).toBe(true);
|
|
350
|
+
expect(deliveryCalls[0].payload.user).toBe("U456");
|
|
351
|
+
expect(deliveryCalls[1].payload.ephemeral).toBe(true);
|
|
352
|
+
expect(deliveryCalls[1].payload.user).toBe("U456");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("does not include ephemeral fields when not set", async () => {
|
|
356
|
+
await deliverRenderedReplyViaCallback({
|
|
357
|
+
callbackUrl: "http://gateway/deliver/slack",
|
|
358
|
+
chatId: "C123",
|
|
359
|
+
textSegments: ["Normal message."],
|
|
360
|
+
interSegmentDelayMs: 0,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(deliveryCalls).toHaveLength(1);
|
|
364
|
+
expect(deliveryCalls[0].payload.ephemeral).toBeUndefined();
|
|
365
|
+
expect(deliveryCalls[0].payload.user).toBeUndefined();
|
|
366
|
+
});
|
|
367
|
+
|
|
337
368
|
it("passes startFromSegment through deliverReplyViaCallback options", async () => {
|
|
338
369
|
conversationMessages.push(
|
|
339
370
|
{ id: "msg-u", role: "user", content: "hi" },
|
|
@@ -42,6 +42,10 @@ describe("CommitEnrichmentService", () => {
|
|
|
42
42
|
beforeEach(() => {
|
|
43
43
|
_resetGitServiceRegistry();
|
|
44
44
|
_resetEnrichmentService();
|
|
45
|
+
// Previous tests' enrichment jobs may leave a stale index.lock if
|
|
46
|
+
// the git process exits but the lock file isn't flushed before the
|
|
47
|
+
// next test runs git operations in the shared testDir.
|
|
48
|
+
rmSync(join(testDir, ".git", "index.lock"), { force: true });
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
afterEach(async () => {
|