@vellumai/assistant 0.11.8-dev.202609021821.6fc920a → 0.11.8-dev.202609022016.7bd8f87
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 +1 -1
- package/docs/guardian-request-flow.md +1 -1
- package/docs/trusted-contact-access.md +1 -7
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -12
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/openapi.yaml +72 -2
- package/package.json +1 -1
- package/src/__tests__/access-request-card-view.test.ts +6 -5
- package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
- package/src/__tests__/channel-reply-delivery.test.ts +317 -134
- package/src/__tests__/channel-retry-sweep.test.ts +31 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +57 -0
- package/src/__tests__/conversation-sync-tags.test.ts +2 -37
- package/src/__tests__/credential-health-service.test.ts +98 -10
- package/src/__tests__/delete-propagation.test.ts +93 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +26 -7
- package/src/__tests__/list-messages-page-latest.test.ts +42 -0
- package/src/__tests__/list-messages-system-card.test.ts +103 -0
- package/src/__tests__/notification-decision-fallback.test.ts +52 -49
- package/src/__tests__/notification-decision-strategy.test.ts +177 -197
- package/src/__tests__/notification-discord-adapter.test.ts +25 -0
- package/src/__tests__/notification-slack-adapter.test.ts +133 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +71 -72
- package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
- package/src/__tests__/reaction-persistence.test.ts +2 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
- package/src/__tests__/thread-backfill.test.ts +5 -3
- package/src/__tests__/unified-turn-context-location.test.ts +76 -0
- package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
- package/src/api/index.ts +0 -8
- package/src/api/package.json +1 -0
- package/src/api/responses/conversation-message.ts +8 -0
- package/src/api/responses/home.ts +7 -18
- package/src/api/surfaces.ts +29 -12
- package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
- package/src/cli/commands/platform/connect.ts +14 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
- package/src/conversations/message-consolidation.ts +4 -3
- package/src/credential-health/credential-health-service.ts +130 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
- package/src/daemon/conversation-runtime-assembly.ts +12 -31
- package/src/daemon/dictation-text-processing.ts +2 -7
- package/src/daemon/reaction-record.test.ts +23 -11
- package/src/daemon/reaction-record.ts +6 -2
- package/src/documents/document-store.ts +1 -5
- package/src/home/conversation-starter-validation.ts +1 -5
- package/src/messaging/provider-message-metadata.ts +27 -0
- package/src/messaging/providers/discord/send.ts +3 -2
- package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
- package/src/messaging/providers/slack/message-metadata.ts +141 -1
- package/src/messaging/reaction-envelopes.test.ts +73 -0
- package/src/messaging/reaction-envelopes.ts +22 -10
- package/src/messaging/read-provider-metadata.ts +4 -3
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +1 -1
- package/src/notifications/__tests__/broadcaster.test.ts +35 -4
- package/src/notifications/access-request-copy.ts +126 -114
- package/src/notifications/adapters/discord.ts +8 -4
- package/src/notifications/adapters/shared.ts +17 -4
- package/src/notifications/adapters/slack.ts +14 -4
- package/src/notifications/adapters/telegram.ts +8 -4
- package/src/notifications/approval-card-data.ts +5 -2
- package/src/notifications/broadcaster.ts +57 -25
- package/src/notifications/copy-composer.ts +2 -2
- package/src/notifications/decision-engine.ts +21 -118
- package/src/notifications/guardian-feed-projection.ts +0 -15
- package/src/notifications/guardian-question-mode.ts +2 -6
- package/src/notifications/notification-utils.ts +83 -4
- package/src/persistence/conversation-crud.ts +20 -3
- package/src/persistence/conversation-queries.ts +4 -6
- package/src/platform/client.ts +55 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
- package/src/plugins/defaults/turn-context/injectors.ts +3 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
- package/src/providers/model-catalog.ts +4 -11
- package/src/runtime/AGENTS.md +24 -14
- package/src/runtime/approval-message-composer.ts +1 -6
- package/src/runtime/assistant-event-hub.ts +2 -39
- package/src/runtime/channel-reply-delivery.ts +100 -130
- package/src/runtime/channel-retry-sweep.ts +7 -0
- package/src/runtime/guardian-action-message-composer.ts +1 -4
- package/src/runtime/http-router.ts +1 -5
- package/src/runtime/question-request-guardian-bridge.ts +1 -1
- package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
- package/src/runtime/routes/conversation-routes.ts +27 -10
- package/src/runtime/routes/diagnostics-routes.ts +71 -12
- package/src/runtime/routes/guardian-action-routes.ts +10 -14
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +2 -0
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +99 -10
- package/src/runtime/sync/resource-sync-events.ts +14 -37
- package/src/runtime/sync/sync-publisher.test.ts +5 -4
- package/src/tools/watch/watch-retro-report.ts +109 -7
- package/src/watch/__tests__/watch-retro.test.ts +57 -0
- package/src/watch/watch-retro.ts +4 -4
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/api/events/conversation-list-invalidated.ts +0 -38
package/ARCHITECTURE.md
CHANGED
|
@@ -146,7 +146,7 @@ All guardian approval decisions, regardless of how they arrive, route through a
|
|
|
146
146
|
**Text fallback path (always available):**
|
|
147
147
|
|
|
148
148
|
- Every prompt includes a `requestCode` (6-char alphanumeric). Guardians can reply with `<requestCode> approve` or `<requestCode> reject` on any channel.
|
|
149
|
-
- `access_request` prompts
|
|
149
|
+
- `access_request` prompts carry the request-code verify/trust/reject/block directive in the card's `plainTextFallback`, appended by a transport only when it sends text without buttons; the `"open invite flow"` phrase for starting the Trusted Contacts invite flow stays in the text on every surface because nothing offers a button for it.
|
|
150
150
|
- `pending_question` prompts (voice-originated) support `<requestCode> <your answer>` for free-text answers.
|
|
151
151
|
- The `routeGuardianReply` router processes text replies through a priority-ordered pipeline: callback parsing -> request code parsing -> NL classification. All paths converge on `applyGuardianDecision`.
|
|
152
152
|
|
|
@@ -21,7 +21,7 @@ anti-pattern was retired in #35642 and again in the ask_question redesign).
|
|
|
21
21
|
notification pipeline (notifications/emit-signal.ts →
|
|
22
22
|
decision engine → destination resolver → decision-engine.ts → destination-resolver.ts →
|
|
23
23
|
broadcaster builds the card context ONCE broadcaster.ts: resolveApprovalContext /
|
|
24
|
-
(generic actions[] + plainTextFallback)
|
|
24
|
+
(generic actions[] + plainTextFallback) resolveQuestionContext)
|
|
25
25
|
│
|
|
26
26
|
▼ per-channel rendering ONLY
|
|
27
27
|
channel adapters (notifications/adapters/{telegram,slack,macos,platform},
|
|
@@ -24,13 +24,7 @@ Design doc defining how unknown users gain access to a Vellum assistant via chan
|
|
|
24
24
|
2. **The message is denied at the ingress ACL.** The gateway resolves the actor against its ACL DB and stamps a trust verdict onto the inbound metadata; the daemon's ACL stage (`runtime/routes/inbound-stages/acl-enforcement.ts`) consumes that verdict, finds no active member, replies _"Hmm looks like you don't have access to talk to me. I'll let <guardian> know you tried talking to me and get back to you."_ and returns `{ denied: true, reason: 'not_a_member' }`.
|
|
25
25
|
3. **Notification pipeline alerts the guardian.** The denial triggers `notifyGuardianOfAccessRequest()` (`runtime/access-request-helper.ts`), which creates an access request in the gateway (`guardian_requests`, `kind: 'access_request'`, `toolName: 'ingress_access_request'`, via the `channels/gateway-guardian-requests.ts` client) and calls `emitNotificationSignal()` with `sourceEventName: 'ingress.access_request'`. The notification routes through the decision engine to the guardian's surfaces (vellum app, Telegram, Slack, etc.). The guardian sees who is requesting access, including a request code for approve/reject and an `open invite flow` option to start the Trusted Contacts invite flow.
|
|
26
26
|
|
|
27
|
-
**Access-request copy contract:** Every guardian-facing access-request notification
|
|
28
|
-
1. **Requester context** — best-available identity (display name, username, external ID, source channel), sanitized to prevent control-character injection.
|
|
29
|
-
2. **Request-code decision directive** — e.g., `Reply "A1B2C3 approve" to grant access or "A1B2C3 reject" to deny.`
|
|
30
|
-
3. **Invite directive** — the exact phrase `Reply "open invite flow" to start Trusted Contacts invite flow.`
|
|
31
|
-
4. **Revoked-member warning** (when applicable) — `Note: this user was previously revoked.`
|
|
32
|
-
|
|
33
|
-
Model-generated phrasing is permitted for the surrounding copy, but a post-generation enforcement step in the decision engine validates that all required directive elements are present. If any are missing, the full deterministic contract text is appended. This ensures the guardian can always parse and act on the notification regardless of LLM output quality.
|
|
27
|
+
**Access-request copy contract:** Every guardian-facing access-request notification carries the requester context (`buildAccessRequestContextText`): best-available identity (display name, username, external ID, source channel) sanitized to prevent control-character injection, the message preview, and a revoked-member warning when applicable (`Note: this user was previously revoked.`). The context also carries the invite directive (`Reply "open invite flow" to start Trusted Contacts invite flow.`), because no surface offers a button for the invite flow and typing the phrase is the only way to start it. The typed-reply mechanics are separate (`buildAccessRequestReplyMechanics`): the request-code directive (`Reply "A1B2C3 verify" ... "A1B2C3 trust" ... "A1B2C3 reject" ... "A1B2C3 block"`, without `verify` when no handshake is offered). It rides only in the card's `plainTextFallback`; a transport appends it when it sends text without buttons, and the decision engine strips any copy of it the model wrote into the composed text, so surfaces with buttons show the context alone.
|
|
34
28
|
|
|
35
29
|
**Guardian identity resolution** is anchored on the assistant's vellum principal (`resolveAnchoredGuardian`), so access requests cannot bind to stale or cross-assistant contacts:
|
|
36
30
|
1. A source-channel guardian binding (from the gateway contact list) that matches the vellum anchor principal.
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"./redacted-credential": "./src/redacted-credential.ts",
|
|
23
23
|
"./secret-detection": "./src/secret-detection.ts",
|
|
24
24
|
"./stripe-currency": "./src/stripe-currency.ts",
|
|
25
|
-
"./error": "./src/error.ts"
|
|
25
|
+
"./error": "./src/error.ts",
|
|
26
|
+
"./reactions": "./src/reactions.ts",
|
|
27
|
+
"./guardian-requests": "./src/guardian-requests.ts",
|
|
28
|
+
"./platform-credential": "./src/platform-credential.ts"
|
|
26
29
|
},
|
|
27
30
|
"scripts": {
|
|
28
31
|
"typecheck": "bunx tsc --noEmit",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical vocabulary for a guardian request's lifecycle status, shared by
|
|
3
|
+
* the gateway that owns the request rows, the daemon that decides and
|
|
4
|
+
* projects them, and the web that renders the projection.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
export const GUARDIAN_REQUEST_STATUS_VALUES = [
|
|
9
|
+
"pending",
|
|
10
|
+
"approved",
|
|
11
|
+
"denied",
|
|
12
|
+
"expired",
|
|
13
|
+
"cancelled",
|
|
14
|
+
] as const;
|
|
15
|
+
export const GuardianRequestStatusSchema = z.enum(
|
|
16
|
+
GUARDIAN_REQUEST_STATUS_VALUES,
|
|
17
|
+
);
|
|
18
|
+
export type GuardianRequestStatus = z.infer<typeof GuardianRequestStatusSchema>;
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./rpc.js";
|
|
|
28
28
|
export * from "./trust-rules.js";
|
|
29
29
|
export * from "./ingress.js";
|
|
30
30
|
export * from "./no-response.js";
|
|
31
|
+
export * from "./platform-credential.js";
|
|
31
32
|
export * from "./remote-web-pairing.js";
|
|
32
33
|
export * from "./twilio-ingress.js";
|
|
33
34
|
export * from "./url-normalization.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract for asking an assistant whether its stored platform-managed
|
|
3
|
+
* credential still authenticates.
|
|
4
|
+
*
|
|
5
|
+
* The daemon route is the authoritative serving side:
|
|
6
|
+
* - `POST /v1/platform/verify-credential`
|
|
7
|
+
* (`assistant/src/runtime/routes/platform-routes.ts`)
|
|
8
|
+
*
|
|
9
|
+
* The daemon's platform client produces the verdict, the route reports it,
|
|
10
|
+
* and the `vellum` CLI reads it before deciding whether a stored key can be
|
|
11
|
+
* re-injected (`cli/src/lib/assistant-api-key-resolution.ts`), so all three
|
|
12
|
+
* share one definition and cannot silently drift. The web app reads the same
|
|
13
|
+
* shape through its generated daemon client.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What the platform said about the stored credential, right now.
|
|
19
|
+
*
|
|
20
|
+
* - `valid`: the platform accepted it.
|
|
21
|
+
* - `rejected`: the platform refused it (unauthorized or forbidden); the
|
|
22
|
+
* credential needs replacing.
|
|
23
|
+
* - `unknown`: the check itself could not run (no credential stored, the
|
|
24
|
+
* platform unreachable, a server error). Not evidence either way.
|
|
25
|
+
*/
|
|
26
|
+
export const PlatformCredentialVerificationStatusSchema = z.enum([
|
|
27
|
+
"valid",
|
|
28
|
+
"rejected",
|
|
29
|
+
"unknown",
|
|
30
|
+
]);
|
|
31
|
+
export type PlatformCredentialVerificationStatus = z.infer<
|
|
32
|
+
typeof PlatformCredentialVerificationStatusSchema
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
/** `POST /v1/platform/verify-credential` response body. */
|
|
36
|
+
export const PlatformVerifyCredentialResponseSchema = z.object({
|
|
37
|
+
status: PlatformCredentialVerificationStatusSchema,
|
|
38
|
+
});
|
|
39
|
+
export type PlatformVerifyCredentialResponse = z.infer<
|
|
40
|
+
typeof PlatformVerifyCredentialResponseSchema
|
|
41
|
+
>;
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical vocabulary for a reaction's emoji, shared by every service that
|
|
3
|
+
* handles one: the gateway that normalizes it, the daemon that stores and
|
|
4
|
+
* projects it, and the web that renders it.
|
|
5
|
+
*
|
|
6
|
+
* The kind is said by the channel rather than inferred from how the emoji
|
|
7
|
+
* is spelled. Modelled on Zulip's `reaction_type`, the one surveyed system
|
|
8
|
+
* that separates the namespace from the name.
|
|
9
|
+
*
|
|
10
|
+
* `shortcode` is a name in a channel's own namespace whose kind the channel
|
|
11
|
+
* does not disclose: Slack sends `+1` for the standard emoji and `blob_wave`
|
|
12
|
+
* for a workspace upload with nothing to tell them apart, and only the
|
|
13
|
+
* workspace token can resolve the second. It is a distinct kind from
|
|
14
|
+
* `unicode`, not a stand-in for an unknown one.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
export const REACTION_EMOJI_KINDS = ["unicode", "shortcode", "custom"] as const;
|
|
19
|
+
export type ReactionEmojiKind = (typeof REACTION_EMOJI_KINDS)[number];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The typed emoji fields every schema that carries a reaction spreads in,
|
|
23
|
+
* so the wire contract, the stored envelopes, and the response projection
|
|
24
|
+
* describe one shape. Optional throughout: a persisted row or a replayed
|
|
25
|
+
* payload may carry only the spelling.
|
|
26
|
+
*/
|
|
27
|
+
export const ReactionEmojiFieldsSchema = z.object({
|
|
28
|
+
/** Which namespace the emoji was drawn from. */
|
|
29
|
+
emojiKind: z.enum(REACTION_EMOJI_KINDS).optional(),
|
|
30
|
+
/**
|
|
31
|
+
* The emoji's name in that namespace: the character itself for `unicode`,
|
|
32
|
+
* the bare name for `shortcode` and `custom`. Never a mention form.
|
|
33
|
+
*/
|
|
34
|
+
emojiName: z.string().optional(),
|
|
35
|
+
/** The channel's id for a `custom` emoji, absent for every other kind. */
|
|
36
|
+
emojiId: z.string().optional(),
|
|
37
|
+
/** Whether a `custom` emoji animates. Absent for every other kind. */
|
|
38
|
+
emojiAnimated: z.boolean().optional(),
|
|
39
|
+
});
|
|
40
|
+
export type ReactionEmojiFields = z.infer<typeof ReactionEmojiFieldsSchema>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The typed emoji fields a source actually carries, with undefined ones
|
|
44
|
+
* omitted: an absent key and a present-but-undefined one serialize alike, but the
|
|
45
|
+
* stored envelope and the response should carry only what was declared. Every writer of a reaction shape (the wire
|
|
46
|
+
* payload, both stored envelopes, the response projection) copies the
|
|
47
|
+
* fields through this rather than restating the four-way pick.
|
|
48
|
+
*/
|
|
49
|
+
export function pickReactionEmojiFields(
|
|
50
|
+
source: ReactionEmojiFields,
|
|
51
|
+
): ReactionEmojiFields {
|
|
52
|
+
return {
|
|
53
|
+
...(source.emojiKind !== undefined ? { emojiKind: source.emojiKind } : {}),
|
|
54
|
+
...(source.emojiName !== undefined ? { emojiName: source.emojiName } : {}),
|
|
55
|
+
...(source.emojiId !== undefined ? { emojiId: source.emojiId } : {}),
|
|
56
|
+
...(source.emojiAnimated !== undefined
|
|
57
|
+
? { emojiAnimated: source.emojiAnimated }
|
|
58
|
+
: {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"./redacted-credential": "./src/redacted-credential.ts",
|
|
23
23
|
"./secret-detection": "./src/secret-detection.ts",
|
|
24
24
|
"./stripe-currency": "./src/stripe-currency.ts",
|
|
25
|
-
"./error": "./src/error.ts"
|
|
25
|
+
"./error": "./src/error.ts",
|
|
26
|
+
"./reactions": "./src/reactions.ts",
|
|
27
|
+
"./guardian-requests": "./src/guardian-requests.ts",
|
|
28
|
+
"./platform-credential": "./src/platform-credential.ts"
|
|
26
29
|
},
|
|
27
30
|
"scripts": {
|
|
28
31
|
"typecheck": "bunx tsc --noEmit",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical vocabulary for a guardian request's lifecycle status, shared by
|
|
3
|
+
* the gateway that owns the request rows, the daemon that decides and
|
|
4
|
+
* projects them, and the web that renders the projection.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
export const GUARDIAN_REQUEST_STATUS_VALUES = [
|
|
9
|
+
"pending",
|
|
10
|
+
"approved",
|
|
11
|
+
"denied",
|
|
12
|
+
"expired",
|
|
13
|
+
"cancelled",
|
|
14
|
+
] as const;
|
|
15
|
+
export const GuardianRequestStatusSchema = z.enum(
|
|
16
|
+
GUARDIAN_REQUEST_STATUS_VALUES,
|
|
17
|
+
);
|
|
18
|
+
export type GuardianRequestStatus = z.infer<typeof GuardianRequestStatusSchema>;
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./rpc.js";
|
|
|
28
28
|
export * from "./trust-rules.js";
|
|
29
29
|
export * from "./ingress.js";
|
|
30
30
|
export * from "./no-response.js";
|
|
31
|
+
export * from "./platform-credential.js";
|
|
31
32
|
export * from "./remote-web-pairing.js";
|
|
32
33
|
export * from "./twilio-ingress.js";
|
|
33
34
|
export * from "./url-normalization.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract for asking an assistant whether its stored platform-managed
|
|
3
|
+
* credential still authenticates.
|
|
4
|
+
*
|
|
5
|
+
* The daemon route is the authoritative serving side:
|
|
6
|
+
* - `POST /v1/platform/verify-credential`
|
|
7
|
+
* (`assistant/src/runtime/routes/platform-routes.ts`)
|
|
8
|
+
*
|
|
9
|
+
* The daemon's platform client produces the verdict, the route reports it,
|
|
10
|
+
* and the `vellum` CLI reads it before deciding whether a stored key can be
|
|
11
|
+
* re-injected (`cli/src/lib/assistant-api-key-resolution.ts`), so all three
|
|
12
|
+
* share one definition and cannot silently drift. The web app reads the same
|
|
13
|
+
* shape through its generated daemon client.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What the platform said about the stored credential, right now.
|
|
19
|
+
*
|
|
20
|
+
* - `valid`: the platform accepted it.
|
|
21
|
+
* - `rejected`: the platform refused it (unauthorized or forbidden); the
|
|
22
|
+
* credential needs replacing.
|
|
23
|
+
* - `unknown`: the check itself could not run (no credential stored, the
|
|
24
|
+
* platform unreachable, a server error). Not evidence either way.
|
|
25
|
+
*/
|
|
26
|
+
export const PlatformCredentialVerificationStatusSchema = z.enum([
|
|
27
|
+
"valid",
|
|
28
|
+
"rejected",
|
|
29
|
+
"unknown",
|
|
30
|
+
]);
|
|
31
|
+
export type PlatformCredentialVerificationStatus = z.infer<
|
|
32
|
+
typeof PlatformCredentialVerificationStatusSchema
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
/** `POST /v1/platform/verify-credential` response body. */
|
|
36
|
+
export const PlatformVerifyCredentialResponseSchema = z.object({
|
|
37
|
+
status: PlatformCredentialVerificationStatusSchema,
|
|
38
|
+
});
|
|
39
|
+
export type PlatformVerifyCredentialResponse = z.infer<
|
|
40
|
+
typeof PlatformVerifyCredentialResponseSchema
|
|
41
|
+
>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical vocabulary for a reaction's emoji, shared by every service that
|
|
3
|
+
* handles one: the gateway that normalizes it, the daemon that stores and
|
|
4
|
+
* projects it, and the web that renders it.
|
|
5
|
+
*
|
|
6
|
+
* The kind is said by the channel rather than inferred from how the emoji
|
|
7
|
+
* is spelled. Modelled on Zulip's `reaction_type`, the one surveyed system
|
|
8
|
+
* that separates the namespace from the name.
|
|
9
|
+
*
|
|
10
|
+
* `shortcode` is a name in a channel's own namespace whose kind the channel
|
|
11
|
+
* does not disclose: Slack sends `+1` for the standard emoji and `blob_wave`
|
|
12
|
+
* for a workspace upload with nothing to tell them apart, and only the
|
|
13
|
+
* workspace token can resolve the second. It is a distinct kind from
|
|
14
|
+
* `unicode`, not a stand-in for an unknown one.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
export const REACTION_EMOJI_KINDS = ["unicode", "shortcode", "custom"] as const;
|
|
19
|
+
export type ReactionEmojiKind = (typeof REACTION_EMOJI_KINDS)[number];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The typed emoji fields every schema that carries a reaction spreads in,
|
|
23
|
+
* so the wire contract, the stored envelopes, and the response projection
|
|
24
|
+
* describe one shape. Optional throughout: a persisted row or a replayed
|
|
25
|
+
* payload may carry only the spelling.
|
|
26
|
+
*/
|
|
27
|
+
export const ReactionEmojiFieldsSchema = z.object({
|
|
28
|
+
/** Which namespace the emoji was drawn from. */
|
|
29
|
+
emojiKind: z.enum(REACTION_EMOJI_KINDS).optional(),
|
|
30
|
+
/**
|
|
31
|
+
* The emoji's name in that namespace: the character itself for `unicode`,
|
|
32
|
+
* the bare name for `shortcode` and `custom`. Never a mention form.
|
|
33
|
+
*/
|
|
34
|
+
emojiName: z.string().optional(),
|
|
35
|
+
/** The channel's id for a `custom` emoji, absent for every other kind. */
|
|
36
|
+
emojiId: z.string().optional(),
|
|
37
|
+
/** Whether a `custom` emoji animates. Absent for every other kind. */
|
|
38
|
+
emojiAnimated: z.boolean().optional(),
|
|
39
|
+
});
|
|
40
|
+
export type ReactionEmojiFields = z.infer<typeof ReactionEmojiFieldsSchema>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The typed emoji fields a source actually carries, with undefined ones
|
|
44
|
+
* omitted: an absent key and a present-but-undefined one serialize alike, but the
|
|
45
|
+
* stored envelope and the response should carry only what was declared. Every writer of a reaction shape (the wire
|
|
46
|
+
* payload, both stored envelopes, the response projection) copies the
|
|
47
|
+
* fields through this rather than restating the four-way pick.
|
|
48
|
+
*/
|
|
49
|
+
export function pickReactionEmojiFields(
|
|
50
|
+
source: ReactionEmojiFields,
|
|
51
|
+
): ReactionEmojiFields {
|
|
52
|
+
return {
|
|
53
|
+
...(source.emojiKind !== undefined ? { emojiKind: source.emojiKind } : {}),
|
|
54
|
+
...(source.emojiName !== undefined ? { emojiName: source.emojiName } : {}),
|
|
55
|
+
...(source.emojiId !== undefined ? { emojiId: source.emojiId } : {}),
|
|
56
|
+
...(source.emojiAnimated !== undefined
|
|
57
|
+
? { emojiAnimated: source.emojiAnimated }
|
|
58
|
+
: {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveInboundEventKind,
|
|
12
12
|
resolveInboundReactionPayload,
|
|
13
13
|
} from "../inbound-event-kind.js";
|
|
14
|
+
import { RuntimeInboundPayloadSchema } from "../inbound-contract.js";
|
|
14
15
|
|
|
15
16
|
describe("resolveInboundEventKind", () => {
|
|
16
17
|
test("a stamped kind wins over every legacy field", () => {
|
|
@@ -83,14 +84,58 @@ describe("inboundEventRefersToAnotherMessage", () => {
|
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
describe("resolveInboundReactionPayload", () => {
|
|
86
|
-
test("a structured payload wins outright", () => {
|
|
87
|
+
test("a structured payload wins outright, typed fields included", () => {
|
|
87
88
|
expect(
|
|
88
89
|
resolveInboundReactionPayload({
|
|
89
90
|
eventKind: "reaction",
|
|
90
|
-
reaction: {
|
|
91
|
+
reaction: {
|
|
92
|
+
op: "removed",
|
|
93
|
+
emoji: "+1",
|
|
94
|
+
emojiKind: "shortcode",
|
|
95
|
+
emojiName: "+1",
|
|
96
|
+
targetMessageId: "11.22",
|
|
97
|
+
},
|
|
91
98
|
callbackData: "reaction:eyes",
|
|
92
99
|
}),
|
|
93
|
-
).toEqual({
|
|
100
|
+
).toEqual({
|
|
101
|
+
op: "removed",
|
|
102
|
+
emoji: "+1",
|
|
103
|
+
emojiKind: "shortcode",
|
|
104
|
+
emojiName: "+1",
|
|
105
|
+
targetMessageId: "11.22",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a stored payload without typed fields recovers its kind", () => {
|
|
110
|
+
// Rows predating the typed fields carry the spelling only. This is the
|
|
111
|
+
// one place the kind is inferred, and each spelling has one answer.
|
|
112
|
+
const at = (emoji: string) =>
|
|
113
|
+
resolveInboundReactionPayload({
|
|
114
|
+
eventKind: "reaction",
|
|
115
|
+
reaction: { op: "added", emoji, targetMessageId: "11.22" },
|
|
116
|
+
});
|
|
117
|
+
expect(at("<:blob_wave:987>")).toMatchObject({
|
|
118
|
+
emojiKind: "custom",
|
|
119
|
+
emojiName: "blob_wave",
|
|
120
|
+
emojiId: "987",
|
|
121
|
+
});
|
|
122
|
+
// The plain form records nothing about animation, so the field is
|
|
123
|
+
// absent rather than a fabricated false; a resolver must not read
|
|
124
|
+
// "not animated" off a row that never knew.
|
|
125
|
+
expect(at("<:blob_wave:987>")).not.toHaveProperty("emojiAnimated");
|
|
126
|
+
expect(at("<a:party:5>")).toMatchObject({
|
|
127
|
+
emojiKind: "custom",
|
|
128
|
+
emojiName: "party",
|
|
129
|
+
emojiId: "5",
|
|
130
|
+
emojiAnimated: true,
|
|
131
|
+
});
|
|
132
|
+
expect(at("thumbsup")).toMatchObject({
|
|
133
|
+
emojiKind: "shortcode",
|
|
134
|
+
emojiName: "thumbsup",
|
|
135
|
+
});
|
|
136
|
+
expect(at("🎉")).toMatchObject({ emojiKind: "unicode", emojiName: "🎉" });
|
|
137
|
+
// The spelling itself is never rewritten: the write path parses it back.
|
|
138
|
+
expect(at("<:blob_wave:987>")?.emoji).toBe("<:blob_wave:987>");
|
|
94
139
|
});
|
|
95
140
|
|
|
96
141
|
test("a replayed payload parses its sentinel and wire target", () => {
|
|
@@ -99,13 +144,25 @@ describe("resolveInboundReactionPayload", () => {
|
|
|
99
144
|
callbackData: "reaction:thumbsup",
|
|
100
145
|
sourceMetadata: { messageId: "33.44" },
|
|
101
146
|
}),
|
|
102
|
-
).toEqual({
|
|
147
|
+
).toEqual({
|
|
148
|
+
op: "added",
|
|
149
|
+
emoji: "thumbsup",
|
|
150
|
+
emojiKind: "shortcode",
|
|
151
|
+
emojiName: "thumbsup",
|
|
152
|
+
targetMessageId: "33.44",
|
|
153
|
+
});
|
|
103
154
|
expect(
|
|
104
155
|
resolveInboundReactionPayload({
|
|
105
156
|
callbackData: "reaction_removed:thumbsup",
|
|
106
157
|
sourceMetadata: { messageId: "33.44" },
|
|
107
158
|
}),
|
|
108
|
-
).toEqual({
|
|
159
|
+
).toEqual({
|
|
160
|
+
op: "removed",
|
|
161
|
+
emoji: "thumbsup",
|
|
162
|
+
emojiKind: "shortcode",
|
|
163
|
+
emojiName: "thumbsup",
|
|
164
|
+
targetMessageId: "33.44",
|
|
165
|
+
});
|
|
109
166
|
});
|
|
110
167
|
|
|
111
168
|
test("no emoji or no target is not a reaction payload", () => {
|
|
@@ -126,3 +183,51 @@ describe("resolveInboundReactionPayload", () => {
|
|
|
126
183
|
).toBeNull();
|
|
127
184
|
});
|
|
128
185
|
});
|
|
186
|
+
|
|
187
|
+
describe("RuntimeInboundPayloadSchema carries a reaction's typed emoji", () => {
|
|
188
|
+
const base = {
|
|
189
|
+
sourceChannel: "discord",
|
|
190
|
+
interface: "discord",
|
|
191
|
+
conversationExternalId: "chan-1",
|
|
192
|
+
externalMessageId: "msg-1:reaction:<:party_blob:111>:user-1:ingest-1",
|
|
193
|
+
content: "",
|
|
194
|
+
eventKind: "reaction",
|
|
195
|
+
actorExternalId: "user-1",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
test("a custom emoji keeps all four typed fields through the wire schema", () => {
|
|
199
|
+
const parsed = RuntimeInboundPayloadSchema.parse({
|
|
200
|
+
...base,
|
|
201
|
+
reaction: {
|
|
202
|
+
op: "added",
|
|
203
|
+
emoji: "<:party_blob:111>",
|
|
204
|
+
emojiKind: "custom",
|
|
205
|
+
emojiName: "party_blob",
|
|
206
|
+
emojiId: "111",
|
|
207
|
+
emojiAnimated: true,
|
|
208
|
+
targetMessageId: "msg-1",
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
expect(parsed.reaction).toEqual({
|
|
212
|
+
op: "added",
|
|
213
|
+
emoji: "<:party_blob:111>",
|
|
214
|
+
emojiKind: "custom",
|
|
215
|
+
emojiName: "party_blob",
|
|
216
|
+
emojiId: "111",
|
|
217
|
+
emojiAnimated: true,
|
|
218
|
+
targetMessageId: "msg-1",
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("a reaction carrying only the spelling still parses", () => {
|
|
223
|
+
const parsed = RuntimeInboundPayloadSchema.parse({
|
|
224
|
+
...base,
|
|
225
|
+
reaction: { op: "removed", emoji: "+1", targetMessageId: "msg-1" },
|
|
226
|
+
});
|
|
227
|
+
expect(parsed.reaction).toEqual({
|
|
228
|
+
op: "removed",
|
|
229
|
+
emoji: "+1",
|
|
230
|
+
targetMessageId: "msg-1",
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* which do not change.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { GuardianRequestStatusSchema } from "@vellumai/service-contracts/guardian-requests";
|
|
20
21
|
import { z } from "zod";
|
|
21
22
|
|
|
22
23
|
import {
|
|
@@ -28,19 +29,11 @@ import {
|
|
|
28
29
|
// Enums
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
"pending",
|
|
33
|
-
"approved",
|
|
34
|
-
"denied",
|
|
35
|
-
"expired",
|
|
36
|
-
"cancelled",
|
|
37
|
-
] as const;
|
|
38
|
-
|
|
39
|
-
export const GuardianRequestStatusSchema = z.enum(
|
|
32
|
+
export {
|
|
40
33
|
GUARDIAN_REQUEST_STATUS_VALUES,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
type GuardianRequestStatus,
|
|
35
|
+
GuardianRequestStatusSchema,
|
|
36
|
+
} from "@vellumai/service-contracts/guardian-requests";
|
|
44
37
|
|
|
45
38
|
const GUARDIAN_REQUEST_KIND_VALUES = [
|
|
46
39
|
"access_request",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import { INBOUND_EVENT_KINDS } from "./inbound-event-kind.js";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
|
|
16
|
+
import { ReactionEmojiFieldsSchema } from "@vellumai/service-contracts/reactions";
|
|
17
|
+
|
|
16
18
|
import { ChannelConversationTypeSchema } from "./channel-permission-contract.js";
|
|
17
19
|
|
|
18
20
|
import { AdmissionPolicySchema } from "./admission-policy-contract.js";
|
|
@@ -183,6 +185,7 @@ export const RuntimeInboundPayloadSchema = z.object({
|
|
|
183
185
|
.object({
|
|
184
186
|
op: z.enum(["added", "removed"]),
|
|
185
187
|
emoji: z.string(),
|
|
188
|
+
...ReactionEmojiFieldsSchema.shape,
|
|
186
189
|
targetMessageId: z.string(),
|
|
187
190
|
})
|
|
188
191
|
.optional(),
|