@vellumai/assistant 0.3.27 → 0.4.0
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 +81 -4
- package/Dockerfile +2 -2
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +9 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +119 -0
- package/src/__tests__/approval-routes-http.test.ts +13 -5
- package/src/__tests__/asset-materialize-tool.test.ts +2 -0
- package/src/__tests__/asset-search-tool.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
- package/src/__tests__/attachments-store.test.ts +2 -0
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/bundled-asset.test.ts +107 -0
- package/src/__tests__/call-controller.test.ts +30 -29
- package/src/__tests__/call-routes-http.test.ts +34 -32
- package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
- package/src/__tests__/canonical-guardian-store.test.ts +636 -0
- package/src/__tests__/channel-approval-routes.test.ts +174 -1
- package/src/__tests__/channel-invite-transport.test.ts +6 -6
- package/src/__tests__/channel-reply-delivery.test.ts +19 -0
- package/src/__tests__/channel-retry-sweep.test.ts +130 -0
- package/src/__tests__/clarification-resolver.test.ts +2 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
- package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +5 -5
- package/src/__tests__/config-watcher.test.ts +3 -1
- package/src/__tests__/connection-policy.test.ts +14 -5
- package/src/__tests__/contacts-tools.test.ts +3 -1
- package/src/__tests__/contradiction-checker.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-routes.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +16 -6
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/credential-vault.test.ts +5 -4
- package/src/__tests__/daemon-lifecycle.test.ts +9 -0
- package/src/__tests__/daemon-server-session-init.test.ts +27 -0
- package/src/__tests__/elevenlabs-config.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
- package/src/__tests__/encrypted-store.test.ts +10 -5
- package/src/__tests__/followup-tools.test.ts +3 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
- package/src/__tests__/guardian-control-plane-policy.test.ts +19 -19
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
- package/src/__tests__/guardian-dispatch.test.ts +21 -19
- package/src/__tests__/guardian-grant-minting.test.ts +68 -1
- package/src/__tests__/guardian-outbound-http.test.ts +12 -9
- package/src/__tests__/guardian-routing-invariants.test.ts +1092 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
- package/src/__tests__/handlers-slack-config.test.ts +3 -1
- package/src/__tests__/handlers-telegram-config.test.ts +3 -1
- package/src/__tests__/handlers-twilio-config.test.ts +3 -1
- package/src/__tests__/handlers-twitter-config.test.ts +3 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
- package/src/__tests__/heartbeat-service.test.ts +20 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
- package/src/__tests__/ingress-reconcile.test.ts +3 -1
- package/src/__tests__/ingress-routes-http.test.ts +231 -4
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +13 -0
- package/src/__tests__/mcp-cli.test.ts +77 -0
- package/src/__tests__/media-generate-image.test.ts +21 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
- package/src/__tests__/memory-regressions.test.ts +20 -20
- package/src/__tests__/non-member-access-request.test.ts +212 -36
- package/src/__tests__/notification-decision-fallback.test.ts +63 -3
- package/src/__tests__/notification-decision-strategy.test.ts +78 -0
- package/src/__tests__/notification-guardian-path.test.ts +15 -15
- package/src/__tests__/oauth-connect-handler.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
- package/src/__tests__/onboarding-template-contract.test.ts +116 -21
- package/src/__tests__/pairing-routes.test.ts +171 -0
- package/src/__tests__/playbook-execution.test.ts +3 -1
- package/src/__tests__/playbook-tools.test.ts +3 -1
- package/src/__tests__/provider-error-scenarios.test.ts +59 -8
- package/src/__tests__/proxy-approval-callback.test.ts +2 -0
- package/src/__tests__/recording-handler.test.ts +11 -0
- package/src/__tests__/recording-intent-handler.test.ts +15 -0
- package/src/__tests__/recording-state-machine.test.ts +13 -2
- package/src/__tests__/registry.test.ts +7 -3
- package/src/__tests__/relay-server.test.ts +148 -28
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
- package/src/__tests__/runtime-events-sse.test.ts +4 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
- package/src/__tests__/schedule-tools.test.ts +3 -1
- package/src/__tests__/secret-scanner-executor.test.ts +59 -0
- package/src/__tests__/secret-scanner.test.ts +8 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -0
- package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
- package/src/__tests__/session-abort-tool-results.test.ts +23 -0
- package/src/__tests__/session-agent-loop.test.ts +16 -0
- package/src/__tests__/session-conflict-gate.test.ts +21 -0
- package/src/__tests__/session-load-history-repair.test.ts +27 -17
- package/src/__tests__/session-pre-run-repair.test.ts +23 -0
- package/src/__tests__/session-profile-injection.test.ts +21 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
- package/src/__tests__/session-queue.test.ts +23 -0
- package/src/__tests__/session-runtime-assembly.test.ts +126 -59
- package/src/__tests__/session-skill-tools.test.ts +27 -5
- package/src/__tests__/session-slash-known.test.ts +23 -0
- package/src/__tests__/session-slash-queue.test.ts +23 -0
- package/src/__tests__/session-slash-unknown.test.ts +23 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
- package/src/__tests__/session-workspace-injection.test.ts +21 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
- package/src/__tests__/shell-credential-ref.test.ts +2 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
- package/src/__tests__/skills.test.ts +8 -4
- package/src/__tests__/slack-channel-config.test.ts +3 -1
- package/src/__tests__/subagent-tools.test.ts +19 -0
- package/src/__tests__/swarm-recursion.test.ts +2 -0
- package/src/__tests__/swarm-session-integration.test.ts +2 -0
- package/src/__tests__/swarm-tool.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +3 -1
- package/src/__tests__/task-compiler.test.ts +3 -1
- package/src/__tests__/task-management-tools.test.ts +3 -1
- package/src/__tests__/task-tools.test.ts +3 -1
- package/src/__tests__/terminal-sandbox.test.ts +13 -12
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
- package/src/__tests__/trusted-contact-verification.test.ts +91 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
- package/src/__tests__/twitter-auth-handler.test.ts +3 -1
- package/src/__tests__/twitter-cli-routing.test.ts +3 -1
- package/src/__tests__/view-image-tool.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +329 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
- package/src/__tests__/voice-session-bridge.test.ts +10 -10
- package/src/__tests__/work-item-output.test.ts +3 -1
- package/src/__tests__/workspace-lifecycle.test.ts +13 -2
- package/src/agent/loop.ts +46 -3
- package/src/approvals/guardian-decision-primitive.ts +285 -0
- package/src/approvals/guardian-request-resolvers.ts +539 -0
- package/src/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/guardian-dispatch.ts +46 -40
- package/src/calls/relay-server.ts +358 -24
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +3 -3
- package/src/cli.ts +12 -0
- package/src/config/agent-schema.ts +14 -3
- package/src/config/calls-schema.ts +6 -6
- package/src/config/core-schema.ts +3 -3
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/mcp-schema.ts +1 -1
- package/src/config/memory-schema.ts +27 -19
- package/src/config/schema.ts +21 -21
- package/src/config/skills-schema.ts +7 -7
- package/src/config/system-prompt.ts +2 -1
- package/src/config/templates/BOOTSTRAP.md +47 -31
- package/src/config/templates/USER.md +5 -0
- package/src/config/update-bulletin-template-path.ts +4 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +149 -21
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/guardian-actions.ts +45 -66
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +22 -16
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/server.ts +18 -0
- package/src/daemon/session-agent-loop-handlers.ts +5 -4
- package/src/daemon/session-agent-loop.ts +33 -6
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +68 -326
- package/src/daemon/session-runtime-assembly.ts +119 -25
- package/src/daemon/session-tool-setup.ts +3 -2
- package/src/daemon/session.ts +4 -3
- package/src/home-base/prebuilt/seed.ts +2 -1
- package/src/hooks/templates.ts +2 -1
- package/src/memory/canonical-guardian-store.ts +586 -0
- package/src/memory/channel-guardian-store.ts +2 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- package/src/memory/guardian-action-store.ts +7 -60
- package/src/memory/guardian-approvals.ts +9 -4
- package/src/memory/guardian-bindings.ts +25 -1
- package/src/memory/indexer.ts +3 -3
- package/src/memory/ingress-invite-store.ts +45 -0
- package/src/memory/job-handlers/backfill.ts +16 -9
- package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
- package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
- package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +56 -0
- package/src/notifications/copy-composer.ts +31 -4
- package/src/notifications/decision-engine.ts +57 -0
- package/src/permissions/defaults.ts +2 -0
- package/src/runtime/access-request-helper.ts +173 -0
- package/src/runtime/actor-trust-resolver.ts +221 -0
- package/src/runtime/channel-guardian-service.ts +12 -4
- package/src/runtime/channel-invite-transports/voice.ts +58 -0
- package/src/runtime/channel-retry-sweep.ts +18 -6
- package/src/runtime/guardian-context-resolver.ts +38 -71
- package/src/runtime/guardian-decision-types.ts +6 -0
- package/src/runtime/guardian-reply-router.ts +717 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
- package/src/runtime/routes/channel-route-shared.ts +1 -1
- package/src/runtime/routes/channel-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +20 -2
- package/src/runtime/routes/guardian-action-routes.ts +100 -109
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +205 -529
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/runtime/tool-grant-request-helper.ts +195 -0
- package/src/tools/executor.ts +13 -1
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/sensitive-output-placeholders.ts +203 -0
- package/src/tools/tool-approval-handler.ts +53 -10
- package/src/tools/types.ts +13 -2
- package/src/util/bundled-asset.ts +31 -0
- package/src/util/canonicalize-identity.ts +52 -0
- package/src/util/logger.ts +20 -8
- package/src/util/platform.ts +10 -0
- package/src/util/voice-code.ts +29 -0
- package/src/daemon/guardian-invite-intent.ts +0 -124
|
@@ -7,36 +7,20 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { createAssistantMessage,createUserMessage } from '../agent/message-types.js';
|
|
10
|
-
import { answerCall } from '../calls/call-domain.js';
|
|
11
|
-
import { isTerminalState } from '../calls/call-state-machine.js';
|
|
12
|
-
import { getCallSession } from '../calls/call-store.js';
|
|
13
10
|
import type { TurnChannelContext, TurnInterfaceContext } from '../channels/types.js';
|
|
14
11
|
import { parseChannelId, parseInterfaceId } from '../channels/types.js';
|
|
15
12
|
import { getConfig } from '../config/loader.js';
|
|
13
|
+
import {
|
|
14
|
+
listCanonicalGuardianRequests,
|
|
15
|
+
listPendingCanonicalGuardianRequestsByDestinationConversation,
|
|
16
|
+
} from '../memory/canonical-guardian-store.js';
|
|
16
17
|
import * as conversationStore from '../memory/conversation-store.js';
|
|
17
18
|
import { provenanceFromGuardianContext } from '../memory/conversation-store.js';
|
|
18
|
-
import {
|
|
19
|
-
finalizeFollowup,
|
|
20
|
-
getDeliveriesByRequestId,
|
|
21
|
-
getExpiredDeliveriesByConversation,
|
|
22
|
-
getFollowupDeliveriesByConversation,
|
|
23
|
-
getGuardianActionRequest,
|
|
24
|
-
getPendingDeliveriesByConversation,
|
|
25
|
-
getPendingRequestByCallSessionId,
|
|
26
|
-
progressFollowupState,
|
|
27
|
-
resolveGuardianActionRequest,
|
|
28
|
-
startFollowupFromExpiredRequest,
|
|
29
|
-
} from '../memory/guardian-action-store.js';
|
|
30
19
|
import { extractPreferences } from '../notifications/preference-extractor.js';
|
|
31
20
|
import { createPreference } from '../notifications/preferences-store.js';
|
|
32
21
|
import type { Message } from '../providers/types.js';
|
|
33
|
-
import {
|
|
34
|
-
import { executeFollowupAction } from '../runtime/guardian-action-followup-executor.js';
|
|
35
|
-
import { tryMintGuardianActionGrant } from '../runtime/guardian-action-grant-minter.js';
|
|
36
|
-
import { composeGuardianActionMessageGenerative } from '../runtime/guardian-action-message-composer.js';
|
|
37
|
-
import type { ApprovalConversationGenerator, GuardianActionCopyGenerator, GuardianFollowUpConversationGenerator } from '../runtime/http-types.js';
|
|
22
|
+
import { routeGuardianReply } from '../runtime/guardian-reply-router.js';
|
|
38
23
|
import { getLogger } from '../util/logger.js';
|
|
39
|
-
import { resolveGuardianInviteIntent } from './guardian-invite-intent.js';
|
|
40
24
|
import { resolveGuardianVerificationIntent } from './guardian-verification-intent.js';
|
|
41
25
|
import type { UsageStats } from './ipc-contract.js';
|
|
42
26
|
import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
|
|
@@ -48,31 +32,6 @@ import type { TraceEmitter } from './trace-emitter.js';
|
|
|
48
32
|
|
|
49
33
|
const log = getLogger('session-process');
|
|
50
34
|
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Module-level generator injection
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// The daemon lifecycle creates the generator once and injects it here so the
|
|
55
|
-
// mac/IPC channel path can classify follow-up replies without threading the
|
|
56
|
-
// generator through Session / DaemonServer constructors.
|
|
57
|
-
let _guardianFollowUpGenerator: GuardianFollowUpConversationGenerator | undefined;
|
|
58
|
-
let _guardianActionCopyGenerator: GuardianActionCopyGenerator | undefined;
|
|
59
|
-
let _approvalConversationGenerator: ApprovalConversationGenerator | undefined;
|
|
60
|
-
|
|
61
|
-
/** Inject the guardian follow-up conversation generator (called from lifecycle.ts). */
|
|
62
|
-
export function setGuardianFollowUpConversationGenerator(gen: GuardianFollowUpConversationGenerator): void {
|
|
63
|
-
_guardianFollowUpGenerator = gen;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Inject the guardian action copy generator (called from lifecycle.ts). */
|
|
67
|
-
export function setGuardianActionCopyGenerator(gen: GuardianActionCopyGenerator): void {
|
|
68
|
-
_guardianActionCopyGenerator = gen;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Inject the approval conversation generator (called from lifecycle.ts). */
|
|
72
|
-
export function setApprovalConversationGenerator(gen: ApprovalConversationGenerator): void {
|
|
73
|
-
_approvalConversationGenerator = gen;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
35
|
/** Build a model_info event with fresh config data. */
|
|
77
36
|
function buildModelInfoEvent(): ServerMessage {
|
|
78
37
|
const config = getConfig();
|
|
@@ -306,15 +265,6 @@ export async function drainQueue(session: ProcessSessionContext, reason: QueueDr
|
|
|
306
265
|
log.info({ conversationId: session.conversationId, channelHint: guardianIntent.channelHint }, 'Guardian verification intent intercepted in queue — forcing skill flow');
|
|
307
266
|
agentLoopContent = guardianIntent.rewrittenContent;
|
|
308
267
|
session.preactivatedSkillIds = ['guardian-verify-setup'];
|
|
309
|
-
} else {
|
|
310
|
-
// Guardian invite intent interception — force invite management
|
|
311
|
-
// requests into the trusted-contacts skill flow.
|
|
312
|
-
const inviteIntent = resolveGuardianInviteIntent(resolvedContent);
|
|
313
|
-
if (inviteIntent.kind === 'invite_management') {
|
|
314
|
-
log.info({ conversationId: session.conversationId, action: inviteIntent.action }, 'Guardian invite intent intercepted in queue — forcing skill flow');
|
|
315
|
-
agentLoopContent = inviteIntent.rewrittenContent;
|
|
316
|
-
session.preactivatedSkillIds = ['trusted-contacts'];
|
|
317
|
-
}
|
|
318
268
|
}
|
|
319
269
|
}
|
|
320
270
|
|
|
@@ -404,275 +354,76 @@ export async function processMessage(
|
|
|
404
354
|
await session.ensureActorScopedHistory();
|
|
405
355
|
session.currentActiveSurfaceId = activeSurfaceId;
|
|
406
356
|
session.currentPage = currentPage;
|
|
357
|
+
const trimmedContent = content.trim();
|
|
358
|
+
const canonicalPendingRequestIdsForConversation = trimmedContent.length > 0
|
|
359
|
+
? Array.from(new Set([
|
|
360
|
+
...listPendingCanonicalGuardianRequestsByDestinationConversation(session.conversationId, 'vellum').map((request) => request.id),
|
|
361
|
+
...listCanonicalGuardianRequests({
|
|
362
|
+
status: 'pending',
|
|
363
|
+
conversationId: session.conversationId,
|
|
364
|
+
}).map((request) => request.id),
|
|
365
|
+
]))
|
|
366
|
+
: [];
|
|
367
|
+
|
|
368
|
+
// ── Canonical guardian reply router (desktop/session path) ──
|
|
369
|
+
// Desktop/session guardian replies are canonical-only. Messages consumed
|
|
370
|
+
// by the router never hit the general agent loop.
|
|
371
|
+
if (trimmedContent.length > 0) {
|
|
372
|
+
const routerResult = await routeGuardianReply({
|
|
373
|
+
messageText: trimmedContent,
|
|
374
|
+
channel: 'vellum',
|
|
375
|
+
actor: {
|
|
376
|
+
externalUserId: undefined,
|
|
377
|
+
channel: 'vellum',
|
|
378
|
+
isTrusted: true,
|
|
379
|
+
},
|
|
380
|
+
conversationId: session.conversationId,
|
|
381
|
+
pendingRequestIds: canonicalPendingRequestIdsForConversation,
|
|
382
|
+
// Desktop path: disable NL classification to avoid consuming non-decision
|
|
383
|
+
// messages while a tool confirmation is pending. Deterministic code-prefix
|
|
384
|
+
// and callback parsing remain active.
|
|
385
|
+
approvalConversationGenerator: undefined,
|
|
386
|
+
});
|
|
407
387
|
|
|
408
|
-
|
|
409
|
-
// Deterministic priority matching: pending → follow-up → expired.
|
|
410
|
-
// When the guardian includes an explicit request code, match it across all
|
|
411
|
-
// states in priority order. When only one actionable request exists,
|
|
412
|
-
// auto-match without requiring a code prefix.
|
|
413
|
-
{
|
|
414
|
-
const allPending = getPendingDeliveriesByConversation(session.conversationId);
|
|
415
|
-
const allFollowup = getFollowupDeliveriesByConversation(session.conversationId);
|
|
416
|
-
const allExpired = getExpiredDeliveriesByConversation(session.conversationId);
|
|
417
|
-
const totalActionable = allPending.length + allFollowup.length + allExpired.length;
|
|
418
|
-
|
|
419
|
-
if (totalActionable > 0) {
|
|
388
|
+
if (routerResult.consumed) {
|
|
420
389
|
const guardianIfCtx = session.getTurnInterfaceContext();
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
{ deliveries: allPending, state: 'pending' },
|
|
429
|
-
{ deliveries: allFollowup, state: 'followup' },
|
|
430
|
-
{ deliveries: allExpired, state: 'expired' },
|
|
431
|
-
];
|
|
432
|
-
for (const { deliveries, state } of orderedSets) {
|
|
433
|
-
for (const d of deliveries) {
|
|
434
|
-
const req = getGuardianActionRequest(d.requestId);
|
|
435
|
-
if (req && upperContent.startsWith(req.requestCode)) {
|
|
436
|
-
codeMatch = { delivery: d, request: req, state, answerText: content.slice(req.requestCode.length).trim() };
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (codeMatch) break;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Explicit code targets a non-pending state: handle terminal superseded
|
|
444
|
-
if (codeMatch && codeMatch.state !== 'pending') {
|
|
445
|
-
const targetReq = codeMatch.request;
|
|
446
|
-
if (targetReq.status === 'expired' && targetReq.expiredReason === 'superseded') {
|
|
447
|
-
const callSession = getCallSession(targetReq.callSessionId);
|
|
448
|
-
const callStillActive = callSession && !isTerminalState(callSession.status);
|
|
449
|
-
if (!callStillActive) {
|
|
450
|
-
const userMsg = createUserMessage(content, attachments);
|
|
451
|
-
const persisted = await conversationStore.addMessage(session.conversationId, 'user', JSON.stringify(userMsg.content), guardianChannelMeta);
|
|
452
|
-
session.messages.push(userMsg);
|
|
453
|
-
const staleText = await composeGuardianActionMessageGenerative({ scenario: 'guardian_stale_superseded' }, {}, _guardianActionCopyGenerator);
|
|
454
|
-
const staleMsg = createAssistantMessage(staleText);
|
|
455
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(staleMsg.content), guardianChannelMeta);
|
|
456
|
-
session.messages.push(staleMsg);
|
|
457
|
-
onEvent({ type: 'assistant_text_delta', text: staleText });
|
|
458
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
459
|
-
return persisted.id;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Auto-match: single actionable request across all states
|
|
465
|
-
if (!codeMatch && totalActionable === 1) {
|
|
466
|
-
const singleDelivery = allPending[0] ?? allFollowup[0] ?? allExpired[0];
|
|
467
|
-
const singleReq = getGuardianActionRequest(singleDelivery.requestId);
|
|
468
|
-
if (singleReq) {
|
|
469
|
-
const state: 'pending' | 'followup' | 'expired' = allPending.length === 1 ? 'pending' : allFollowup.length === 1 ? 'followup' : 'expired';
|
|
470
|
-
let text = content;
|
|
471
|
-
if (upperContent.startsWith(singleReq.requestCode)) {
|
|
472
|
-
text = content.slice(singleReq.requestCode.length).trim();
|
|
473
|
-
}
|
|
474
|
-
codeMatch = { delivery: singleDelivery, request: singleReq, state, answerText: text };
|
|
475
|
-
}
|
|
476
|
-
}
|
|
390
|
+
const routerChannelMeta = {
|
|
391
|
+
userMessageChannel: 'vellum' as const,
|
|
392
|
+
assistantMessageChannel: 'vellum' as const,
|
|
393
|
+
userMessageInterface: guardianIfCtx?.userMessageInterface ?? 'vellum',
|
|
394
|
+
assistantMessageInterface: guardianIfCtx?.assistantMessageInterface ?? 'vellum',
|
|
395
|
+
provenanceTrustClass: 'guardian' as const,
|
|
396
|
+
};
|
|
477
397
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
.filter((code): code is string => typeof code === 'string');
|
|
487
|
-
if (!knownCodes.includes(candidateCode)) {
|
|
488
|
-
const userMsg = createUserMessage(content, attachments);
|
|
489
|
-
const persisted = await conversationStore.addMessage(session.conversationId, 'user', JSON.stringify(userMsg.content), guardianChannelMeta);
|
|
490
|
-
session.messages.push(userMsg);
|
|
491
|
-
const unknownText = await composeGuardianActionMessageGenerative({ scenario: 'guardian_unknown_code', unknownCode: candidateCode }, {}, _guardianActionCopyGenerator);
|
|
492
|
-
const unknownMsg = createAssistantMessage(unknownText);
|
|
493
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(unknownMsg.content), guardianChannelMeta);
|
|
494
|
-
session.messages.push(unknownMsg);
|
|
495
|
-
onEvent({ type: 'assistant_text_delta', text: unknownText });
|
|
496
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
497
|
-
return persisted.id;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
398
|
+
const userMsg = createUserMessage(content, attachments);
|
|
399
|
+
const persisted = await conversationStore.addMessage(
|
|
400
|
+
session.conversationId,
|
|
401
|
+
'user',
|
|
402
|
+
JSON.stringify(userMsg.content),
|
|
403
|
+
routerChannelMeta,
|
|
404
|
+
);
|
|
405
|
+
session.messages.push(userMsg);
|
|
501
406
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
session.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
? 'guardian_pending_disambiguation' as const
|
|
513
|
-
: allFollowup.length > 0
|
|
514
|
-
? 'guardian_followup_disambiguation' as const
|
|
515
|
-
: 'guardian_expired_disambiguation' as const;
|
|
516
|
-
const disambiguationText = await composeGuardianActionMessageGenerative(
|
|
517
|
-
{ scenario: disambiguationScenario, requestCodes: codes },
|
|
518
|
-
{ requiredKeywords: codes },
|
|
519
|
-
_guardianActionCopyGenerator,
|
|
520
|
-
);
|
|
521
|
-
const disambiguationMsg = createAssistantMessage(disambiguationText);
|
|
522
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(disambiguationMsg.content), guardianChannelMeta);
|
|
523
|
-
session.messages.push(disambiguationMsg);
|
|
524
|
-
onEvent({ type: 'assistant_text_delta', text: disambiguationText });
|
|
525
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
526
|
-
return persisted.id;
|
|
527
|
-
}
|
|
407
|
+
const replyText = routerResult.replyText
|
|
408
|
+
?? (routerResult.decisionApplied ? 'Decision applied.' : 'Request already resolved.');
|
|
409
|
+
const assistantMsg = createAssistantMessage(replyText);
|
|
410
|
+
await conversationStore.addMessage(
|
|
411
|
+
session.conversationId,
|
|
412
|
+
'assistant',
|
|
413
|
+
JSON.stringify(assistantMsg.content),
|
|
414
|
+
routerChannelMeta,
|
|
415
|
+
);
|
|
416
|
+
session.messages.push(assistantMsg);
|
|
528
417
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const { request, state, answerText } = codeMatch;
|
|
532
|
-
|
|
533
|
-
// PENDING state handler
|
|
534
|
-
if (state === 'pending' && request.status === 'pending') {
|
|
535
|
-
const userMsg = createUserMessage(content, attachments);
|
|
536
|
-
const persisted = await conversationStore.addMessage(session.conversationId, 'user', JSON.stringify(userMsg.content), guardianChannelMeta);
|
|
537
|
-
session.messages.push(userMsg);
|
|
538
|
-
|
|
539
|
-
const answerResult = await answerCall({ callSessionId: request.callSessionId, answer: answerText, pendingQuestionId: request.pendingQuestionId });
|
|
540
|
-
|
|
541
|
-
if ('ok' in answerResult && answerResult.ok) {
|
|
542
|
-
const resolved = resolveGuardianActionRequest(request.id, answerText, 'vellum');
|
|
543
|
-
if (resolved) {
|
|
544
|
-
await tryMintGuardianActionGrant({ request, answerText, decisionChannel: 'vellum', approvalConversationGenerator: _approvalConversationGenerator });
|
|
545
|
-
}
|
|
546
|
-
const replyText = resolved
|
|
547
|
-
? 'Your answer has been relayed to the call.'
|
|
548
|
-
: await composeGuardianActionMessageGenerative({ scenario: 'guardian_stale_answered' }, {}, _guardianActionCopyGenerator);
|
|
549
|
-
const replyMsg = createAssistantMessage(replyText);
|
|
550
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(replyMsg.content), guardianChannelMeta);
|
|
551
|
-
session.messages.push(replyMsg);
|
|
552
|
-
onEvent({ type: 'assistant_text_delta', text: replyText });
|
|
553
|
-
} else {
|
|
554
|
-
const errorDetail = 'error' in answerResult ? answerResult.error : 'Unknown error';
|
|
555
|
-
log.warn({ callSessionId: request.callSessionId, error: errorDetail }, 'answerCall failed for mac guardian answer');
|
|
556
|
-
const failureText = await composeGuardianActionMessageGenerative({ scenario: 'guardian_answer_delivery_failed' }, {}, _guardianActionCopyGenerator);
|
|
557
|
-
const failMsg = createAssistantMessage(failureText);
|
|
558
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(failMsg.content), guardianChannelMeta);
|
|
559
|
-
session.messages.push(failMsg);
|
|
560
|
-
onEvent({ type: 'assistant_text_delta', text: failureText });
|
|
561
|
-
}
|
|
562
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
563
|
-
return persisted.id;
|
|
564
|
-
}
|
|
418
|
+
onEvent({ type: 'assistant_text_delta', text: replyText });
|
|
419
|
+
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
565
420
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
session.messages.push(userMsg);
|
|
571
|
-
|
|
572
|
-
const turnResult = await processGuardianFollowUpTurn(
|
|
573
|
-
{ questionText: request.questionText, lateAnswerText: request.lateAnswerText ?? '', guardianReply: answerText },
|
|
574
|
-
_guardianFollowUpGenerator,
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
let stateApplied = true;
|
|
578
|
-
if (turnResult.disposition === 'call_back' || turnResult.disposition === 'message_back') {
|
|
579
|
-
stateApplied = progressFollowupState(request.id, 'dispatching', turnResult.disposition) !== null;
|
|
580
|
-
} else if (turnResult.disposition === 'decline') {
|
|
581
|
-
stateApplied = finalizeFollowup(request.id, 'declined') !== null;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (!stateApplied) {
|
|
585
|
-
log.warn({ requestId: request.id, disposition: turnResult.disposition }, 'Follow-up state transition failed (already resolved)');
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const replyText = stateApplied
|
|
589
|
-
? turnResult.replyText
|
|
590
|
-
: await composeGuardianActionMessageGenerative({ scenario: 'guardian_stale_followup' }, {}, _guardianActionCopyGenerator);
|
|
591
|
-
const replyMsg = createAssistantMessage(replyText);
|
|
592
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(replyMsg.content), guardianChannelMeta);
|
|
593
|
-
session.messages.push(replyMsg);
|
|
594
|
-
onEvent({ type: 'assistant_text_delta', text: replyText });
|
|
595
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
596
|
-
|
|
597
|
-
if (stateApplied && (turnResult.disposition === 'call_back' || turnResult.disposition === 'message_back')) {
|
|
598
|
-
void (async () => {
|
|
599
|
-
try {
|
|
600
|
-
const execResult = await executeFollowupAction(request.id, turnResult.disposition as 'call_back' | 'message_back', _guardianActionCopyGenerator);
|
|
601
|
-
const completionMsg = createAssistantMessage(execResult.guardianReplyText);
|
|
602
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(completionMsg.content), guardianChannelMeta);
|
|
603
|
-
session.messages.push(completionMsg);
|
|
604
|
-
onEvent({ type: 'assistant_text_delta', text: execResult.guardianReplyText });
|
|
605
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
606
|
-
} catch (execErr) {
|
|
607
|
-
log.error({ err: execErr, requestId: request.id }, 'Follow-up action execution or completion message failed');
|
|
608
|
-
}
|
|
609
|
-
})();
|
|
610
|
-
}
|
|
611
|
-
return persisted.id;
|
|
612
|
-
}
|
|
421
|
+
log.info(
|
|
422
|
+
{ conversationId: session.conversationId, routerType: routerResult.type, requestId: routerResult.requestId },
|
|
423
|
+
'Session guardian reply routed through canonical pipeline',
|
|
424
|
+
);
|
|
613
425
|
|
|
614
|
-
|
|
615
|
-
if (state === 'expired' && request.status === 'expired' && request.followupState === 'none') {
|
|
616
|
-
const userMsg = createUserMessage(content, attachments);
|
|
617
|
-
const persisted = await conversationStore.addMessage(session.conversationId, 'user', JSON.stringify(userMsg.content), guardianChannelMeta);
|
|
618
|
-
session.messages.push(userMsg);
|
|
619
|
-
|
|
620
|
-
// Superseded remap
|
|
621
|
-
if (request.expiredReason === 'superseded') {
|
|
622
|
-
const callSession = getCallSession(request.callSessionId);
|
|
623
|
-
const callStillActive = callSession && !isTerminalState(callSession.status);
|
|
624
|
-
const currentPending = callStillActive ? getPendingRequestByCallSessionId(request.callSessionId) : null;
|
|
625
|
-
|
|
626
|
-
if (callStillActive && currentPending) {
|
|
627
|
-
const currentDeliveries = getDeliveriesByRequestId(currentPending.id);
|
|
628
|
-
const guardianExtUserId = session.guardianContext?.guardianExternalUserId;
|
|
629
|
-
// When guardianExternalUserId is present, verify the sender has a
|
|
630
|
-
// matching delivery on the current pending request. When it's absent
|
|
631
|
-
// (trusted Vellum/HTTP session), allow the remap without delivery check.
|
|
632
|
-
const senderHasDelivery = guardianExtUserId
|
|
633
|
-
? currentDeliveries.some((d) => d.destinationExternalUserId === guardianExtUserId)
|
|
634
|
-
: true;
|
|
635
|
-
if (!senderHasDelivery) {
|
|
636
|
-
log.info({ supersededRequestId: request.id, currentRequestId: currentPending.id, guardianExternalUserId: guardianExtUserId }, 'Superseded remap skipped: sender has no delivery on current pending request');
|
|
637
|
-
} else {
|
|
638
|
-
const remapResult = await answerCall({ callSessionId: currentPending.callSessionId, answer: answerText, pendingQuestionId: currentPending.pendingQuestionId });
|
|
639
|
-
if ('ok' in remapResult && remapResult.ok) {
|
|
640
|
-
const resolved = resolveGuardianActionRequest(currentPending.id, answerText, 'vellum');
|
|
641
|
-
if (resolved) {
|
|
642
|
-
await tryMintGuardianActionGrant({ request: currentPending, answerText, decisionChannel: 'vellum', approvalConversationGenerator: _approvalConversationGenerator });
|
|
643
|
-
}
|
|
644
|
-
const remapText = await composeGuardianActionMessageGenerative({ scenario: 'guardian_superseded_remap', questionText: currentPending.questionText }, {}, _guardianActionCopyGenerator);
|
|
645
|
-
const remapMsg = createAssistantMessage(remapText);
|
|
646
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(remapMsg.content), guardianChannelMeta);
|
|
647
|
-
session.messages.push(remapMsg);
|
|
648
|
-
onEvent({ type: 'assistant_text_delta', text: remapText });
|
|
649
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
650
|
-
log.info({ supersededRequestId: request.id, remappedToRequestId: currentPending.id }, 'Late approval for superseded request remapped to current pending request');
|
|
651
|
-
return persisted.id;
|
|
652
|
-
}
|
|
653
|
-
log.warn({ callSessionId: currentPending.callSessionId, error: 'error' in remapResult ? remapResult.error : 'unknown' }, 'Superseded remap answerCall failed, falling through to follow-up');
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const followupResult = startFollowupFromExpiredRequest(request.id, answerText);
|
|
659
|
-
if (followupResult) {
|
|
660
|
-
const followupText = await composeGuardianActionMessageGenerative({ scenario: 'guardian_late_answer_followup', questionText: request.questionText, lateAnswerText: answerText }, {}, _guardianActionCopyGenerator);
|
|
661
|
-
const replyMsg = createAssistantMessage(followupText);
|
|
662
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(replyMsg.content), guardianChannelMeta);
|
|
663
|
-
session.messages.push(replyMsg);
|
|
664
|
-
onEvent({ type: 'assistant_text_delta', text: followupText });
|
|
665
|
-
} else {
|
|
666
|
-
const staleText = await composeGuardianActionMessageGenerative({ scenario: 'guardian_stale_expired' }, {}, _guardianActionCopyGenerator);
|
|
667
|
-
const staleMsg = createAssistantMessage(staleText);
|
|
668
|
-
await conversationStore.addMessage(session.conversationId, 'assistant', JSON.stringify(staleMsg.content), guardianChannelMeta);
|
|
669
|
-
session.messages.push(staleMsg);
|
|
670
|
-
onEvent({ type: 'assistant_text_delta', text: staleText });
|
|
671
|
-
}
|
|
672
|
-
onEvent({ type: 'message_complete', sessionId: session.conversationId });
|
|
673
|
-
return persisted.id;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
426
|
+
return persisted.id;
|
|
676
427
|
}
|
|
677
428
|
}
|
|
678
429
|
|
|
@@ -759,15 +510,6 @@ export async function processMessage(
|
|
|
759
510
|
log.info({ conversationId: session.conversationId, channelHint: guardianIntent.channelHint }, 'Guardian verification intent intercepted — forcing skill flow');
|
|
760
511
|
agentLoopContent = guardianIntent.rewrittenContent;
|
|
761
512
|
session.preactivatedSkillIds = ['guardian-verify-setup'];
|
|
762
|
-
} else {
|
|
763
|
-
// Guardian invite intent interception — force invite management
|
|
764
|
-
// requests into the trusted-contacts skill flow.
|
|
765
|
-
const inviteIntent = resolveGuardianInviteIntent(resolvedContent);
|
|
766
|
-
if (inviteIntent.kind === 'invite_management') {
|
|
767
|
-
log.info({ conversationId: session.conversationId, action: inviteIntent.action }, 'Guardian invite intent intercepted — forcing skill flow');
|
|
768
|
-
agentLoopContent = inviteIntent.rewrittenContent;
|
|
769
|
-
session.preactivatedSkillIds = ['trusted-contacts'];
|
|
770
|
-
}
|
|
771
513
|
}
|
|
772
514
|
}
|
|
773
515
|
|