@vellumai/assistant 0.3.27 → 0.4.0
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 +81 -4
- package/Dockerfile +2 -2
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +9 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +119 -0
- package/src/__tests__/approval-routes-http.test.ts +13 -5
- package/src/__tests__/asset-materialize-tool.test.ts +2 -0
- package/src/__tests__/asset-search-tool.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
- package/src/__tests__/attachments-store.test.ts +2 -0
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/bundled-asset.test.ts +107 -0
- package/src/__tests__/call-controller.test.ts +30 -29
- package/src/__tests__/call-routes-http.test.ts +34 -32
- package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
- package/src/__tests__/canonical-guardian-store.test.ts +636 -0
- package/src/__tests__/channel-approval-routes.test.ts +174 -1
- package/src/__tests__/channel-invite-transport.test.ts +6 -6
- package/src/__tests__/channel-reply-delivery.test.ts +19 -0
- package/src/__tests__/channel-retry-sweep.test.ts +130 -0
- package/src/__tests__/clarification-resolver.test.ts +2 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
- package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +5 -5
- package/src/__tests__/config-watcher.test.ts +3 -1
- package/src/__tests__/connection-policy.test.ts +14 -5
- package/src/__tests__/contacts-tools.test.ts +3 -1
- package/src/__tests__/contradiction-checker.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-routes.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +16 -6
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/credential-vault.test.ts +5 -4
- package/src/__tests__/daemon-lifecycle.test.ts +9 -0
- package/src/__tests__/daemon-server-session-init.test.ts +27 -0
- package/src/__tests__/elevenlabs-config.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
- package/src/__tests__/encrypted-store.test.ts +10 -5
- package/src/__tests__/followup-tools.test.ts +3 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
- package/src/__tests__/guardian-control-plane-policy.test.ts +19 -19
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
- package/src/__tests__/guardian-dispatch.test.ts +21 -19
- package/src/__tests__/guardian-grant-minting.test.ts +68 -1
- package/src/__tests__/guardian-outbound-http.test.ts +12 -9
- package/src/__tests__/guardian-routing-invariants.test.ts +1092 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
- package/src/__tests__/handlers-slack-config.test.ts +3 -1
- package/src/__tests__/handlers-telegram-config.test.ts +3 -1
- package/src/__tests__/handlers-twilio-config.test.ts +3 -1
- package/src/__tests__/handlers-twitter-config.test.ts +3 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
- package/src/__tests__/heartbeat-service.test.ts +20 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
- package/src/__tests__/ingress-reconcile.test.ts +3 -1
- package/src/__tests__/ingress-routes-http.test.ts +231 -4
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +13 -0
- package/src/__tests__/mcp-cli.test.ts +77 -0
- package/src/__tests__/media-generate-image.test.ts +21 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
- package/src/__tests__/memory-regressions.test.ts +20 -20
- package/src/__tests__/non-member-access-request.test.ts +212 -36
- package/src/__tests__/notification-decision-fallback.test.ts +63 -3
- package/src/__tests__/notification-decision-strategy.test.ts +78 -0
- package/src/__tests__/notification-guardian-path.test.ts +15 -15
- package/src/__tests__/oauth-connect-handler.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
- package/src/__tests__/onboarding-template-contract.test.ts +116 -21
- package/src/__tests__/pairing-routes.test.ts +171 -0
- package/src/__tests__/playbook-execution.test.ts +3 -1
- package/src/__tests__/playbook-tools.test.ts +3 -1
- package/src/__tests__/provider-error-scenarios.test.ts +59 -8
- package/src/__tests__/proxy-approval-callback.test.ts +2 -0
- package/src/__tests__/recording-handler.test.ts +11 -0
- package/src/__tests__/recording-intent-handler.test.ts +15 -0
- package/src/__tests__/recording-state-machine.test.ts +13 -2
- package/src/__tests__/registry.test.ts +7 -3
- package/src/__tests__/relay-server.test.ts +148 -28
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
- package/src/__tests__/runtime-events-sse.test.ts +4 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
- package/src/__tests__/schedule-tools.test.ts +3 -1
- package/src/__tests__/secret-scanner-executor.test.ts +59 -0
- package/src/__tests__/secret-scanner.test.ts +8 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -0
- package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
- package/src/__tests__/session-abort-tool-results.test.ts +23 -0
- package/src/__tests__/session-agent-loop.test.ts +16 -0
- package/src/__tests__/session-conflict-gate.test.ts +21 -0
- package/src/__tests__/session-load-history-repair.test.ts +27 -17
- package/src/__tests__/session-pre-run-repair.test.ts +23 -0
- package/src/__tests__/session-profile-injection.test.ts +21 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
- package/src/__tests__/session-queue.test.ts +23 -0
- package/src/__tests__/session-runtime-assembly.test.ts +126 -59
- package/src/__tests__/session-skill-tools.test.ts +27 -5
- package/src/__tests__/session-slash-known.test.ts +23 -0
- package/src/__tests__/session-slash-queue.test.ts +23 -0
- package/src/__tests__/session-slash-unknown.test.ts +23 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
- package/src/__tests__/session-workspace-injection.test.ts +21 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
- package/src/__tests__/shell-credential-ref.test.ts +2 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
- package/src/__tests__/skills.test.ts +8 -4
- package/src/__tests__/slack-channel-config.test.ts +3 -1
- package/src/__tests__/subagent-tools.test.ts +19 -0
- package/src/__tests__/swarm-recursion.test.ts +2 -0
- package/src/__tests__/swarm-session-integration.test.ts +2 -0
- package/src/__tests__/swarm-tool.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +3 -1
- package/src/__tests__/task-compiler.test.ts +3 -1
- package/src/__tests__/task-management-tools.test.ts +3 -1
- package/src/__tests__/task-tools.test.ts +3 -1
- package/src/__tests__/terminal-sandbox.test.ts +13 -12
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
- package/src/__tests__/trusted-contact-verification.test.ts +91 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
- package/src/__tests__/twitter-auth-handler.test.ts +3 -1
- package/src/__tests__/twitter-cli-routing.test.ts +3 -1
- package/src/__tests__/view-image-tool.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +329 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
- package/src/__tests__/voice-session-bridge.test.ts +10 -10
- package/src/__tests__/work-item-output.test.ts +3 -1
- package/src/__tests__/workspace-lifecycle.test.ts +13 -2
- package/src/agent/loop.ts +46 -3
- package/src/approvals/guardian-decision-primitive.ts +285 -0
- package/src/approvals/guardian-request-resolvers.ts +539 -0
- package/src/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/guardian-dispatch.ts +46 -40
- package/src/calls/relay-server.ts +358 -24
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +3 -3
- package/src/cli.ts +12 -0
- package/src/config/agent-schema.ts +14 -3
- package/src/config/calls-schema.ts +6 -6
- package/src/config/core-schema.ts +3 -3
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/mcp-schema.ts +1 -1
- package/src/config/memory-schema.ts +27 -19
- package/src/config/schema.ts +21 -21
- package/src/config/skills-schema.ts +7 -7
- package/src/config/system-prompt.ts +2 -1
- package/src/config/templates/BOOTSTRAP.md +47 -31
- package/src/config/templates/USER.md +5 -0
- package/src/config/update-bulletin-template-path.ts +4 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +149 -21
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/guardian-actions.ts +45 -66
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +22 -16
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/server.ts +18 -0
- package/src/daemon/session-agent-loop-handlers.ts +5 -4
- package/src/daemon/session-agent-loop.ts +33 -6
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +68 -326
- package/src/daemon/session-runtime-assembly.ts +119 -25
- package/src/daemon/session-tool-setup.ts +3 -2
- package/src/daemon/session.ts +4 -3
- package/src/home-base/prebuilt/seed.ts +2 -1
- package/src/hooks/templates.ts +2 -1
- package/src/memory/canonical-guardian-store.ts +586 -0
- package/src/memory/channel-guardian-store.ts +2 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- package/src/memory/guardian-action-store.ts +7 -60
- package/src/memory/guardian-approvals.ts +9 -4
- package/src/memory/guardian-bindings.ts +25 -1
- package/src/memory/indexer.ts +3 -3
- package/src/memory/ingress-invite-store.ts +45 -0
- package/src/memory/job-handlers/backfill.ts +16 -9
- package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
- package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
- package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +56 -0
- package/src/notifications/copy-composer.ts +31 -4
- package/src/notifications/decision-engine.ts +57 -0
- package/src/permissions/defaults.ts +2 -0
- package/src/runtime/access-request-helper.ts +173 -0
- package/src/runtime/actor-trust-resolver.ts +221 -0
- package/src/runtime/channel-guardian-service.ts +12 -4
- package/src/runtime/channel-invite-transports/voice.ts +58 -0
- package/src/runtime/channel-retry-sweep.ts +18 -6
- package/src/runtime/guardian-context-resolver.ts +38 -71
- package/src/runtime/guardian-decision-types.ts +6 -0
- package/src/runtime/guardian-reply-router.ts +717 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
- package/src/runtime/routes/channel-route-shared.ts +1 -1
- package/src/runtime/routes/channel-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +20 -2
- package/src/runtime/routes/guardian-action-routes.ts +100 -109
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +205 -529
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/runtime/tool-grant-request-helper.ts +195 -0
- package/src/tools/executor.ts +13 -1
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/sensitive-output-placeholders.ts +203 -0
- package/src/tools/tool-approval-handler.ts +53 -10
- package/src/tools/types.ts +13 -2
- package/src/util/bundled-asset.ts +31 -0
- package/src/util/canonicalize-identity.ts +52 -0
- package/src/util/logger.ts +20 -8
- package/src/util/platform.ts +10 -0
- package/src/util/voice-code.ts +29 -0
- package/src/daemon/guardian-invite-intent.ts +0 -124
package/src/tools/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SecretPromptResult } from '../permissions/secret-prompter.js';
|
|
2
2
|
import type { AllowlistOption, RiskLevel, ScopeOption } from '../permissions/types.js';
|
|
3
3
|
import type { ContentBlock,ToolDefinition } from '../providers/types.js';
|
|
4
|
+
import type { SensitiveOutputBinding } from './sensitive-output-placeholders.js';
|
|
4
5
|
|
|
5
6
|
export type ExecutionTarget = 'sandbox' | 'host';
|
|
6
7
|
|
|
@@ -136,14 +137,16 @@ export interface ToolContext {
|
|
|
136
137
|
proxyApprovalCallback?: import('./network/script-proxy/types.js').ProxyApprovalCallback;
|
|
137
138
|
/** Optional principal identifier propagated to sub-tool confirmation flows. */
|
|
138
139
|
principal?: string;
|
|
139
|
-
/**
|
|
140
|
-
|
|
140
|
+
/** Inbound trust classification for the session — used by trust/policy gates. */
|
|
141
|
+
guardianTrustClass?: 'guardian' | 'trusted_contact' | 'unknown';
|
|
141
142
|
/** Channel through which the tool invocation originates (e.g. 'telegram', 'voice'). Used for scoped grant consumption. */
|
|
142
143
|
executionChannel?: string;
|
|
143
144
|
/** Voice/call session ID, if the invocation originates from a call. Used for scoped grant consumption. */
|
|
144
145
|
callSessionId?: string;
|
|
145
146
|
/** External user ID of the requester (non-guardian actor). Used for scoped grant consumption. */
|
|
146
147
|
requesterExternalUserId?: string;
|
|
148
|
+
/** Chat ID of the requester (non-guardian actor). Used for tool grant request escalation notifications. */
|
|
149
|
+
requesterChatId?: string;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
export interface DiffInfo {
|
|
@@ -161,6 +164,14 @@ export interface ToolExecutionResult {
|
|
|
161
164
|
status?: string;
|
|
162
165
|
/** Optional rich content blocks (e.g. images) to include alongside text in the tool result. */
|
|
163
166
|
contentBlocks?: ContentBlock[];
|
|
167
|
+
/**
|
|
168
|
+
* Runtime-internal sensitive output bindings (placeholder -> real value).
|
|
169
|
+
* Populated by the executor when tool output contains
|
|
170
|
+
* `<vellum-sensitive-output>` directives. The agent loop merges these
|
|
171
|
+
* into a per-run substitution map for deterministic post-generation
|
|
172
|
+
* replacement. MUST NOT be emitted in client-facing events or logs.
|
|
173
|
+
*/
|
|
174
|
+
sensitiveBindings?: SensitiveOutputBinding[];
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
export interface Tool {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the path to a bundled asset directory, handling compiled Bun binaries
|
|
6
|
+
* where `import.meta.dirname` points to the `/$bunfs/` virtual filesystem and
|
|
7
|
+
* non-JS files (.md, .html, .json, etc.) are not embedded.
|
|
8
|
+
*
|
|
9
|
+
* Falls back to:
|
|
10
|
+
* 1. `Contents/Resources/<bundleName>` (macOS .app bundle)
|
|
11
|
+
* 2. `<execDir>/<bundleName>` (next to the binary, non-app-bundle deployments)
|
|
12
|
+
* 3. Original resolved path (source mode, or last resort)
|
|
13
|
+
*
|
|
14
|
+
* This matches the pattern established by bundled-skills and WASM resolution.
|
|
15
|
+
*
|
|
16
|
+
* @param callerDir `import.meta.dirname ?? __dirname` from the call site
|
|
17
|
+
* @param relativePath Relative path from the source file (used in source/dev mode)
|
|
18
|
+
* @param bundleName Name of the asset directory in the app bundle
|
|
19
|
+
*/
|
|
20
|
+
export function resolveBundledDir(callerDir: string, relativePath: string, bundleName: string): string {
|
|
21
|
+
if (callerDir.startsWith('/$bunfs/')) {
|
|
22
|
+
const execDir = dirname(process.execPath);
|
|
23
|
+
// macOS .app bundle: binary in Contents/MacOS/, resources in Contents/Resources/
|
|
24
|
+
const resourcesPath = join(execDir, '..', 'Resources', bundleName);
|
|
25
|
+
if (existsSync(resourcesPath)) return resourcesPath;
|
|
26
|
+
// Next to the binary itself (non-app-bundle deployments)
|
|
27
|
+
const execDirPath = join(execDir, bundleName);
|
|
28
|
+
if (existsSync(execDirPath)) return execDirPath;
|
|
29
|
+
}
|
|
30
|
+
return join(callerDir, relativePath);
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-agnostic inbound identity canonicalization.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes raw sender identifiers into a stable canonical form so that
|
|
5
|
+
* trust lookups, member matching, and guardian binding comparisons are
|
|
6
|
+
* immune to formatting variance across channels.
|
|
7
|
+
*
|
|
8
|
+
* Phone-like channels (sms, voice, whatsapp) normalize to E.164 using the
|
|
9
|
+
* existing phone utilities. Non-phone channels (telegram, slack, etc.)
|
|
10
|
+
* pass through the platform-stable ID as-is after whitespace trimming.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ChannelId } from '../channels/types.js';
|
|
14
|
+
import { normalizePhoneNumber } from './phone.js';
|
|
15
|
+
|
|
16
|
+
/** Channels whose raw sender IDs are phone numbers. */
|
|
17
|
+
const PHONE_CHANNELS: ReadonlySet<ChannelId> = new Set(['sms', 'voice', 'whatsapp']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Canonicalize a raw inbound sender identity for the given channel.
|
|
21
|
+
*
|
|
22
|
+
* - For phone-like channels: attempts E.164 normalization. Returns the
|
|
23
|
+
* normalized E.164 string on success, or the trimmed raw ID if
|
|
24
|
+
* normalization fails (defensive: don't discard an identity just because
|
|
25
|
+
* it doesn't parse as a phone number).
|
|
26
|
+
* - For non-phone channels: returns the trimmed raw ID unchanged (these
|
|
27
|
+
* platforms provide stable, unique identifiers that don't need normalization).
|
|
28
|
+
*
|
|
29
|
+
* Returns `null` only when `rawId` is empty/whitespace-only.
|
|
30
|
+
*/
|
|
31
|
+
export function canonicalizeInboundIdentity(channel: ChannelId, rawId: string): string | null {
|
|
32
|
+
const trimmed = rawId.trim();
|
|
33
|
+
if (trimmed.length === 0) return null;
|
|
34
|
+
|
|
35
|
+
if (PHONE_CHANNELS.has(channel)) {
|
|
36
|
+
const e164 = normalizePhoneNumber(trimmed);
|
|
37
|
+
// Defensive: if normalization fails, preserve the raw ID so downstream
|
|
38
|
+
// lookups don't silently lose the identity.
|
|
39
|
+
return e164 ?? trimmed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check whether a channel uses phone-number-based identity.
|
|
47
|
+
* Useful for call sites that need to know whether E.164 normalization
|
|
48
|
+
* applies without re-importing the channel set.
|
|
49
|
+
*/
|
|
50
|
+
export function isPhoneChannel(channel: ChannelId): boolean {
|
|
51
|
+
return PHONE_CHANNELS.has(channel);
|
|
52
|
+
}
|
package/src/util/logger.ts
CHANGED
|
@@ -3,12 +3,22 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { Writable } from 'node:stream';
|
|
4
4
|
|
|
5
5
|
import pino from 'pino';
|
|
6
|
+
import type { PrettyOptions } from 'pino-pretty';
|
|
6
7
|
import pinoPretty from 'pino-pretty';
|
|
7
8
|
|
|
8
9
|
import { getDebugMode, getDebugStdoutLogs,getLogStderr } from '../config/env-registry.js';
|
|
9
10
|
import { logSerializers } from './log-redact.js';
|
|
10
11
|
import { getLogPath } from './platform.js';
|
|
11
12
|
|
|
13
|
+
/** Common pino-pretty options that inline [module] into the message prefix. */
|
|
14
|
+
function prettyOpts(extra?: PrettyOptions): PrettyOptions {
|
|
15
|
+
return {
|
|
16
|
+
messageFormat: '[{module}] {msg}',
|
|
17
|
+
ignore: 'module',
|
|
18
|
+
...extra,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
export type LogFileConfig = {
|
|
13
23
|
dir: string | undefined;
|
|
14
24
|
retentionDays: number;
|
|
@@ -59,7 +69,7 @@ let activeLogFileConfig: LogFileConfig | null = null;
|
|
|
59
69
|
|
|
60
70
|
function buildRotatingLogger(config: LogFileConfig): pino.Logger {
|
|
61
71
|
if (!config.dir) {
|
|
62
|
-
return pino({ name: 'assistant', serializers: logSerializers }, pinoPretty({ destination: 1 }));
|
|
72
|
+
return pino({ name: 'assistant', serializers: logSerializers }, pinoPretty(prettyOpts({ destination: 1 })));
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
if (!existsSync(config.dir)) {
|
|
@@ -68,9 +78,10 @@ function buildRotatingLogger(config: LogFileConfig): pino.Logger {
|
|
|
68
78
|
|
|
69
79
|
const today = formatDate(new Date());
|
|
70
80
|
const filePath = logFilePathForDate(config.dir, new Date());
|
|
71
|
-
const
|
|
81
|
+
const fileDest = pino.destination({ dest: filePath, sync: false, mkdir: true, mode: 0o600 });
|
|
72
82
|
// Tighten permissions on pre-existing log files that may have been created with looser modes
|
|
73
83
|
try { chmodSync(filePath, 0o600); } catch { /* best-effort */ }
|
|
84
|
+
const fileStream = pinoPretty(prettyOpts({ destination: fileDest, colorize: false }));
|
|
74
85
|
|
|
75
86
|
activeLogDate = today;
|
|
76
87
|
activeLogFileConfig = config;
|
|
@@ -78,7 +89,7 @@ function buildRotatingLogger(config: LogFileConfig): pino.Logger {
|
|
|
78
89
|
const level = getDebugMode() ? 'debug' : 'info';
|
|
79
90
|
|
|
80
91
|
if (getDebugMode()) {
|
|
81
|
-
const prettyStream = pinoPretty({ destination: 2 });
|
|
92
|
+
const prettyStream = pinoPretty(prettyOpts({ destination: 2 }));
|
|
82
93
|
return pino(
|
|
83
94
|
{ name: 'assistant', level, serializers: logSerializers },
|
|
84
95
|
pino.multistream([
|
|
@@ -92,7 +103,7 @@ function buildRotatingLogger(config: LogFileConfig): pino.Logger {
|
|
|
92
103
|
{ name: 'assistant', level, serializers: logSerializers },
|
|
93
104
|
pino.multistream([
|
|
94
105
|
{ stream: fileStream, level: 'info' as const },
|
|
95
|
-
{ stream: pinoPretty({ destination: 1 }), level: 'info' as const },
|
|
106
|
+
{ stream: pinoPretty(prettyOpts({ destination: 1 })), level: 'info' as const },
|
|
96
107
|
]),
|
|
97
108
|
);
|
|
98
109
|
}
|
|
@@ -135,12 +146,13 @@ function getRootLogger(): pino.Logger {
|
|
|
135
146
|
|
|
136
147
|
try {
|
|
137
148
|
const logPath = getLogPath();
|
|
138
|
-
const
|
|
149
|
+
const fileDest = pino.destination({ dest: logPath, sync: false, mkdir: true, mode: 0o600 });
|
|
139
150
|
// Tighten permissions on pre-existing log files that may have been created with looser modes
|
|
140
151
|
try { chmodSync(logPath, 0o600); } catch { /* best-effort */ }
|
|
152
|
+
const fileStream = pinoPretty(prettyOpts({ destination: fileDest, colorize: false }));
|
|
141
153
|
|
|
142
154
|
if (getDebugMode()) {
|
|
143
|
-
const prettyStream = pinoPretty({ destination: 2 });
|
|
155
|
+
const prettyStream = pinoPretty(prettyOpts({ destination: 2 }));
|
|
144
156
|
const multi = pino.multistream([
|
|
145
157
|
{ stream: fileStream, level: 'info' as const },
|
|
146
158
|
{ stream: prettyStream, level: 'debug' as const },
|
|
@@ -151,14 +163,14 @@ function getRootLogger(): pino.Logger {
|
|
|
151
163
|
{ level: 'info', serializers: logSerializers },
|
|
152
164
|
pino.multistream([
|
|
153
165
|
{ stream: fileStream, level: 'info' as const },
|
|
154
|
-
{ stream: pinoPretty({ destination: 1 }), level: 'info' as const },
|
|
166
|
+
{ stream: pinoPretty(prettyOpts({ destination: 1 })), level: 'info' as const },
|
|
155
167
|
]),
|
|
156
168
|
);
|
|
157
169
|
} else {
|
|
158
170
|
rootLogger = pino({ level: 'info', serializers: logSerializers }, fileStream);
|
|
159
171
|
}
|
|
160
172
|
} catch {
|
|
161
|
-
rootLogger = pino({ level: getDebugMode() ? 'debug' : 'info', serializers: logSerializers }, pinoPretty({ destination: 2 }));
|
|
173
|
+
rootLogger = pino({ level: getDebugMode() ? 'debug' : 'info', serializers: logSerializers }, pinoPretty(prettyOpts({ destination: 2 })));
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
176
|
return rootLogger;
|
package/src/util/platform.ts
CHANGED
|
@@ -121,6 +121,15 @@ export function getDataDir(): string {
|
|
|
121
121
|
return join(getWorkspaceDir(), 'data');
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Returns the embedding models directory (~/.vellum/workspace/embedding-models).
|
|
126
|
+
* Downloaded embedding runtime (onnxruntime-node, transformers bundle, model weights)
|
|
127
|
+
* is stored here, downloaded post-hatch rather than shipped with the app.
|
|
128
|
+
*/
|
|
129
|
+
export function getEmbeddingModelsDir(): string {
|
|
130
|
+
return join(getWorkspaceDir(), 'embedding-models');
|
|
131
|
+
}
|
|
132
|
+
|
|
124
133
|
/**
|
|
125
134
|
* Returns the IPC blob directory (~/.vellum/workspace/data/ipc-blobs).
|
|
126
135
|
* Temporary blob files for zero-copy IPC payloads live here.
|
|
@@ -357,6 +366,7 @@ export function ensureDataDir(): void {
|
|
|
357
366
|
workspace,
|
|
358
367
|
join(workspace, 'hooks'),
|
|
359
368
|
join(workspace, 'skills'),
|
|
369
|
+
join(workspace, 'embedding-models'),
|
|
360
370
|
// Data sub-dirs under workspace
|
|
361
371
|
wsData,
|
|
362
372
|
join(wsData, 'db'),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic voice invite code generation and hashing.
|
|
3
|
+
*
|
|
4
|
+
* Generates short numeric codes (default 6 digits) for voice-channel invite
|
|
5
|
+
* redemption. The plaintext code is returned once at creation time and never
|
|
6
|
+
* stored — only its SHA-256 hash is persisted.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createHash, randomInt } from 'node:crypto';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate a cryptographically random numeric code of the given length.
|
|
13
|
+
* Uses node:crypto randomInt for uniform distribution.
|
|
14
|
+
*/
|
|
15
|
+
export function generateVoiceCode(digits: number = 6): string {
|
|
16
|
+
if (digits < 4 || digits > 10) {
|
|
17
|
+
throw new Error(`Voice code digit count must be between 4 and 10, got ${digits}`);
|
|
18
|
+
}
|
|
19
|
+
const min = Math.pow(10, digits - 1); // e.g. 100000 for 6 digits
|
|
20
|
+
const max = Math.pow(10, digits); // e.g. 1000000 for 6 digits
|
|
21
|
+
return String(randomInt(min, max));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* SHA-256 hash a voice code for storage comparison.
|
|
26
|
+
*/
|
|
27
|
+
export function hashVoiceCode(code: string): string {
|
|
28
|
+
return createHash('sha256').update(code).digest('hex');
|
|
29
|
+
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
// Guardian invite intent resolution for deterministic first-turn routing.
|
|
2
|
-
// Exports `resolveGuardianInviteIntent` as the single public entry point.
|
|
3
|
-
// When a guardian invite management request is detected, the session pipeline
|
|
4
|
-
// rewrites the message to force immediate entry into the trusted-contacts
|
|
5
|
-
// skill flow, bypassing the normal agent loop's tendency to produce conceptual
|
|
6
|
-
// preambles before loading the skill.
|
|
7
|
-
|
|
8
|
-
export type GuardianInviteIntentResult =
|
|
9
|
-
| { kind: 'none' }
|
|
10
|
-
| { kind: 'invite_management'; rewrittenContent: string; action?: 'create' | 'list' | 'revoke' };
|
|
11
|
-
|
|
12
|
-
// ── Direct invite patterns ────────────────────────────────────────────────
|
|
13
|
-
// These capture imperative requests to manage Telegram invite links.
|
|
14
|
-
|
|
15
|
-
const CREATE_INVITE_PATTERNS: RegExp[] = [
|
|
16
|
-
/\bcreate\s+(?:an?\s+)?(?:telegram\s+)?invite\s*(?:link)?\b/i,
|
|
17
|
-
/\binvite\s+(?:someone|somebody|a\s+friend|a\s+person)\s+(?:on|to|via|through)\s+telegram\b/i,
|
|
18
|
-
/\b(?:make|generate|get)\s+(?:a\s+|an\s+)?(?:telegram\s+)?invite\s*(?:link)?\b/i,
|
|
19
|
-
/\btelegram\s+invite\s*(?:link)?\b/i,
|
|
20
|
-
/\bsend\s+(?:a\s+|an\s+)?invite\s+(?:link\s+)?(?:on|for|via|through)\s+telegram\b/i,
|
|
21
|
-
/\bshare\s+(?:a\s+|an\s+)?(?:telegram\s+)?invite\s*(?:link)?\b/i,
|
|
22
|
-
/\binvite\s+(?:link\s+)?for\s+telegram\b/i,
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const LIST_INVITE_PATTERNS: RegExp[] = [
|
|
26
|
-
/\b(?:show|list|view|see|display)\s+(?:my\s+)?(?:active\s+)?invite(?:s|\s*links?)\b/i,
|
|
27
|
-
/\b(?:show|list|view|see|display)\s+(?:my\s+)?(?:telegram\s+)?invite(?:s|\s*links?)\b/i,
|
|
28
|
-
/\bwhat\s+invite(?:s|\s*links?)\s+(?:do\s+I\s+have|are\s+active|exist)\b/i,
|
|
29
|
-
/\bhow\s+many\s+invite(?:s|\s*links?)\b/i,
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const REVOKE_INVITE_PATTERNS: RegExp[] = [
|
|
33
|
-
/\b(?:revoke|cancel|disable|invalidate|delete|remove)\s+(?:the\s+|my\s+|an?\s+)?invite\s*(?:link)?\b/i,
|
|
34
|
-
/\b(?:revoke|cancel|disable|invalidate|delete|remove)\s+(?:the\s+|my\s+|an?\s+)?(?:telegram\s+)?invite\s*(?:link)?\b/i,
|
|
35
|
-
/\binvite\s*(?:link)?\s+(?:revoke|cancel|disable|invalidate|delete|remove)\b/i,
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// ── Conceptual / question patterns ──────────────────────────────────────
|
|
39
|
-
// These indicate the user is asking *about* invites rather than requesting
|
|
40
|
-
// to manage them. Return passthrough for these.
|
|
41
|
-
|
|
42
|
-
const CONCEPTUAL_PATTERNS: RegExp[] = [
|
|
43
|
-
/^\s*(?:how|what|why|when|where|who|which)\b.*\binvite/i,
|
|
44
|
-
/\bwhat\s+(?:is|are)\s+(?:an?\s+)?invite\s*(?:link)?\b/i,
|
|
45
|
-
/\bhow\s+(?:do|does|can)\s+(?:invite|invitation)s?\s+work\b/i,
|
|
46
|
-
/\bexplain\s+(?:the\s+)?invite\b/i,
|
|
47
|
-
/\btell\s+me\s+about\s+invite\b/i,
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
/** Common polite/filler words stripped before checking intent-only status. */
|
|
51
|
-
const FILLER_PATTERN =
|
|
52
|
-
/\b(please|pls|plz|can\s+you|could\s+you|would\s+you|now|right\s+now|thanks|thank\s+you|thx|ty|for\s+me|ok(ay)?|hey|hi|hello|just|i\s+want\s+to|i'd\s+like\s+to|i\s+need\s+to|let's|let\s+me)\b/gi;
|
|
53
|
-
|
|
54
|
-
// ── Internal helpers ─────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
function isConceptualQuestion(text: string): boolean {
|
|
57
|
-
const cleaned = text.replace(/^\s*(hey|hi|hello|please|pls|plz)[,\s]+/i, '');
|
|
58
|
-
// Allow actionable requests through even though they start with
|
|
59
|
-
// question-like words — these are imperative invite management requests.
|
|
60
|
-
if (LIST_INVITE_PATTERNS.some((p) => p.test(cleaned))) return false;
|
|
61
|
-
if (CREATE_INVITE_PATTERNS.some((p) => p.test(cleaned))) return false;
|
|
62
|
-
if (REVOKE_INVITE_PATTERNS.some((p) => p.test(cleaned))) return false;
|
|
63
|
-
return CONCEPTUAL_PATTERNS.some((p) => p.test(cleaned));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function detectAction(text: string): 'create' | 'list' | 'revoke' | undefined {
|
|
67
|
-
// Check revoke and list before create — create patterns include the broad
|
|
68
|
-
// `telegram invite link` matcher that would otherwise swallow revoke/list inputs.
|
|
69
|
-
if (REVOKE_INVITE_PATTERNS.some((p) => p.test(text))) return 'revoke';
|
|
70
|
-
if (LIST_INVITE_PATTERNS.some((p) => p.test(text))) return 'list';
|
|
71
|
-
if (CREATE_INVITE_PATTERNS.some((p) => p.test(text))) return 'create';
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ── Structured intent resolver ───────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Resolves guardian invite management intent from user text.
|
|
79
|
-
*
|
|
80
|
-
* Pipeline:
|
|
81
|
-
* 1. Skip slash commands entirely
|
|
82
|
-
* 2. Conceptual question gate -- questions return `none`
|
|
83
|
-
* 3. Detect create/list/revoke invite patterns
|
|
84
|
-
* 4. On match, build a deterministic model instruction to load trusted-contacts
|
|
85
|
-
*/
|
|
86
|
-
export function resolveGuardianInviteIntent(text: string): GuardianInviteIntentResult {
|
|
87
|
-
const trimmed = text.trim();
|
|
88
|
-
|
|
89
|
-
// Never intercept slash commands
|
|
90
|
-
if (trimmed.startsWith('/')) {
|
|
91
|
-
return { kind: 'none' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Conceptual questions pass through to normal agent processing
|
|
95
|
-
if (isConceptualQuestion(trimmed)) {
|
|
96
|
-
return { kind: 'none' };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Strip fillers for pattern matching but keep original for context
|
|
100
|
-
const withoutFillers = trimmed.replace(FILLER_PATTERN, '').replace(/\s{2,}/g, ' ').trim();
|
|
101
|
-
|
|
102
|
-
const action = detectAction(withoutFillers);
|
|
103
|
-
if (!action) {
|
|
104
|
-
return { kind: 'none' };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Build the rewritten content that deterministically loads the skill
|
|
108
|
-
const actionDescriptions: Record<string, string> = {
|
|
109
|
-
create: 'The user wants to create a Telegram invite link. Create the invite, look up the bot username, and present the shareable deep link with copy-paste instructions.',
|
|
110
|
-
list: 'The user wants to see their invite links. List all invites (especially active ones for Telegram) and present them in a readable format.',
|
|
111
|
-
revoke: 'The user wants to revoke an invite link. List invites to identify the target, confirm with the user, then revoke it.',
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const rewrittenContent = [
|
|
115
|
-
actionDescriptions[action],
|
|
116
|
-
'Please invoke the "Trusted Contacts" skill (ID: trusted-contacts) immediately using skill_load.',
|
|
117
|
-
].join('\n');
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
kind: 'invite_management',
|
|
121
|
-
rewrittenContent,
|
|
122
|
-
action,
|
|
123
|
-
};
|
|
124
|
-
}
|