@vellumai/assistant 0.10.3 → 0.10.4-staging.1
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/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -33,13 +33,12 @@ function makeContact(
|
|
|
33
33
|
id: "ct_1",
|
|
34
34
|
displayName: "Alice",
|
|
35
35
|
notes: null,
|
|
36
|
+
role: "contact",
|
|
36
37
|
lastInteraction: null,
|
|
37
38
|
interactionCount: 0,
|
|
38
39
|
createdAt: 1,
|
|
39
40
|
updatedAt: 1,
|
|
40
|
-
role: "contact",
|
|
41
41
|
contactType: "human",
|
|
42
|
-
principalId: null,
|
|
43
42
|
userFile: null,
|
|
44
43
|
channels: [],
|
|
45
44
|
...overrides,
|
|
@@ -66,8 +66,9 @@ mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
|
66
66
|
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
67
67
|
}));
|
|
68
68
|
|
|
69
|
+
// Member ACL rides on memberRecord via the member-verdict cache; no local
|
|
70
|
+
// contact here.
|
|
69
71
|
mock.module("../../../contacts/contact-store.js", () => ({
|
|
70
|
-
findGuardianForChannel: (_channelType: string) => null,
|
|
71
72
|
findContactByAddress: () => null,
|
|
72
73
|
}));
|
|
73
74
|
|
|
@@ -78,15 +78,24 @@ function withChannelCompat<T extends { channels: { address: string }[] }>(
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/** Compose both response transforms (guardian display name + channel compat).
|
|
81
|
+
/** Compose both response transforms (guardian display name + channel compat).
|
|
82
|
+
* Also coerces nullable gateway-sourced fields to their DB defaults so the
|
|
83
|
+
* response satisfies the strict enum schema even in degraded mode (assistant
|
|
84
|
+
* DB unreachable → gateway soft-fail join produces nulls).
|
|
85
|
+
*/
|
|
82
86
|
function prepareContactResponse<
|
|
83
87
|
T extends {
|
|
84
88
|
role: string;
|
|
85
89
|
displayName: string;
|
|
90
|
+
contactType?: string | null;
|
|
86
91
|
channels: { address: string }[];
|
|
87
92
|
},
|
|
88
93
|
>(contact: T): T {
|
|
89
|
-
|
|
94
|
+
const coerced =
|
|
95
|
+
contact.contactType == null
|
|
96
|
+
? { ...contact, contactType: "human" as T["contactType"] }
|
|
97
|
+
: contact;
|
|
98
|
+
return withChannelCompat(withGuardianNameOverride(coerced));
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
const VALID_CONTACT_TYPES: readonly ContactType[] = ["human", "assistant"];
|
|
@@ -99,6 +108,13 @@ function isContactType(value: string): value is ContactType {
|
|
|
99
108
|
// Response schemas (drive OpenAPI spec → codegen → typed SDK)
|
|
100
109
|
// ---------------------------------------------------------------------------
|
|
101
110
|
|
|
111
|
+
// Channel ACL fields (status/policy/verifiedAt/verifiedVia/revokedReason/
|
|
112
|
+
// blockedReason) are gateway-owned and present ONLY on gateway-relayed reads
|
|
113
|
+
// (`contacts_list_rich`/`contacts_get_rich`). Daemon-native filtered reads
|
|
114
|
+
// (search / contactType) omit them, so they are `.optional()`. Contact-level
|
|
115
|
+
// `role` is stored locally (NOT NULL, default "contact") and returned on all
|
|
116
|
+
// paths. INFO telemetry (lastSeenAt/interactionCount/lastInteraction) is
|
|
117
|
+
// locally hydrated on every read path and stays required.
|
|
102
118
|
const contactChannelSchema = z.object({
|
|
103
119
|
id: z.string(),
|
|
104
120
|
contactId: z.string(),
|
|
@@ -107,23 +123,23 @@ const contactChannelSchema = z.object({
|
|
|
107
123
|
isPrimary: z.boolean(),
|
|
108
124
|
/** @deprecated Echoes `address` for backwards compatibility with older macOS clients. */
|
|
109
125
|
externalUserId: z.string().nullable(),
|
|
110
|
-
status: z.string(),
|
|
111
|
-
policy: z.string(),
|
|
112
|
-
verifiedAt: z.number().nullable(),
|
|
113
|
-
verifiedVia: z.string().nullable(),
|
|
126
|
+
status: z.string().optional(),
|
|
127
|
+
policy: z.string().optional(),
|
|
128
|
+
verifiedAt: z.number().nullable().optional(),
|
|
129
|
+
verifiedVia: z.string().nullable().optional(),
|
|
114
130
|
lastSeenAt: z.number().nullable(),
|
|
115
131
|
interactionCount: z.number(),
|
|
116
132
|
lastInteraction: z.number().nullable(),
|
|
117
|
-
revokedReason: z.string().nullable(),
|
|
118
|
-
blockedReason: z.string().nullable(),
|
|
133
|
+
revokedReason: z.string().nullable().optional(),
|
|
134
|
+
blockedReason: z.string().nullable().optional(),
|
|
119
135
|
});
|
|
120
136
|
|
|
121
137
|
const contactSchema = z.object({
|
|
122
138
|
id: z.string(),
|
|
123
139
|
displayName: z.string(),
|
|
124
|
-
role: z.
|
|
140
|
+
role: z.enum(["guardian", "contact"]),
|
|
125
141
|
notes: z.string().nullable().optional(),
|
|
126
|
-
contactType: z.
|
|
142
|
+
contactType: z.enum(["human", "assistant"]),
|
|
127
143
|
lastInteraction: z.number().nullable().optional(),
|
|
128
144
|
interactionCount: z.number(),
|
|
129
145
|
createdAt: z.number(),
|
|
@@ -142,10 +158,7 @@ const contactSchema = z.object({
|
|
|
142
158
|
* a relay failure surfaces as an error rather than reading ACL from the
|
|
143
159
|
* assistant DB.
|
|
144
160
|
*/
|
|
145
|
-
async function relayListContacts(
|
|
146
|
-
limit: number,
|
|
147
|
-
role: ContactRole | undefined,
|
|
148
|
-
) {
|
|
161
|
+
async function relayListContacts(limit: number, role: ContactRole | undefined) {
|
|
149
162
|
try {
|
|
150
163
|
const result = await ipcCallPersistent("contacts_list_rich", {
|
|
151
164
|
limit,
|
|
@@ -188,7 +201,6 @@ export async function handleListContacts(queryParams: Record<string, string>) {
|
|
|
188
201
|
query,
|
|
189
202
|
channelAddress,
|
|
190
203
|
channelType,
|
|
191
|
-
role,
|
|
192
204
|
contactType,
|
|
193
205
|
limit,
|
|
194
206
|
});
|
|
@@ -206,7 +218,7 @@ export async function handleListContacts(queryParams: Record<string, string>) {
|
|
|
206
218
|
log.debug(
|
|
207
219
|
"handleListContacts: contactType-filtered read served daemon-native (gateway-native contactType filtering is design-blocked, pending ACL classification)",
|
|
208
220
|
);
|
|
209
|
-
const contacts = listContacts(limit,
|
|
221
|
+
const contacts = listContacts(limit, contactType);
|
|
210
222
|
return {
|
|
211
223
|
ok: true,
|
|
212
224
|
contacts: contacts.map(prepareContactResponse),
|
|
@@ -254,7 +266,9 @@ export async function handleGetContact(contactId: string) {
|
|
|
254
266
|
// ~5s fallback plus IPC overhead on both nested hops. (List/revoke keep the default.)
|
|
255
267
|
const INVITE_CREATE_RELAY_TIMEOUT_MS = 30_000;
|
|
256
268
|
|
|
257
|
-
export async function handleListInvites({
|
|
269
|
+
export async function handleListInvites({
|
|
270
|
+
queryParams = {},
|
|
271
|
+
}: RouteHandlerArgs) {
|
|
258
272
|
try {
|
|
259
273
|
const result = (await ipcCallPersistent("invites_list", {
|
|
260
274
|
...(queryParams.sourceChannel
|
|
@@ -294,7 +308,9 @@ export async function handleCreateInvite({ body = {} }: RouteHandlerArgs) {
|
|
|
294
308
|
}
|
|
295
309
|
}
|
|
296
310
|
|
|
297
|
-
export async function handleRevokeInvite({
|
|
311
|
+
export async function handleRevokeInvite({
|
|
312
|
+
pathParams = {},
|
|
313
|
+
}: RouteHandlerArgs) {
|
|
298
314
|
try {
|
|
299
315
|
const result = (await ipcCallPersistent("invites_revoke", {
|
|
300
316
|
id: pathParams.id,
|
|
@@ -312,9 +328,7 @@ export async function handleRevokeInvite({ pathParams = {} }: RouteHandlerArgs)
|
|
|
312
328
|
* `invites_redeem_voice` method. Wraps the identity-bound
|
|
313
329
|
* `redeemVoiceInviteCode` path.
|
|
314
330
|
*/
|
|
315
|
-
export async function handleRedeemVoiceInvite({
|
|
316
|
-
body = {},
|
|
317
|
-
}: RouteHandlerArgs) {
|
|
331
|
+
export async function handleRedeemVoiceInvite({ body = {} }: RouteHandlerArgs) {
|
|
318
332
|
const callerExternalUserId = body.callerExternalUserId as string | undefined;
|
|
319
333
|
const code = body.code as string | undefined;
|
|
320
334
|
|
|
@@ -348,9 +362,7 @@ export async function handleRedeemVoiceInvite({
|
|
|
348
362
|
* `invites_redeem_token` method. Wraps the generic `redeemIngressInvite`
|
|
349
363
|
* token path.
|
|
350
364
|
*/
|
|
351
|
-
export async function handleRedeemTokenInvite({
|
|
352
|
-
body = {},
|
|
353
|
-
}: RouteHandlerArgs) {
|
|
365
|
+
export async function handleRedeemTokenInvite({ body = {} }: RouteHandlerArgs) {
|
|
354
366
|
const result = await redeemIngressInvite({
|
|
355
367
|
token: body.token as string | undefined,
|
|
356
368
|
externalUserId: body.externalUserId as string | undefined,
|
|
@@ -641,7 +653,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
641
653
|
// No-filter "search" is a list read — relay to the gateway so it returns
|
|
642
654
|
// the same source-of-truth data as `contacts list`.
|
|
643
655
|
if (!hasFilter) {
|
|
644
|
-
const { contacts } = await relayListContacts(
|
|
656
|
+
const { contacts } = await relayListContacts(
|
|
657
|
+
parsed.limit ?? 50,
|
|
658
|
+
undefined,
|
|
659
|
+
);
|
|
645
660
|
return contacts;
|
|
646
661
|
}
|
|
647
662
|
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import type { ConversationType } from "../../memory/conversation-types.js";
|
|
32
32
|
import { getBindingsForConversations } from "../../memory/external-conversation-store.js";
|
|
33
33
|
import { listGroups } from "../../memory/group-crud.js";
|
|
34
|
+
import { channelBindingSchema } from "../../messaging/channel-binding-schema.js";
|
|
34
35
|
import { UserError } from "../../util/errors.js";
|
|
35
36
|
import { getLogger } from "../../util/logger.js";
|
|
36
37
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
@@ -86,35 +87,6 @@ const assistantAttentionSchema = z.object({
|
|
|
86
87
|
.optional(),
|
|
87
88
|
});
|
|
88
89
|
|
|
89
|
-
const slackThreadSchema = z.object({
|
|
90
|
-
channelId: z.string(),
|
|
91
|
-
threadTs: z.string(),
|
|
92
|
-
link: z
|
|
93
|
-
.object({
|
|
94
|
-
appUrl: z.string().optional(),
|
|
95
|
-
webUrl: z.string().optional(),
|
|
96
|
-
})
|
|
97
|
-
.optional(),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const slackChannelSchema = z.object({
|
|
101
|
-
channelId: z.string(),
|
|
102
|
-
name: z.string().optional(),
|
|
103
|
-
link: z.object({ webUrl: z.string() }).optional(),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const channelBindingSchema = z.object({
|
|
107
|
-
sourceChannel: z.string(),
|
|
108
|
-
externalChatId: z.string(),
|
|
109
|
-
externalChatName: z.string().optional(),
|
|
110
|
-
externalThreadId: z.string().optional(),
|
|
111
|
-
externalUserId: z.string().nullable(),
|
|
112
|
-
displayName: z.string().nullable(),
|
|
113
|
-
username: z.string().nullable(),
|
|
114
|
-
slackThread: slackThreadSchema.optional(),
|
|
115
|
-
slackChannel: slackChannelSchema.optional(),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
90
|
const forkParentSchema = z.object({
|
|
119
91
|
conversationId: z.string(),
|
|
120
92
|
messageId: z.string(),
|
|
@@ -90,9 +90,11 @@ import {
|
|
|
90
90
|
addMessage,
|
|
91
91
|
extractImageSourcePaths,
|
|
92
92
|
getConversation,
|
|
93
|
+
getConversationPersistedSeq,
|
|
93
94
|
getMessages,
|
|
94
95
|
getMessagesPaginated,
|
|
95
96
|
hasMessages,
|
|
97
|
+
isConversationProcessing,
|
|
96
98
|
type MessageRow,
|
|
97
99
|
provenanceFromTrustContext,
|
|
98
100
|
setConversationInferenceProfile,
|
|
@@ -122,7 +124,6 @@ import {
|
|
|
122
124
|
} from "../../util/platform.js";
|
|
123
125
|
import { silentlyWithLog } from "../../util/silently.js";
|
|
124
126
|
import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
|
|
125
|
-
import { getPersistedSeq } from "../assistant-stream-state.js";
|
|
126
127
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
127
128
|
import {
|
|
128
129
|
type GuardianPendingScope,
|
|
@@ -393,6 +394,8 @@ function buildSlackHistoryMessage(
|
|
|
393
394
|
: {}),
|
|
394
395
|
...(messageLink ? { messageLink } : {}),
|
|
395
396
|
...(threadLink ? { threadLink } : {}),
|
|
397
|
+
...(slackMeta.eventKind ? { eventKind: slackMeta.eventKind } : {}),
|
|
398
|
+
...(slackMeta.reaction ? { reaction: slackMeta.reaction } : {}),
|
|
396
399
|
};
|
|
397
400
|
}
|
|
398
401
|
|
|
@@ -657,6 +660,7 @@ export function handleListMessages({
|
|
|
657
660
|
oldestTimestamp: null,
|
|
658
661
|
oldestMessageId: null,
|
|
659
662
|
seq: null,
|
|
663
|
+
processing: false,
|
|
660
664
|
};
|
|
661
665
|
}
|
|
662
666
|
return { messages: [] };
|
|
@@ -1006,11 +1010,19 @@ export function handleListMessages({
|
|
|
1006
1010
|
});
|
|
1007
1011
|
|
|
1008
1012
|
// Snapshot↔stream alignment token: the `seq` of the last event whose
|
|
1009
|
-
// content is durably persisted for this conversation
|
|
1010
|
-
//
|
|
1011
|
-
// client can apply only stream events with a higher `seq`.
|
|
1012
|
-
// nothing has been persisted
|
|
1013
|
-
|
|
1013
|
+
// content is durably persisted for this conversation, read from the
|
|
1014
|
+
// `conversations.seq` column. Returned on every resolved-conversation
|
|
1015
|
+
// response so a client can apply only stream events with a higher `seq`.
|
|
1016
|
+
// Null when nothing has been persisted (the conversation was created before
|
|
1017
|
+
// any stream activity, or predates the column) -- the client cold-starts.
|
|
1018
|
+
const persistedSeq = getConversationPersistedSeq(resolvedConversationId);
|
|
1019
|
+
|
|
1020
|
+
// Authoritative "is the agent mid-turn?" signal, sourced from the
|
|
1021
|
+
// `processing_started_at` column (persisted, survives daemon restarts).
|
|
1022
|
+
// Clients use this to distinguish a live turn still in flight from a
|
|
1023
|
+
// turn that silently died — without it, a dropped SSE stream leaves the
|
|
1024
|
+
// UI spinning forever with no way to learn the server is actually idle.
|
|
1025
|
+
const processing = isConversationProcessing(resolvedConversationId);
|
|
1014
1026
|
|
|
1015
1027
|
if (isPaginated) {
|
|
1016
1028
|
// Prefer the page's oldest visible row (the documented cursor semantic).
|
|
@@ -1034,6 +1046,7 @@ export function handleListMessages({
|
|
|
1034
1046
|
oldestTimestamp: oldestTimestamp ?? null,
|
|
1035
1047
|
oldestMessageId: oldestMessageId ?? null,
|
|
1036
1048
|
seq: persistedSeq,
|
|
1049
|
+
processing,
|
|
1037
1050
|
};
|
|
1038
1051
|
}
|
|
1039
1052
|
|
|
@@ -1043,10 +1056,11 @@ export function handleListMessages({
|
|
|
1043
1056
|
...(oldestTimestamp != null ? { oldestTimestamp } : {}),
|
|
1044
1057
|
...(oldestMessageId != null ? { oldestMessageId } : {}),
|
|
1045
1058
|
seq: persistedSeq,
|
|
1059
|
+
processing,
|
|
1046
1060
|
};
|
|
1047
1061
|
}
|
|
1048
1062
|
|
|
1049
|
-
return { messages, seq: persistedSeq };
|
|
1063
|
+
return { messages, seq: persistedSeq, processing };
|
|
1050
1064
|
}
|
|
1051
1065
|
|
|
1052
1066
|
/**
|
|
@@ -2688,6 +2702,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2688
2702
|
.describe(
|
|
2689
2703
|
"Global SSE `seq` of the last event whose content is durably persisted for this conversation in the current daemon process. A client can align this snapshot with the `/events` stream by applying only events with `seq` greater than this value. Null when no events have been persisted in this process (cold conversation, after a daemon restart, or when the conversation has aged out of the in-memory map) — clients should cold-start in that case. Absent on older daemons that predate this field.",
|
|
2690
2704
|
),
|
|
2705
|
+
processing: z
|
|
2706
|
+
.boolean()
|
|
2707
|
+
.optional()
|
|
2708
|
+
.describe(
|
|
2709
|
+
"Whether the agent is currently mid-turn for this conversation, sourced authoritatively from the persisted `processing_started_at` column. `true` means a turn is in flight; `false` means the conversation is idle. Clients use this to recover from a dropped SSE stream: if a turn appears to be running locally but the server reports `processing: false`, the turn has ended (or died) and the UI should stop waiting rather than spin indefinitely. Absent on older daemons that predate this field.",
|
|
2710
|
+
),
|
|
2691
2711
|
}),
|
|
2692
2712
|
handler: (args) => handleListMessages(args),
|
|
2693
2713
|
},
|
|
@@ -9,10 +9,6 @@ import { isInviteCodeRedemptionEnabled } from "../../../channels/config.js";
|
|
|
9
9
|
import type { ChannelId } from "../../../channels/types.js";
|
|
10
10
|
import { getGuardianDelivery } from "../../../contacts/guardian-delivery-reader.js";
|
|
11
11
|
import { channelStatusToMemberStatus } from "../../../contacts/member-status.js";
|
|
12
|
-
import type {
|
|
13
|
-
ContactChannel,
|
|
14
|
-
ContactWithChannels,
|
|
15
|
-
} from "../../../contacts/types.js";
|
|
16
12
|
import { deleteInbound, recordInbound } from "../../../memory/delivery-crud.js";
|
|
17
13
|
import { markProcessed } from "../../../memory/delivery-status.js";
|
|
18
14
|
import {
|
|
@@ -96,12 +92,6 @@ export interface AclEnforcementParams {
|
|
|
96
92
|
effectiveAdmissionPolicy?: AdmissionPolicy;
|
|
97
93
|
}
|
|
98
94
|
|
|
99
|
-
/** Resolved contact + channel pair from ACL enforcement. */
|
|
100
|
-
export type ResolvedMember = {
|
|
101
|
-
contact: ContactWithChannels;
|
|
102
|
-
channel: ContactChannel;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
95
|
export interface AclResult {
|
|
106
96
|
resolvedMember: VerdictMember | null;
|
|
107
97
|
/** When set, the caller must return this response immediately. */
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* focused on orchestration.
|
|
9
9
|
*/
|
|
10
10
|
import type { ChannelId, InterfaceId } from "../../../channels/types.js";
|
|
11
|
-
import { findGuardianForChannel } from "../../../contacts/contact-store.js";
|
|
12
11
|
import {
|
|
13
12
|
getGuardianDelivery,
|
|
14
13
|
guardianForChannel,
|
|
@@ -964,17 +963,14 @@ function startTrustedContactApprovalNotifier(params: {
|
|
|
964
963
|
|
|
965
964
|
if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
|
|
966
965
|
globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
|
|
967
|
-
// Gateway-resolved guardian display name
|
|
968
|
-
// local fallback on null/no-match (display-only). Removed in Combo 11.
|
|
966
|
+
// Gateway-resolved guardian display name (display-only).
|
|
969
967
|
const guardians = await getGuardianDelivery({
|
|
970
968
|
channelTypes: [sourceChannel],
|
|
971
969
|
});
|
|
972
|
-
const
|
|
973
|
-
? guardianForChannel(guardians, sourceChannel)?.displayName
|
|
970
|
+
const displayName = guardians
|
|
971
|
+
? (guardianForChannel(guardians, sourceChannel)?.displayName ??
|
|
972
|
+
undefined)
|
|
974
973
|
: undefined;
|
|
975
|
-
const displayName =
|
|
976
|
-
gatewayDisplayName ??
|
|
977
|
-
findGuardianForChannel(sourceChannel)?.contact.displayName;
|
|
978
974
|
const guardianName = resolveGuardianName(displayName);
|
|
979
975
|
const waitingText = `Waiting for ${guardianName}'s approval...`;
|
|
980
976
|
try {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { SourceMetadata } from "@vellumai/gateway-client";
|
|
19
19
|
|
|
20
20
|
import type { ChannelId, InterfaceId } from "../../../channels/types.js";
|
|
21
|
+
import { getGuardianDeliveryFresh } from "../../../contacts/guardian-delivery-reader.js";
|
|
21
22
|
import { getDiskPressureStatus } from "../../../daemon/disk-pressure-guard.js";
|
|
22
23
|
import { classifyDiskPressureTurnPolicy } from "../../../daemon/disk-pressure-policy.js";
|
|
23
24
|
import { addMessage } from "../../../memory/conversation-crud.js";
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
import { getLogger } from "../../../util/logger.js";
|
|
36
37
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
|
|
37
38
|
import type { ApprovalConversationGenerator } from "../../http-types.js";
|
|
39
|
+
import { setMemberVerdict } from "../../member-verdict-cache.js";
|
|
38
40
|
import { resolveTrustContext } from "../../trust-context-resolver.js";
|
|
39
41
|
import { handleGuardianReplyIntercept } from "./guardian-reply-intercept.js";
|
|
40
42
|
|
|
@@ -126,6 +128,23 @@ export async function handleSlackReactionIntercept(
|
|
|
126
128
|
approvalConversationGenerator,
|
|
127
129
|
} = params;
|
|
128
130
|
|
|
131
|
+
// Warm the channel-specific guardian-delivery cache before the SYNC trust
|
|
132
|
+
// resolve below. The sync resolver reads the IO-free cache snapshot; on a
|
|
133
|
+
// cold process only `vellum` is warmed at startup, so a Slack guardian
|
|
134
|
+
// reaction would otherwise misclassify as `unknown` and drop. Read fresh:
|
|
135
|
+
// gateway-side binding writes don't invalidate the daemon cache, so a stale
|
|
136
|
+
// empty snapshot would otherwise survive the TTL. This await runs in the
|
|
137
|
+
// already-async intercept, off the sync resolver's hot path.
|
|
138
|
+
await getGuardianDeliveryFresh({ channelTypes: [sourceChannel] });
|
|
139
|
+
|
|
140
|
+
// Reactions carry the gateway-stamped verdict but skip getInboundTrustVerdict,
|
|
141
|
+
// which warms the member-verdict cache the sync resolver reads. Seed it from
|
|
142
|
+
// the stamped verdict so an active non-guardian contact's reaction resolves
|
|
143
|
+
// instead of failing closed to `unknown` on a cold cache.
|
|
144
|
+
if (sourceMetadata?.trustVerdict) {
|
|
145
|
+
setMemberVerdict(sourceChannel, rawSenderId, sourceMetadata.trustVerdict);
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
// Classify the reactor. No timezone enrichment — reactions never drive an
|
|
130
149
|
// agent turn, so only the trust class / guardian principal matter.
|
|
131
150
|
const trustCtx = resolveTrustContext({
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "../../config/env.js";
|
|
16
16
|
import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
|
|
17
17
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
18
|
+
import { getGuardianDelivery } from "../../contacts/guardian-delivery-reader.js";
|
|
18
19
|
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
19
20
|
import {
|
|
20
21
|
computeGatewayTarget,
|
|
@@ -303,8 +304,12 @@ async function handleOAuthConnectStart({ body = {} }: RouteHandlerArgs) {
|
|
|
303
304
|
// Workspace files (list/read)
|
|
304
305
|
// ---------------------------------------------------------------------------
|
|
305
306
|
|
|
306
|
-
function getWorkspaceFiles(): string[] {
|
|
307
|
+
async function getWorkspaceFiles(): Promise<string[]> {
|
|
307
308
|
const files = ["IDENTITY.md", "SOUL.md", "skills/"];
|
|
309
|
+
// Warm the vellum guardian-delivery cache so the sync persona resolution
|
|
310
|
+
// below hits a fresh key instead of falling back to default.md on a cold or
|
|
311
|
+
// TTL-expired cache.
|
|
312
|
+
await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
308
313
|
const guardianPath = resolveGuardianPersonaPath();
|
|
309
314
|
if (guardianPath) {
|
|
310
315
|
files.push(`users/${basename(guardianPath)}`);
|
|
@@ -312,9 +317,9 @@ function getWorkspaceFiles(): string[] {
|
|
|
312
317
|
return files;
|
|
313
318
|
}
|
|
314
319
|
|
|
315
|
-
function handleWorkspaceFilesList() {
|
|
320
|
+
async function handleWorkspaceFilesList() {
|
|
316
321
|
const base = getWorkspaceDir();
|
|
317
|
-
const files = getWorkspaceFiles().map((name) => ({
|
|
322
|
+
const files = (await getWorkspaceFiles()).map((name) => ({
|
|
318
323
|
path: name,
|
|
319
324
|
name,
|
|
320
325
|
exists: pathExists(join(base, name)),
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { parseChannelId } from "../../channels/types.js";
|
|
11
|
-
import { getConfig } from "../../config/loader.js";
|
|
12
11
|
import { normalizeConversationType } from "../../daemon/message-types/shared.js";
|
|
13
12
|
import {
|
|
14
13
|
type AttentionState,
|
|
@@ -24,10 +23,7 @@ import {
|
|
|
24
23
|
} from "../../memory/conversation-crud.js";
|
|
25
24
|
import type { ExternalConversationBinding } from "../../memory/external-conversation-store.js";
|
|
26
25
|
import { getBindingsForConversations } from "../../memory/external-conversation-store.js";
|
|
27
|
-
import {
|
|
28
|
-
buildSlackMessageDeepLinks,
|
|
29
|
-
buildSlackWebChannelUrl,
|
|
30
|
-
} from "../../messaging/providers/slack/deep-link.js";
|
|
26
|
+
import { buildChannelBindingMetadata } from "../../messaging/channel-binding-metadata.js";
|
|
31
27
|
|
|
32
28
|
// ---------------------------------------------------------------------------
|
|
33
29
|
// Helpers
|
|
@@ -126,48 +122,7 @@ function resolveSerializedGroupId(
|
|
|
126
122
|
}
|
|
127
123
|
|
|
128
124
|
function buildChannelBinding(binding: ExternalConversationBinding) {
|
|
129
|
-
const externalChatName =
|
|
130
|
-
binding.externalChatName?.trim() ||
|
|
131
|
-
(binding.sourceChannel === "slack" ? binding.externalChatId : undefined);
|
|
132
|
-
const slackConfig =
|
|
133
|
-
binding.sourceChannel === "slack" ? getConfig().slack : undefined;
|
|
134
|
-
const slackThreadLink =
|
|
135
|
-
slackConfig && binding.externalThreadId
|
|
136
|
-
? buildSlackMessageDeepLinks({
|
|
137
|
-
teamId: slackConfig.teamId,
|
|
138
|
-
teamUrl: slackConfig.teamUrl,
|
|
139
|
-
channelId: binding.externalChatId,
|
|
140
|
-
messageTs: binding.externalThreadId,
|
|
141
|
-
})
|
|
142
|
-
: undefined;
|
|
143
|
-
const slackThread =
|
|
144
|
-
binding.sourceChannel === "slack" && binding.externalThreadId
|
|
145
|
-
? {
|
|
146
|
-
channelId: binding.externalChatId,
|
|
147
|
-
threadTs: binding.externalThreadId,
|
|
148
|
-
...(slackThreadLink ? { link: slackThreadLink } : {}),
|
|
149
|
-
}
|
|
150
|
-
: undefined;
|
|
151
|
-
const slackChannelWebUrl = slackConfig
|
|
152
|
-
? buildSlackWebChannelUrl({
|
|
153
|
-
teamUrl: slackConfig.teamUrl,
|
|
154
|
-
channelId: binding.externalChatId,
|
|
155
|
-
})
|
|
156
|
-
: undefined;
|
|
157
|
-
const slackChannel =
|
|
158
|
-
binding.sourceChannel === "slack"
|
|
159
|
-
? {
|
|
160
|
-
channelId: binding.externalChatId,
|
|
161
|
-
name: externalChatName,
|
|
162
|
-
...(slackChannelWebUrl
|
|
163
|
-
? {
|
|
164
|
-
link: {
|
|
165
|
-
webUrl: slackChannelWebUrl,
|
|
166
|
-
},
|
|
167
|
-
}
|
|
168
|
-
: {}),
|
|
169
|
-
}
|
|
170
|
-
: undefined;
|
|
125
|
+
const externalChatName = binding.externalChatName?.trim() || undefined;
|
|
171
126
|
|
|
172
127
|
return {
|
|
173
128
|
sourceChannel: binding.sourceChannel,
|
|
@@ -179,8 +134,10 @@ function buildChannelBinding(binding: ExternalConversationBinding) {
|
|
|
179
134
|
externalUserId: binding.externalUserId,
|
|
180
135
|
displayName: binding.displayName,
|
|
181
136
|
username: binding.username,
|
|
182
|
-
|
|
183
|
-
|
|
137
|
+
// Channel-specific enrichment (e.g. Slack deep links) is contributed by
|
|
138
|
+
// the source channel's binding-metadata builder, keeping this serializer
|
|
139
|
+
// channel-agnostic.
|
|
140
|
+
...buildChannelBindingMetadata(binding),
|
|
184
141
|
};
|
|
185
142
|
}
|
|
186
143
|
|
|
@@ -118,21 +118,6 @@ export function textToSlackBlocks(text: string): KnownBlock[] | undefined {
|
|
|
118
118
|
return blocks.length > 0 ? blocks : undefined;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/**
|
|
122
|
-
* Detect whether a callback URL points to the gateway's Slack delivery endpoint.
|
|
123
|
-
*/
|
|
124
|
-
export function isSlackCallbackUrl(callbackUrl: string): boolean {
|
|
125
|
-
try {
|
|
126
|
-
const url = new URL(callbackUrl);
|
|
127
|
-
return (
|
|
128
|
-
url.pathname === "/deliver/slack" ||
|
|
129
|
-
url.pathname.startsWith("/deliver/slack?")
|
|
130
|
-
);
|
|
131
|
-
} catch {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
121
|
// ---------------------------------------------------------------------------
|
|
137
122
|
// Internals
|
|
138
123
|
// ---------------------------------------------------------------------------
|