@vellumai/assistant 0.11.5 → 0.11.6-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/AGENTS.md +5 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +70 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +16 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +6 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +121 -61
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/openapi.yaml +421 -15
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/app-pin-store.test.ts +149 -0
- package/src/__tests__/channel-availability-routes.test.ts +23 -1
- package/src/__tests__/channel-readiness-discord.test.ts +231 -0
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +141 -0
- package/src/__tests__/channel-reply-delivery.test.ts +4 -4
- package/src/__tests__/client-os-metadata-persistence.test.ts +23 -10
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-seed-composer.test.ts +8 -0
- package/src/__tests__/conversation-slash-commands.test.ts +8 -0
- package/src/__tests__/disk-pressure-policy.test.ts +6 -0
- package/src/__tests__/gemini-provider.test.ts +138 -0
- package/src/__tests__/history-repair.test.ts +105 -3
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +45 -0
- package/src/__tests__/migration-import-from-path.test.ts +349 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +102 -0
- package/src/__tests__/oauth-commands-routes.test.ts +89 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +7 -6
- package/src/__tests__/openai-provider.test.ts +18 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/platform-callback-registration.test.ts +184 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +71 -3
- package/src/__tests__/pricing.test.ts +2 -2
- package/src/__tests__/public-ingress-urls.test.ts +36 -0
- package/src/__tests__/resolve-trust-class.test.ts +0 -48
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/agent/history-repair/history-repair.ts +45 -14
- package/src/agent/loop.ts +4 -1
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +11 -0
- package/src/apps/app-pin-reconciler.ts +92 -0
- package/src/apps/app-pin-store.ts +125 -0
- package/src/channels/gateway-channel-socket-health.ts +32 -0
- package/src/channels/gateway-discord-admission.ts +32 -0
- package/src/channels/types.ts +20 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +67 -2
- package/src/cli/commands/channels/__tests__/channels.test.ts +85 -0
- package/src/cli/commands/channels/index.ts +45 -31
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +28 -2
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/request.test.ts +290 -0
- package/src/cli/commands/oauth/request.ts +57 -41
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/conversation-error.ts +24 -2
- package/src/daemon/conversation-slash.ts +6 -15
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/disk-pressure-policy.ts +7 -1
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +26 -0
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/trust-context.ts +0 -37
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/platform-callback-registration.ts +28 -2
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +25 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +8 -2
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +212 -10
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +5 -2
- package/src/live-voice/live-voice-connection.ts +46 -8
- package/src/live-voice/live-voice-manager.ts +25 -0
- package/src/live-voice/live-voice-session-manager.ts +318 -2
- package/src/live-voice/live-voice-session.ts +52 -2
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +126 -68
- package/src/messaging/providers/channel-transport.ts +64 -47
- package/src/messaging/providers/discord/send.test.ts +46 -1
- package/src/messaging/providers/discord/send.ts +51 -0
- package/src/messaging/providers/discord/transport.ts +26 -3
- package/src/messaging/providers/index.ts +22 -47
- package/src/messaging/providers/slack/send.test.ts +83 -26
- package/src/messaging/providers/slack/send.ts +120 -51
- package/src/messaging/providers/slack/stream-tasks.test.ts +26 -0
- package/src/messaging/providers/slack/stream-tasks.ts +39 -0
- package/src/messaging/providers/slack/transport.ts +24 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +109 -12
- package/src/messaging/providers/telegram-bot/send.ts +43 -0
- package/src/messaging/providers/telegram-bot/transport.ts +25 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +30 -7
- package/src/notifications/adapters/telegram.ts +48 -1
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/conversation-seed-composer.ts +7 -2
- package/src/oauth/byo-connection.test.ts +63 -0
- package/src/oauth/byo-connection.ts +16 -15
- package/src/oauth/connection.test.ts +111 -0
- package/src/oauth/connection.ts +142 -1
- package/src/oauth/platform-connection.test.ts +34 -0
- package/src/oauth/platform-connection.ts +28 -5
- package/src/oauth/seed-providers.ts +15 -1
- package/src/permissions/types.ts +3 -1
- package/src/persistence/conversation-crud.ts +46 -0
- package/src/persistence/conversation-types.ts +11 -9
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/369-create-app-pins.ts +37 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +17 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +6 -0
- package/src/plugins/mtime-cache.ts +11 -0
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/connection-resolution.ts +23 -1
- package/src/providers/content-blocks.ts +9 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +13 -5
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +0 -9
- package/src/providers/inference/credential-rotation.ts +61 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/model-catalog.ts +170 -125
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +42 -60
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/orphan-tool-result-guard.test.ts +34 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +75 -29
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/openrouter/client.ts +0 -1
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/providers/vercel-ai-gateway/client.ts +0 -1
- package/src/runtime/AGENTS.md +1 -0
- package/src/runtime/__tests__/desktop-presence.test.ts +27 -4
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/channel-readiness-service.ts +214 -14
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +2 -2
- package/src/runtime/desktop-presence.ts +24 -21
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-server.ts +181 -1
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +104 -0
- package/src/runtime/migrations/staged-import-path.ts +116 -0
- package/src/runtime/routes/__tests__/app-pin-routes.test.ts +383 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -56
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/app-management-routes.ts +140 -29
- package/src/runtime/routes/channel-availability-routes.ts +1 -0
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/guardian-approval-interception.ts +24 -33
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +2 -0
- package/src/runtime/routes/inbound-message-handler.ts +10 -7
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +166 -308
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +158 -335
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +24 -4
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/live-voice-routes.test.ts +40 -1
- package/src/runtime/routes/live-voice-routes.ts +34 -0
- package/src/runtime/routes/migration-routes.ts +218 -10
- package/src/runtime/routes/oauth-commands-routes.ts +23 -16
- package/src/runtime/routes/plugins-routes.ts +12 -28
- package/src/runtime/routes/question-routes.ts +6 -0
- package/src/runtime/routes/secret-routes.ts +7 -27
- package/src/runtime/routes/settings-routes.ts +9 -6
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +230 -121
- package/src/runtime/slack-reply-session.ts +113 -81
- package/src/runtime/{slack-task-progress.test.ts → task-progress.test.ts} +1 -28
- package/src/runtime/{slack-task-progress.ts → task-progress.ts} +30 -51
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +108 -0
- package/src/telemetry/live-voice-funnel.ts +75 -8
- package/src/tools/credentials/store.ts +18 -6
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +97 -2
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +141 -55
- package/src/tools/types.ts +2 -1
- package/src/util/oauth-request-body.test.ts +74 -0
- package/src/util/oauth-request-body.ts +60 -0
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
package/AGENTS.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
For error handling conventions (throw vs result objects vs null), see [docs/error-handling.md](docs/error-handling.md).
|
|
4
4
|
|
|
5
|
-
Subdirectory-scoped rules live in local AGENTS.md files
|
|
5
|
+
Subdirectory-scoped rules live in local AGENTS.md files throughout `src/`, some
|
|
6
|
+
of them nested several levels down. Read the one nearest the code you are
|
|
7
|
+
changing plus any above it, and follow the nearest where two disagree.
|
|
8
|
+
`git ls-files ':(top)assistant/src/**/AGENTS.md'` lists them from anywhere in
|
|
9
|
+
the tree.
|
|
6
10
|
|
|
7
11
|
## Adding new environment variables
|
|
8
12
|
|
|
@@ -2,7 +2,12 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeHttpPublicBaseUrl,
|
|
5
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash,
|
|
5
6
|
normalizePublicBaseUrl,
|
|
7
|
+
parseRecordedAssistantId,
|
|
8
|
+
parseTunnelRecord,
|
|
9
|
+
trimmedNonEmptyString,
|
|
10
|
+
TUNNEL_PROVIDERS,
|
|
6
11
|
velayHostForPlatformHost,
|
|
7
12
|
} from "../ingress.js";
|
|
8
13
|
import {
|
|
@@ -85,6 +90,35 @@ describe("normalizeHttpPublicBaseUrl", () => {
|
|
|
85
90
|
});
|
|
86
91
|
});
|
|
87
92
|
|
|
93
|
+
describe("normalizeHttpPublicBaseUrlWithoutTrailingSlash", () => {
|
|
94
|
+
test("drops the root path its sibling always emits", () => {
|
|
95
|
+
expect(
|
|
96
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test"),
|
|
97
|
+
).toBe("https://x.test");
|
|
98
|
+
expect(
|
|
99
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test/"),
|
|
100
|
+
).toBe("https://x.test");
|
|
101
|
+
expect(
|
|
102
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(" https://x.test/api/// "),
|
|
103
|
+
).toBe("https://x.test/api");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("rejects everything its sibling rejects", () => {
|
|
107
|
+
for (const value of [
|
|
108
|
+
"",
|
|
109
|
+
" ",
|
|
110
|
+
"notaurl",
|
|
111
|
+
"ftp://x.test",
|
|
112
|
+
"https://x.test?a=b",
|
|
113
|
+
42,
|
|
114
|
+
]) {
|
|
115
|
+
expect(
|
|
116
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(value),
|
|
117
|
+
).toBeUndefined();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
88
122
|
describe("Twilio ingress helpers", () => {
|
|
89
123
|
test("resolves public base URL with fallback", () => {
|
|
90
124
|
expect(
|
|
@@ -123,3 +157,87 @@ describe("Twilio ingress helpers", () => {
|
|
|
123
157
|
});
|
|
124
158
|
});
|
|
125
159
|
});
|
|
160
|
+
|
|
161
|
+
describe("parseTunnelRecord", () => {
|
|
162
|
+
const TUNNEL_URL = "https://assistant-1.example.ts.net";
|
|
163
|
+
|
|
164
|
+
test("accepts every provider in the registry", () => {
|
|
165
|
+
for (const provider of TUNNEL_PROVIDERS) {
|
|
166
|
+
expect(
|
|
167
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
168
|
+
).toEqual({ provider, publicBaseUrl: TUNNEL_URL });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("returns the URL in the shape its own validator produces", () => {
|
|
173
|
+
// Padding and trailing slashes are normalized away rather than handed
|
|
174
|
+
// back, so readers never re-normalize what they were given.
|
|
175
|
+
for (const publicBaseUrl of [
|
|
176
|
+
` ${TUNNEL_URL} `,
|
|
177
|
+
`${TUNNEL_URL}/`,
|
|
178
|
+
`${TUNNEL_URL}///`,
|
|
179
|
+
]) {
|
|
180
|
+
expect(
|
|
181
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
182
|
+
).toEqual({ provider: "ngrok", publicBaseUrl: TUNNEL_URL });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("rejects values that are not a record", () => {
|
|
187
|
+
for (const value of [undefined, null, "nonsense", 42, []]) {
|
|
188
|
+
expect(parseTunnelRecord(value)).toBeNull();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("rejects a record missing either field", () => {
|
|
193
|
+
expect(parseTunnelRecord({ provider: "ngrok" })).toBeNull();
|
|
194
|
+
expect(parseTunnelRecord({ publicBaseUrl: TUNNEL_URL })).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("rejects a provider outside the registry", () => {
|
|
198
|
+
// Readers render the provider into a `vellum tunnel --provider <name>`
|
|
199
|
+
// command, so a hand-edited config must not reach a terminal.
|
|
200
|
+
for (const provider of ["wireguard", "; rm -rf /", "", 42]) {
|
|
201
|
+
expect(
|
|
202
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
203
|
+
).toBeNull();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("rejects a URL that is not absolute HTTP(S)", () => {
|
|
208
|
+
for (const publicBaseUrl of [
|
|
209
|
+
"",
|
|
210
|
+
" ",
|
|
211
|
+
"not-a-url",
|
|
212
|
+
"example.ts.net",
|
|
213
|
+
"ftp://x.test",
|
|
214
|
+
"https://one.ngrok.app?token=x",
|
|
215
|
+
42,
|
|
216
|
+
]) {
|
|
217
|
+
expect(
|
|
218
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
219
|
+
).toBeNull();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("trimmedNonEmptyString", () => {
|
|
225
|
+
test("trims and rejects blank or non-string values", () => {
|
|
226
|
+
expect(trimmedNonEmptyString(" assistant-1 ")).toBe("assistant-1");
|
|
227
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
228
|
+
expect(trimmedNonEmptyString(value)).toBeUndefined();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("parseRecordedAssistantId", () => {
|
|
234
|
+
test("trims a recorded id", () => {
|
|
235
|
+
expect(parseRecordedAssistantId(" assistant-1 ")).toBe("assistant-1");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("rejects blank and non-string values", () => {
|
|
239
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
240
|
+
expect(parseRecordedAssistantId(value)).toBeNull();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts
CHANGED
|
@@ -48,3 +48,106 @@ export function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {
|
|
|
48
48
|
return undefined;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The same validation as `normalizeHttpPublicBaseUrl`, minus the root path it
|
|
54
|
+
* always emits (`https://x` becomes `https://x/`). Callers that append a path
|
|
55
|
+
* or persist the result want this shape, so they neither join through a double
|
|
56
|
+
* slash nor re-strip what the normalizer just added.
|
|
57
|
+
*/
|
|
58
|
+
export function normalizeHttpPublicBaseUrlWithoutTrailingSlash(
|
|
59
|
+
value: unknown,
|
|
60
|
+
): string | undefined {
|
|
61
|
+
return normalizePublicBaseUrl(normalizeHttpPublicBaseUrl(value));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A trimmed string, or undefined when the value is not a non-blank string.
|
|
66
|
+
*
|
|
67
|
+
* Exported because the identity check in the daemon's tunnel probe compares an
|
|
68
|
+
* id recorded here against one an edge serves, and the two sides have to trim
|
|
69
|
+
* alike: a padded served id must read as a match, not as a different
|
|
70
|
+
* assistant.
|
|
71
|
+
*/
|
|
72
|
+
export function trimmedNonEmptyString(value: unknown): string | undefined {
|
|
73
|
+
if (typeof value !== "string") {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Records `vellum tunnel` leaves in the workspace config's `ingress` section.
|
|
81
|
+
// The CLI writes them and the daemon reads them, and the two are separate
|
|
82
|
+
// build units that cannot import each other, so the key names, the provider
|
|
83
|
+
// allowlist, and the validation all live here rather than in a copy on each
|
|
84
|
+
// side.
|
|
85
|
+
|
|
86
|
+
/** Key under `ingress` holding the tunnel that most recently ran. */
|
|
87
|
+
export const INGRESS_LAST_TUNNEL_KEY = "lastTunnel";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Key under `ingress` holding a tunnel published for device pairing alone.
|
|
91
|
+
*
|
|
92
|
+
* A tailnet-only tunnel started while webhook integrations are configured
|
|
93
|
+
* cannot carry their callbacks, so it leaves `publicBaseUrl` (the callback
|
|
94
|
+
* base) as it is and records itself here. Readers that answer the pairing card
|
|
95
|
+
* prefer this address over `publicBaseUrl`; teardown drops the key.
|
|
96
|
+
*/
|
|
97
|
+
export const INGRESS_PAIRING_TUNNEL_KEY = "pairingTunnel";
|
|
98
|
+
|
|
99
|
+
/** Key under `ingress` holding the assistant id that tunnel fronted. */
|
|
100
|
+
export const INGRESS_ASSISTANT_ID_KEY = "assistantId";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The local tunnel providers the CLI can start, in the order they are offered.
|
|
104
|
+
* `vellum tunnel`'s accepted `--provider` values and the persisted-record
|
|
105
|
+
* validation below both derive from it.
|
|
106
|
+
*/
|
|
107
|
+
export const TUNNEL_PROVIDERS = ["ngrok", "cloudflare", "tailscale"] as const;
|
|
108
|
+
|
|
109
|
+
export type TunnelProviderName = (typeof TUNNEL_PROVIDERS)[number];
|
|
110
|
+
|
|
111
|
+
/** A recorded tunnel, kept after teardown so UIs can name the command to restart. */
|
|
112
|
+
export interface TunnelRecord {
|
|
113
|
+
provider: TunnelProviderName;
|
|
114
|
+
publicBaseUrl: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isTunnelProviderName(value: unknown): value is TunnelProviderName {
|
|
118
|
+
return (
|
|
119
|
+
typeof value === "string" &&
|
|
120
|
+
(TUNNEL_PROVIDERS as readonly string[]).includes(value)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parse an `ingress.lastTunnel` or `ingress.pairingTunnel` value, or null when
|
|
126
|
+
* it is absent or malformed.
|
|
127
|
+
*
|
|
128
|
+
* A hand-edited config must not hand callers an unusable address or a provider
|
|
129
|
+
* name no command accepts: the provider is checked against the allowlist
|
|
130
|
+
* because readers render it into a restart command, and the URL faces the same
|
|
131
|
+
* absolute HTTP(S) constraint as every other public base URL. The URL comes
|
|
132
|
+
* back in the shape the validator produced, so readers never re-normalize it.
|
|
133
|
+
*/
|
|
134
|
+
export function parseTunnelRecord(value: unknown): TunnelRecord | null {
|
|
135
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const { provider, publicBaseUrl } = value as Record<string, unknown>;
|
|
139
|
+
if (!isTunnelProviderName(provider)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const normalized =
|
|
143
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);
|
|
144
|
+
if (normalized === undefined) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return { provider, publicBaseUrl: normalized };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Parse an `ingress.assistantId` value, or null when it is absent or blank. */
|
|
151
|
+
export function parseRecordedAssistantId(value: unknown): string | null {
|
|
152
|
+
return trimmedNonEmptyString(value) ?? null;
|
|
153
|
+
}
|
|
@@ -2,7 +2,12 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeHttpPublicBaseUrl,
|
|
5
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash,
|
|
5
6
|
normalizePublicBaseUrl,
|
|
7
|
+
parseRecordedAssistantId,
|
|
8
|
+
parseTunnelRecord,
|
|
9
|
+
trimmedNonEmptyString,
|
|
10
|
+
TUNNEL_PROVIDERS,
|
|
6
11
|
velayHostForPlatformHost,
|
|
7
12
|
} from "../ingress.js";
|
|
8
13
|
import {
|
|
@@ -85,6 +90,35 @@ describe("normalizeHttpPublicBaseUrl", () => {
|
|
|
85
90
|
});
|
|
86
91
|
});
|
|
87
92
|
|
|
93
|
+
describe("normalizeHttpPublicBaseUrlWithoutTrailingSlash", () => {
|
|
94
|
+
test("drops the root path its sibling always emits", () => {
|
|
95
|
+
expect(
|
|
96
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test"),
|
|
97
|
+
).toBe("https://x.test");
|
|
98
|
+
expect(
|
|
99
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test/"),
|
|
100
|
+
).toBe("https://x.test");
|
|
101
|
+
expect(
|
|
102
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(" https://x.test/api/// "),
|
|
103
|
+
).toBe("https://x.test/api");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("rejects everything its sibling rejects", () => {
|
|
107
|
+
for (const value of [
|
|
108
|
+
"",
|
|
109
|
+
" ",
|
|
110
|
+
"notaurl",
|
|
111
|
+
"ftp://x.test",
|
|
112
|
+
"https://x.test?a=b",
|
|
113
|
+
42,
|
|
114
|
+
]) {
|
|
115
|
+
expect(
|
|
116
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(value),
|
|
117
|
+
).toBeUndefined();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
88
122
|
describe("Twilio ingress helpers", () => {
|
|
89
123
|
test("resolves public base URL with fallback", () => {
|
|
90
124
|
expect(
|
|
@@ -123,3 +157,87 @@ describe("Twilio ingress helpers", () => {
|
|
|
123
157
|
});
|
|
124
158
|
});
|
|
125
159
|
});
|
|
160
|
+
|
|
161
|
+
describe("parseTunnelRecord", () => {
|
|
162
|
+
const TUNNEL_URL = "https://assistant-1.example.ts.net";
|
|
163
|
+
|
|
164
|
+
test("accepts every provider in the registry", () => {
|
|
165
|
+
for (const provider of TUNNEL_PROVIDERS) {
|
|
166
|
+
expect(
|
|
167
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
168
|
+
).toEqual({ provider, publicBaseUrl: TUNNEL_URL });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("returns the URL in the shape its own validator produces", () => {
|
|
173
|
+
// Padding and trailing slashes are normalized away rather than handed
|
|
174
|
+
// back, so readers never re-normalize what they were given.
|
|
175
|
+
for (const publicBaseUrl of [
|
|
176
|
+
` ${TUNNEL_URL} `,
|
|
177
|
+
`${TUNNEL_URL}/`,
|
|
178
|
+
`${TUNNEL_URL}///`,
|
|
179
|
+
]) {
|
|
180
|
+
expect(
|
|
181
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
182
|
+
).toEqual({ provider: "ngrok", publicBaseUrl: TUNNEL_URL });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("rejects values that are not a record", () => {
|
|
187
|
+
for (const value of [undefined, null, "nonsense", 42, []]) {
|
|
188
|
+
expect(parseTunnelRecord(value)).toBeNull();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("rejects a record missing either field", () => {
|
|
193
|
+
expect(parseTunnelRecord({ provider: "ngrok" })).toBeNull();
|
|
194
|
+
expect(parseTunnelRecord({ publicBaseUrl: TUNNEL_URL })).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("rejects a provider outside the registry", () => {
|
|
198
|
+
// Readers render the provider into a `vellum tunnel --provider <name>`
|
|
199
|
+
// command, so a hand-edited config must not reach a terminal.
|
|
200
|
+
for (const provider of ["wireguard", "; rm -rf /", "", 42]) {
|
|
201
|
+
expect(
|
|
202
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
203
|
+
).toBeNull();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("rejects a URL that is not absolute HTTP(S)", () => {
|
|
208
|
+
for (const publicBaseUrl of [
|
|
209
|
+
"",
|
|
210
|
+
" ",
|
|
211
|
+
"not-a-url",
|
|
212
|
+
"example.ts.net",
|
|
213
|
+
"ftp://x.test",
|
|
214
|
+
"https://one.ngrok.app?token=x",
|
|
215
|
+
42,
|
|
216
|
+
]) {
|
|
217
|
+
expect(
|
|
218
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
219
|
+
).toBeNull();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("trimmedNonEmptyString", () => {
|
|
225
|
+
test("trims and rejects blank or non-string values", () => {
|
|
226
|
+
expect(trimmedNonEmptyString(" assistant-1 ")).toBe("assistant-1");
|
|
227
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
228
|
+
expect(trimmedNonEmptyString(value)).toBeUndefined();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("parseRecordedAssistantId", () => {
|
|
234
|
+
test("trims a recorded id", () => {
|
|
235
|
+
expect(parseRecordedAssistantId(" assistant-1 ")).toBe("assistant-1");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("rejects blank and non-string values", () => {
|
|
239
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
240
|
+
expect(parseRecordedAssistantId(value)).toBeNull();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -48,3 +48,106 @@ export function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {
|
|
|
48
48
|
return undefined;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The same validation as `normalizeHttpPublicBaseUrl`, minus the root path it
|
|
54
|
+
* always emits (`https://x` becomes `https://x/`). Callers that append a path
|
|
55
|
+
* or persist the result want this shape, so they neither join through a double
|
|
56
|
+
* slash nor re-strip what the normalizer just added.
|
|
57
|
+
*/
|
|
58
|
+
export function normalizeHttpPublicBaseUrlWithoutTrailingSlash(
|
|
59
|
+
value: unknown,
|
|
60
|
+
): string | undefined {
|
|
61
|
+
return normalizePublicBaseUrl(normalizeHttpPublicBaseUrl(value));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A trimmed string, or undefined when the value is not a non-blank string.
|
|
66
|
+
*
|
|
67
|
+
* Exported because the identity check in the daemon's tunnel probe compares an
|
|
68
|
+
* id recorded here against one an edge serves, and the two sides have to trim
|
|
69
|
+
* alike: a padded served id must read as a match, not as a different
|
|
70
|
+
* assistant.
|
|
71
|
+
*/
|
|
72
|
+
export function trimmedNonEmptyString(value: unknown): string | undefined {
|
|
73
|
+
if (typeof value !== "string") {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Records `vellum tunnel` leaves in the workspace config's `ingress` section.
|
|
81
|
+
// The CLI writes them and the daemon reads them, and the two are separate
|
|
82
|
+
// build units that cannot import each other, so the key names, the provider
|
|
83
|
+
// allowlist, and the validation all live here rather than in a copy on each
|
|
84
|
+
// side.
|
|
85
|
+
|
|
86
|
+
/** Key under `ingress` holding the tunnel that most recently ran. */
|
|
87
|
+
export const INGRESS_LAST_TUNNEL_KEY = "lastTunnel";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Key under `ingress` holding a tunnel published for device pairing alone.
|
|
91
|
+
*
|
|
92
|
+
* A tailnet-only tunnel started while webhook integrations are configured
|
|
93
|
+
* cannot carry their callbacks, so it leaves `publicBaseUrl` (the callback
|
|
94
|
+
* base) as it is and records itself here. Readers that answer the pairing card
|
|
95
|
+
* prefer this address over `publicBaseUrl`; teardown drops the key.
|
|
96
|
+
*/
|
|
97
|
+
export const INGRESS_PAIRING_TUNNEL_KEY = "pairingTunnel";
|
|
98
|
+
|
|
99
|
+
/** Key under `ingress` holding the assistant id that tunnel fronted. */
|
|
100
|
+
export const INGRESS_ASSISTANT_ID_KEY = "assistantId";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The local tunnel providers the CLI can start, in the order they are offered.
|
|
104
|
+
* `vellum tunnel`'s accepted `--provider` values and the persisted-record
|
|
105
|
+
* validation below both derive from it.
|
|
106
|
+
*/
|
|
107
|
+
export const TUNNEL_PROVIDERS = ["ngrok", "cloudflare", "tailscale"] as const;
|
|
108
|
+
|
|
109
|
+
export type TunnelProviderName = (typeof TUNNEL_PROVIDERS)[number];
|
|
110
|
+
|
|
111
|
+
/** A recorded tunnel, kept after teardown so UIs can name the command to restart. */
|
|
112
|
+
export interface TunnelRecord {
|
|
113
|
+
provider: TunnelProviderName;
|
|
114
|
+
publicBaseUrl: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isTunnelProviderName(value: unknown): value is TunnelProviderName {
|
|
118
|
+
return (
|
|
119
|
+
typeof value === "string" &&
|
|
120
|
+
(TUNNEL_PROVIDERS as readonly string[]).includes(value)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parse an `ingress.lastTunnel` or `ingress.pairingTunnel` value, or null when
|
|
126
|
+
* it is absent or malformed.
|
|
127
|
+
*
|
|
128
|
+
* A hand-edited config must not hand callers an unusable address or a provider
|
|
129
|
+
* name no command accepts: the provider is checked against the allowlist
|
|
130
|
+
* because readers render it into a restart command, and the URL faces the same
|
|
131
|
+
* absolute HTTP(S) constraint as every other public base URL. The URL comes
|
|
132
|
+
* back in the shape the validator produced, so readers never re-normalize it.
|
|
133
|
+
*/
|
|
134
|
+
export function parseTunnelRecord(value: unknown): TunnelRecord | null {
|
|
135
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const { provider, publicBaseUrl } = value as Record<string, unknown>;
|
|
139
|
+
if (!isTunnelProviderName(provider)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const normalized =
|
|
143
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);
|
|
144
|
+
if (normalized === undefined) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return { provider, publicBaseUrl: normalized };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Parse an `ingress.assistantId` value, or null when it is absent or blank. */
|
|
151
|
+
export function parseRecordedAssistantId(value: unknown): string | null {
|
|
152
|
+
return trimmedNonEmptyString(value) ?? null;
|
|
153
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Shared IPC contracts for assistant-to-gateway gateway-owned reads.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { CHANNEL_IDS } from "@vellumai/service-contracts/channels";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
|
|
7
8
|
export const GATEWAY_LOG_LEVEL_NAMES = [
|
|
@@ -506,3 +507,72 @@ export const ClassifyRiskIpcResponseSchema = z.object({
|
|
|
506
507
|
export type ClassifyRiskIpcResponse = z.infer<
|
|
507
508
|
typeof ClassifyRiskIpcResponseSchema
|
|
508
509
|
>;
|
|
510
|
+
|
|
511
|
+
// ── Channel socket health ────────────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Whether a channel that holds a long-lived inbound socket is currently
|
|
515
|
+
* receiving.
|
|
516
|
+
*
|
|
517
|
+
* Only channels whose ingress is a socket the gateway owns can answer this.
|
|
518
|
+
* Webhook channels answer the same question from the daemon side by checking
|
|
519
|
+
* their registration instead, so they report `unsupported` here rather than a
|
|
520
|
+
* misleading `disconnected`.
|
|
521
|
+
*/
|
|
522
|
+
export const CHANNEL_SOCKET_HEALTH_STATUSES = [
|
|
523
|
+
/** A live connection the liveness watchdog is vouching for. */
|
|
524
|
+
"connected",
|
|
525
|
+
/** The channel is configured and running, but holds no live connection. */
|
|
526
|
+
"disconnected",
|
|
527
|
+
/** No client exists, because the channel's credentials are not configured. */
|
|
528
|
+
"not_configured",
|
|
529
|
+
/** This channel's ingress is not a gateway-owned socket. */
|
|
530
|
+
"unsupported",
|
|
531
|
+
] as const;
|
|
532
|
+
|
|
533
|
+
export const ChannelSocketHealthIpcParamsSchema = z.object({
|
|
534
|
+
channel: z.enum(CHANNEL_IDS),
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
export type ChannelSocketHealthIpcParams = z.infer<
|
|
538
|
+
typeof ChannelSocketHealthIpcParamsSchema
|
|
539
|
+
>;
|
|
540
|
+
|
|
541
|
+
export const ChannelSocketHealthIpcResponseSchema = z.object({
|
|
542
|
+
channel: z.enum(CHANNEL_IDS),
|
|
543
|
+
status: z.enum(CHANNEL_SOCKET_HEALTH_STATUSES),
|
|
544
|
+
/**
|
|
545
|
+
* Epoch millis when the transport last proved it was alive, by whatever
|
|
546
|
+
* means that transport proves it: a pong on Slack, an op 11 ACK on Discord.
|
|
547
|
+
*
|
|
548
|
+
* Absent means "not proven yet", never "proven dead". A connection's first
|
|
549
|
+
* proof is one full interval after it opens, so a healthy reconnect reports
|
|
550
|
+
* `connected` with no timestamp. Corroborating evidence, not a verdict.
|
|
551
|
+
*/
|
|
552
|
+
lastLivenessAt: z.number().optional(),
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
export type ChannelSocketHealthIpcResponse = z.infer<
|
|
556
|
+
typeof ChannelSocketHealthIpcResponseSchema
|
|
557
|
+
>;
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Whether Discord admits anything in the guilds it has joined.
|
|
561
|
+
*
|
|
562
|
+
* Discord's allow-list is fail-closed and lives in gateway-owned config: an
|
|
563
|
+
* absent or empty setting admits nothing, because being invited to a guild is
|
|
564
|
+
* not consent to every channel in it. The daemon owns the readiness surface
|
|
565
|
+
* that reports this, so the count has to cross the boundary, the same way a
|
|
566
|
+
* socket's liveness does.
|
|
567
|
+
*
|
|
568
|
+
* Discord-shaped rather than channel-keyed, because Discord is the only
|
|
569
|
+
* channel with an allow-list. A second one generalizes this.
|
|
570
|
+
*/
|
|
571
|
+
export const DiscordAdmissionIpcResponseSchema = z.object({
|
|
572
|
+
/** How many channel ids the bot may act in. Zero admits no guild message. */
|
|
573
|
+
admittedChannelCount: z.number().int().nonnegative(),
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
export type DiscordAdmissionIpcResponse = z.infer<
|
|
577
|
+
typeof DiscordAdmissionIpcResponseSchema
|
|
578
|
+
>;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
|
|
15
|
+
import { ChannelConversationTypeSchema } from "./channel-permission-contract.js";
|
|
16
|
+
|
|
15
17
|
import { AdmissionPolicySchema } from "./admission-policy-contract.js";
|
|
16
18
|
import { TrustVerdictSchema } from "./trust-verdict-contract.js";
|
|
17
19
|
|
|
@@ -37,8 +39,21 @@ export const SourceMetadataSchema = z
|
|
|
37
39
|
updateId: z.string().optional(),
|
|
38
40
|
/** Provider message ID (e.g. Slack message `ts`). */
|
|
39
41
|
messageId: z.string().optional(),
|
|
40
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Provider chat type (e.g. Telegram "private", "group"). Answers whether a
|
|
44
|
+
* room is multi-party, which drives group etiquette.
|
|
45
|
+
*/
|
|
41
46
|
chatType: z.string().optional(),
|
|
47
|
+
/**
|
|
48
|
+
* How visible the conversation is, on the permission matrix's axis, as
|
|
49
|
+
* decided by the sending channel's own normalizer. A different question
|
|
50
|
+
* from `chatType`: a group DM is multi-party and private, a public channel
|
|
51
|
+
* is multi-party and public.
|
|
52
|
+
*
|
|
53
|
+
* Absent means not established rather than public, so a permissive rule
|
|
54
|
+
* cannot reach a room whose visibility nobody proved.
|
|
55
|
+
*/
|
|
56
|
+
conversationType: ChannelConversationTypeSchema.optional(),
|
|
42
57
|
/** Thread/conversation-group ID (e.g. Slack `thread_ts`). */
|
|
43
58
|
threadId: z.string().optional(),
|
|
44
59
|
/** Channel name (e.g. Slack channel display name). */
|
|
@@ -28,7 +28,9 @@ export {
|
|
|
28
28
|
ChannelReplyPayloadSchema,
|
|
29
29
|
MessageAudienceSchema,
|
|
30
30
|
PermissionRequestDetailsSchema,
|
|
31
|
-
|
|
31
|
+
StreamOpSchema,
|
|
32
|
+
StreamPlanSchema,
|
|
33
|
+
StreamPlanStepSchema,
|
|
32
34
|
SlackStreamTaskSchema,
|
|
33
35
|
} from "./outbound-contract.js";
|
|
34
36
|
|
|
@@ -40,7 +42,9 @@ export type {
|
|
|
40
42
|
ChannelReplyPayload,
|
|
41
43
|
MessageAudience,
|
|
42
44
|
PermissionRequestDetails,
|
|
43
|
-
|
|
45
|
+
StreamOp,
|
|
46
|
+
StreamPlan,
|
|
47
|
+
StreamPlanStep,
|
|
44
48
|
SlackStreamTask,
|
|
45
49
|
} from "./outbound-contract.js";
|
|
46
50
|
|