@vellumai/assistant 0.5.11 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
} from "node:fs";
|
|
2
9
|
import { join } from "node:path";
|
|
3
10
|
|
|
4
11
|
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
5
|
-
import {
|
|
12
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
6
13
|
import { getConfig } from "../config/loader.js";
|
|
7
14
|
import { skillFlagKey } from "../config/skill-state.js";
|
|
8
15
|
import { loadSkillCatalog, type SkillSummary } from "../config/skills.js";
|
|
@@ -10,6 +17,7 @@ import { listConnections } from "../oauth/oauth-store.js";
|
|
|
10
17
|
import { resolveBundledDir } from "../util/bundled-asset.js";
|
|
11
18
|
import { getLogger } from "../util/logger.js";
|
|
12
19
|
import {
|
|
20
|
+
getConversationsDir,
|
|
13
21
|
getWorkspaceDir,
|
|
14
22
|
getWorkspacePromptPath,
|
|
15
23
|
isMacOS,
|
|
@@ -84,6 +92,24 @@ export function ensurePromptFiles(): void {
|
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
|
|
95
|
+
// Auto-delete stale BOOTSTRAP.md at startup. The model is instructed to
|
|
96
|
+
// delete it at the end of the first conversation, but if the user closes
|
|
97
|
+
// the app or starts a new thread before the model gets another turn, it
|
|
98
|
+
// never gets the chance. If BOOTSTRAP.md still exists but prior
|
|
99
|
+
// conversations are present, the onboarding window has passed — clean up.
|
|
100
|
+
const bootstrapCleanup = getWorkspacePromptPath("BOOTSTRAP.md");
|
|
101
|
+
if (!isFirstRun && existsSync(bootstrapCleanup)) {
|
|
102
|
+
const convDir = getConversationsDir();
|
|
103
|
+
try {
|
|
104
|
+
if (existsSync(convDir) && readdirSync(convDir).length > 0) {
|
|
105
|
+
unlinkSync(bootstrapCleanup);
|
|
106
|
+
log.info("Auto-deleted stale BOOTSTRAP.md — prior conversations exist");
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
log.warn({ err }, "Failed to auto-delete stale BOOTSTRAP.md");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
87
113
|
// Seed NOW.md scratchpad — always created if missing, regardless of whether
|
|
88
114
|
// this is a fresh install or not. Kept out of PROMPT_FILES because NOW.md is
|
|
89
115
|
// ephemeral state, not identity context.
|
|
@@ -171,6 +197,7 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
|
|
|
171
197
|
// System Permissions section removed — guidance lives in request_system_permission tool description.
|
|
172
198
|
// Parallel Task Orchestration section removed — orchestration skill description + hints cover this.
|
|
173
199
|
staticParts.push(buildAccessPreferenceSection(hasNoClient));
|
|
200
|
+
staticParts.push(buildCredentialSecuritySection());
|
|
174
201
|
// Memory Persistence, Memory Recall, Workspace Reflection, Learning from Mistakes
|
|
175
202
|
// sections removed — guidance lives in memory_manage/memory_recall tool descriptions
|
|
176
203
|
// and the Proactive Workspace Editing subsection in Configuration.
|
|
@@ -206,7 +233,15 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
|
|
|
206
233
|
const userIsTemplate = isTemplateContent(user, "USER.md");
|
|
207
234
|
|
|
208
235
|
if (identity && !identityIsTemplate) {
|
|
209
|
-
|
|
236
|
+
// Strip placeholder lines (e.g. "- **Name:** _(not yet chosen)_") so
|
|
237
|
+
// the model doesn't treat unresolved fields as prompts to ask the user.
|
|
238
|
+
const cleanedIdentity = identity
|
|
239
|
+
.split("\n")
|
|
240
|
+
.filter((line) => !/_\(not yet (?:chosen|established)\)_/.test(line))
|
|
241
|
+
.join("\n");
|
|
242
|
+
if (cleanedIdentity.trim()) {
|
|
243
|
+
dynamicParts.push(cleanedIdentity);
|
|
244
|
+
}
|
|
210
245
|
}
|
|
211
246
|
if (soul) dynamicParts.push(soul);
|
|
212
247
|
if (options?.userPersona) dynamicParts.push(options.userPersona);
|
|
@@ -275,6 +310,8 @@ function buildInChatConfigurationSection(): string {
|
|
|
275
310
|
"## In-Chat Configuration",
|
|
276
311
|
"",
|
|
277
312
|
"When the user needs to configure a value, collect it conversationally in the chat. Never direct the user to the Settings page for initial setup - Settings is for reviewing and updating existing configuration.",
|
|
313
|
+
"",
|
|
314
|
+
'The Settings tabs are: General, Models & Services, Voice, Sounds, Permissions & Privacy, Billing, Archived Conversations, Schedules, Developer. There is NO "Integrations" tab — never refer to "Settings > Integrations". For API keys and provider configuration, the correct tab is "Models & Services".',
|
|
278
315
|
].join("\n");
|
|
279
316
|
}
|
|
280
317
|
|
|
@@ -300,6 +337,14 @@ function buildAccessPreferenceSection(hasNoClient: boolean): string {
|
|
|
300
337
|
].join("\n");
|
|
301
338
|
}
|
|
302
339
|
|
|
340
|
+
function buildCredentialSecuritySection(): string {
|
|
341
|
+
return [
|
|
342
|
+
"## Credential Security",
|
|
343
|
+
"",
|
|
344
|
+
'Never ask users to share secrets (API keys, tokens, passwords, webhook secrets) in chat — secret messages may be blocked at ingress. Use the `credential_store` tool with `action: "prompt"` instead; it collects secrets through a secure UI that never exposes the value in the conversation. Non-secret values (Client IDs, Account SIDs, usernames) may be collected conversationally.',
|
|
345
|
+
].join("\n");
|
|
346
|
+
}
|
|
347
|
+
|
|
303
348
|
function buildIntegrationSection(): string {
|
|
304
349
|
let connections: { providerKey: string; accountInfo?: string | null }[];
|
|
305
350
|
try {
|
|
@@ -323,16 +368,16 @@ function buildIntegrationSection(): string {
|
|
|
323
368
|
}
|
|
324
369
|
|
|
325
370
|
function buildContainerizedSection(): string {
|
|
326
|
-
const
|
|
371
|
+
const workspaceDir = getWorkspaceDir();
|
|
327
372
|
return [
|
|
328
373
|
"## Running in a Container - Data Persistence",
|
|
329
374
|
"",
|
|
330
|
-
`You are running inside a container. Only the directory \`${
|
|
375
|
+
`You are running inside a container. Only the directory \`${workspaceDir}\` is mounted to a persistent volume.`,
|
|
331
376
|
"",
|
|
332
377
|
"**Any new files or data you create MUST be written inside that directory, or they will be lost when the container restarts.**",
|
|
333
378
|
"",
|
|
334
379
|
"Rules:",
|
|
335
|
-
`- Always store new data, notes, memories, configs, and downloads under \`${
|
|
380
|
+
`- Always store new data, notes, memories, configs, and downloads under \`${workspaceDir}\``,
|
|
336
381
|
"- Never write persistent data to system directories, `/tmp`, or paths outside the mounted volume",
|
|
337
382
|
"- When in doubt, prefer paths nested under the data directory",
|
|
338
383
|
"- If you create a file that is only needed temporarily (scratch files, intermediate outputs, download staging), delete it when you are done - disk space on the persistent volume is finite and will grow unboundedly if temp files are not cleaned up",
|
|
@@ -435,9 +480,9 @@ function readPromptFile(path: string): string | null {
|
|
|
435
480
|
* This is useful for injecting identity context into subsystems (e.g. memory
|
|
436
481
|
* extraction) that run outside the main system prompt pipeline.
|
|
437
482
|
*/
|
|
438
|
-
export function buildCoreIdentityContext(
|
|
439
|
-
|
|
440
|
-
): string | null {
|
|
483
|
+
export function buildCoreIdentityContext(opts?: {
|
|
484
|
+
userPersona?: string | null;
|
|
485
|
+
}): string | null {
|
|
441
486
|
const parts: string[] = [];
|
|
442
487
|
for (const file of PROMPT_FILES) {
|
|
443
488
|
const content = readPromptFile(getWorkspacePromptPath(file));
|
|
@@ -194,9 +194,13 @@ When saving to `USER.md`, mark declined fields so you don't re-ask later (e.g.,
|
|
|
194
194
|
|
|
195
195
|
## Saving What You Learn
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
**Call `file_edit` immediately whenever you learn something, in the same turn.** Don't batch saves for later. Don't wait until onboarding is "done." The moment the user gives you a name, call `file_edit` on IDENTITY.md in that same response. The moment you infer their communication style, call `file_edit` on SOUL.md. Every piece of information gets saved the turn you learn it.
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
**The contents of IDENTITY.md, SOUL.md, and USER.md are already in your system prompt.** Use the exact text you see there for the `old_string` in `file_edit`. Do not guess or invent content that isn't in your context.
|
|
200
|
+
|
|
201
|
+
Update `IDENTITY.md` (name, nature, personality, style tendency) and `USER.md` (their name, how to address them, goals, locale, work role, hobbies, daily tools). If the conversation reveals how the user wants you to behave (e.g., "be direct," "don't be too chatty"), save those behavioral guidelines to `SOUL.md`.
|
|
202
|
+
|
|
203
|
+
Do it silently. Never tell the user you're saving, never mention file names or tool names. Just call `file_edit` alongside your normal response.
|
|
200
204
|
|
|
201
205
|
When saving to `IDENTITY.md`, be specific about the tone, energy, and conversational style you discovered during onboarding. This file persists after onboarding, so everything about how you should come across needs to be captured there. Not just your name, but the full vibe: how you talk, how much energy you bring, whether you're blunt or gentle, funny or serious.
|
|
202
206
|
|
|
@@ -204,11 +208,18 @@ When saving to `SOUL.md`, also add an `## Identity Intro` section with a very sh
|
|
|
204
208
|
|
|
205
209
|
## Wrapping Up
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
**Always delete `BOOTSTRAP.md` at the end of this conversation, regardless of how far you got through the phases.** Onboarding is a one-shot opportunity. If the user skipped steps, partially completed them, or blazed through everything, it doesn't matter. Delete it. Never let a second conversation start with the first-run script.
|
|
212
|
+
|
|
213
|
+
Deletion triggers:
|
|
214
|
+
- The conversation is ending (user stops responding, says bye, moves on)
|
|
215
|
+
- The user completed Phase 2 (ideal case)
|
|
216
|
+
- The user named you but skipped other steps
|
|
217
|
+
- The user explicitly skipped onboarding ("skip", "not now", "later")
|
|
218
|
+
- The user ignored the onboarding pitch and just did tasks
|
|
208
219
|
|
|
209
|
-
If
|
|
220
|
+
If onboarding was partial, that's fine. IDENTITY.md, SOUL.md, and USER.md persist. You can organically pick up incomplete personalization in future conversations by checking those files, without replaying the bootstrap script.
|
|
210
221
|
|
|
211
|
-
|
|
222
|
+
If you still haven't shown the two suggestions (Phase 2 step 4), try to fit them in before wrapping, but do NOT let that block deletion of BOOTSTRAP.md.
|
|
212
223
|
|
|
213
224
|
---
|
|
214
225
|
|
|
@@ -253,8 +253,7 @@ function expandCollapsedAssistantTurns(
|
|
|
253
253
|
|
|
254
254
|
for (const block of content) {
|
|
255
255
|
const type = (block as { type: string }).type;
|
|
256
|
-
const isThinking =
|
|
257
|
-
type === "thinking" || type === "redacted_thinking";
|
|
256
|
+
const isThinking = type === "thinking" || type === "redacted_thinking";
|
|
258
257
|
|
|
259
258
|
if (isThinking && segmentHasToolUse) {
|
|
260
259
|
segments.push(current);
|
|
@@ -310,10 +309,7 @@ function expandCollapsedAssistantTurns(
|
|
|
310
309
|
// tool_results that were already distributed to intermediate segments.
|
|
311
310
|
if (nextIsUser) {
|
|
312
311
|
const remainingResults = Array.from(toolResultMap.values());
|
|
313
|
-
const rebuiltUserContent = [
|
|
314
|
-
...remainingResults,
|
|
315
|
-
...nonToolResultContent,
|
|
316
|
-
];
|
|
312
|
+
const rebuiltUserContent = [...remainingResults, ...nonToolResultContent];
|
|
317
313
|
// Replace the original user message with the rebuilt one
|
|
318
314
|
result.push({
|
|
319
315
|
role: "user" as const,
|
|
@@ -949,10 +945,6 @@ export class AnthropicProvider implements Provider {
|
|
|
949
945
|
signal: timeoutSignal,
|
|
950
946
|
}) as unknown as UnifiedStream;
|
|
951
947
|
|
|
952
|
-
// Track whether we've seen a text content block so we can insert a
|
|
953
|
-
// separator between consecutive text blocks in the same response.
|
|
954
|
-
let hasSeenTextBlock = false;
|
|
955
|
-
|
|
956
948
|
stream.on("text", (text) => {
|
|
957
949
|
onEvent?.({ type: "text_delta", text });
|
|
958
950
|
});
|
|
@@ -969,29 +961,6 @@ export class AnthropicProvider implements Provider {
|
|
|
969
961
|
let pendingInputJsonFlush: ReturnType<typeof setTimeout> | undefined;
|
|
970
962
|
|
|
971
963
|
stream.on("streamEvent", (event) => {
|
|
972
|
-
// Insert a space separator when a new text content block starts
|
|
973
|
-
// after a previous one, so consecutive text blocks don't get
|
|
974
|
-
// concatenated without whitespace (e.g. "sentence.NextSentence").
|
|
975
|
-
// Uses a space instead of \n because the client's MarkdownRenderer
|
|
976
|
-
// can collapse soft line breaks (\n) within a paragraph.
|
|
977
|
-
if (
|
|
978
|
-
event.type === "content_block_start" &&
|
|
979
|
-
event.content_block.type === "text"
|
|
980
|
-
) {
|
|
981
|
-
if (hasSeenTextBlock) {
|
|
982
|
-
onEvent?.({ type: "text_delta", text: " " });
|
|
983
|
-
}
|
|
984
|
-
hasSeenTextBlock = true;
|
|
985
|
-
} else if (
|
|
986
|
-
event.type === "content_block_start" &&
|
|
987
|
-
event.content_block.type === "tool_use"
|
|
988
|
-
) {
|
|
989
|
-
// Reset only for client-side tool_use blocks, which create visual
|
|
990
|
-
// separators in the UI. Server-side tool blocks (server_tool_use,
|
|
991
|
-
// web_search_tool_result) are transparent in the text stream and
|
|
992
|
-
// need the space preserved between surrounding text blocks.
|
|
993
|
-
hasSeenTextBlock = false;
|
|
994
|
-
}
|
|
995
964
|
if (
|
|
996
965
|
event.type === "content_block_start" &&
|
|
997
966
|
event.content_block.type === "tool_use"
|
|
@@ -42,7 +42,7 @@ export interface ProcessGuardianDecisionParams {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export type ProcessGuardianDecisionResult =
|
|
45
|
-
| { ok: true; applied: true; requestId: string }
|
|
45
|
+
| { ok: true; applied: true; requestId: string; replyText?: string }
|
|
46
46
|
| {
|
|
47
47
|
ok: true;
|
|
48
48
|
applied: false;
|
|
@@ -116,7 +116,12 @@ export async function processGuardianDecision(
|
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
applied: true,
|
|
122
|
+
requestId: canonicalResult.requestId,
|
|
123
|
+
replyText: canonicalResult.resolverReplyText,
|
|
124
|
+
};
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
return {
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
9
|
import { join, resolve } from "node:path";
|
|
11
10
|
|
|
12
11
|
import type { ServerWebSocket } from "bun";
|
|
@@ -28,13 +27,11 @@ import {
|
|
|
28
27
|
handleVoiceWebhook,
|
|
29
28
|
} from "../calls/twilio-routes.js";
|
|
30
29
|
import { parseChannelId } from "../channels/types.js";
|
|
31
|
-
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
32
30
|
import {
|
|
33
31
|
getGatewayInternalBaseUrl,
|
|
34
32
|
hasUngatedHttpAuthDisabled,
|
|
35
33
|
isHttpAuthDisabled,
|
|
36
34
|
} from "../config/env.js";
|
|
37
|
-
import { getConfig } from "../config/loader.js";
|
|
38
35
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
39
36
|
import { PairingStore } from "../daemon/pairing-store.js";
|
|
40
37
|
import {
|
|
@@ -64,6 +61,7 @@ import {
|
|
|
64
61
|
} from "../security/oauth-callback-registry.js";
|
|
65
62
|
import { UserError } from "../util/errors.js";
|
|
66
63
|
import { getLogger } from "../util/logger.js";
|
|
64
|
+
import { getRootDir } from "../util/platform.js";
|
|
67
65
|
import { buildAssistantEvent } from "./assistant-event.js";
|
|
68
66
|
import { assistantEventHub } from "./assistant-event-hub.js";
|
|
69
67
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
|
|
@@ -251,6 +249,7 @@ export class RuntimeHttpServer {
|
|
|
251
249
|
private getWatchDeps?: RuntimeHttpServerOptions["getWatchDeps"];
|
|
252
250
|
private getRecordingDeps?: RuntimeHttpServerOptions["getRecordingDeps"];
|
|
253
251
|
private getCesClient?: RuntimeHttpServerOptions["getCesClient"];
|
|
252
|
+
private onProviderCredentialsChanged?: RuntimeHttpServerOptions["onProviderCredentialsChanged"];
|
|
254
253
|
private getHeartbeatService?: RuntimeHttpServerOptions["getHeartbeatService"];
|
|
255
254
|
private router: HttpRouter;
|
|
256
255
|
|
|
@@ -274,6 +273,7 @@ export class RuntimeHttpServer {
|
|
|
274
273
|
this.getWatchDeps = options.getWatchDeps;
|
|
275
274
|
this.getRecordingDeps = options.getRecordingDeps;
|
|
276
275
|
this.getCesClient = options.getCesClient;
|
|
276
|
+
this.onProviderCredentialsChanged = options.onProviderCredentialsChanged;
|
|
277
277
|
this.getHeartbeatService = options.getHeartbeatService;
|
|
278
278
|
this.router = new HttpRouter(this.buildRouteTable());
|
|
279
279
|
}
|
|
@@ -296,8 +296,7 @@ export class RuntimeHttpServer {
|
|
|
296
296
|
/** Read the feature-flag client token from disk so it can be included in pairing approval responses. */
|
|
297
297
|
private readFeatureFlagToken(): string | undefined {
|
|
298
298
|
try {
|
|
299
|
-
const
|
|
300
|
-
const tokenPath = join(baseDir, ".vellum", "feature-flag-token");
|
|
299
|
+
const tokenPath = join(getRootDir(), "feature-flag-token");
|
|
301
300
|
const token = readFileSync(tokenPath, "utf-8").trim();
|
|
302
301
|
return token || undefined;
|
|
303
302
|
} catch {
|
|
@@ -944,6 +943,7 @@ export class RuntimeHttpServer {
|
|
|
944
943
|
...appManagementRouteDefinitions(),
|
|
945
944
|
...secretRouteDefinitions({
|
|
946
945
|
getCesClient: this.getCesClient,
|
|
946
|
+
onProviderCredentialsChanged: this.onProviderCredentialsChanged,
|
|
947
947
|
}),
|
|
948
948
|
...identityRouteDefinitions(),
|
|
949
949
|
...upgradeBroadcastRouteDefinitions(),
|
|
@@ -1028,17 +1028,11 @@ export class RuntimeHttpServer {
|
|
|
1028
1028
|
handler: ({ url }) => {
|
|
1029
1029
|
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
1030
1030
|
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
1031
|
-
const
|
|
1032
|
-
"
|
|
1033
|
-
|
|
1034
|
-
);
|
|
1035
|
-
const
|
|
1036
|
-
limit,
|
|
1037
|
-
includeBackground,
|
|
1038
|
-
offset,
|
|
1039
|
-
);
|
|
1040
|
-
const totalCount = countConversations(includeBackground);
|
|
1041
|
-
const conversationIds = conversations.map((c) => c.id);
|
|
1031
|
+
const backgroundOnly =
|
|
1032
|
+
url.searchParams.get("conversationType") === "background";
|
|
1033
|
+
const rows = listConversations(limit, backgroundOnly, offset);
|
|
1034
|
+
const totalCount = countConversations(backgroundOnly);
|
|
1035
|
+
const conversationIds = rows.map((c) => c.id);
|
|
1042
1036
|
const displayMeta = getDisplayMetaForConversations(conversationIds);
|
|
1043
1037
|
const bindings =
|
|
1044
1038
|
externalConversationStore.getBindingsForConversations(
|
|
@@ -1048,7 +1042,7 @@ export class RuntimeHttpServer {
|
|
|
1048
1042
|
getAttentionStateByConversationIds(conversationIds);
|
|
1049
1043
|
const parentCache = new Map<string, ConversationRow | null>();
|
|
1050
1044
|
return Response.json({
|
|
1051
|
-
conversations:
|
|
1045
|
+
conversations: rows.map((conversation) =>
|
|
1052
1046
|
this.serializeConversationSummary({
|
|
1053
1047
|
conversation,
|
|
1054
1048
|
binding: bindings.get(conversation.id),
|
|
@@ -1057,7 +1051,7 @@ export class RuntimeHttpServer {
|
|
|
1057
1051
|
parentCache,
|
|
1058
1052
|
}),
|
|
1059
1053
|
),
|
|
1060
|
-
hasMore: offset +
|
|
1054
|
+
hasMore: offset + rows.length < totalCount,
|
|
1061
1055
|
});
|
|
1062
1056
|
},
|
|
1063
1057
|
},
|
|
@@ -228,8 +228,15 @@ export interface RuntimeHttpServerOptions {
|
|
|
228
228
|
getRecordingDeps?: () => import("./routes/recording-routes.js").RecordingDeps;
|
|
229
229
|
/** Accessor for the CES client, used to push API key updates to CES after hatch. */
|
|
230
230
|
getCesClient?: () => CesClient | undefined;
|
|
231
|
+
/**
|
|
232
|
+
* Called after provider-affecting credentials reload so live conversations
|
|
233
|
+
* can be recreated with fresh provider instances.
|
|
234
|
+
*/
|
|
235
|
+
onProviderCredentialsChanged?: () => void | Promise<void>;
|
|
231
236
|
/** Accessor for the heartbeat service (for run-now and config routes). */
|
|
232
|
-
getHeartbeatService?: () =>
|
|
237
|
+
getHeartbeatService?: () =>
|
|
238
|
+
| import("../heartbeat/heartbeat-service.js").HeartbeatService
|
|
239
|
+
| undefined;
|
|
233
240
|
}
|
|
234
241
|
|
|
235
242
|
export interface RuntimeAttachmentMetadata {
|
|
@@ -106,7 +106,7 @@ const TASK_DEFINITIONS: readonly TaskDefinition[] = [
|
|
|
106
106
|
"Secrets are redacted in export bundles for security. Re-enter all API keys, tokens, and credentials in the destination instance.",
|
|
107
107
|
required: true,
|
|
108
108
|
helpText:
|
|
109
|
-
"Navigate to Settings >
|
|
109
|
+
"Navigate to Settings > Models & Services to re-enter provider API keys (e.g., Anthropic, OpenAI). Check Settings > Models & Services for any custom secrets used by skills.",
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
id: "rebind-channels",
|
|
@@ -133,7 +133,7 @@ const TASK_DEFINITIONS: readonly TaskDefinition[] = [
|
|
|
133
133
|
"Ensure all webhook URLs registered with external services point to the new instance's public ingress URL.",
|
|
134
134
|
required: false,
|
|
135
135
|
helpText:
|
|
136
|
-
"Review the public ingress URL in Settings >
|
|
136
|
+
"Review the public ingress URL in Settings > Developer. Update any external services (GitHub, calendar providers, etc.) that send webhooks to this assistant.",
|
|
137
137
|
},
|
|
138
138
|
] as const;
|
|
139
139
|
|
|
@@ -18,7 +18,9 @@ import { z } from "zod";
|
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
batchSetDisplayOrders,
|
|
21
|
+
countConversationsByScheduleJobId,
|
|
21
22
|
deleteConversation,
|
|
23
|
+
getConversation,
|
|
22
24
|
PRIVATE_CONVERSATION_FORK_ERROR,
|
|
23
25
|
wipeConversation,
|
|
24
26
|
} from "../../memory/conversation-crud.js";
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
setConversationKeyIfAbsent,
|
|
30
32
|
} from "../../memory/conversation-key-store.js";
|
|
31
33
|
import { enqueueMemoryJob } from "../../memory/jobs-store.js";
|
|
34
|
+
import { deleteSchedule } from "../../schedule/schedule-store.js";
|
|
32
35
|
import { UserError } from "../../util/errors.js";
|
|
33
36
|
import { getLogger } from "../../util/logger.js";
|
|
34
37
|
import { httpError } from "../http-errors.js";
|
|
@@ -318,6 +321,20 @@ export function conversationManagementRouteDefinitions(
|
|
|
318
321
|
404,
|
|
319
322
|
);
|
|
320
323
|
}
|
|
324
|
+
|
|
325
|
+
// Cancel the associated schedule job (if any) before wiping the
|
|
326
|
+
// conversation — but only when this is the last conversation that
|
|
327
|
+
// references the schedule. Recurring schedules create a new
|
|
328
|
+
// conversation per run, so we must not cancel the schedule when
|
|
329
|
+
// earlier run conversations are cleaned up.
|
|
330
|
+
const conv = getConversation(resolvedId);
|
|
331
|
+
if (
|
|
332
|
+
conv?.scheduleJobId &&
|
|
333
|
+
countConversationsByScheduleJobId(conv.scheduleJobId) <= 1
|
|
334
|
+
) {
|
|
335
|
+
deleteSchedule(conv.scheduleJobId);
|
|
336
|
+
}
|
|
337
|
+
|
|
321
338
|
deps.destroyConversation(resolvedId);
|
|
322
339
|
const result = wipeConversation(resolvedId);
|
|
323
340
|
// Enqueue Qdrant vector cleanup jobs
|
|
@@ -372,6 +389,20 @@ export function conversationManagementRouteDefinitions(
|
|
|
372
389
|
404,
|
|
373
390
|
);
|
|
374
391
|
}
|
|
392
|
+
|
|
393
|
+
// Cancel the associated schedule job (if any) before deleting the
|
|
394
|
+
// conversation — but only when this is the last conversation that
|
|
395
|
+
// references the schedule. Recurring schedules create a new
|
|
396
|
+
// conversation per run, so we must not cancel the schedule when
|
|
397
|
+
// earlier run conversations are cleaned up.
|
|
398
|
+
const conv = getConversation(resolvedId);
|
|
399
|
+
if (
|
|
400
|
+
conv?.scheduleJobId &&
|
|
401
|
+
countConversationsByScheduleJobId(conv.scheduleJobId) <= 1
|
|
402
|
+
) {
|
|
403
|
+
deleteSchedule(conv.scheduleJobId);
|
|
404
|
+
}
|
|
405
|
+
|
|
375
406
|
// Tear down the in-memory conversation (abort + dispose) before removing
|
|
376
407
|
// persistence so that a running agent loop doesn't write to a deleted
|
|
377
408
|
// conversation row, tripping FK constraints.
|
|
@@ -48,7 +48,10 @@ import {
|
|
|
48
48
|
} from "../../memory/canonical-guardian-store.js";
|
|
49
49
|
import {
|
|
50
50
|
addMessage,
|
|
51
|
+
getLastAssistantTimestampBefore,
|
|
51
52
|
getMessages,
|
|
53
|
+
getMessagesPaginated,
|
|
54
|
+
type MessageRow,
|
|
52
55
|
provenanceFromTrustContext,
|
|
53
56
|
setConversationOriginChannelIfUnset,
|
|
54
57
|
setConversationOriginInterfaceIfUnset,
|
|
@@ -360,7 +363,49 @@ export function handleListMessages(
|
|
|
360
363
|
if (!resolvedConversationId) {
|
|
361
364
|
return Response.json({ messages: [] });
|
|
362
365
|
}
|
|
363
|
-
|
|
366
|
+
|
|
367
|
+
const beforeTimestampRaw = url.searchParams.get("beforeTimestamp");
|
|
368
|
+
const limitRaw = url.searchParams.get("limit");
|
|
369
|
+
|
|
370
|
+
// Validate: reject NaN values with 400
|
|
371
|
+
if (beforeTimestampRaw !== null && isNaN(Number(beforeTimestampRaw))) {
|
|
372
|
+
return httpError(
|
|
373
|
+
"BAD_REQUEST",
|
|
374
|
+
"beforeTimestamp must be a valid number",
|
|
375
|
+
400,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
if (limitRaw !== null && isNaN(Number(limitRaw))) {
|
|
379
|
+
return httpError("BAD_REQUEST", "limit must be a valid number", 400);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const beforeTimestamp = beforeTimestampRaw
|
|
383
|
+
? Number(beforeTimestampRaw)
|
|
384
|
+
: undefined;
|
|
385
|
+
// Clamp limit to 1-500 range
|
|
386
|
+
const limit = limitRaw
|
|
387
|
+
? Math.min(Math.max(Math.floor(Number(limitRaw)), 1), 500)
|
|
388
|
+
: undefined;
|
|
389
|
+
|
|
390
|
+
// Option A: only paginate when beforeTimestamp is present.
|
|
391
|
+
// Initial load and reconnect send limit but no beforeTimestamp — those must continue
|
|
392
|
+
// returning all messages for zero regression risk.
|
|
393
|
+
const isPaginated = beforeTimestamp != null;
|
|
394
|
+
|
|
395
|
+
let rawMessages: MessageRow[];
|
|
396
|
+
let hasMore = false;
|
|
397
|
+
|
|
398
|
+
if (isPaginated) {
|
|
399
|
+
const result = getMessagesPaginated(
|
|
400
|
+
resolvedConversationId,
|
|
401
|
+
limit,
|
|
402
|
+
beforeTimestamp,
|
|
403
|
+
);
|
|
404
|
+
rawMessages = result.messages;
|
|
405
|
+
hasMore = result.hasMore;
|
|
406
|
+
} else {
|
|
407
|
+
rawMessages = getMessages(resolvedConversationId);
|
|
408
|
+
}
|
|
364
409
|
|
|
365
410
|
// Parse content blocks and extract text + tool calls
|
|
366
411
|
const parsed = rawMessages.map((msg) => {
|
|
@@ -429,6 +474,12 @@ export function handleListMessages(
|
|
|
429
474
|
const interfaceFiles = getInterfaceFilesWithMtimes(interfacesDir);
|
|
430
475
|
|
|
431
476
|
let prevAssistantTimestamp = 0;
|
|
477
|
+
if (isPaginated && rawMessages.length > 0) {
|
|
478
|
+
prevAssistantTimestamp = getLastAssistantTimestampBefore(
|
|
479
|
+
resolvedConversationId!,
|
|
480
|
+
rawMessages[0].createdAt,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
432
483
|
const messages: RuntimeMessagePayload[] = parsed.map((m) => {
|
|
433
484
|
let msgAttachments: RuntimeAttachmentMetadata[] = [];
|
|
434
485
|
if (m.id) {
|
|
@@ -498,6 +549,19 @@ export function handleListMessages(
|
|
|
498
549
|
};
|
|
499
550
|
});
|
|
500
551
|
|
|
552
|
+
if (isPaginated) {
|
|
553
|
+
const oldestTimestamp =
|
|
554
|
+
rawMessages.length > 0 ? rawMessages[0].createdAt : undefined;
|
|
555
|
+
const oldestMessageId =
|
|
556
|
+
rawMessages.length > 0 ? rawMessages[0].id : undefined;
|
|
557
|
+
return Response.json({
|
|
558
|
+
messages,
|
|
559
|
+
hasMore,
|
|
560
|
+
...(oldestTimestamp != null ? { oldestTimestamp } : {}),
|
|
561
|
+
...(oldestMessageId != null ? { oldestMessageId } : {}),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
501
565
|
return Response.json({ messages });
|
|
502
566
|
}
|
|
503
567
|
|
|
@@ -1502,9 +1566,20 @@ export function conversationRouteDefinitions(deps: {
|
|
|
1502
1566
|
tags: ["messages"],
|
|
1503
1567
|
responseBody: z.object({
|
|
1504
1568
|
messages: z.array(z.unknown()).describe("Array of message objects"),
|
|
1505
|
-
|
|
1506
|
-
.
|
|
1507
|
-
.
|
|
1569
|
+
hasMore: z
|
|
1570
|
+
.boolean()
|
|
1571
|
+
.optional()
|
|
1572
|
+
.describe("Whether older messages exist beyond this page"),
|
|
1573
|
+
oldestTimestamp: z
|
|
1574
|
+
.number()
|
|
1575
|
+
.optional()
|
|
1576
|
+
.describe(
|
|
1577
|
+
"Timestamp of the oldest message in this page (ms since epoch)",
|
|
1578
|
+
),
|
|
1579
|
+
oldestMessageId: z
|
|
1580
|
+
.string()
|
|
1581
|
+
.optional()
|
|
1582
|
+
.describe("ID of the oldest message in this page"),
|
|
1508
1583
|
}),
|
|
1509
1584
|
handler: ({ url }) => handleListMessages(url, deps.interfacesDir),
|
|
1510
1585
|
},
|
|
@@ -121,7 +121,11 @@ export async function handleGuardianActionDecision(
|
|
|
121
121
|
return httpError("BAD_REQUEST", result.message, 400);
|
|
122
122
|
}
|
|
123
123
|
if (result.applied) {
|
|
124
|
-
return Response.json({
|
|
124
|
+
return Response.json({
|
|
125
|
+
applied: true,
|
|
126
|
+
requestId: result.requestId,
|
|
127
|
+
...(result.replyText ? { replyText: result.replyText } : {}),
|
|
128
|
+
});
|
|
125
129
|
}
|
|
126
130
|
return result.reason === "not_found"
|
|
127
131
|
? httpError(
|
|
@@ -297,7 +301,16 @@ export function guardianActionRouteDefinitions(): RouteDefinition[] {
|
|
|
297
301
|
responseBody: z.object({
|
|
298
302
|
applied: z.boolean(),
|
|
299
303
|
requestId: z.string(),
|
|
300
|
-
reason: z
|
|
304
|
+
reason: z
|
|
305
|
+
.string()
|
|
306
|
+
.optional()
|
|
307
|
+
.describe("Decline reason (present only when applied is false)"),
|
|
308
|
+
replyText: z
|
|
309
|
+
.string()
|
|
310
|
+
.optional()
|
|
311
|
+
.describe(
|
|
312
|
+
"Resolver reply text for the guardian (e.g. verification code)",
|
|
313
|
+
),
|
|
301
314
|
}),
|
|
302
315
|
handler: async ({ req, authContext }) =>
|
|
303
316
|
handleGuardianActionDecision(req, authContext),
|