@vellumai/assistant 0.3.19 → 0.3.20
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 +151 -15
- package/Dockerfile +1 -0
- package/README.md +40 -4
- package/docs/architecture/integrations.md +7 -11
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +54 -0
- package/src/__tests__/approval-primitive.test.ts +540 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +206 -0
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +198 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +272 -0
- package/src/__tests__/call-controller.test.ts +439 -108
- package/src/__tests__/channel-invite-transport.test.ts +264 -0
- package/src/__tests__/cli.test.ts +42 -1
- package/src/__tests__/config-schema.test.ts +11 -127
- package/src/__tests__/config-watcher.test.ts +0 -8
- package/src/__tests__/daemon-lifecycle.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +8 -2
- package/src/__tests__/diff.test.ts +22 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +300 -32
- package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
- package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
- package/src/__tests__/guardian-dispatch.test.ts +124 -0
- package/src/__tests__/guardian-grant-minting.test.ts +6 -17
- package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
- package/src/__tests__/invite-redemption-service.test.ts +306 -0
- package/src/__tests__/ipc-snapshot.test.ts +57 -0
- package/src/__tests__/notification-decision-fallback.test.ts +88 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
- package/src/__tests__/sandbox-host-parity.test.ts +6 -13
- package/src/__tests__/scoped-approval-grants.test.ts +6 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -4
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
- package/src/__tests__/session-load-history-repair.test.ts +169 -2
- package/src/__tests__/session-runtime-assembly.test.ts +33 -5
- package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
- package/src/__tests__/skill-feature-flags.test.ts +188 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
- package/src/__tests__/skill-mirror-parity.test.ts +1 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/terminal-sandbox.test.ts +142 -9
- package/src/__tests__/terminal-tools.test.ts +2 -93
- package/src/__tests__/thread-seed-composer.test.ts +18 -0
- package/src/__tests__/tool-approval-handler.test.ts +350 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +46 -84
- package/src/agent/loop.ts +36 -1
- package/src/approvals/approval-primitive.ts +381 -0
- package/src/approvals/guardian-decision-primitive.ts +191 -0
- package/src/calls/call-controller.ts +252 -209
- package/src/calls/call-domain.ts +44 -6
- package/src/calls/guardian-dispatch.ts +48 -0
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +46 -30
- package/src/cli/core-commands.ts +0 -4
- package/src/cli.ts +76 -34
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
- package/src/config/assistant-feature-flags.ts +162 -0
- package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
- package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/reminder/SKILL.md +49 -2
- package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
- package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
- package/src/config/core-schema.ts +1 -1
- package/src/config/env-registry.ts +10 -0
- package/src/config/feature-flag-registry.json +61 -0
- package/src/config/loader.ts +22 -1
- package/src/config/sandbox-schema.ts +0 -39
- package/src/config/schema.ts +6 -2
- package/src/config/skill-state.ts +34 -0
- package/src/config/skills-schema.ts +0 -1
- package/src/config/skills.ts +9 -0
- package/src/config/system-prompt.ts +110 -46
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +19 -1
- package/src/config/vellum-skills/catalog.json +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
- package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +104 -3
- package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/guardian-invite-intent.ts +124 -0
- package/src/daemon/handlers/avatar.ts +68 -0
- package/src/daemon/handlers/browser.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +120 -0
- package/src/daemon/handlers/index.ts +4 -0
- package/src/daemon/handlers/sessions.ts +19 -0
- package/src/daemon/handlers/shared.ts +3 -1
- package/src/daemon/install-cli-launchers.ts +58 -13
- package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -2
- package/src/daemon/ipc-contract/settings.ts +25 -2
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +4 -0
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/main.ts +1 -0
- package/src/daemon/server.ts +1 -0
- package/src/daemon/session-lifecycle.ts +52 -7
- package/src/daemon/session-memory.ts +45 -0
- package/src/daemon/session-process.ts +258 -432
- package/src/daemon/session-runtime-assembly.ts +12 -0
- package/src/daemon/session-skill-tools.ts +14 -1
- package/src/daemon/session-tool-setup.ts +5 -0
- package/src/daemon/session.ts +11 -0
- package/src/daemon/tool-side-effects.ts +35 -9
- package/src/index.ts +0 -2
- package/src/memory/conversation-display-order-migration.ts +44 -0
- package/src/memory/conversation-queries.ts +2 -0
- package/src/memory/conversation-store.ts +91 -0
- package/src/memory/db-init.ts +5 -1
- package/src/memory/embedding-local.ts +13 -8
- package/src/memory/guardian-action-store.ts +125 -2
- package/src/memory/ingress-invite-store.ts +95 -1
- package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
- package/src/memory/migrations/index.ts +2 -1
- package/src/memory/schema.ts +5 -1
- package/src/memory/scoped-approval-grants.ts +14 -5
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/notifications/decision-engine.ts +49 -12
- package/src/notifications/emit-signal.ts +7 -0
- package/src/notifications/signal.ts +7 -0
- package/src/notifications/thread-seed-composer.ts +2 -1
- package/src/runtime/channel-approval-types.ts +16 -6
- package/src/runtime/channel-approvals.ts +19 -15
- package/src/runtime/channel-invite-transport.ts +85 -0
- package/src/runtime/channel-invite-transports/telegram.ts +105 -0
- package/src/runtime/guardian-action-grant-minter.ts +92 -35
- package/src/runtime/guardian-action-message-composer.ts +30 -0
- package/src/runtime/guardian-decision-types.ts +91 -0
- package/src/runtime/http-server.ts +23 -1
- package/src/runtime/ingress-service.ts +22 -0
- package/src/runtime/invite-redemption-service.ts +181 -0
- package/src/runtime/invite-redemption-templates.ts +39 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/guardian-action-routes.ts +206 -0
- package/src/runtime/routes/guardian-approval-interception.ts +66 -190
- package/src/runtime/routes/inbound-message-handler.ts +486 -394
- package/src/runtime/routes/pairing-routes.ts +4 -0
- package/src/security/encrypted-store.ts +31 -17
- package/src/security/keychain.ts +176 -2
- package/src/security/secure-keys.ts +97 -0
- package/src/security/tool-approval-digest.ts +1 -1
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/browser-manager.ts +46 -32
- package/src/tools/browser/browser-screencast.ts +2 -2
- package/src/tools/calls/call-start.ts +1 -1
- package/src/tools/executor.ts +22 -17
- package/src/tools/network/script-proxy/session-manager.ts +1 -5
- package/src/tools/skills/load.ts +22 -8
- package/src/tools/system/avatar-generator.ts +119 -0
- package/src/tools/system/navigate-settings.ts +65 -0
- package/src/tools/system/open-system-settings.ts +75 -0
- package/src/tools/system/voice-config.ts +121 -32
- package/src/tools/terminal/backends/native.ts +40 -19
- package/src/tools/terminal/backends/types.ts +3 -3
- package/src/tools/terminal/parser.ts +1 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
- package/src/tools/terminal/sandbox.ts +1 -12
- package/src/tools/terminal/shell.ts +3 -31
- package/src/tools/tool-approval-handler.ts +141 -3
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +6 -0
- package/src/util/diff.ts +36 -13
- package/Dockerfile.sandbox +0 -5
- package/src/__tests__/doordash-client.test.ts +0 -187
- package/src/__tests__/doordash-session.test.ts +0 -154
- package/src/__tests__/signup-e2e.test.ts +0 -354
- package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
- package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
- package/src/cli/doordash.ts +0 -1057
- package/src/config/bundled-skills/doordash/SKILL.md +0 -163
- package/src/config/templates/LOOKS.md +0 -25
- package/src/doordash/cart-queries.ts +0 -787
- package/src/doordash/client.ts +0 -1016
- package/src/doordash/order-queries.ts +0 -85
- package/src/doordash/queries.ts +0 -13
- package/src/doordash/query-extractor.ts +0 -94
- package/src/doordash/search-queries.ts +0 -203
- package/src/doordash/session.ts +0 -84
- package/src/doordash/store-queries.ts +0 -246
- package/src/doordash/types.ts +0 -367
- package/src/tools/terminal/backends/docker.ts +0 -379
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Approval interception: checks for pending approvals and handles inbound
|
|
3
3
|
* messages as decisions, reminders, or conversational follow-ups.
|
|
4
4
|
*/
|
|
5
|
+
import { applyGuardianDecision } from '../../approvals/guardian-decision-primitive.js';
|
|
5
6
|
import type { ChannelId } from '../../channels/types.js';
|
|
6
7
|
import {
|
|
7
8
|
getAllPendingApprovalsByGuardianChat,
|
|
@@ -11,9 +12,7 @@ import {
|
|
|
11
12
|
type GuardianApprovalRequest,
|
|
12
13
|
updateApprovalDecision,
|
|
13
14
|
} from '../../memory/channel-guardian-store.js';
|
|
14
|
-
import { createScopedApprovalGrant } from '../../memory/scoped-approval-grants.js';
|
|
15
15
|
import { emitNotificationSignal } from '../../notifications/emit-signal.js';
|
|
16
|
-
import { computeToolApprovalDigest } from '../../security/tool-approval-digest.js';
|
|
17
16
|
import { getLogger } from '../../util/logger.js';
|
|
18
17
|
import { runApprovalConversationTurn } from '../approval-conversation-turn.js';
|
|
19
18
|
import { composeApprovalMessageGenerative } from '../approval-message-composer.js';
|
|
@@ -25,7 +24,6 @@ import {
|
|
|
25
24
|
getApprovalInfoByConversation,
|
|
26
25
|
getChannelApprovalPrompt,
|
|
27
26
|
handleChannelDecision,
|
|
28
|
-
type PendingApprovalInfo,
|
|
29
27
|
} from '../channel-approvals.js';
|
|
30
28
|
import { deliverChannelReply } from '../gateway-client.js';
|
|
31
29
|
import type {
|
|
@@ -49,68 +47,6 @@ import {
|
|
|
49
47
|
|
|
50
48
|
const log = getLogger('runtime-http');
|
|
51
49
|
|
|
52
|
-
/** TTL for scoped approval grants minted on guardian approve_once decisions. */
|
|
53
|
-
export const GRANT_TTL_MS = 5 * 60 * 1000;
|
|
54
|
-
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// Scoped grant minting on guardian tool-approval decisions
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Mint a `tool_signature` scoped grant when a guardian approves a tool-approval
|
|
61
|
-
* request. Only mints when the approval info contains a tool invocation with
|
|
62
|
-
* input (so we can compute the input digest). Informational ASK_GUARDIAN
|
|
63
|
-
* requests that lack tool input are skipped.
|
|
64
|
-
*
|
|
65
|
-
* Fails silently on error — grant minting is best-effort and must never block
|
|
66
|
-
* the approval flow.
|
|
67
|
-
*/
|
|
68
|
-
function tryMintToolApprovalGrant(params: {
|
|
69
|
-
approvalInfo: PendingApprovalInfo;
|
|
70
|
-
approval: GuardianApprovalRequest;
|
|
71
|
-
decisionChannel: ChannelId;
|
|
72
|
-
guardianExternalUserId: string;
|
|
73
|
-
}): void {
|
|
74
|
-
const { approvalInfo, approval, decisionChannel, guardianExternalUserId } = params;
|
|
75
|
-
|
|
76
|
-
// Only mint for requests that carry a tool name — the presence of toolName
|
|
77
|
-
// distinguishes tool-approval requests from informational ones.
|
|
78
|
-
// computeToolApprovalDigest can deterministically hash {} so zero-argument
|
|
79
|
-
// tool invocations must still receive a grant.
|
|
80
|
-
if (!approvalInfo.toolName) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const inputDigest = computeToolApprovalDigest(approvalInfo.toolName, approvalInfo.input);
|
|
86
|
-
|
|
87
|
-
createScopedApprovalGrant({
|
|
88
|
-
assistantId: approval.assistantId,
|
|
89
|
-
scopeMode: 'tool_signature',
|
|
90
|
-
toolName: approvalInfo.toolName,
|
|
91
|
-
inputDigest,
|
|
92
|
-
requestChannel: approval.channel,
|
|
93
|
-
decisionChannel,
|
|
94
|
-
executionChannel: null,
|
|
95
|
-
conversationId: approval.conversationId,
|
|
96
|
-
callSessionId: null,
|
|
97
|
-
guardianExternalUserId,
|
|
98
|
-
requesterExternalUserId: approval.requesterExternalUserId,
|
|
99
|
-
expiresAt: new Date(Date.now() + GRANT_TTL_MS).toISOString(),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
log.info(
|
|
103
|
-
{ toolName: approvalInfo.toolName, conversationId: approval.conversationId },
|
|
104
|
-
'Minted scoped approval grant for guardian tool-approval decision',
|
|
105
|
-
);
|
|
106
|
-
} catch (err) {
|
|
107
|
-
log.error(
|
|
108
|
-
{ err, toolName: approvalInfo.toolName, conversationId: approval.conversationId },
|
|
109
|
-
'Failed to mint scoped approval grant (non-fatal)',
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
50
|
export interface ApprovalInterceptionParams {
|
|
115
51
|
conversationId: string;
|
|
116
52
|
callbackData?: string;
|
|
@@ -250,13 +186,6 @@ export async function handleApprovalInterception(
|
|
|
250
186
|
}
|
|
251
187
|
|
|
252
188
|
if (callbackDecision) {
|
|
253
|
-
// approve_always is not available for guardian approvals — guardians
|
|
254
|
-
// should not be able to permanently allowlist tools on behalf of the
|
|
255
|
-
// requester. Downgrade to approve_once.
|
|
256
|
-
if (callbackDecision.action === 'approve_always') {
|
|
257
|
-
callbackDecision = { ...callbackDecision, action: 'approve_once' };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
189
|
// Access request approvals don't have a pending interaction in the
|
|
261
190
|
// session tracker, so they need a separate decision path that creates
|
|
262
191
|
// a verification session instead of resuming an agent loop.
|
|
@@ -272,44 +201,22 @@ export async function handleApprovalInterception(
|
|
|
272
201
|
return accessResult;
|
|
273
202
|
}
|
|
274
203
|
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const result = handleChannelDecision(
|
|
285
|
-
guardianApproval.conversationId,
|
|
286
|
-
callbackDecision,
|
|
287
|
-
);
|
|
204
|
+
// Apply the decision through the unified guardian decision primitive.
|
|
205
|
+
// The primitive handles approve_always downgrade, approval info capture,
|
|
206
|
+
// record update, and scoped grant minting.
|
|
207
|
+
const result = applyGuardianDecision({
|
|
208
|
+
approval: guardianApproval,
|
|
209
|
+
decision: callbackDecision,
|
|
210
|
+
actorExternalUserId: senderExternalUserId,
|
|
211
|
+
actorChannel: sourceChannel,
|
|
212
|
+
});
|
|
288
213
|
|
|
289
214
|
if (result.applied) {
|
|
290
|
-
// Update the guardian approval request record only when the decision
|
|
291
|
-
// was actually applied. If the request was already resolved (race with
|
|
292
|
-
// expiry sweep or concurrent callback), skip to avoid inconsistency.
|
|
293
|
-
const approvalStatus = callbackDecision.action === 'reject' ? 'denied' as const : 'approved' as const;
|
|
294
|
-
updateApprovalDecision(guardianApproval.id, {
|
|
295
|
-
status: approvalStatus,
|
|
296
|
-
decidedByExternalUserId: senderExternalUserId,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Mint a scoped grant when a guardian approves a tool-approval request
|
|
300
|
-
if (callbackDecision.action !== 'reject' && cbMatchedInfo) {
|
|
301
|
-
tryMintToolApprovalGrant({
|
|
302
|
-
approvalInfo: cbMatchedInfo,
|
|
303
|
-
approval: guardianApproval,
|
|
304
|
-
decisionChannel: sourceChannel,
|
|
305
|
-
guardianExternalUserId: senderExternalUserId,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
215
|
// Notify the requester's chat about the outcome with the tool name
|
|
216
|
+
const effectiveAction = callbackDecision.action === 'approve_always' ? 'approve_once' : callbackDecision.action;
|
|
310
217
|
const outcomeText = await composeApprovalMessageGenerative({
|
|
311
218
|
scenario: 'guardian_decision_outcome',
|
|
312
|
-
decision:
|
|
219
|
+
decision: effectiveAction === 'reject' ? 'denied' : 'approved',
|
|
313
220
|
toolName: guardianApproval.toolName,
|
|
314
221
|
channel: sourceChannel,
|
|
315
222
|
}, {}, approvalCopyGenerator);
|
|
@@ -428,38 +335,15 @@ export async function handleApprovalInterception(
|
|
|
428
335
|
...(engineResult.targetRequestId ? { requestId: engineResult.targetRequestId } : {}),
|
|
429
336
|
};
|
|
430
337
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
:
|
|
437
|
-
|
|
438
|
-
const result = handleChannelDecision(
|
|
439
|
-
targetApproval.conversationId,
|
|
440
|
-
engineDecision,
|
|
441
|
-
);
|
|
338
|
+
// Apply the decision through the unified guardian decision primitive.
|
|
339
|
+
const result = applyGuardianDecision({
|
|
340
|
+
approval: targetApproval,
|
|
341
|
+
decision: engineDecision,
|
|
342
|
+
actorExternalUserId: senderExternalUserId,
|
|
343
|
+
actorChannel: sourceChannel,
|
|
344
|
+
});
|
|
442
345
|
|
|
443
346
|
if (result.applied) {
|
|
444
|
-
// Update the guardian approval request record only when the decision
|
|
445
|
-
// was actually applied. If the request was already resolved (race with
|
|
446
|
-
// expiry sweep or concurrent callback), skip to avoid inconsistency.
|
|
447
|
-
const approvalStatus = decisionAction === 'reject' ? 'denied' as const : 'approved' as const;
|
|
448
|
-
updateApprovalDecision(targetApproval.id, {
|
|
449
|
-
status: approvalStatus,
|
|
450
|
-
decidedByExternalUserId: senderExternalUserId,
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// Mint a scoped grant when a guardian approves a tool-approval request
|
|
454
|
-
if (decisionAction !== 'reject' && engineMatchedInfo) {
|
|
455
|
-
tryMintToolApprovalGrant({
|
|
456
|
-
approvalInfo: engineMatchedInfo,
|
|
457
|
-
approval: targetApproval,
|
|
458
|
-
decisionChannel: sourceChannel,
|
|
459
|
-
guardianExternalUserId: senderExternalUserId,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
347
|
// Notify the requester's chat about the outcome
|
|
464
348
|
const outcomeText = await composeApprovalMessageGenerative({
|
|
465
349
|
scenario: 'guardian_decision_outcome',
|
|
@@ -591,35 +475,15 @@ export async function handleApprovalInterception(
|
|
|
591
475
|
return accessResult;
|
|
592
476
|
}
|
|
593
477
|
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
:
|
|
600
|
-
|
|
601
|
-
const result = handleChannelDecision(
|
|
602
|
-
targetLegacyApproval.conversationId,
|
|
603
|
-
legacyGuardianDecision,
|
|
604
|
-
);
|
|
478
|
+
// Apply the decision through the unified guardian decision primitive.
|
|
479
|
+
const result = applyGuardianDecision({
|
|
480
|
+
approval: targetLegacyApproval,
|
|
481
|
+
decision: legacyGuardianDecision,
|
|
482
|
+
actorExternalUserId: senderExternalUserId,
|
|
483
|
+
actorChannel: sourceChannel,
|
|
484
|
+
});
|
|
605
485
|
|
|
606
486
|
if (result.applied) {
|
|
607
|
-
const approvalStatus = legacyGuardianDecision.action === 'reject' ? 'denied' as const : 'approved' as const;
|
|
608
|
-
updateApprovalDecision(targetLegacyApproval.id, {
|
|
609
|
-
status: approvalStatus,
|
|
610
|
-
decidedByExternalUserId: senderExternalUserId,
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
// Mint a scoped grant when a guardian approves a tool-approval request
|
|
614
|
-
if (legacyGuardianDecision.action !== 'reject' && legacyMatchedInfo) {
|
|
615
|
-
tryMintToolApprovalGrant({
|
|
616
|
-
approvalInfo: legacyMatchedInfo,
|
|
617
|
-
approval: targetLegacyApproval,
|
|
618
|
-
decisionChannel: sourceChannel,
|
|
619
|
-
guardianExternalUserId: senderExternalUserId,
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
|
|
623
487
|
// Notify the requester's chat about the outcome
|
|
624
488
|
const outcomeText = await composeApprovalMessageGenerative({
|
|
625
489
|
scenario: 'guardian_decision_outcome',
|
|
@@ -742,13 +606,15 @@ export async function handleApprovalInterception(
|
|
|
742
606
|
action: 'reject',
|
|
743
607
|
source: 'plain_text',
|
|
744
608
|
};
|
|
745
|
-
|
|
609
|
+
// Apply the cancel decision through the unified primitive.
|
|
610
|
+
// The primitive handles record update and (no-op) grant logic.
|
|
611
|
+
const cancelApplyResult = applyGuardianDecision({
|
|
612
|
+
approval: guardianApprovalForRequest,
|
|
613
|
+
decision: rejectDecision,
|
|
614
|
+
actorExternalUserId: senderExternalUserId,
|
|
615
|
+
actorChannel: sourceChannel,
|
|
616
|
+
});
|
|
746
617
|
if (cancelApplyResult.applied) {
|
|
747
|
-
updateApprovalDecision(guardianApprovalForRequest.id, {
|
|
748
|
-
status: 'denied',
|
|
749
|
-
decidedByExternalUserId: senderExternalUserId,
|
|
750
|
-
});
|
|
751
|
-
|
|
752
618
|
// Notify requester
|
|
753
619
|
const replyText = cancelReplyText ?? await composeApprovalMessageGenerative({
|
|
754
620
|
scenario: 'requester_cancel',
|
|
@@ -1152,29 +1018,39 @@ async function handleAccessRequestApproval(
|
|
|
1152
1018
|
});
|
|
1153
1019
|
}
|
|
1154
1020
|
|
|
1155
|
-
//
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
isAsyncBackground: false,
|
|
1165
|
-
visibleInSourceNow: false,
|
|
1166
|
-
},
|
|
1167
|
-
contextPayload: {
|
|
1021
|
+
// Don't emit guardian_decision for approvals that still require code
|
|
1022
|
+
// verification — the guardian already received the code, and emitting
|
|
1023
|
+
// this signal prematurely causes the notification pipeline to deliver
|
|
1024
|
+
// a confusing "approved" message before the requester has verified.
|
|
1025
|
+
// The guardian_decision signal should only fire once access is fully granted
|
|
1026
|
+
// (i.e. after code consumption), which is handled in the verification path.
|
|
1027
|
+
if (!decisionResult.verificationSessionId) {
|
|
1028
|
+
void emitNotificationSignal({
|
|
1029
|
+
sourceEventName: 'ingress.trusted_contact.guardian_decision',
|
|
1168
1030
|
sourceChannel: approval.channel,
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1031
|
+
sourceSessionId: approval.conversationId,
|
|
1032
|
+
assistantId,
|
|
1033
|
+
attentionHints: {
|
|
1034
|
+
requiresAction: false,
|
|
1035
|
+
urgency: 'medium',
|
|
1036
|
+
isAsyncBackground: false,
|
|
1037
|
+
visibleInSourceNow: false,
|
|
1038
|
+
},
|
|
1039
|
+
contextPayload: {
|
|
1040
|
+
sourceChannel: approval.channel,
|
|
1041
|
+
requesterExternalUserId: approval.requesterExternalUserId,
|
|
1042
|
+
requesterChatId: approval.requesterChatId,
|
|
1043
|
+
decidedByExternalUserId,
|
|
1044
|
+
decision: 'approved',
|
|
1045
|
+
},
|
|
1046
|
+
dedupeKey: `trusted-contact:guardian-decision:${approval.id}`,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1176
1049
|
|
|
1177
|
-
//
|
|
1050
|
+
// Emit verification_sent with visibleInSourceNow=true so the notification
|
|
1051
|
+
// pipeline suppresses delivery — the guardian already received the
|
|
1052
|
+
// verification code directly. Without this flag, the pipeline generates
|
|
1053
|
+
// a redundant LLM message like "Good news! Your request has been approved."
|
|
1178
1054
|
if (decisionResult.verificationSessionId && codeDelivered) {
|
|
1179
1055
|
void emitNotificationSignal({
|
|
1180
1056
|
sourceEventName: 'ingress.trusted_contact.verification_sent',
|
|
@@ -1185,7 +1061,7 @@ async function handleAccessRequestApproval(
|
|
|
1185
1061
|
requiresAction: false,
|
|
1186
1062
|
urgency: 'low',
|
|
1187
1063
|
isAsyncBackground: true,
|
|
1188
|
-
visibleInSourceNow:
|
|
1064
|
+
visibleInSourceNow: true,
|
|
1189
1065
|
},
|
|
1190
1066
|
contextPayload: {
|
|
1191
1067
|
sourceChannel: approval.channel,
|