@vellumai/assistant 0.3.4 → 0.3.5
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/Dockerfile +2 -0
- package/README.md +37 -2
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +13 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +100 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +70 -1
- package/src/__tests__/call-routes-http.test.ts +27 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -17
- package/src/__tests__/channel-approvals.test.ts +48 -1
- package/src/__tests__/channel-guardian.test.ts +74 -22
- package/src/__tests__/channel-readiness-service.test.ts +257 -0
- package/src/__tests__/config-schema.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/handlers-twilio-config.test.ts +407 -0
- package/src/__tests__/ipc-snapshot.test.ts +63 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +4 -0
- package/src/__tests__/run-orchestrator.test.ts +22 -0
- package/src/__tests__/session-runtime-assembly.test.ts +85 -1
- package/src/__tests__/sms-messaging-provider.test.ts +125 -0
- package/src/__tests__/twilio-routes.test.ts +39 -3
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/calls/call-domain.ts +8 -5
- package/src/calls/call-orchestrator.ts +22 -11
- package/src/calls/twilio-config.ts +17 -11
- package/src/calls/twilio-rest.ts +276 -0
- package/src/calls/twilio-routes.ts +39 -1
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +199 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +320 -0
- package/src/config/bundled-skills/media-processing/services/capability-registry.ts +137 -0
- package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +280 -0
- package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +144 -0
- package/src/config/bundled-skills/media-processing/services/feedback-store.ts +136 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +261 -0
- package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +95 -0
- package/src/config/bundled-skills/media-processing/services/timeline-service.ts +267 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +301 -0
- package/src/config/bundled-skills/media-processing/tools/detect-events.ts +110 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +190 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +166 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +300 -0
- package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +235 -0
- package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +142 -0
- package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +150 -0
- package/src/config/bundled-skills/messaging/SKILL.md +21 -6
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/defaults.ts +2 -1
- package/src/config/schema.ts +9 -3
- package/src/config/system-prompt.ts +24 -0
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/vellum-skills/catalog.json +6 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +118 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/daemon/handlers/config.ts +783 -9
- package/src/daemon/handlers/dictation.ts +182 -0
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +2 -0
- package/src/daemon/handlers/sessions.ts +2 -0
- package/src/daemon/handlers/shared.ts +3 -0
- package/src/daemon/handlers/work-items.ts +15 -7
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +108 -4
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/ride-shotgun-handler.ts +1 -1
- package/src/daemon/server.ts +6 -2
- package/src/daemon/session-agent-loop.ts +5 -1
- package/src/daemon/session-runtime-assembly.ts +55 -0
- package/src/daemon/session-tool-setup.ts +2 -0
- package/src/daemon/session.ts +11 -1
- package/src/inbound/public-ingress-urls.ts +3 -3
- package/src/memory/channel-guardian-store.ts +2 -1
- package/src/memory/db-init.ts +144 -0
- package/src/memory/job-handlers/media-processing.ts +100 -0
- package/src/memory/jobs-store.ts +2 -1
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/media-store.ts +759 -0
- package/src/memory/retriever.ts +6 -1
- package/src/memory/schema.ts +98 -0
- package/src/memory/search/entity.ts +208 -25
- package/src/memory/search/ranking.ts +6 -1
- package/src/memory/search/types.ts +24 -0
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +204 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/permissions/checker.ts +16 -2
- package/src/runtime/approval-message-composer.ts +143 -0
- package/src/runtime/channel-approvals.ts +12 -4
- package/src/runtime/channel-guardian-service.ts +44 -18
- package/src/runtime/channel-readiness-service.ts +292 -0
- package/src/runtime/channel-readiness-types.ts +29 -0
- package/src/runtime/http-server.ts +53 -27
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-routes.ts +67 -21
- package/src/runtime/run-orchestrator.ts +35 -2
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/network/web-search.ts +1 -1
- package/src/tools/types.ts +2 -0
- package/src/twitter/router.ts +1 -1
- package/src/util/platform.ts +35 -0
|
@@ -44,6 +44,8 @@ import type {
|
|
|
44
44
|
MessageProcessor,
|
|
45
45
|
RuntimeAttachmentMetadata,
|
|
46
46
|
} from '../http-types.js';
|
|
47
|
+
import type { GuardianRuntimeContext } from '../../daemon/session-runtime-assembly.js';
|
|
48
|
+
import { composeApprovalMessage } from '../approval-message-composer.js';
|
|
47
49
|
|
|
48
50
|
const log = getLogger('runtime-http');
|
|
49
51
|
|
|
@@ -110,6 +112,19 @@ export interface GuardianContext {
|
|
|
110
112
|
denialReason?: DenialReason;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
function toGuardianRuntimeContext(sourceChannel: string, ctx: GuardianContext): GuardianRuntimeContext {
|
|
116
|
+
return {
|
|
117
|
+
sourceChannel,
|
|
118
|
+
actorRole: ctx.actorRole,
|
|
119
|
+
guardianChatId: ctx.guardianChatId,
|
|
120
|
+
guardianExternalUserId: ctx.guardianExternalUserId,
|
|
121
|
+
requesterIdentifier: ctx.requesterIdentifier,
|
|
122
|
+
requesterExternalUserId: ctx.requesterExternalUserId,
|
|
123
|
+
requesterChatId: ctx.requesterChatId,
|
|
124
|
+
denialReason: ctx.denialReason,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
/** Guardian approval request expiry (30 minutes). */
|
|
114
129
|
const GUARDIAN_APPROVAL_TTL_MS = 30 * 60 * 1000;
|
|
115
130
|
|
|
@@ -138,10 +153,10 @@ function buildGuardianDenyContext(
|
|
|
138
153
|
sourceChannel: string,
|
|
139
154
|
): string {
|
|
140
155
|
if (denialReason === 'no_identity') {
|
|
141
|
-
return `Permission denied:
|
|
156
|
+
return `Permission denied: ${composeApprovalMessage({ scenario: 'guardian_deny_no_identity', toolName, channel: sourceChannel })} Do not retry yet. Ask the user to message from a verifiable direct account/chat, and then retry after identity is available.`;
|
|
142
157
|
}
|
|
143
158
|
|
|
144
|
-
return `Permission denied:
|
|
159
|
+
return `Permission denied: ${composeApprovalMessage({ scenario: 'guardian_deny_no_binding', toolName, channel: sourceChannel })} Do not retry yet. Offer to set up guardian verification. The setup flow will provide a verification token to send as /guardian_verify <token>.`;
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
// ---------------------------------------------------------------------------
|
|
@@ -396,11 +411,13 @@ export async function handleChannelInbound(
|
|
|
396
411
|
token,
|
|
397
412
|
body.senderExternalUserId,
|
|
398
413
|
externalChatId,
|
|
414
|
+
body.senderUsername,
|
|
415
|
+
body.senderName,
|
|
399
416
|
);
|
|
400
417
|
|
|
401
418
|
const replyText = verifyResult.success
|
|
402
|
-
?
|
|
403
|
-
:
|
|
419
|
+
? composeApprovalMessage({ scenario: 'guardian_verify_success' })
|
|
420
|
+
: verifyResult.reason;
|
|
404
421
|
|
|
405
422
|
try {
|
|
406
423
|
await deliverChannelReply(replyCallbackUrl, {
|
|
@@ -427,15 +444,25 @@ export async function handleChannelInbound(
|
|
|
427
444
|
// side-effect controls and their approvals route to the guardian's chat.
|
|
428
445
|
//
|
|
429
446
|
// Guardian actor-role resolution always runs.
|
|
430
|
-
let guardianCtx: GuardianContext
|
|
447
|
+
let guardianCtx: GuardianContext;
|
|
431
448
|
if (body.senderExternalUserId) {
|
|
449
|
+
const requesterLabel = body.senderUsername
|
|
450
|
+
? `@${body.senderUsername}`
|
|
451
|
+
: body.senderExternalUserId;
|
|
432
452
|
const senderIsGuardian = isGuardian(assistantId, sourceChannel, body.senderExternalUserId);
|
|
433
|
-
if (
|
|
453
|
+
if (senderIsGuardian) {
|
|
454
|
+
const binding = getGuardianBinding(assistantId, sourceChannel);
|
|
455
|
+
guardianCtx = {
|
|
456
|
+
actorRole: 'guardian',
|
|
457
|
+
guardianChatId: binding?.guardianDeliveryChatId ?? externalChatId,
|
|
458
|
+
guardianExternalUserId: binding?.guardianExternalUserId ?? body.senderExternalUserId,
|
|
459
|
+
requesterIdentifier: requesterLabel,
|
|
460
|
+
requesterExternalUserId: body.senderExternalUserId,
|
|
461
|
+
requesterChatId: externalChatId,
|
|
462
|
+
};
|
|
463
|
+
} else {
|
|
434
464
|
const binding = getGuardianBinding(assistantId, sourceChannel);
|
|
435
465
|
if (binding) {
|
|
436
|
-
const requesterLabel = body.senderUsername
|
|
437
|
-
? `@${body.senderUsername}`
|
|
438
|
-
: body.senderExternalUserId;
|
|
439
466
|
guardianCtx = {
|
|
440
467
|
actorRole: 'non-guardian',
|
|
441
468
|
guardianChatId: binding.guardianDeliveryChatId,
|
|
@@ -450,6 +477,7 @@ export async function handleChannelInbound(
|
|
|
450
477
|
guardianCtx = {
|
|
451
478
|
actorRole: 'unverified_channel',
|
|
452
479
|
denialReason: 'no_binding',
|
|
480
|
+
requesterIdentifier: requesterLabel,
|
|
453
481
|
requesterExternalUserId: body.senderExternalUserId,
|
|
454
482
|
requesterChatId: externalChatId,
|
|
455
483
|
};
|
|
@@ -462,6 +490,7 @@ export async function handleChannelInbound(
|
|
|
462
490
|
guardianCtx = {
|
|
463
491
|
actorRole: 'unverified_channel',
|
|
464
492
|
denialReason: 'no_identity',
|
|
493
|
+
requesterIdentifier: body.senderUsername ? `@${body.senderUsername}` : undefined,
|
|
465
494
|
requesterExternalUserId: undefined,
|
|
466
495
|
requesterChatId: externalChatId,
|
|
467
496
|
};
|
|
@@ -524,6 +553,7 @@ export async function handleChannelInbound(
|
|
|
524
553
|
senderName: body.senderName,
|
|
525
554
|
senderExternalUserId: body.senderExternalUserId,
|
|
526
555
|
senderUsername: body.senderUsername,
|
|
556
|
+
guardianCtx,
|
|
527
557
|
replyCallbackUrl,
|
|
528
558
|
assistantId,
|
|
529
559
|
});
|
|
@@ -562,6 +592,8 @@ export async function handleChannelInbound(
|
|
|
562
592
|
bearerToken,
|
|
563
593
|
guardianCtx,
|
|
564
594
|
assistantId,
|
|
595
|
+
metadataHints,
|
|
596
|
+
metadataUxBrief,
|
|
565
597
|
});
|
|
566
598
|
} else {
|
|
567
599
|
// Fire-and-forget: process the message and deliver the reply in the background.
|
|
@@ -574,6 +606,7 @@ export async function handleChannelInbound(
|
|
|
574
606
|
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
575
607
|
sourceChannel,
|
|
576
608
|
externalChatId,
|
|
609
|
+
guardianCtx,
|
|
577
610
|
metadataHints,
|
|
578
611
|
metadataUxBrief,
|
|
579
612
|
replyCallbackUrl,
|
|
@@ -598,6 +631,7 @@ interface BackgroundProcessingParams {
|
|
|
598
631
|
attachmentIds?: string[];
|
|
599
632
|
sourceChannel: string;
|
|
600
633
|
externalChatId: string;
|
|
634
|
+
guardianCtx: GuardianContext;
|
|
601
635
|
metadataHints: string[];
|
|
602
636
|
metadataUxBrief?: string;
|
|
603
637
|
replyCallbackUrl?: string;
|
|
@@ -614,6 +648,7 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
614
648
|
attachmentIds,
|
|
615
649
|
sourceChannel,
|
|
616
650
|
externalChatId,
|
|
651
|
+
guardianCtx,
|
|
617
652
|
metadataHints,
|
|
618
653
|
metadataUxBrief,
|
|
619
654
|
replyCallbackUrl,
|
|
@@ -633,6 +668,8 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
633
668
|
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
634
669
|
uxBrief: metadataUxBrief,
|
|
635
670
|
},
|
|
671
|
+
assistantId,
|
|
672
|
+
guardianContext: toGuardianRuntimeContext(sourceChannel, guardianCtx),
|
|
636
673
|
},
|
|
637
674
|
sourceChannel,
|
|
638
675
|
);
|
|
@@ -695,6 +732,8 @@ interface ApprovalProcessingParams {
|
|
|
695
732
|
bearerToken?: string;
|
|
696
733
|
guardianCtx: GuardianContext;
|
|
697
734
|
assistantId: string;
|
|
735
|
+
metadataHints: string[];
|
|
736
|
+
metadataUxBrief?: string;
|
|
698
737
|
}
|
|
699
738
|
|
|
700
739
|
/**
|
|
@@ -722,6 +761,8 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
722
761
|
bearerToken,
|
|
723
762
|
guardianCtx,
|
|
724
763
|
assistantId,
|
|
764
|
+
metadataHints,
|
|
765
|
+
metadataUxBrief,
|
|
725
766
|
} = params;
|
|
726
767
|
|
|
727
768
|
const isNonGuardian = guardianCtx.actorRole === 'non-guardian';
|
|
@@ -736,6 +777,10 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
736
777
|
{
|
|
737
778
|
...((isNonGuardian || isUnverifiedChannel) ? { forceStrictSideEffects: true } : {}),
|
|
738
779
|
sourceChannel,
|
|
780
|
+
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
781
|
+
uxBrief: metadataUxBrief,
|
|
782
|
+
assistantId,
|
|
783
|
+
guardianContext: toGuardianRuntimeContext(sourceChannel, guardianCtx),
|
|
739
784
|
},
|
|
740
785
|
);
|
|
741
786
|
|
|
@@ -826,7 +871,7 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
826
871
|
try {
|
|
827
872
|
await deliverChannelReply(replyCallbackUrl, {
|
|
828
873
|
chatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
829
|
-
text:
|
|
874
|
+
text: composeApprovalMessage({ scenario: 'guardian_delivery_failed', toolName: pending[0].toolName }),
|
|
830
875
|
assistantId,
|
|
831
876
|
}, bearerToken);
|
|
832
877
|
} catch (notifyErr) {
|
|
@@ -839,7 +884,7 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
839
884
|
try {
|
|
840
885
|
await deliverChannelReply(replyCallbackUrl, {
|
|
841
886
|
chatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
842
|
-
text:
|
|
887
|
+
text: composeApprovalMessage({ scenario: 'guardian_request_forwarded', toolName: pending[0].toolName }),
|
|
843
888
|
assistantId,
|
|
844
889
|
}, bearerToken);
|
|
845
890
|
} catch (err) {
|
|
@@ -1066,7 +1111,7 @@ async function handleApprovalInterception(
|
|
|
1066
1111
|
try {
|
|
1067
1112
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1068
1113
|
chatId: externalChatId,
|
|
1069
|
-
text:
|
|
1114
|
+
text: composeApprovalMessage({ scenario: 'guardian_disambiguation', pendingCount: allPending.length }),
|
|
1070
1115
|
assistantId,
|
|
1071
1116
|
}, bearerToken);
|
|
1072
1117
|
} catch (err) {
|
|
@@ -1099,7 +1144,7 @@ async function handleApprovalInterception(
|
|
|
1099
1144
|
try {
|
|
1100
1145
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1101
1146
|
chatId: externalChatId,
|
|
1102
|
-
text:
|
|
1147
|
+
text: composeApprovalMessage({ scenario: 'guardian_identity_mismatch' }),
|
|
1103
1148
|
assistantId,
|
|
1104
1149
|
}, bearerToken);
|
|
1105
1150
|
} catch (err) {
|
|
@@ -1133,10 +1178,11 @@ async function handleApprovalInterception(
|
|
|
1133
1178
|
|
|
1134
1179
|
if (result.applied) {
|
|
1135
1180
|
// Notify the requester's chat about the outcome with the tool name
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
:
|
|
1181
|
+
const outcomeText = composeApprovalMessage({
|
|
1182
|
+
scenario: 'guardian_decision_outcome',
|
|
1183
|
+
decision: decision.action === 'reject' ? 'denied' : 'approved',
|
|
1184
|
+
toolName: guardianApproval.toolName,
|
|
1185
|
+
});
|
|
1140
1186
|
try {
|
|
1141
1187
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1142
1188
|
chatId: guardianApproval.requesterChatId,
|
|
@@ -1242,7 +1288,7 @@ async function handleApprovalInterception(
|
|
|
1242
1288
|
try {
|
|
1243
1289
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1244
1290
|
chatId: externalChatId,
|
|
1245
|
-
text:
|
|
1291
|
+
text: composeApprovalMessage({ scenario: 'request_pending_guardian' }),
|
|
1246
1292
|
assistantId,
|
|
1247
1293
|
}, bearerToken);
|
|
1248
1294
|
} catch (err) {
|
|
@@ -1269,7 +1315,7 @@ async function handleApprovalInterception(
|
|
|
1269
1315
|
try {
|
|
1270
1316
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1271
1317
|
chatId: externalChatId,
|
|
1272
|
-
text:
|
|
1318
|
+
text: composeApprovalMessage({ scenario: 'guardian_expired_requester', toolName: pending[0].toolName }),
|
|
1273
1319
|
assistantId,
|
|
1274
1320
|
}, bearerToken);
|
|
1275
1321
|
} catch (err) {
|
|
@@ -1538,7 +1584,7 @@ export function sweepExpiredGuardianApprovals(
|
|
|
1538
1584
|
// Notify the requester that the approval expired
|
|
1539
1585
|
deliverChannelReply(deliverUrl, {
|
|
1540
1586
|
chatId: approval.requesterChatId,
|
|
1541
|
-
text:
|
|
1587
|
+
text: composeApprovalMessage({ scenario: 'guardian_expired_requester', toolName: approval.toolName }),
|
|
1542
1588
|
assistantId: approval.assistantId,
|
|
1543
1589
|
}, bearerToken).catch((err) => {
|
|
1544
1590
|
log.error({ err, runId: approval.runId }, 'Failed to notify requester of guardian approval expiry');
|
|
@@ -1547,7 +1593,7 @@ export function sweepExpiredGuardianApprovals(
|
|
|
1547
1593
|
// Notify the guardian that the approval expired
|
|
1548
1594
|
deliverChannelReply(deliverUrl, {
|
|
1549
1595
|
chatId: approval.guardianChatId,
|
|
1550
|
-
text:
|
|
1596
|
+
text: composeApprovalMessage({ scenario: 'guardian_expired_guardian', toolName: approval.toolName, requesterIdentifier: approval.requesterExternalUserId }),
|
|
1551
1597
|
assistantId: approval.assistantId,
|
|
1552
1598
|
}, bearerToken).catch((err) => {
|
|
1553
1599
|
log.error({ err, runId: approval.runId }, 'Failed to notify guardian of approval expiry');
|
|
@@ -18,6 +18,7 @@ import type { Run } from '../memory/runs-store.js';
|
|
|
18
18
|
import type { Session } from '../daemon/session.js';
|
|
19
19
|
import type { ServerMessage } from '../daemon/ipc-protocol.js';
|
|
20
20
|
import { resolveChannelCapabilities } from '../daemon/session-runtime-assembly.js';
|
|
21
|
+
import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
21
22
|
import type { UserDecision } from '../permissions/types.js';
|
|
22
23
|
import { checkIngressForSecrets } from '../security/secret-ingress.js';
|
|
23
24
|
import { IngressBlockedError } from '../util/errors.js';
|
|
@@ -37,7 +38,11 @@ interface PendingRunState {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export interface RunOrchestratorDeps {
|
|
40
|
-
getOrCreateSession: (conversationId: string
|
|
41
|
+
getOrCreateSession: (conversationId: string, transport?: {
|
|
42
|
+
channelId: string;
|
|
43
|
+
hints?: string[];
|
|
44
|
+
uxBrief?: string;
|
|
45
|
+
}) => Promise<Session>;
|
|
41
46
|
resolveAttachments: (attachmentIds: string[]) => Array<{
|
|
42
47
|
id: string;
|
|
43
48
|
filename: string;
|
|
@@ -67,6 +72,20 @@ export interface RunStartOptions {
|
|
|
67
72
|
* default 'http-api'.
|
|
68
73
|
*/
|
|
69
74
|
sourceChannel?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Transport hints from sourceMetadata (e.g. reply-context cues).
|
|
77
|
+
* Forwarded to the session so the agent loop can incorporate them.
|
|
78
|
+
*/
|
|
79
|
+
hints?: string[];
|
|
80
|
+
/**
|
|
81
|
+
* Brief UX context from sourceMetadata (e.g. UI surface description).
|
|
82
|
+
* Forwarded to the session so the agent loop can tailor its response.
|
|
83
|
+
*/
|
|
84
|
+
uxBrief?: string;
|
|
85
|
+
/** Assistant scope for multi-assistant channels. */
|
|
86
|
+
assistantId?: string;
|
|
87
|
+
/** Guardian trust/identity context for the inbound actor. */
|
|
88
|
+
guardianContext?: GuardianRuntimeContext;
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
// ---------------------------------------------------------------------------
|
|
@@ -104,7 +123,17 @@ export class RunOrchestrator {
|
|
|
104
123
|
throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
|
|
105
124
|
}
|
|
106
125
|
|
|
107
|
-
|
|
126
|
+
// Build transport metadata when channel context is available so the
|
|
127
|
+
// session receives the same hints/uxBrief as the non-orchestrator path.
|
|
128
|
+
const transport = options?.sourceChannel
|
|
129
|
+
? {
|
|
130
|
+
channelId: options.sourceChannel,
|
|
131
|
+
hints: options.hints,
|
|
132
|
+
uxBrief: options.uxBrief,
|
|
133
|
+
}
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
const session = await this.deps.getOrCreateSession(conversationId, transport);
|
|
108
137
|
|
|
109
138
|
if (session.isProcessing()) {
|
|
110
139
|
throw new Error('Session is already processing a message');
|
|
@@ -121,6 +150,8 @@ export class RunOrchestrator {
|
|
|
121
150
|
...session.memoryPolicy,
|
|
122
151
|
strictSideEffects,
|
|
123
152
|
};
|
|
153
|
+
session.setAssistantId(options?.assistantId ?? 'self');
|
|
154
|
+
session.setGuardianContext(options?.guardianContext ?? null);
|
|
124
155
|
|
|
125
156
|
const attachments = attachmentIds
|
|
126
157
|
? this.deps.resolveAttachments(attachmentIds)
|
|
@@ -201,6 +232,8 @@ export class RunOrchestrator {
|
|
|
201
232
|
// Reset channel capabilities so a subsequent IPC/desktop session on the
|
|
202
233
|
// same conversation is not incorrectly treated as an HTTP-API client.
|
|
203
234
|
session.setChannelCapabilities(null);
|
|
235
|
+
session.setGuardianContext(null);
|
|
236
|
+
session.setAssistantId('self');
|
|
204
237
|
// Reset the session's client callback to a no-op so the stale
|
|
205
238
|
// closure doesn't intercept events from future runs on the same session.
|
|
206
239
|
// Set hasNoClient=true here since the run is done and no HTTP caller
|
|
@@ -41,8 +41,8 @@ function formatBytes(bytes: number): string {
|
|
|
41
41
|
/**
|
|
42
42
|
* Load an attachment row (including base64 data) by its primary key.
|
|
43
43
|
*
|
|
44
|
-
* Not scoped by assistantId because
|
|
45
|
-
*
|
|
44
|
+
* Not scoped by assistantId because attachment access is enforced by
|
|
45
|
+
* conversation visibility checks in execute().
|
|
46
46
|
*/
|
|
47
47
|
function loadAttachmentById(
|
|
48
48
|
attachmentId: string,
|
|
@@ -54,6 +54,7 @@ class CallStartTool implements Tool {
|
|
|
54
54
|
task: input.task as string,
|
|
55
55
|
context: input.context as string | undefined,
|
|
56
56
|
conversationId: context.conversationId,
|
|
57
|
+
assistantId: context.assistantId,
|
|
57
58
|
callerIdentityMode: input.caller_identity_mode as 'assistant_number' | 'user_number' | undefined,
|
|
58
59
|
});
|
|
59
60
|
|
|
@@ -630,7 +630,7 @@ class CredentialStoreTool implements Tool {
|
|
|
630
630
|
const dmChannel = await conversationsOpen(botToken, installingUserId);
|
|
631
631
|
const welcomeMsg =
|
|
632
632
|
`You have installed ${identity.user}, an AI Assistant, on ${identity.team}. ` +
|
|
633
|
-
`
|
|
633
|
+
`You can manage the assistant experience for this workspace by chatting with the assistant or from the Settings page.`;
|
|
634
634
|
await postMessage(botToken, dmChannel.channel.id, welcomeMsg);
|
|
635
635
|
}
|
|
636
636
|
} catch (err) {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ExecutionTarget } from './types.js';
|
|
2
2
|
import { getTool } from './registry.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export interface ManifestOverride {
|
|
5
|
+
risk: 'low' | 'medium' | 'high';
|
|
6
|
+
execution_target: 'host' | 'sandbox';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveExecutionTarget(toolName: string, manifestOverride?: ManifestOverride): ExecutionTarget {
|
|
5
10
|
const tool = getTool(toolName);
|
|
6
11
|
// Manifest-declared execution target is authoritative — check it first so
|
|
7
12
|
// skill tools with host_/computer_use_ prefixes aren't mis-classified.
|
|
@@ -13,6 +18,11 @@ export function resolveExecutionTarget(toolName: string): ExecutionTarget {
|
|
|
13
18
|
if (tool?.executionMode === 'proxy') {
|
|
14
19
|
return 'host';
|
|
15
20
|
}
|
|
21
|
+
// Use manifest metadata for unregistered skill tools so the Permission
|
|
22
|
+
// Simulator shows accurate execution targets instead of defaulting to sandbox.
|
|
23
|
+
if (!tool && manifestOverride) {
|
|
24
|
+
return manifestOverride.execution_target;
|
|
25
|
+
}
|
|
16
26
|
// Prefix heuristics for core tools that don't declare an explicit target.
|
|
17
27
|
if (toolName.startsWith('host_') || toolName.startsWith('computer_use_')) {
|
|
18
28
|
return 'host';
|
|
@@ -268,7 +268,7 @@ class WebSearchTool implements Tool {
|
|
|
268
268
|
apiKey = fallbackKey;
|
|
269
269
|
} else {
|
|
270
270
|
return {
|
|
271
|
-
content: 'Error: No web search API key configured. Set PERPLEXITY_API_KEY or BRAVE_API_KEY environment variable, or configure
|
|
271
|
+
content: 'Error: No web search API key configured. Set a PERPLEXITY_API_KEY or BRAVE_API_KEY environment variable, or configure it from the Settings page under API Keys.',
|
|
272
272
|
isError: true,
|
|
273
273
|
};
|
|
274
274
|
}
|
package/src/tools/types.ts
CHANGED
|
@@ -87,6 +87,8 @@ export interface ToolContext {
|
|
|
87
87
|
workingDir: string;
|
|
88
88
|
sessionId: string;
|
|
89
89
|
conversationId: string;
|
|
90
|
+
/** Logical assistant scope for multi-assistant routing. */
|
|
91
|
+
assistantId?: string;
|
|
90
92
|
/** When set, the tool execution is part of a task run. Used to retrieve ephemeral permission rules. */
|
|
91
93
|
taskRunId?: string;
|
|
92
94
|
/** Per-message request ID for log correlation across session/connection boundaries. */
|
package/src/twitter/router.ts
CHANGED
|
@@ -41,7 +41,7 @@ export async function routedPostTweet(
|
|
|
41
41
|
if (strategy === 'oauth') {
|
|
42
42
|
// User explicitly wants OAuth
|
|
43
43
|
if (!oauthIsAvailable()) {
|
|
44
|
-
throw Object.assign(new Error('OAuth is not configured.
|
|
44
|
+
throw Object.assign(new Error('OAuth is not configured. Provide your X developer credentials here in the chat to set up OAuth, or switch to browser strategy: `vellum x strategy set browser`.'), {
|
|
45
45
|
pathUsed: 'oauth' as const,
|
|
46
46
|
suggestAlternative: 'browser' as const,
|
|
47
47
|
});
|
package/src/util/platform.ts
CHANGED
|
@@ -45,6 +45,41 @@ export function getClipboardCommand(): string | null {
|
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Read and parse the lockfile, trying the primary path (~/.vellum.lock.json)
|
|
50
|
+
* first, then falling back to the legacy path (~/.vellum.lockfile.json).
|
|
51
|
+
* Respects BASE_DATA_DIR for non-standard home directories.
|
|
52
|
+
* Returns null if neither file exists or both are malformed.
|
|
53
|
+
*/
|
|
54
|
+
export function readLockfile(): Record<string, unknown> | null {
|
|
55
|
+
const base = process.env.BASE_DATA_DIR?.trim() || homedir();
|
|
56
|
+
const candidates = [
|
|
57
|
+
join(base, '.vellum.lock.json'),
|
|
58
|
+
join(base, '.vellum.lockfile.json'),
|
|
59
|
+
];
|
|
60
|
+
for (const lockPath of candidates) {
|
|
61
|
+
if (!existsSync(lockPath)) continue;
|
|
62
|
+
try {
|
|
63
|
+
const raw = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
64
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
65
|
+
return raw as Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// malformed JSON — try next
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Write data to the primary lockfile (~/.vellum.lock.json).
|
|
76
|
+
* Respects BASE_DATA_DIR for non-standard home directories.
|
|
77
|
+
*/
|
|
78
|
+
export function writeLockfile(data: Record<string, unknown>): void {
|
|
79
|
+
const base = process.env.BASE_DATA_DIR?.trim() || homedir();
|
|
80
|
+
writeFileSync(join(base, '.vellum.lock.json'), JSON.stringify(data, null, 2) + '\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
48
83
|
/**
|
|
49
84
|
* Returns the root ~/.vellum directory. User-facing files (config, prompt
|
|
50
85
|
* files, skills) and runtime files (socket, PID) live here.
|