@vellumai/assistant 0.9.0 → 0.9.1-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/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +4 -4
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +135 -59
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
- package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/config-loader-backfill.test.ts +103 -6
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +3 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -13
- package/src/__tests__/dynamic-page-surface.test.ts +101 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/invite-redemption-service.test.ts +4 -4
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +0 -22
- package/src/__tests__/llm-resolver.test.ts +49 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +11 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/skill-execute-input.test.ts +46 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/subagent-tools.test.ts +116 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +6 -6
- package/src/__tests__/voice-invite-redemption.test.ts +2 -2
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/agent/loop.ts +25 -5
- package/src/api/README.md +6 -6
- package/src/api/responses/conversation-message.ts +2 -4
- package/src/api/responses/home.ts +0 -4
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/plugins.ts +143 -2
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +228 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/upgrade-plugin.ts +270 -10
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +0 -1
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +12 -2
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/loader.ts +4 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/seed-inference-profiles.ts +25 -20
- package/src/contacts/contact-store.ts +87 -96
- package/src/contacts/contacts-write.ts +5 -21
- package/src/context/compactor.ts +2 -2
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop.ts +15 -4
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-tool-setup.ts +11 -3
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/handlers/config-channels.ts +20 -16
- package/src/daemon/handlers/config-slack-channel.ts +2 -3
- package/src/daemon/lifecycle.ts +0 -7
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/home/relationship-state.ts +2 -4
- package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/db-init.ts +7 -17
- package/src/memory/embedding-backend.ts +23 -0
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +52 -0
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-job.ts +19 -227
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/schema/contacts.ts +0 -4
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +5 -32
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/broadcaster.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +4 -3
- package/src/notifications/notification-utils.ts +17 -19
- package/src/notifications/types.ts +7 -0
- package/src/oauth/AGENTS.md +5 -24
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +6 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
- package/src/prompts/persona-resolver.ts +2 -2
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/actor-trust-resolver.ts +12 -44
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/channel-approval-types.ts +18 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-verification-service.ts +4 -3
- package/src/runtime/invite-redemption-service.ts +3 -3
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +171 -5
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/workflow-routes.test.ts +4 -43
- package/src/runtime/routes/workflow-routes.ts +0 -28
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/schedule/inference-profile.ts +2 -14
- package/src/subagent/manager.ts +6 -0
- package/src/subagent/types.ts +6 -0
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/schedule/create.ts +3 -9
- package/src/tools/schedule/update.ts +2 -10
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +34 -0
- package/src/tools/subagent/spawn.ts +34 -9
- package/src/tools/tool-approval-handler.ts +1 -3
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/util/platform.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/run-manager.test.ts +0 -25
- package/src/workflows/run-manager.ts +2 -24
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -27,18 +27,11 @@ import {
|
|
|
27
27
|
setCachedEmptyStateGreeting,
|
|
28
28
|
} from "./empty-state-greeting-cache.js";
|
|
29
29
|
import { BadRequestError, ServiceUnavailableError } from "./errors.js";
|
|
30
|
-
import {
|
|
31
|
-
getCachedIntro,
|
|
32
|
-
readWorkspaceGreetings,
|
|
33
|
-
setCachedIntro,
|
|
34
|
-
} from "./identity-intro-cache.js";
|
|
35
30
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
31
|
+
import { readWorkspaceGreetings } from "./workspace-greetings.js";
|
|
36
32
|
|
|
37
33
|
const log = getLogger("btw-routes");
|
|
38
34
|
|
|
39
|
-
/** Conversation key used by the client for identity intro generation. */
|
|
40
|
-
const IDENTITY_INTRO_KEY = "identity-intro";
|
|
41
|
-
|
|
42
35
|
/** Conversation key used by the client for empty-state greeting generation. */
|
|
43
36
|
const GREETING_KEY = "greeting";
|
|
44
37
|
|
|
@@ -72,15 +65,6 @@ async function handleBtw({
|
|
|
72
65
|
|
|
73
66
|
const trimmedContent = content.trim();
|
|
74
67
|
|
|
75
|
-
// ----- Cached identity intro fast-path -----
|
|
76
|
-
if (conversationKey === IDENTITY_INTRO_KEY) {
|
|
77
|
-
const fastText = getCachedIntro()?.greetings[0];
|
|
78
|
-
if (fastText) {
|
|
79
|
-
log.debug("Returning identity intro fast-path");
|
|
80
|
-
return streamText(fastText);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
68
|
// ----- Empty-state greeting fast-path -----
|
|
85
69
|
// User-authored `## Greetings` win; otherwise replay a cached greeting when
|
|
86
70
|
// one is fresh within the configurable TTL (`ui.emptyStateGreetingCacheTtlMs`,
|
|
@@ -126,7 +110,6 @@ async function handleBtw({
|
|
|
126
110
|
start(controller) {
|
|
127
111
|
(async () => {
|
|
128
112
|
try {
|
|
129
|
-
const isIntroRequest = conversationKey === IDENTITY_INTRO_KEY;
|
|
130
113
|
const isGreeting = conversationKey === GREETING_KEY;
|
|
131
114
|
const result = await runBtwSidechain({
|
|
132
115
|
content: effectiveContent,
|
|
@@ -153,15 +136,6 @@ async function handleBtw({
|
|
|
153
136
|
);
|
|
154
137
|
}
|
|
155
138
|
|
|
156
|
-
if (isIntroRequest && result.text) {
|
|
157
|
-
try {
|
|
158
|
-
setCachedIntro([result.text]);
|
|
159
|
-
log.debug("Cached identity intro text");
|
|
160
|
-
} catch {
|
|
161
|
-
// Non-fatal — next request will regenerate.
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
139
|
if (isGreeting && result.text) {
|
|
166
140
|
// setCachedEmptyStateGreeting is a no-op when the TTL is 0.
|
|
167
141
|
setCachedEmptyStateGreeting(result.text);
|
|
@@ -72,7 +72,7 @@ const log = getLogger("conversation-compaction-routes");
|
|
|
72
72
|
* `responseBody` (below) is the source-of-truth for the generated
|
|
73
73
|
* OpenAPI client type the frontend imports
|
|
74
74
|
* (`ConversationsByIdCompactionGetResponse` in
|
|
75
|
-
* `
|
|
75
|
+
* `clients/web/src/generated/daemon/types.gen`); regenerate the client with
|
|
76
76
|
* `bun run openapi-ts` after changing the schema.
|
|
77
77
|
*
|
|
78
78
|
* `null` means the value isn't known for the underlying row — either the
|
|
@@ -944,8 +944,8 @@ export function handleListMessages({
|
|
|
944
944
|
...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
|
|
945
945
|
...(m.clientMessageId ? { clientMessageId: m.clientMessageId } : {}),
|
|
946
946
|
role: m.role,
|
|
947
|
-
// Flat plain-text body the
|
|
948
|
-
//
|
|
947
|
+
// Flat plain-text body; see the `content` field on
|
|
948
|
+
// ConversationMessageSchema for why this must stay.
|
|
949
949
|
content: text,
|
|
950
950
|
timestamp: new Date(displayTimestamp).toISOString(),
|
|
951
951
|
attachments: msgAttachments,
|
|
@@ -141,7 +141,9 @@ interface CredentialLookup {
|
|
|
141
141
|
* Resolve a credential lookup from service+field or UUID.
|
|
142
142
|
* Throws BadRequestError when neither is provided or the UUID is not found.
|
|
143
143
|
*/
|
|
144
|
-
function resolveCredentialLookup(
|
|
144
|
+
function resolveCredentialLookup(
|
|
145
|
+
body: Record<string, unknown>,
|
|
146
|
+
): CredentialLookup {
|
|
145
147
|
const { service, field, id } = body as {
|
|
146
148
|
service?: string;
|
|
147
149
|
field?: string;
|
|
@@ -243,8 +245,9 @@ async function handleCredentialsInspect({ body }: RouteHandlerArgs) {
|
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
const lookup = resolveCredentialLookup(body);
|
|
246
|
-
const { value: secret, unreachable } =
|
|
247
|
-
|
|
248
|
+
const { value: secret, unreachable } = await getSecureKeyResultAsync(
|
|
249
|
+
lookup.storageKey,
|
|
250
|
+
);
|
|
248
251
|
|
|
249
252
|
if (!lookup.metadata && (secret == null || secret.length === 0)) {
|
|
250
253
|
if (unreachable) {
|
|
@@ -290,8 +293,9 @@ async function handleCredentialsReveal({ body }: RouteHandlerArgs) {
|
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
const lookup = resolveCredentialLookup(body);
|
|
293
|
-
const { value: secret, unreachable } =
|
|
294
|
-
|
|
296
|
+
const { value: secret, unreachable } = await getSecureKeyResultAsync(
|
|
297
|
+
lookup.storageKey,
|
|
298
|
+
);
|
|
295
299
|
|
|
296
300
|
if (secret == null || secret.length === 0) {
|
|
297
301
|
if (unreachable) {
|
|
@@ -372,11 +376,19 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
372
376
|
|
|
373
377
|
assertMetadataWritable();
|
|
374
378
|
|
|
379
|
+
// The Slack user_token only grants read access to channels the bot isn't a
|
|
380
|
+
// member of; Socket Mode itself runs on the bot + app tokens. Deleting just
|
|
381
|
+
// the user_token must leave the oauth_connection intact — disconnecting the
|
|
382
|
+
// provider would flap the integration's connected state until the next sync.
|
|
383
|
+
// Every other step (secret + metadata removal, not-found handling) is the
|
|
384
|
+
// same as a normal credential delete.
|
|
385
|
+
const preserveOAuthConnection =
|
|
386
|
+
service === "slack_channel" && field === "user_token";
|
|
387
|
+
|
|
375
388
|
const key = credentialKey(service, field);
|
|
376
389
|
const existing = await getSecureKeyAsync(key);
|
|
377
|
-
const deleteResult =
|
|
378
|
-
? await deleteSecureKeyAsync(key)
|
|
379
|
-
: "not-found";
|
|
390
|
+
const deleteResult =
|
|
391
|
+
existing != null ? await deleteSecureKeyAsync(key) : "not-found";
|
|
380
392
|
|
|
381
393
|
if (deleteResult === "error") {
|
|
382
394
|
throw new InternalError(
|
|
@@ -388,10 +400,12 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
388
400
|
|
|
389
401
|
// Clean up OAuth connection (best-effort).
|
|
390
402
|
let oauthResult: "disconnected" | "not-found" | "error" = "not-found";
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
403
|
+
if (!preserveOAuthConnection) {
|
|
404
|
+
try {
|
|
405
|
+
oauthResult = await disconnectOAuthProvider(service);
|
|
406
|
+
} catch {
|
|
407
|
+
// Best-effort — OAuth tables may not exist yet
|
|
408
|
+
}
|
|
395
409
|
}
|
|
396
410
|
|
|
397
411
|
if (oauthResult === "error") {
|
|
@@ -437,8 +451,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
437
451
|
search: z.string().optional().describe("Filter by substring match"),
|
|
438
452
|
}),
|
|
439
453
|
responseBody: z.object({
|
|
440
|
-
credentials: z
|
|
441
|
-
|
|
454
|
+
credentials: z
|
|
455
|
+
.array(z.unknown())
|
|
456
|
+
.describe("Local credentials with metadata"),
|
|
457
|
+
managedCredentials: z
|
|
458
|
+
.array(z.unknown())
|
|
459
|
+
.describe("Platform-managed credentials"),
|
|
442
460
|
}),
|
|
443
461
|
handler: handleCredentialsList,
|
|
444
462
|
},
|
|
@@ -507,8 +525,14 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
507
525
|
field: z.string().describe("Field name (e.g. client_secret)"),
|
|
508
526
|
value: z.string().describe("Secret value to store"),
|
|
509
527
|
label: z.string().optional().describe("Human-friendly label"),
|
|
510
|
-
description: z
|
|
511
|
-
|
|
528
|
+
description: z
|
|
529
|
+
.string()
|
|
530
|
+
.optional()
|
|
531
|
+
.describe("What this credential is used for"),
|
|
532
|
+
allowedTools: z
|
|
533
|
+
.array(z.string())
|
|
534
|
+
.optional()
|
|
535
|
+
.describe("Tool names that may use this credential"),
|
|
512
536
|
}),
|
|
513
537
|
responseBody: z.object({
|
|
514
538
|
credentialId: z.string(),
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* the greeting regenerates on every request. This is the knob a workspace
|
|
9
9
|
* sets to always receive a fresh greeting.
|
|
10
10
|
*
|
|
11
|
-
* Storage uses the existing `memory_checkpoints` table
|
|
12
|
-
* store), mirroring {@link ./identity-intro-cache.ts}.
|
|
11
|
+
* Storage uses the existing `memory_checkpoints` table.
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
import { getConfig } from "../../config/loader.js";
|
|
@@ -8,13 +8,9 @@ import { availableParallelism, cpus, totalmem } from "node:os";
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
import { getCpuLimit, getIsPlatform } from "../../config/env-registry.js";
|
|
11
|
-
import { resolveCallSiteConfig } from "../../config/llm-resolver.js";
|
|
12
|
-
import { getConfig } from "../../config/loader.js";
|
|
13
11
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
14
12
|
import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
|
|
15
13
|
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
16
|
-
import { buildSystemPrompt } from "../../prompts/system-prompt.js";
|
|
17
|
-
import { getConfiguredProvider } from "../../providers/provider-send-message.js";
|
|
18
14
|
import { getCesClient } from "../../security/secure-keys.js";
|
|
19
15
|
import {
|
|
20
16
|
getDiskUsageInfo,
|
|
@@ -27,15 +23,8 @@ import { resolveHatchedAtReadOnly } from "../../workspace/hatched-date.js";
|
|
|
27
23
|
import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
|
|
28
24
|
import { getLastWorkspaceMigrationId } from "../../workspace/migrations/runner.js";
|
|
29
25
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
30
|
-
import { runBtwSidechain } from "../btw-sidechain.js";
|
|
31
26
|
import { NotFoundError } from "./errors.js";
|
|
32
|
-
import {
|
|
33
|
-
getCachedIntro,
|
|
34
|
-
readWorkspaceGreetings,
|
|
35
|
-
readWorkspaceIdentityIntro,
|
|
36
|
-
setCachedIntro,
|
|
37
|
-
} from "./identity-intro-cache.js";
|
|
38
|
-
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
27
|
+
import type { RouteDefinition } from "./types.js";
|
|
39
28
|
|
|
40
29
|
interface MemoryInfo {
|
|
41
30
|
currentMb: number;
|
|
@@ -405,256 +394,6 @@ function resolveIdentityCreatedAt(identityPath: string): string | undefined {
|
|
|
405
394
|
return resolveHatchedAtReadOnly(identityPath);
|
|
406
395
|
}
|
|
407
396
|
|
|
408
|
-
const FALLBACK_GREETINGS = [
|
|
409
|
-
"What are we working on?",
|
|
410
|
-
"I'm here whenever you need me.",
|
|
411
|
-
"What's on your mind?",
|
|
412
|
-
"Ready when you are.",
|
|
413
|
-
];
|
|
414
|
-
|
|
415
|
-
const GENERATED_GREETING_LIMIT = 5;
|
|
416
|
-
const GREETING_GENERATION_TIMEOUT_MS = 10_000;
|
|
417
|
-
const GREETING_GENERATION_FAILURE_COOLDOWN_MS = 60_000;
|
|
418
|
-
const EMPTY_STATE_GREETING_CALLSITE = "emptyStateGreeting" as const;
|
|
419
|
-
const EXPLICIT_TIME_OF_DAY_PATTERN =
|
|
420
|
-
/\b(?:morning|afternoon|evening|tonight|midnight|noon|sunrise|sunset)\b/i;
|
|
421
|
-
|
|
422
|
-
type IdentityIntroSource = "workspace" | "cache" | "fallback";
|
|
423
|
-
|
|
424
|
-
interface IdentityIntroResponse {
|
|
425
|
-
greetings: string[];
|
|
426
|
-
text: string;
|
|
427
|
-
source: IdentityIntroSource;
|
|
428
|
-
refreshing: boolean;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
let greetingGenerationInFlight: Promise<void> | null = null;
|
|
432
|
-
let lastGreetingGenerationFailureAt = 0;
|
|
433
|
-
|
|
434
|
-
function identityIntroResponse(
|
|
435
|
-
greetings: string[],
|
|
436
|
-
source: IdentityIntroSource,
|
|
437
|
-
refreshing = false,
|
|
438
|
-
): IdentityIntroResponse {
|
|
439
|
-
return {
|
|
440
|
-
greetings,
|
|
441
|
-
text: greetings[0] ?? "",
|
|
442
|
-
source,
|
|
443
|
-
refreshing,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function getIdentityIntro({
|
|
448
|
-
queryParams = {},
|
|
449
|
-
}: RouteHandlerArgs = {}): IdentityIntroResponse {
|
|
450
|
-
const localTimeContext = buildLocalTimeContext(queryParams);
|
|
451
|
-
|
|
452
|
-
// 1. User-defined greetings from SOUL.md `## Greetings`
|
|
453
|
-
const workspaceGreetings = readWorkspaceGreetings();
|
|
454
|
-
if (workspaceGreetings) {
|
|
455
|
-
return identityIntroResponse(workspaceGreetings, "workspace");
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// 2. Cached LLM-generated greetings
|
|
459
|
-
const cached = getCachedIntro();
|
|
460
|
-
if (cached) {
|
|
461
|
-
return identityIntroResponse(cached.greetings, "cache");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// 3. Identity intro tagline from `## Identity Intro` in IDENTITY.md
|
|
465
|
-
// (written during onboarding by BOOTSTRAP.md instructions)
|
|
466
|
-
const identityIntro = readWorkspaceIdentityIntro();
|
|
467
|
-
if (identityIntro) {
|
|
468
|
-
// Still trigger background generation so the next request gets
|
|
469
|
-
// LLM-generated greetings instead of the static tagline.
|
|
470
|
-
const refreshing = triggerEmptyStateGreetingGeneration(localTimeContext);
|
|
471
|
-
return identityIntroResponse(
|
|
472
|
-
[identityIntro, ...FALLBACK_GREETINGS],
|
|
473
|
-
"workspace",
|
|
474
|
-
refreshing,
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// 4. Trigger fresh generation without blocking the empty-state UI.
|
|
479
|
-
const refreshing = triggerEmptyStateGreetingGeneration(localTimeContext);
|
|
480
|
-
|
|
481
|
-
// 5. Generic fallback only when generation is unavailable.
|
|
482
|
-
return identityIntroResponse(FALLBACK_GREETINGS, "fallback", refreshing);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function buildLocalTimeContext(
|
|
486
|
-
queryParams: Record<string, string>,
|
|
487
|
-
): string | null {
|
|
488
|
-
const rawHour = queryParams.localHour;
|
|
489
|
-
const rawMinute = queryParams.localMinute;
|
|
490
|
-
if (rawHour === undefined || rawMinute === undefined) {
|
|
491
|
-
return null;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const hour = Number(rawHour);
|
|
495
|
-
const minute = Number(rawMinute);
|
|
496
|
-
if (
|
|
497
|
-
!Number.isInteger(hour) ||
|
|
498
|
-
!Number.isInteger(minute) ||
|
|
499
|
-
hour < 0 ||
|
|
500
|
-
hour > 23 ||
|
|
501
|
-
minute < 0 ||
|
|
502
|
-
minute > 59
|
|
503
|
-
) {
|
|
504
|
-
return null;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const period =
|
|
508
|
-
hour >= 5 && hour < 12
|
|
509
|
-
? "morning"
|
|
510
|
-
: hour >= 12 && hour < 17
|
|
511
|
-
? "afternoon"
|
|
512
|
-
: hour >= 17 && hour < 21
|
|
513
|
-
? "evening"
|
|
514
|
-
: "late night";
|
|
515
|
-
const paddedHour = String(hour).padStart(2, "0");
|
|
516
|
-
const paddedMinute = String(minute).padStart(2, "0");
|
|
517
|
-
return `${period} (${paddedHour}:${paddedMinute})`;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function triggerEmptyStateGreetingGeneration(
|
|
521
|
-
localTimeContext: string | null,
|
|
522
|
-
): boolean {
|
|
523
|
-
if (greetingGenerationInFlight) {
|
|
524
|
-
return true;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (
|
|
528
|
-
lastGreetingGenerationFailureAt > 0 &&
|
|
529
|
-
Date.now() - lastGreetingGenerationFailureAt <
|
|
530
|
-
GREETING_GENERATION_FAILURE_COOLDOWN_MS
|
|
531
|
-
) {
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
greetingGenerationInFlight = new Promise<void>((resolve) => {
|
|
536
|
-
queueMicrotask(() => {
|
|
537
|
-
void generateEmptyStateGreetings(localTimeContext)
|
|
538
|
-
.then((greetings) => {
|
|
539
|
-
lastGreetingGenerationFailureAt = greetings === null ? Date.now() : 0;
|
|
540
|
-
})
|
|
541
|
-
.finally(() => {
|
|
542
|
-
greetingGenerationInFlight = null;
|
|
543
|
-
resolve();
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
return true;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
async function generateEmptyStateGreetings(
|
|
552
|
-
localTimeContext: string | null,
|
|
553
|
-
): Promise<string[] | null> {
|
|
554
|
-
try {
|
|
555
|
-
const provider = await getConfiguredProvider(EMPTY_STATE_GREETING_CALLSITE);
|
|
556
|
-
if (!provider) {
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const resolved = resolveCallSiteConfig(
|
|
561
|
-
EMPTY_STATE_GREETING_CALLSITE,
|
|
562
|
-
getConfig().llm,
|
|
563
|
-
);
|
|
564
|
-
const systemPrompt = buildSystemPrompt({
|
|
565
|
-
excludeBootstrap: true,
|
|
566
|
-
excludeCustomPrefix: true,
|
|
567
|
-
});
|
|
568
|
-
const localTimeInstruction = localTimeContext
|
|
569
|
-
? ` Current user-local time for subtle tone only: ${localTimeContext}.`
|
|
570
|
-
: "";
|
|
571
|
-
const result = await runBtwSidechain({
|
|
572
|
-
content:
|
|
573
|
-
`Generate ${GENERATED_GREETING_LIMIT} short first-person greeting options for the empty new-chat screen. ` +
|
|
574
|
-
"Use the assistant identity, voice, and relationship guidance from IDENTITY.md and SOUL.md. " +
|
|
575
|
-
"Each greeting should feel personal and inviting, not like a generic assistant introduction. " +
|
|
576
|
-
"Return only a JSON array of strings. No markdown, keys, or explanation. " +
|
|
577
|
-
"Generated greetings are cached for 4 hours, so do not mention the current time " +
|
|
578
|
-
"or use explicit time-of-day words like morning, afternoon, evening, or tonight." +
|
|
579
|
-
localTimeInstruction,
|
|
580
|
-
provider,
|
|
581
|
-
systemPrompt,
|
|
582
|
-
messages: [],
|
|
583
|
-
tools: [],
|
|
584
|
-
callSite: EMPTY_STATE_GREETING_CALLSITE,
|
|
585
|
-
maxTokens: resolved.maxTokens,
|
|
586
|
-
timeoutMs: GREETING_GENERATION_TIMEOUT_MS,
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
const greetings = parseGeneratedGreetings(result.text);
|
|
590
|
-
if (greetings.length === 0) {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
setCachedIntro(greetings);
|
|
595
|
-
return greetings;
|
|
596
|
-
} catch (err) {
|
|
597
|
-
getLogger("identity").warn(
|
|
598
|
-
{ err },
|
|
599
|
-
"Failed to generate empty-state greetings",
|
|
600
|
-
);
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function parseGeneratedGreetings(text: string): string[] {
|
|
606
|
-
const cleaned = text
|
|
607
|
-
.trim()
|
|
608
|
-
.replace(/^```(?:json)?\s*/i, "")
|
|
609
|
-
.replace(/\s*```$/i, "")
|
|
610
|
-
.trim();
|
|
611
|
-
|
|
612
|
-
try {
|
|
613
|
-
const parsed = JSON.parse(cleaned) as unknown;
|
|
614
|
-
if (Array.isArray(parsed)) {
|
|
615
|
-
return normalizeGeneratedGreetings(parsed);
|
|
616
|
-
}
|
|
617
|
-
if (
|
|
618
|
-
parsed &&
|
|
619
|
-
typeof parsed === "object" &&
|
|
620
|
-
Array.isArray((parsed as { greetings?: unknown }).greetings)
|
|
621
|
-
) {
|
|
622
|
-
return normalizeGeneratedGreetings(
|
|
623
|
-
(parsed as { greetings: unknown[] }).greetings,
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
} catch {
|
|
627
|
-
// Fall through to line parsing for non-JSON model output.
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
return normalizeGeneratedGreetings(cleaned.split("\n"));
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function normalizeGeneratedGreetings(values: unknown[]): string[] {
|
|
634
|
-
const greetings: string[] = [];
|
|
635
|
-
const seen = new Set<string>();
|
|
636
|
-
|
|
637
|
-
for (const value of values) {
|
|
638
|
-
if (typeof value !== "string") continue;
|
|
639
|
-
const greeting = value
|
|
640
|
-
.trim()
|
|
641
|
-
.replace(/^(?:[-*+]\s+|\d+[.)]\s+)/, "")
|
|
642
|
-
.replace(/^["'`]+|["'`]+$/g, "")
|
|
643
|
-
.trim();
|
|
644
|
-
if (!greeting) continue;
|
|
645
|
-
if (EXPLICIT_TIME_OF_DAY_PATTERN.test(greeting)) continue;
|
|
646
|
-
|
|
647
|
-
const key = greeting.toLowerCase();
|
|
648
|
-
if (seen.has(key)) continue;
|
|
649
|
-
seen.add(key);
|
|
650
|
-
greetings.push(greeting);
|
|
651
|
-
|
|
652
|
-
if (greetings.length >= GENERATED_GREETING_LIMIT) break;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
return greetings;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
397
|
// ---------------------------------------------------------------------------
|
|
659
398
|
// Zod schemas for profiler health metadata
|
|
660
399
|
// ---------------------------------------------------------------------------
|
|
@@ -787,38 +526,4 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
787
526
|
createdAt: z.string().optional(),
|
|
788
527
|
}),
|
|
789
528
|
},
|
|
790
|
-
{
|
|
791
|
-
operationId: "identity_intro",
|
|
792
|
-
endpoint: "identity/intro",
|
|
793
|
-
method: "GET",
|
|
794
|
-
policy: {
|
|
795
|
-
requiredScopes: ["settings.read"],
|
|
796
|
-
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
797
|
-
},
|
|
798
|
-
handler: getIdentityIntro,
|
|
799
|
-
summary: "Get identity greetings",
|
|
800
|
-
description:
|
|
801
|
-
"Returns greetings sourced from SOUL.md, the generated cache, or generic fallbacks while background generation refreshes the cache.",
|
|
802
|
-
tags: ["identity"],
|
|
803
|
-
queryParams: [
|
|
804
|
-
{
|
|
805
|
-
name: "localHour",
|
|
806
|
-
schema: { type: "integer", minimum: 0, maximum: 23 },
|
|
807
|
-
description:
|
|
808
|
-
"Optional client-local hour of day used only when refreshing generated greetings.",
|
|
809
|
-
},
|
|
810
|
-
{
|
|
811
|
-
name: "localMinute",
|
|
812
|
-
schema: { type: "integer", minimum: 0, maximum: 59 },
|
|
813
|
-
description:
|
|
814
|
-
"Optional client-local minute used only when refreshing generated greetings.",
|
|
815
|
-
},
|
|
816
|
-
],
|
|
817
|
-
responseBody: z.object({
|
|
818
|
-
greetings: z.array(z.string()),
|
|
819
|
-
text: z.string(),
|
|
820
|
-
source: z.enum(["workspace", "cache", "fallback"]),
|
|
821
|
-
refreshing: z.boolean(),
|
|
822
|
-
}),
|
|
823
|
-
},
|
|
824
529
|
];
|
|
@@ -166,7 +166,7 @@ export async function enforceIngressAcl(
|
|
|
166
166
|
if (canonicalSenderId) {
|
|
167
167
|
const contactResult = findContactChannel({
|
|
168
168
|
channelType: sourceChannel,
|
|
169
|
-
|
|
169
|
+
address: canonicalSenderId,
|
|
170
170
|
externalChatId: conversationExternalId,
|
|
171
171
|
});
|
|
172
172
|
resolvedMember = contactResult
|