@vellumai/assistant 0.4.29 → 0.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +39 -37
- package/README.md +5 -6
- package/docs/runbook-trusted-contacts.md +79 -43
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
- package/scripts/test.sh +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
- package/src/__tests__/actor-token-service.test.ts +4 -3
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +44 -44
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +15 -5
- package/src/__tests__/channel-reply-delivery.test.ts +31 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +4 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +34 -11
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
- package/src/__tests__/integrations-cli.test.ts +3 -27
- package/src/__tests__/intent-routing.test.ts +3 -0
- package/src/__tests__/invite-redemption-service.test.ts +1 -1
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
- package/src/__tests__/ipc-snapshot.test.ts +4 -31
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +3 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +1 -1
- package/src/amazon/client.ts +7 -24
- package/src/calls/relay-server.ts +39 -11
- package/src/channels/config.ts +1 -1
- package/src/cli/integrations.ts +10 -66
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/contacts/SKILL.md +42 -35
- package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +38 -58
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +11 -31
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +19 -37
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
- package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
- package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +89 -2
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-skills/weather/TOOLS.json +4 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/contacts/contact-store.ts +195 -4
- package/src/contacts/types.ts +26 -0
- package/src/daemon/assistant-attachments.ts +23 -3
- package/src/daemon/guardian-verification-intent.ts +7 -4
- package/src/daemon/handlers/apps.ts +1 -2
- package/src/daemon/handlers/config-inbox.ts +16 -134
- package/src/daemon/handlers/guardian-actions.ts +20 -87
- package/src/daemon/handlers/sessions.ts +0 -1
- package/src/daemon/ipc-contract/apps.ts +0 -1
- package/src/daemon/ipc-contract/inbox.ts +7 -66
- package/src/daemon/ipc-contract/sessions.ts +1 -0
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +14 -2
- package/src/daemon/session-agent-loop-handlers.ts +9 -0
- package/src/daemon/session-agent-loop.ts +1 -0
- package/src/daemon/session-attachments.ts +5 -1
- package/src/daemon/session-error.ts +18 -0
- package/src/daemon/session-lifecycle.ts +4 -5
- package/src/daemon/session-media-retry.ts +15 -1
- package/src/daemon/session-surfaces.ts +0 -1
- package/src/daemon/session-tool-setup.ts +7 -4
- package/src/events/domain-events.ts +2 -1
- package/src/home-base/prebuilt/seed.ts +0 -1
- package/src/influencer/client.ts +7 -24
- package/src/media/gemini-image-service.ts +48 -3
- package/src/memory/app-store.ts +0 -4
- package/src/memory/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +12 -0
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/destination-resolver.ts +42 -1
- package/src/notifications/emit-signal.ts +17 -1
- package/src/oauth/provider-profiles.ts +22 -0
- package/src/providers/anthropic/client.ts +3 -0
- package/src/providers/openai/client.ts +3 -0
- package/src/providers/retry.ts +9 -1
- package/src/runtime/actor-trust-resolver.ts +8 -0
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +4 -8
- package/src/runtime/channel-approval-types.ts +18 -0
- package/src/runtime/channel-approvals.ts +8 -0
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +62 -3
- package/src/runtime/gateway-client.ts +36 -2
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-service.ts +127 -0
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +20 -49
- package/src/runtime/invite-redemption-service.ts +1 -1
- package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/routes/approval-routes.ts +1 -40
- package/src/runtime/routes/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +196 -28
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +76 -0
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +222 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/schedule/scheduler.ts +11 -2
- package/src/tools/apps/executors.ts +16 -15
- package/src/tools/calls/call-end.ts +1 -1
- package/src/tools/computer-use/definitions.ts +16 -0
- package/src/tools/credentials/vault.ts +86 -2
- package/src/tools/network/script-proxy/session-manager.ts +28 -3
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/terminal/shell.ts +15 -5
- package/src/tools/tool-approval-handler.ts +48 -4
- package/src/tools/types.ts +38 -1
- package/src/util/errors.ts +5 -1
- package/src/util/retry.ts +21 -0
- package/src/watcher/providers/slack.ts +33 -3
- /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
getCanonicalGuardianRequest,
|
|
4
|
-
isRequestInConversationScope,
|
|
5
|
-
} from "../../memory/canonical-guardian-store.js";
|
|
6
|
-
import type { ApprovalAction } from "../../runtime/channel-approval-types.js";
|
|
1
|
+
import { processGuardianDecision } from "../../runtime/guardian-action-service.js";
|
|
7
2
|
import { resolveLocalIpcTrustContext } from "../../runtime/local-actor-identity.js";
|
|
8
3
|
import { listGuardianDecisionPrompts } from "../../runtime/routes/guardian-action-routes.js";
|
|
9
4
|
import type {
|
|
@@ -12,14 +7,6 @@ import type {
|
|
|
12
7
|
} from "../ipc-protocol.js";
|
|
13
8
|
import { defineHandlers, log } from "./shared.js";
|
|
14
9
|
|
|
15
|
-
const VALID_ACTIONS = new Set<string>([
|
|
16
|
-
"approve_once",
|
|
17
|
-
"approve_10m",
|
|
18
|
-
"approve_thread",
|
|
19
|
-
"approve_always",
|
|
20
|
-
"reject",
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
10
|
export const guardianActionsHandlers = defineHandlers({
|
|
24
11
|
guardian_actions_pending_request: (
|
|
25
12
|
msg: GuardianActionsPendingRequest,
|
|
@@ -43,95 +30,41 @@ export const guardianActionsHandlers = defineHandlers({
|
|
|
43
30
|
ctx,
|
|
44
31
|
) => {
|
|
45
32
|
try {
|
|
46
|
-
// Validate the action is one of the known actions
|
|
47
|
-
if (!VALID_ACTIONS.has(msg.action)) {
|
|
48
|
-
log.warn(
|
|
49
|
-
{ requestId: msg.requestId, action: msg.action },
|
|
50
|
-
"Invalid guardian action",
|
|
51
|
-
);
|
|
52
|
-
ctx.send(socket, {
|
|
53
|
-
type: "guardian_action_decision_response",
|
|
54
|
-
applied: false,
|
|
55
|
-
reason: "invalid_action",
|
|
56
|
-
requestId: msg.requestId,
|
|
57
|
-
});
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Verify conversationId scoping before applying the canonical decision.
|
|
62
|
-
// The decision is allowed when the conversationId matches the request's
|
|
63
|
-
// source conversation OR a recorded delivery destination conversation.
|
|
64
|
-
if (msg.conversationId) {
|
|
65
|
-
const canonicalRequest = getCanonicalGuardianRequest(msg.requestId);
|
|
66
|
-
if (
|
|
67
|
-
canonicalRequest &&
|
|
68
|
-
canonicalRequest.conversationId &&
|
|
69
|
-
!isRequestInConversationScope(
|
|
70
|
-
msg.requestId,
|
|
71
|
-
msg.conversationId,
|
|
72
|
-
"vellum",
|
|
73
|
-
)
|
|
74
|
-
) {
|
|
75
|
-
log.warn(
|
|
76
|
-
{
|
|
77
|
-
requestId: msg.requestId,
|
|
78
|
-
expected: canonicalRequest.conversationId,
|
|
79
|
-
got: msg.conversationId,
|
|
80
|
-
},
|
|
81
|
-
"conversationId not in scope",
|
|
82
|
-
);
|
|
83
|
-
ctx.send(socket, {
|
|
84
|
-
type: "guardian_action_decision_response",
|
|
85
|
-
applied: false,
|
|
86
|
-
reason: "not_found",
|
|
87
|
-
requestId: msg.requestId,
|
|
88
|
-
});
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Resolve the local IPC actor's principal via the vellum guardian binding.
|
|
94
33
|
const localTrustCtx = resolveLocalIpcTrustContext("vellum");
|
|
95
|
-
|
|
34
|
+
|
|
35
|
+
const result = await processGuardianDecision({
|
|
96
36
|
requestId: msg.requestId,
|
|
97
|
-
action: msg.action
|
|
37
|
+
action: msg.action,
|
|
38
|
+
conversationId: msg.conversationId,
|
|
39
|
+
channel: "vellum",
|
|
98
40
|
actorContext: {
|
|
99
41
|
externalUserId: localTrustCtx.guardianExternalUserId,
|
|
100
|
-
channel: "vellum",
|
|
101
42
|
guardianPrincipalId: localTrustCtx.guardianPrincipalId ?? undefined,
|
|
102
43
|
},
|
|
103
|
-
userText: undefined,
|
|
104
44
|
});
|
|
105
45
|
|
|
106
|
-
if (
|
|
107
|
-
// When the CAS committed but the resolver failed, the side effect
|
|
108
|
-
// (e.g. minting a verification session) did not happen. From the
|
|
109
|
-
// caller's perspective the decision was not truly applied.
|
|
110
|
-
if (canonicalResult.resolverFailed) {
|
|
111
|
-
ctx.send(socket, {
|
|
112
|
-
type: "guardian_action_decision_response",
|
|
113
|
-
applied: false,
|
|
114
|
-
reason: "resolver_failed",
|
|
115
|
-
resolverFailureReason: canonicalResult.resolverFailureReason,
|
|
116
|
-
requestId: canonicalResult.requestId,
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
46
|
+
if (!result.ok) {
|
|
121
47
|
ctx.send(socket, {
|
|
122
48
|
type: "guardian_action_decision_response",
|
|
123
|
-
applied:
|
|
124
|
-
|
|
49
|
+
applied: false,
|
|
50
|
+
reason: result.error,
|
|
51
|
+
requestId: msg.requestId,
|
|
125
52
|
});
|
|
126
53
|
return;
|
|
127
54
|
}
|
|
128
55
|
|
|
129
|
-
// Return the reason for failure (stale, expired, not_found, etc.)
|
|
130
56
|
ctx.send(socket, {
|
|
131
57
|
type: "guardian_action_decision_response",
|
|
132
|
-
applied:
|
|
133
|
-
|
|
134
|
-
|
|
58
|
+
applied: result.applied,
|
|
59
|
+
...(result.applied
|
|
60
|
+
? { requestId: result.requestId }
|
|
61
|
+
: {
|
|
62
|
+
reason: result.reason,
|
|
63
|
+
...(result.resolverFailureReason
|
|
64
|
+
? { resolverFailureReason: result.resolverFailureReason }
|
|
65
|
+
: {}),
|
|
66
|
+
requestId: result.requestId ?? msg.requestId,
|
|
67
|
+
}),
|
|
135
68
|
});
|
|
136
69
|
} catch (err) {
|
|
137
70
|
log.error(
|
|
@@ -1648,7 +1648,6 @@ export function handleHistoryRequest(
|
|
|
1648
1648
|
? {
|
|
1649
1649
|
...(s.data.preview ? { preview: s.data.preview } : {}),
|
|
1650
1650
|
...(s.data.appId ? { appId: s.data.appId } : {}),
|
|
1651
|
-
...(s.data.appType ? { appType: s.data.appType } : {}),
|
|
1652
1651
|
}
|
|
1653
1652
|
: {}),
|
|
1654
1653
|
} as Record<string, unknown>,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Contacts access control: invite management, member management, and escalation decisions.
|
|
2
2
|
|
|
3
3
|
// === Client → Server ===
|
|
4
4
|
|
|
5
|
-
export interface
|
|
6
|
-
type: "
|
|
5
|
+
export interface ContactsInviteRequest {
|
|
6
|
+
type: "contacts_invite";
|
|
7
7
|
action: "create" | "list" | "revoke" | "redeem";
|
|
8
8
|
/** Source channel for the invite (required for create and redeem). */
|
|
9
9
|
sourceChannel?: string;
|
|
@@ -29,31 +29,6 @@ export interface IngressInviteRequest {
|
|
|
29
29
|
guardianName?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export interface IngressContactRequest {
|
|
33
|
-
type: "ingress_member"; // Legacy discriminator — kept for client compatibility
|
|
34
|
-
action: "list" | "upsert" | "revoke" | "block";
|
|
35
|
-
/** Assistant ID for scoping member operations (defaults to 'self'). */
|
|
36
|
-
assistantId?: string;
|
|
37
|
-
/** Source channel (required for upsert, optional filter for list). */
|
|
38
|
-
sourceChannel?: string;
|
|
39
|
-
/** External user ID (upsert only). */
|
|
40
|
-
externalUserId?: string;
|
|
41
|
-
/** External chat ID (upsert only). */
|
|
42
|
-
externalChatId?: string;
|
|
43
|
-
/** Display name (upsert only). */
|
|
44
|
-
displayName?: string;
|
|
45
|
-
/** Username (upsert only). */
|
|
46
|
-
username?: string;
|
|
47
|
-
/** Access policy (upsert only). */
|
|
48
|
-
policy?: "allow" | "deny" | "escalate";
|
|
49
|
-
/** Member status (upsert only for setting, list only for filtering). */
|
|
50
|
-
status?: "pending" | "active";
|
|
51
|
-
/** Member ID (revoke and block only). */
|
|
52
|
-
memberId?: string;
|
|
53
|
-
/** Reason for revoke or block (revoke and block only). */
|
|
54
|
-
reason?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
32
|
export interface AssistantInboxEscalationRequest {
|
|
58
33
|
type: "assistant_inbox_escalation";
|
|
59
34
|
action: "list" | "decide";
|
|
@@ -71,8 +46,8 @@ export interface AssistantInboxEscalationRequest {
|
|
|
71
46
|
|
|
72
47
|
// === Server → Client ===
|
|
73
48
|
|
|
74
|
-
export interface
|
|
75
|
-
type: "
|
|
49
|
+
export interface ContactsInviteResponse {
|
|
50
|
+
type: "contacts_invite_response";
|
|
76
51
|
success: boolean;
|
|
77
52
|
error?: string;
|
|
78
53
|
/** Single invite (returned on create/revoke). Token field is only present on create. */
|
|
@@ -102,38 +77,6 @@ export interface IngressInviteResponse {
|
|
|
102
77
|
}>;
|
|
103
78
|
}
|
|
104
79
|
|
|
105
|
-
export interface IngressContactResponse {
|
|
106
|
-
type: "ingress_member_response"; // Legacy discriminator — kept for client compatibility
|
|
107
|
-
success: boolean;
|
|
108
|
-
error?: string;
|
|
109
|
-
/** Single member (returned on upsert/revoke/block). */
|
|
110
|
-
member?: {
|
|
111
|
-
id: string;
|
|
112
|
-
sourceChannel: string;
|
|
113
|
-
externalUserId?: string;
|
|
114
|
-
externalChatId?: string;
|
|
115
|
-
displayName?: string;
|
|
116
|
-
username?: string;
|
|
117
|
-
status: string;
|
|
118
|
-
policy: string;
|
|
119
|
-
lastSeenAt?: number;
|
|
120
|
-
createdAt: number;
|
|
121
|
-
};
|
|
122
|
-
/** List of members (returned on list). */
|
|
123
|
-
members?: Array<{
|
|
124
|
-
id: string;
|
|
125
|
-
sourceChannel: string;
|
|
126
|
-
externalUserId?: string;
|
|
127
|
-
externalChatId?: string;
|
|
128
|
-
displayName?: string;
|
|
129
|
-
username?: string;
|
|
130
|
-
status: string;
|
|
131
|
-
policy: string;
|
|
132
|
-
lastSeenAt?: number;
|
|
133
|
-
createdAt: number;
|
|
134
|
-
}>;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
80
|
export interface AssistantInboxEscalationResponse {
|
|
138
81
|
type: "assistant_inbox_escalation_response";
|
|
139
82
|
success: boolean;
|
|
@@ -161,11 +104,9 @@ export interface AssistantInboxEscalationResponse {
|
|
|
161
104
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
162
105
|
|
|
163
106
|
export type _InboxClientMessages =
|
|
164
|
-
|
|
|
165
|
-
| IngressContactRequest
|
|
107
|
+
| ContactsInviteRequest
|
|
166
108
|
| AssistantInboxEscalationRequest;
|
|
167
109
|
|
|
168
110
|
export type _InboxServerMessages =
|
|
169
|
-
|
|
|
170
|
-
| IngressContactResponse
|
|
111
|
+
| ContactsInviteResponse
|
|
171
112
|
| AssistantInboxEscalationResponse;
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"cancel",
|
|
70
70
|
"confirmation_response",
|
|
71
71
|
"contacts",
|
|
72
|
+
"contacts_invite",
|
|
72
73
|
"conversation_search",
|
|
73
74
|
"conversation_seen_signal",
|
|
74
75
|
"cu_observation",
|
|
@@ -99,8 +100,6 @@
|
|
|
99
100
|
"identity_get",
|
|
100
101
|
"image_gen_model_set",
|
|
101
102
|
"ingress_config",
|
|
102
|
-
"ingress_invite",
|
|
103
|
-
"ingress_member",
|
|
104
103
|
"integration_connect",
|
|
105
104
|
"integration_disconnect",
|
|
106
105
|
"integration_list",
|
|
@@ -215,6 +214,7 @@
|
|
|
215
214
|
"confirmation_request",
|
|
216
215
|
"confirmation_state_changed",
|
|
217
216
|
"contacts_changed",
|
|
217
|
+
"contacts_invite_response",
|
|
218
218
|
"contacts_response",
|
|
219
219
|
"context_compacted",
|
|
220
220
|
"conversation_search_response",
|
|
@@ -252,8 +252,6 @@
|
|
|
252
252
|
"identity_changed",
|
|
253
253
|
"identity_get_response",
|
|
254
254
|
"ingress_config_response",
|
|
255
|
-
"ingress_invite_response",
|
|
256
|
-
"ingress_member_response",
|
|
257
255
|
"integration_connect_result",
|
|
258
256
|
"integration_list_response",
|
|
259
257
|
"ipc_blob_probe_result",
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -312,8 +312,20 @@ export async function runDaemon(): Promise<void> {
|
|
|
312
312
|
registerBroadcastFn((msg) => server.broadcast(msg));
|
|
313
313
|
|
|
314
314
|
const scheduler = startScheduler(
|
|
315
|
-
async (conversationId, message) => {
|
|
316
|
-
await server.processMessage(
|
|
315
|
+
async (conversationId, message, options) => {
|
|
316
|
+
await server.processMessage(
|
|
317
|
+
conversationId,
|
|
318
|
+
message,
|
|
319
|
+
undefined,
|
|
320
|
+
options?.trustClass
|
|
321
|
+
? {
|
|
322
|
+
trustContext: {
|
|
323
|
+
sourceChannel: "vellum",
|
|
324
|
+
trustClass: options.trustClass,
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
: undefined,
|
|
328
|
+
);
|
|
317
329
|
},
|
|
318
330
|
(reminder) => {
|
|
319
331
|
void emitNotificationSignal({
|
|
@@ -56,6 +56,8 @@ export interface EventHandlerState {
|
|
|
56
56
|
readonly persistedToolUseIds: Set<string>;
|
|
57
57
|
readonly accumulatedDirectives: DirectiveRequest[];
|
|
58
58
|
readonly accumulatedToolContentBlocks: ContentBlock[];
|
|
59
|
+
/** Maps index in accumulatedToolContentBlocks → tool name that produced it. */
|
|
60
|
+
readonly toolContentBlockToolNames: Map<number, string>;
|
|
59
61
|
readonly directiveWarnings: string[];
|
|
60
62
|
readonly toolUseIdToName: Map<string, string>;
|
|
61
63
|
currentTurnToolNames: string[];
|
|
@@ -99,6 +101,7 @@ export function createEventHandlerState(): EventHandlerState {
|
|
|
99
101
|
persistedToolUseIds: new Set(),
|
|
100
102
|
accumulatedDirectives: [],
|
|
101
103
|
accumulatedToolContentBlocks: [],
|
|
104
|
+
toolContentBlockToolNames: new Map(),
|
|
102
105
|
directiveWarnings: [],
|
|
103
106
|
toolUseIdToName: new Map(),
|
|
104
107
|
currentTurnToolNames: [],
|
|
@@ -400,6 +403,12 @@ export function handleToolResult(
|
|
|
400
403
|
for (const cb of event.contentBlocks) {
|
|
401
404
|
if (cb.type === "image" || cb.type === "file") {
|
|
402
405
|
state.accumulatedToolContentBlocks.push(cb);
|
|
406
|
+
if (toolName) {
|
|
407
|
+
state.toolContentBlockToolNames.set(
|
|
408
|
+
state.accumulatedToolContentBlocks.length - 1,
|
|
409
|
+
toolName,
|
|
410
|
+
);
|
|
411
|
+
}
|
|
403
412
|
}
|
|
404
413
|
}
|
|
405
414
|
}
|
|
@@ -124,6 +124,7 @@ export async function resolveAssistantAttachments(
|
|
|
124
124
|
workingDir: string,
|
|
125
125
|
approveHostRead: ApproveHostRead,
|
|
126
126
|
lastAssistantMessageId: string | undefined,
|
|
127
|
+
toolContentBlockToolNames?: ReadonlyMap<number, string>,
|
|
127
128
|
): Promise<AttachmentResolutionResult> {
|
|
128
129
|
let assistantAttachments: AssistantAttachmentDraft[] = [];
|
|
129
130
|
const emittedAttachments: UserMessageAttachment[] = [];
|
|
@@ -170,7 +171,10 @@ export async function resolveAssistantAttachments(
|
|
|
170
171
|
"Directive resolution complete",
|
|
171
172
|
);
|
|
172
173
|
|
|
173
|
-
const toolDrafts = contentBlocksToDrafts(
|
|
174
|
+
const toolDrafts = contentBlocksToDrafts(
|
|
175
|
+
accumulatedToolContentBlocks,
|
|
176
|
+
toolContentBlockToolNames,
|
|
177
|
+
);
|
|
174
178
|
// Most recent tool outputs (e.g., final browser screenshot) should win
|
|
175
179
|
// the MAX_ASSISTANT_ATTACHMENTS cap over older intermediate screenshots.
|
|
176
180
|
toolDrafts.reverse();
|
|
@@ -188,6 +188,24 @@ function classifyCore(
|
|
|
188
188
|
retryable: false,
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
+
if (/credit balance is too low|insufficient.*credits?/i.test(message)) {
|
|
192
|
+
return {
|
|
193
|
+
code: "PROVIDER_BILLING",
|
|
194
|
+
userMessage: "Your API key has insufficient credits.",
|
|
195
|
+
retryable: false,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
/invalid.*api.?key|invalid.*x-api-key|authentication.?error|invalid.authentication/i.test(
|
|
200
|
+
message,
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
return {
|
|
204
|
+
code: "PROVIDER_BILLING",
|
|
205
|
+
userMessage: "Your API key is invalid.",
|
|
206
|
+
retryable: false,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
191
209
|
return {
|
|
192
210
|
code: "PROVIDER_API",
|
|
193
211
|
userMessage: "The AI provider rejected the request.",
|
|
@@ -13,7 +13,10 @@ import * as conversationStore from "../memory/conversation-store.js";
|
|
|
13
13
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
14
14
|
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
15
15
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
16
|
-
import
|
|
16
|
+
import {
|
|
17
|
+
isUntrustedTrustClass,
|
|
18
|
+
type TrustClass,
|
|
19
|
+
} from "../runtime/actor-trust-resolver.js";
|
|
17
20
|
import { unregisterSessionSender } from "../tools/browser/browser-screencast.js";
|
|
18
21
|
import { getLogger } from "../util/logger.js";
|
|
19
22
|
import { repairHistory } from "./history-repair.js";
|
|
@@ -47,10 +50,6 @@ function parseProvenanceTrustClass(
|
|
|
47
50
|
return undefined;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function isUntrustedTrustClass(trustClass: TrustClass | undefined): boolean {
|
|
51
|
-
return trustClass === "trusted_contact" || trustClass === "unknown";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
53
|
function filterMessagesForUntrustedActor(
|
|
55
54
|
messages: conversationStore.MessageRow[],
|
|
56
55
|
): conversationStore.MessageRow[] {
|
|
@@ -19,15 +19,29 @@ export function stripMediaPayloadsForRetry(messages: Message[]): {
|
|
|
19
19
|
latestUserIndex: number | null;
|
|
20
20
|
} {
|
|
21
21
|
let latestUserIndex: number | null = null;
|
|
22
|
+
let lastSummaryUserIndex: number | null = null;
|
|
22
23
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
23
24
|
const msg = messages[i];
|
|
24
25
|
if (msg.role !== "user") continue;
|
|
25
|
-
if (getSummaryFromContextMessage(msg) != null) continue;
|
|
26
26
|
if (isToolResultOnlyMessage(msg)) continue;
|
|
27
|
+
if (getSummaryFromContextMessage(msg) != null) {
|
|
28
|
+
// Track the last summary message as a fallback — after aggressive
|
|
29
|
+
// compaction (minKeepRecentUserTurns: 0), the summary may be the only
|
|
30
|
+
// user message left and it can contain preserved image blocks that
|
|
31
|
+
// should not be stripped.
|
|
32
|
+
if (lastSummaryUserIndex == null) lastSummaryUserIndex = i;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
27
35
|
latestUserIndex = i;
|
|
28
36
|
break;
|
|
29
37
|
}
|
|
30
38
|
|
|
39
|
+
// Fall back to the summary message when compaction consumed all user turns,
|
|
40
|
+
// so images preserved by compaction are not unconditionally stripped.
|
|
41
|
+
if (latestUserIndex == null && lastSummaryUserIndex != null) {
|
|
42
|
+
latestUserIndex = lastSummaryUserIndex;
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
let modified = false;
|
|
32
46
|
let replacedBlocks = 0;
|
|
33
47
|
let keptLatestMediaBlocks = 0;
|
|
@@ -7,10 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { isHttpAuthDisabled } from "../config/env.js";
|
|
10
|
-
import
|
|
11
|
-
ProxyApprovalCallback,
|
|
12
|
-
ProxyApprovalRequest,
|
|
13
|
-
} from "../outbound-proxy/index.js";
|
|
10
|
+
import { getBindingByConversation } from "../memory/external-conversation-store.js";
|
|
14
11
|
import {
|
|
15
12
|
generateAllowlistOptions,
|
|
16
13
|
generateScopeOptions,
|
|
@@ -32,6 +29,8 @@ import { requestComputerControlTool } from "../tools/computer-use/request-comput
|
|
|
32
29
|
import type { ToolExecutor } from "../tools/executor.js";
|
|
33
30
|
import { getAllToolDefinitions } from "../tools/registry.js";
|
|
34
31
|
import type {
|
|
32
|
+
ProxyApprovalCallback,
|
|
33
|
+
ProxyApprovalRequest,
|
|
35
34
|
ToolExecutionResult,
|
|
36
35
|
ToolLifecycleEventHandler,
|
|
37
36
|
} from "../tools/types.js";
|
|
@@ -163,6 +162,10 @@ export function createToolExecutor(
|
|
|
163
162
|
ctx.surfaceActionRequestIds?.has(ctx.currentRequestId ?? "") ?? false,
|
|
164
163
|
requesterExternalUserId: ctx.trustContext?.requesterExternalUserId,
|
|
165
164
|
requesterChatId: ctx.trustContext?.requesterChatId,
|
|
165
|
+
channelPermissionChannelId:
|
|
166
|
+
ctx.trustContext?.sourceChannel === "slack"
|
|
167
|
+
? getBindingByConversation(ctx.conversationId)?.externalChatId
|
|
168
|
+
: undefined,
|
|
166
169
|
onOutput,
|
|
167
170
|
signal: ctx.abortController?.signal,
|
|
168
171
|
sandboxOverride: ctx.sandboxOverride,
|
package/src/influencer/client.ts
CHANGED
|
@@ -50,11 +50,8 @@ import type {
|
|
|
50
50
|
ExtensionResponse,
|
|
51
51
|
} from "../browser-extension-relay/protocol.js";
|
|
52
52
|
import { extensionRelayServer } from "../browser-extension-relay/server.js";
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
isSigningKeyInitialized,
|
|
56
|
-
mintEdgeRelayToken,
|
|
57
|
-
} from "../runtime/auth/token-service.js";
|
|
53
|
+
import { isSigningKeyInitialized } from "../runtime/auth/token-service.js";
|
|
54
|
+
import { gatewayPost } from "../runtime/gateway-internal-client.js";
|
|
58
55
|
|
|
59
56
|
// ---------------------------------------------------------------------------
|
|
60
57
|
// Types
|
|
@@ -145,26 +142,12 @@ async function sendRelayCommand(
|
|
|
145
142
|
"Auth signing key not initialized — browser-relay commands require the daemon to be running",
|
|
146
143
|
);
|
|
147
144
|
}
|
|
148
|
-
const token = mintEdgeRelayToken();
|
|
149
|
-
|
|
150
|
-
const resp = await fetch(
|
|
151
|
-
`${getGatewayInternalBaseUrl()}/v1/browser-relay/command`,
|
|
152
|
-
{
|
|
153
|
-
method: "POST",
|
|
154
|
-
headers: {
|
|
155
|
-
"Content-Type": "application/json",
|
|
156
|
-
Authorization: `Bearer ${token}`,
|
|
157
|
-
},
|
|
158
|
-
body: JSON.stringify(command),
|
|
159
|
-
},
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
if (!resp.ok) {
|
|
163
|
-
const body = await resp.text();
|
|
164
|
-
throw new Error(`Relay HTTP command failed (${resp.status}): ${body}`);
|
|
165
|
-
}
|
|
166
145
|
|
|
167
|
-
|
|
146
|
+
const { data } = await gatewayPost<ExtensionResponse>(
|
|
147
|
+
"/v1/browser-relay/command",
|
|
148
|
+
command,
|
|
149
|
+
);
|
|
150
|
+
return data;
|
|
168
151
|
}
|
|
169
152
|
|
|
170
153
|
// ---------------------------------------------------------------------------
|
|
@@ -16,6 +16,8 @@ export interface ImageGenerationRequest {
|
|
|
16
16
|
export interface GeneratedImage {
|
|
17
17
|
mimeType: string;
|
|
18
18
|
dataBase64: string;
|
|
19
|
+
/** Short title derived from the model's text response, if available. */
|
|
20
|
+
title?: string;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface ImageGenerationResult {
|
|
@@ -74,10 +76,12 @@ export async function generateImage(
|
|
|
74
76
|
|
|
75
77
|
const client = new GoogleGenAI({ apiKey });
|
|
76
78
|
|
|
77
|
-
// Build contents array
|
|
79
|
+
// Build contents array — append a title request so the model's text
|
|
80
|
+
// response contains a short filename-safe title for the generated image.
|
|
81
|
+
const promptWithTitle = `${request.prompt}\n\nAlso respond with a short title (max 6 words) for the image on its own line, prefixed with "Title: ".`;
|
|
78
82
|
const parts: Array<
|
|
79
83
|
{ text: string } | { inlineData: { mimeType: string; data: string } }
|
|
80
|
-
> = [{ text:
|
|
84
|
+
> = [{ text: promptWithTitle }];
|
|
81
85
|
|
|
82
86
|
if (request.mode === "edit" && request.sourceImages) {
|
|
83
87
|
for (const img of request.sourceImages) {
|
|
@@ -114,7 +118,15 @@ export async function generateImage(
|
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
// Extract title from the text response and apply to images
|
|
122
|
+
const title = extractTitle(text);
|
|
123
|
+
if (title) {
|
|
124
|
+
for (const img of images) {
|
|
125
|
+
img.title = title;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { images, text: stripTitleLine(text), title };
|
|
118
130
|
};
|
|
119
131
|
|
|
120
132
|
if (variants === 1) {
|
|
@@ -141,3 +153,36 @@ export async function generateImage(
|
|
|
141
153
|
|
|
142
154
|
return { images: allImages, text: combinedText, resolvedModel: model };
|
|
143
155
|
}
|
|
156
|
+
|
|
157
|
+
// --- Title extraction helpers ---
|
|
158
|
+
|
|
159
|
+
const TITLE_RE = /^Title:\s*(.+)/im;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Extract a title from the model's text response.
|
|
163
|
+
* Looks for a line starting with "Title: " and sanitizes it for use as a filename.
|
|
164
|
+
*/
|
|
165
|
+
function extractTitle(text?: string): string | undefined {
|
|
166
|
+
if (!text) return undefined;
|
|
167
|
+
const match = TITLE_RE.exec(text);
|
|
168
|
+
if (!match?.[1]) return undefined;
|
|
169
|
+
return match[1]
|
|
170
|
+
.trim()
|
|
171
|
+
.replace(/[^\w\s-]/g, "")
|
|
172
|
+
.replace(/\s+/g, "-")
|
|
173
|
+
.toLowerCase()
|
|
174
|
+
.slice(0, 60);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Remove the "Title: ..." line from text so it doesn't appear in
|
|
179
|
+
* the tool result content shown to the user.
|
|
180
|
+
*/
|
|
181
|
+
function stripTitleLine(text?: string): string | undefined {
|
|
182
|
+
if (!text) return undefined;
|
|
183
|
+
const stripped = text
|
|
184
|
+
.replace(TITLE_RE, "")
|
|
185
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
186
|
+
.trim();
|
|
187
|
+
return stripped.length > 0 ? stripped : undefined;
|
|
188
|
+
}
|