@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
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Downloads and manages the local embedding runtime post-hatch.
|
|
3
|
+
*
|
|
4
|
+
* Instead of shipping heavy native + JS dependencies inside the .app bundle,
|
|
5
|
+
* we download them from npm and run embeddings in a **separate bun process**.
|
|
6
|
+
* The compiled daemon binary cannot resolve bare specifiers in dynamically
|
|
7
|
+
* imported files, so we spawn a standalone bun process that runs an embed
|
|
8
|
+
* worker script communicating via JSON-lines over stdin/stdout.
|
|
9
|
+
*
|
|
10
|
+
* The runtime is stored in ~/.vellum/workspace/embedding-models/ and used
|
|
11
|
+
* by embedding-local.ts on demand.
|
|
12
|
+
*
|
|
13
|
+
* Follows the same download/install pattern as qdrant-manager.ts.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { arch, platform } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
import { getLogger } from '../util/logger.js';
|
|
21
|
+
import { getEmbeddingModelsDir } from '../util/platform.js';
|
|
22
|
+
import { PromiseGuard } from '../util/promise-guard.js';
|
|
23
|
+
|
|
24
|
+
const log = getLogger('embedding-runtime-manager');
|
|
25
|
+
|
|
26
|
+
// Pinned versions matching assistant/bun.lock
|
|
27
|
+
const ONNXRUNTIME_NODE_VERSION = '1.21.0';
|
|
28
|
+
const ONNXRUNTIME_COMMON_VERSION = '1.21.0';
|
|
29
|
+
const TRANSFORMERS_VERSION = '3.8.1';
|
|
30
|
+
|
|
31
|
+
/** Bun version to download when system bun is not available. */
|
|
32
|
+
const BUN_VERSION = '1.2.0';
|
|
33
|
+
|
|
34
|
+
/** Composite version string for cache invalidation. */
|
|
35
|
+
const RUNTIME_VERSION = `ort-${ONNXRUNTIME_NODE_VERSION}_hf-${TRANSFORMERS_VERSION}`;
|
|
36
|
+
|
|
37
|
+
const WORKER_FILENAME = 'embed-worker.mjs';
|
|
38
|
+
|
|
39
|
+
/** Module-level guard so concurrent in-process calls share one download. */
|
|
40
|
+
const installGuard = new PromiseGuard<void>();
|
|
41
|
+
|
|
42
|
+
interface VersionManifest {
|
|
43
|
+
runtimeVersion: string;
|
|
44
|
+
onnxruntimeNodeVersion: string;
|
|
45
|
+
onnxruntimeCommonVersion: string;
|
|
46
|
+
transformersVersion: string;
|
|
47
|
+
platform: string;
|
|
48
|
+
arch: string;
|
|
49
|
+
installedAt: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── npm tarball helpers ─────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function npmTarballUrl(pkg: string, version: string): string {
|
|
55
|
+
// Scoped packages encode the scope in the URL
|
|
56
|
+
const encoded = pkg.replace('/', '%2f');
|
|
57
|
+
const basename = pkg.startsWith('@') ? pkg.split('/')[1] : pkg;
|
|
58
|
+
return `https://registry.npmjs.org/${encoded}/-/${basename}-${version}.tgz`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function downloadAndExtract(
|
|
62
|
+
url: string,
|
|
63
|
+
targetDir: string,
|
|
64
|
+
signal?: AbortSignal,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
log.info({ url, targetDir }, 'Downloading npm package');
|
|
67
|
+
|
|
68
|
+
const response = await fetch(url, { signal });
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const tarball = await response.arrayBuffer();
|
|
74
|
+
|
|
75
|
+
// npm tarballs extract to package/, we need to redirect to targetDir
|
|
76
|
+
mkdirSync(targetDir, { recursive: true });
|
|
77
|
+
|
|
78
|
+
const tmpTar = join(targetDir, `download-${Date.now()}.tgz`);
|
|
79
|
+
writeFileSync(tmpTar, Buffer.from(tarball));
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Extract tarball, stripping the leading "package/" directory
|
|
83
|
+
const proc = Bun.spawn({
|
|
84
|
+
cmd: ['tar', 'xzf', tmpTar, '-C', targetDir, '--strip-components=1'],
|
|
85
|
+
stdout: 'ignore',
|
|
86
|
+
stderr: 'pipe',
|
|
87
|
+
});
|
|
88
|
+
await proc.exited;
|
|
89
|
+
if (proc.exitCode !== 0) {
|
|
90
|
+
const stderr = await new Response(proc.stderr).text();
|
|
91
|
+
throw new Error(`Failed to extract ${url}: ${stderr}`);
|
|
92
|
+
}
|
|
93
|
+
} finally {
|
|
94
|
+
try { rmSync(tmpTar); } catch { /* ignore */ }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Worker script content ───────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
function generateWorkerScript(): string {
|
|
101
|
+
// This script is run by a standalone bun process (not the compiled daemon).
|
|
102
|
+
// Because it runs in a real bun runtime, bare specifier resolution works
|
|
103
|
+
// normally — node_modules/ in the same directory is found automatically.
|
|
104
|
+
return `\
|
|
105
|
+
// embed-worker.mjs — Auto-generated by EmbeddingRuntimeManager
|
|
106
|
+
// Runs in a separate bun process, communicates via JSON-lines over stdin/stdout.
|
|
107
|
+
import { pipeline, env } from '@huggingface/transformers';
|
|
108
|
+
|
|
109
|
+
const model = process.argv[2];
|
|
110
|
+
const cacheDir = process.argv[3];
|
|
111
|
+
if (cacheDir && env) env.cacheDir = cacheDir;
|
|
112
|
+
|
|
113
|
+
let extractor;
|
|
114
|
+
try {
|
|
115
|
+
extractor = await pipeline('feature-extraction', model, { dtype: 'fp32' });
|
|
116
|
+
process.stdout.write(JSON.stringify({ type: 'ready' }) + '\\n');
|
|
117
|
+
} catch (err) {
|
|
118
|
+
process.stdout.write(JSON.stringify({ type: 'error', error: err.message || String(err) }) + '\\n');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Sequential request queue to avoid concurrent ONNX inference
|
|
123
|
+
const decoder = new TextDecoder();
|
|
124
|
+
let buffer = '';
|
|
125
|
+
let processing = false;
|
|
126
|
+
const queue = [];
|
|
127
|
+
|
|
128
|
+
process.stdin.on('data', (chunk) => {
|
|
129
|
+
buffer += typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
|
|
130
|
+
let idx;
|
|
131
|
+
while ((idx = buffer.indexOf('\\n')) !== -1) {
|
|
132
|
+
const line = buffer.slice(0, idx);
|
|
133
|
+
buffer = buffer.slice(idx + 1);
|
|
134
|
+
if (line.trim()) queue.push(line);
|
|
135
|
+
}
|
|
136
|
+
if (!processing) processQueue();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
async function processQueue() {
|
|
140
|
+
processing = true;
|
|
141
|
+
while (queue.length > 0) {
|
|
142
|
+
const line = queue.shift();
|
|
143
|
+
let req;
|
|
144
|
+
try { req = JSON.parse(line); } catch { continue; }
|
|
145
|
+
try {
|
|
146
|
+
const output = await extractor(req.texts, { pooling: 'cls', normalize: true });
|
|
147
|
+
const vectors = output.tolist();
|
|
148
|
+
process.stdout.write(JSON.stringify({ id: req.id, vectors }) + '\\n');
|
|
149
|
+
} catch (err) {
|
|
150
|
+
process.stdout.write(JSON.stringify({ id: req.id, error: err.message || String(err) }) + '\\n');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
processing = false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
process.stdin.on('end', () => process.exit(0));
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Main manager ────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
export class EmbeddingRuntimeManager {
|
|
163
|
+
private readonly baseDir: string;
|
|
164
|
+
|
|
165
|
+
constructor(baseDir?: string) {
|
|
166
|
+
this.baseDir = baseDir ?? getEmbeddingModelsDir();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Check if the embedding runtime is installed and up-to-date. */
|
|
170
|
+
isReady(): boolean {
|
|
171
|
+
const manifest = this.readManifest();
|
|
172
|
+
if (!manifest) return false;
|
|
173
|
+
if (manifest.runtimeVersion !== RUNTIME_VERSION) return false;
|
|
174
|
+
|
|
175
|
+
// Verify the worker script exists and a bun binary is available
|
|
176
|
+
return existsSync(this.getWorkerPath()) && this.getBunPath() !== undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Path to the embed worker script. */
|
|
180
|
+
getWorkerPath(): string {
|
|
181
|
+
return join(this.baseDir, WORKER_FILENAME);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Find a usable bun binary.
|
|
186
|
+
* Checks: downloaded copy → common install locations.
|
|
187
|
+
*/
|
|
188
|
+
getBunPath(): string | undefined {
|
|
189
|
+
// 1. Downloaded bun
|
|
190
|
+
const downloadedBun = join(this.baseDir, 'bin', 'bun');
|
|
191
|
+
if (existsSync(downloadedBun)) return downloadedBun;
|
|
192
|
+
|
|
193
|
+
// 2. Common installation paths — the compiled daemon inherits a
|
|
194
|
+
// restricted PATH, so we check well-known prefixes directly.
|
|
195
|
+
const home = process.env.HOME ?? '';
|
|
196
|
+
for (const p of [
|
|
197
|
+
join(home, '.bun', 'bin', 'bun'),
|
|
198
|
+
'/opt/homebrew/bin/bun',
|
|
199
|
+
'/usr/local/bin/bun',
|
|
200
|
+
]) {
|
|
201
|
+
if (existsSync(p)) return p;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Download and install the embedding runtime if not already present.
|
|
209
|
+
* Safe to call concurrently — in-process calls share one promise via
|
|
210
|
+
* PromiseGuard, and cross-process calls are serialized via a lock file.
|
|
211
|
+
*/
|
|
212
|
+
async ensureInstalled(signal?: AbortSignal): Promise<void> {
|
|
213
|
+
if (this.isReady()) return;
|
|
214
|
+
|
|
215
|
+
// Deduplicate concurrent in-process calls
|
|
216
|
+
await installGuard.run(() => this.acquireLockAndInstall(signal));
|
|
217
|
+
|
|
218
|
+
// If another process was downloading and we skipped, or if the download
|
|
219
|
+
// somehow failed silently, reset the guard so we can retry next time.
|
|
220
|
+
if (!this.isReady()) {
|
|
221
|
+
installGuard.reset();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private async acquireLockAndInstall(signal?: AbortSignal): Promise<void> {
|
|
226
|
+
// Re-check after acquiring the in-process guard
|
|
227
|
+
if (this.isReady()) return;
|
|
228
|
+
|
|
229
|
+
// Cross-process lock to prevent duplicate downloads
|
|
230
|
+
const lockPath = join(this.baseDir, '.downloading');
|
|
231
|
+
if (existsSync(lockPath)) {
|
|
232
|
+
try {
|
|
233
|
+
const lockContent = readFileSync(lockPath, 'utf-8').trim();
|
|
234
|
+
const lockPid = parseInt(lockContent, 10);
|
|
235
|
+
if (!isNaN(lockPid) && lockPid !== process.pid) {
|
|
236
|
+
try {
|
|
237
|
+
process.kill(lockPid, 0);
|
|
238
|
+
log.info({ lockPid }, 'Another process is downloading the embedding runtime, skipping');
|
|
239
|
+
return;
|
|
240
|
+
} catch {
|
|
241
|
+
log.info({ lockPid }, 'Cleaning up stale download lock');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
// Can't read lock file, proceed
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
mkdirSync(this.baseDir, { recursive: true });
|
|
250
|
+
|
|
251
|
+
// Write a .gitignore so the workspace git repo ignores this directory
|
|
252
|
+
const gitignorePath = join(this.baseDir, '.gitignore');
|
|
253
|
+
if (!existsSync(gitignorePath)) {
|
|
254
|
+
writeFileSync(gitignorePath, '*\n!.gitignore\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
writeFileSync(lockPath, String(process.pid));
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
await this.install(signal);
|
|
261
|
+
} finally {
|
|
262
|
+
try { rmSync(lockPath); } catch { /* ignore */ }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async install(signal?: AbortSignal): Promise<void> {
|
|
267
|
+
const os = platform();
|
|
268
|
+
const cpu = arch();
|
|
269
|
+
log.info({ os, cpu, runtimeVersion: RUNTIME_VERSION }, 'Installing embedding runtime');
|
|
270
|
+
|
|
271
|
+
// Work in a temp directory for atomic install
|
|
272
|
+
const tmpDir = join(this.baseDir, `.installing-${Date.now()}`);
|
|
273
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
// Step 1: Download npm packages (and bun if needed) in parallel
|
|
277
|
+
const nodeModules = join(tmpDir, 'node_modules');
|
|
278
|
+
const downloads: Promise<void>[] = [
|
|
279
|
+
downloadAndExtract(
|
|
280
|
+
npmTarballUrl('onnxruntime-node', ONNXRUNTIME_NODE_VERSION),
|
|
281
|
+
join(nodeModules, 'onnxruntime-node'),
|
|
282
|
+
signal,
|
|
283
|
+
),
|
|
284
|
+
downloadAndExtract(
|
|
285
|
+
npmTarballUrl('onnxruntime-common', ONNXRUNTIME_COMMON_VERSION),
|
|
286
|
+
join(nodeModules, 'onnxruntime-common'),
|
|
287
|
+
signal,
|
|
288
|
+
),
|
|
289
|
+
downloadAndExtract(
|
|
290
|
+
npmTarballUrl('@huggingface/transformers', TRANSFORMERS_VERSION),
|
|
291
|
+
join(nodeModules, '@huggingface', 'transformers'),
|
|
292
|
+
signal,
|
|
293
|
+
),
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
// Download bun binary if not already available on the system
|
|
297
|
+
if (!this.getBunPath()) {
|
|
298
|
+
downloads.push(this.downloadBunBinary(tmpDir, signal));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await Promise.all(downloads);
|
|
302
|
+
|
|
303
|
+
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
|
|
304
|
+
|
|
305
|
+
log.info('npm packages downloaded, stripping non-platform binaries');
|
|
306
|
+
|
|
307
|
+
// Step 2: Strip non-platform native binaries
|
|
308
|
+
const onnxBinDir = join(nodeModules, 'onnxruntime-node', 'bin', 'napi-v3');
|
|
309
|
+
if (existsSync(onnxBinDir)) {
|
|
310
|
+
const entries = readdirSync(onnxBinDir);
|
|
311
|
+
for (const entry of entries) {
|
|
312
|
+
// Keep all darwin architectures (arm64 and x86_64) since uname -m
|
|
313
|
+
// is unreliable under Rosetta (returns x86_64 on Apple Silicon)
|
|
314
|
+
if (entry !== os) {
|
|
315
|
+
rmSync(join(onnxBinDir, entry), { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Strip non-runtime files to reduce disk usage.
|
|
321
|
+
// Keep lib/ directories — they contain JS entry points needed for bare
|
|
322
|
+
// specifier imports in the worker subprocess.
|
|
323
|
+
const onnxNodeDir = join(nodeModules, 'onnxruntime-node');
|
|
324
|
+
rmSync(join(onnxNodeDir, 'script'), { recursive: true, force: true });
|
|
325
|
+
rmSync(join(onnxNodeDir, 'README.md'), { force: true });
|
|
326
|
+
rmSync(join(nodeModules, 'onnxruntime-common', 'README.md'), { force: true });
|
|
327
|
+
|
|
328
|
+
// Step 3: Create a stub "sharp" package so that the pre-built
|
|
329
|
+
// transformers.node.mjs can import it without error. The bundle checks
|
|
330
|
+
// `if (sharp)` at module initialization time — the stub must be truthy.
|
|
331
|
+
// We only use text embeddings, never image processing.
|
|
332
|
+
const sharpDir = join(nodeModules, 'sharp');
|
|
333
|
+
mkdirSync(sharpDir, { recursive: true });
|
|
334
|
+
writeFileSync(join(sharpDir, 'package.json'), '{"name":"sharp","version":"0.0.0","main":"index.js"}\n');
|
|
335
|
+
writeFileSync(join(sharpDir, 'index.js'), [
|
|
336
|
+
'// Stub: only text embeddings are used, no image processing.',
|
|
337
|
+
'// Must be a truthy function so transformers.node.mjs initialization passes.',
|
|
338
|
+
'function sharp() { throw new Error("sharp stub: image processing not available"); }',
|
|
339
|
+
'sharp.format = {};',
|
|
340
|
+
'module.exports = sharp;',
|
|
341
|
+
'',
|
|
342
|
+
].join('\n'));
|
|
343
|
+
|
|
344
|
+
// Step 4: Write embed worker script
|
|
345
|
+
writeFileSync(join(tmpDir, WORKER_FILENAME), generateWorkerScript());
|
|
346
|
+
|
|
347
|
+
// Step 5: Write version manifest
|
|
348
|
+
const manifest: VersionManifest = {
|
|
349
|
+
runtimeVersion: RUNTIME_VERSION,
|
|
350
|
+
onnxruntimeNodeVersion: ONNXRUNTIME_NODE_VERSION,
|
|
351
|
+
onnxruntimeCommonVersion: ONNXRUNTIME_COMMON_VERSION,
|
|
352
|
+
transformersVersion: TRANSFORMERS_VERSION,
|
|
353
|
+
platform: os,
|
|
354
|
+
arch: cpu,
|
|
355
|
+
installedAt: new Date().toISOString(),
|
|
356
|
+
};
|
|
357
|
+
writeFileSync(join(tmpDir, 'version.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
358
|
+
|
|
359
|
+
// Step 6: Atomic swap — remove old install and rename temp to final
|
|
360
|
+
// Preserve model-cache/, bin/ (downloaded bun), and .gitignore
|
|
361
|
+
const modelCacheDir = join(this.baseDir, 'model-cache');
|
|
362
|
+
const hadModelCache = existsSync(modelCacheDir);
|
|
363
|
+
let tmpModelCache: string | null = null;
|
|
364
|
+
if (hadModelCache) {
|
|
365
|
+
tmpModelCache = join(this.baseDir, `.model-cache-preserve-${Date.now()}`);
|
|
366
|
+
renameSync(modelCacheDir, tmpModelCache);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Preserve downloaded bun binary if it exists and we didn't just download a new one
|
|
370
|
+
const existingBinDir = join(this.baseDir, 'bin');
|
|
371
|
+
const newBinDir = join(tmpDir, 'bin');
|
|
372
|
+
const hadBinDir = existsSync(existingBinDir) && !existsSync(newBinDir);
|
|
373
|
+
let tmpBinDir: string | null = null;
|
|
374
|
+
if (hadBinDir) {
|
|
375
|
+
tmpBinDir = join(this.baseDir, `.bin-preserve-${Date.now()}`);
|
|
376
|
+
renameSync(existingBinDir, tmpBinDir);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Remove old install (preserving dotfiles like .gitignore, .downloading, temp dirs)
|
|
380
|
+
for (const entry of readdirSync(this.baseDir)) {
|
|
381
|
+
if (entry.startsWith('.') || entry === tmpDir.split('/').pop()) continue;
|
|
382
|
+
rmSync(join(this.baseDir, entry), { recursive: true, force: true });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Move new files into place
|
|
386
|
+
for (const entry of readdirSync(tmpDir)) {
|
|
387
|
+
renameSync(join(tmpDir, entry), join(this.baseDir, entry));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Restore model cache
|
|
391
|
+
if (tmpModelCache && existsSync(tmpModelCache)) {
|
|
392
|
+
renameSync(tmpModelCache, modelCacheDir);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Restore bun binary
|
|
396
|
+
if (tmpBinDir && existsSync(tmpBinDir)) {
|
|
397
|
+
renameSync(tmpBinDir, existingBinDir);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
log.info({ runtimeVersion: RUNTIME_VERSION }, 'Embedding runtime installed successfully');
|
|
401
|
+
} catch (err) {
|
|
402
|
+
log.error({ err }, 'Failed to install embedding runtime');
|
|
403
|
+
throw err;
|
|
404
|
+
} finally {
|
|
405
|
+
// Clean up temp directory
|
|
406
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private async downloadBunBinary(installDir: string, signal?: AbortSignal): Promise<void> {
|
|
411
|
+
const os = platform();
|
|
412
|
+
const cpu = arch() === 'arm64' ? 'aarch64' : arch();
|
|
413
|
+
const target = `${os}-${cpu}`;
|
|
414
|
+
const url = `https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-${target}.zip`;
|
|
415
|
+
|
|
416
|
+
log.info({ url, target, bunVersion: BUN_VERSION }, 'Downloading bun binary');
|
|
417
|
+
|
|
418
|
+
const response = await fetch(url, {
|
|
419
|
+
signal,
|
|
420
|
+
redirect: 'follow',
|
|
421
|
+
});
|
|
422
|
+
if (!response.ok) {
|
|
423
|
+
throw new Error(`Failed to download bun: ${response.status} ${response.statusText}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const zipData = await response.arrayBuffer();
|
|
427
|
+
const binDir = join(installDir, 'bin');
|
|
428
|
+
mkdirSync(binDir, { recursive: true });
|
|
429
|
+
|
|
430
|
+
const tmpZip = join(binDir, `bun-download-${Date.now()}.zip`);
|
|
431
|
+
writeFileSync(tmpZip, Buffer.from(zipData));
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
// Extract zip
|
|
435
|
+
const proc = Bun.spawn({
|
|
436
|
+
cmd: ['unzip', '-o', tmpZip, '-d', binDir],
|
|
437
|
+
stdout: 'ignore',
|
|
438
|
+
stderr: 'pipe',
|
|
439
|
+
});
|
|
440
|
+
await proc.exited;
|
|
441
|
+
if (proc.exitCode !== 0) {
|
|
442
|
+
const stderr = await new Response(proc.stderr).text();
|
|
443
|
+
throw new Error(`Failed to extract bun zip: ${stderr}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Move binary from bun-{target}/bun to bin/bun
|
|
447
|
+
const extractedBun = join(binDir, `bun-${target}`, 'bun');
|
|
448
|
+
if (existsSync(extractedBun)) {
|
|
449
|
+
renameSync(extractedBun, join(binDir, 'bun'));
|
|
450
|
+
rmSync(join(binDir, `bun-${target}`), { recursive: true, force: true });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Make executable
|
|
454
|
+
chmodSync(join(binDir, 'bun'), 0o755);
|
|
455
|
+
} finally {
|
|
456
|
+
try { rmSync(tmpZip); } catch { /* ignore */ }
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
log.info('Bun binary downloaded successfully');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private readManifest(): VersionManifest | null {
|
|
463
|
+
const manifestPath = join(this.baseDir, 'version.json');
|
|
464
|
+
if (!existsSync(manifestPath)) return null;
|
|
465
|
+
try {
|
|
466
|
+
return JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
467
|
+
} catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* answer resolves the request and all other deliveries are marked answered.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { and,
|
|
10
|
+
import { and, desc, eq, inArray, lt } from 'drizzle-orm';
|
|
11
11
|
import { v4 as uuid } from 'uuid';
|
|
12
12
|
|
|
13
13
|
import { getLogger } from '../util/logger.js';
|
|
@@ -136,6 +136,12 @@ function generateRequestCode(): string {
|
|
|
136
136
|
// Guardian Action Requests
|
|
137
137
|
// ---------------------------------------------------------------------------
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* @internal Test-only helper. Production code should create guardian requests
|
|
141
|
+
* via `createCanonicalGuardianRequest` in canonical-guardian-store.ts.
|
|
142
|
+
* This function is retained solely so that existing test fixtures that seed
|
|
143
|
+
* legacy guardian action rows continue to compile.
|
|
144
|
+
*/
|
|
139
145
|
export function createGuardianActionRequest(params: {
|
|
140
146
|
assistantId?: string;
|
|
141
147
|
kind: string;
|
|
@@ -226,65 +232,6 @@ export function getPendingRequestByCallSessionId(callSessionId: string): Guardia
|
|
|
226
232
|
return row ? rowToRequest(row) : null;
|
|
227
233
|
}
|
|
228
234
|
|
|
229
|
-
/**
|
|
230
|
-
* Count pending guardian action requests for a given call session.
|
|
231
|
-
* Used as a candidate-affinity hint so the decision engine knows how many
|
|
232
|
-
* active guardian requests already exist for the current call.
|
|
233
|
-
*/
|
|
234
|
-
export function countPendingRequestsByCallSessionId(callSessionId: string): number {
|
|
235
|
-
const db = getDb();
|
|
236
|
-
const row = db
|
|
237
|
-
.select({ count: count() })
|
|
238
|
-
.from(guardianActionRequests)
|
|
239
|
-
.where(
|
|
240
|
-
and(
|
|
241
|
-
eq(guardianActionRequests.callSessionId, callSessionId),
|
|
242
|
-
eq(guardianActionRequests.status, 'pending'),
|
|
243
|
-
),
|
|
244
|
-
)
|
|
245
|
-
.get();
|
|
246
|
-
return row?.count ?? 0;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Look up the vellum conversation ID used for the first guardian question
|
|
251
|
-
* delivery in a given call session. Returns the conversation ID when one
|
|
252
|
-
* exists, or null if no vellum delivery has been recorded yet.
|
|
253
|
-
*
|
|
254
|
-
* Used by guardian-dispatch to enforce deterministic thread affinity:
|
|
255
|
-
* all guardian questions within the same call session should route to
|
|
256
|
-
* the same vellum conversation.
|
|
257
|
-
*/
|
|
258
|
-
export function getGuardianConversationIdForCallSession(callSessionId: string): string | null {
|
|
259
|
-
try {
|
|
260
|
-
const db = getDb();
|
|
261
|
-
const row = db
|
|
262
|
-
.select({ conversationId: guardianActionDeliveries.destinationConversationId })
|
|
263
|
-
.from(guardianActionDeliveries)
|
|
264
|
-
.innerJoin(
|
|
265
|
-
guardianActionRequests,
|
|
266
|
-
eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
|
|
267
|
-
)
|
|
268
|
-
.where(
|
|
269
|
-
and(
|
|
270
|
-
eq(guardianActionRequests.callSessionId, callSessionId),
|
|
271
|
-
eq(guardianActionDeliveries.destinationChannel, 'vellum'),
|
|
272
|
-
isNotNull(guardianActionDeliveries.destinationConversationId),
|
|
273
|
-
),
|
|
274
|
-
)
|
|
275
|
-
.orderBy(guardianActionDeliveries.createdAt)
|
|
276
|
-
.limit(1)
|
|
277
|
-
.get();
|
|
278
|
-
return row?.conversationId ?? null;
|
|
279
|
-
} catch (err) {
|
|
280
|
-
if (err instanceof Error && err.message.includes('no such table')) {
|
|
281
|
-
log.warn({ err }, 'guardian tables not yet created');
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
throw err;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
235
|
/**
|
|
289
236
|
* First-response-wins resolution. Checks that the request is still
|
|
290
237
|
* 'pending' before updating; returns the updated request on success
|
|
@@ -70,6 +70,12 @@ function rowToApprovalRequest(row: typeof channelGuardianApprovalRequests.$infer
|
|
|
70
70
|
// Operations
|
|
71
71
|
// ---------------------------------------------------------------------------
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @internal Test-only helper. Production code should create guardian requests
|
|
75
|
+
* via `createCanonicalGuardianRequest` in canonical-guardian-store.ts.
|
|
76
|
+
* This function is retained solely so that existing test fixtures that seed
|
|
77
|
+
* legacy approval rows continue to compile.
|
|
78
|
+
*/
|
|
73
79
|
export function createApprovalRequest(params: {
|
|
74
80
|
runId: string;
|
|
75
81
|
requestId?: string;
|
|
@@ -535,10 +541,9 @@ export function countPendingByConversation(
|
|
|
535
541
|
}
|
|
536
542
|
|
|
537
543
|
/**
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* requests while one is already pending.
|
|
544
|
+
* @internal Test-only helper. Production code should query canonical guardian
|
|
545
|
+
* requests via `listCanonicalGuardianRequests` in canonical-guardian-store.ts.
|
|
546
|
+
* Retained for existing test fixtures that check legacy approval dedup.
|
|
542
547
|
*/
|
|
543
548
|
export function findPendingAccessRequestForRequester(
|
|
544
549
|
assistantId: string,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* for a given (assistantId, channel) pair.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { and, eq } from 'drizzle-orm';
|
|
8
|
+
import { and, asc, desc, eq } from 'drizzle-orm';
|
|
9
9
|
import { v4 as uuid } from 'uuid';
|
|
10
10
|
|
|
11
11
|
import { getDb } from './db.js';
|
|
@@ -103,6 +103,30 @@ export function getActiveBinding(assistantId: string, channel: string): Guardian
|
|
|
103
103
|
return row ? rowToBinding(row) : null;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* List all active guardian bindings for an assistant across all channels.
|
|
108
|
+
* Deterministic ordering: verifiedAt DESC (most recently verified first),
|
|
109
|
+
* then channel ASC (alphabetical tiebreaker).
|
|
110
|
+
*/
|
|
111
|
+
export function listActiveBindingsByAssistant(assistantId: string): GuardianBinding[] {
|
|
112
|
+
const db = getDb();
|
|
113
|
+
return db
|
|
114
|
+
.select()
|
|
115
|
+
.from(channelGuardianBindings)
|
|
116
|
+
.where(
|
|
117
|
+
and(
|
|
118
|
+
eq(channelGuardianBindings.assistantId, assistantId),
|
|
119
|
+
eq(channelGuardianBindings.status, 'active'),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
.orderBy(
|
|
123
|
+
desc(channelGuardianBindings.verifiedAt),
|
|
124
|
+
asc(channelGuardianBindings.channel),
|
|
125
|
+
)
|
|
126
|
+
.all()
|
|
127
|
+
.map(rowToBinding);
|
|
128
|
+
}
|
|
129
|
+
|
|
106
130
|
export function revokeBinding(assistantId: string, channel: string): boolean {
|
|
107
131
|
const db = getDb();
|
|
108
132
|
const now = Date.now();
|
package/src/memory/indexer.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface IndexMessageInput {
|
|
|
23
23
|
createdAt: number;
|
|
24
24
|
scopeId?: string;
|
|
25
25
|
// Provenance for trust-aware extraction gating (M3)
|
|
26
|
-
|
|
26
|
+
provenanceTrustClass?: 'guardian' | 'trusted_contact' | 'unknown';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface IndexMessageResult {
|
|
@@ -39,7 +39,7 @@ export function indexMessageNow(
|
|
|
39
39
|
|
|
40
40
|
// Provenance-based trust gating: only guardian and legacy (undefined) actors
|
|
41
41
|
// are trusted for extraction and conflict resolution.
|
|
42
|
-
const isTrustedActor = input.
|
|
42
|
+
const isTrustedActor = input.provenanceTrustClass === 'guardian' || input.provenanceTrustClass === undefined;
|
|
43
43
|
|
|
44
44
|
const text = extractTextFromStoredMessageContent(input.content);
|
|
45
45
|
if (text.length === 0) {
|
|
@@ -123,7 +123,7 @@ export function indexMessageNow(
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
if (!isTrustedActor && (shouldExtract || shouldResolveConflicts)) {
|
|
126
|
-
log.info(`Skipping extraction/conflict jobs for untrusted actor (
|
|
126
|
+
log.info(`Skipping extraction/conflict jobs for untrusted actor (trustClass=${input.provenanceTrustClass})`);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
enqueueSummaryRollupJobsIfDue();
|