@vellumai/assistant 0.3.28 → 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 +33 -3
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +3 -3
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- 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__/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__/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__/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-control-plane-policy.test.ts +19 -19
- package/src/__tests__/guardian-dispatch.test.ts +2 -0
- 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 +138 -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__/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 +183 -9
- package/src/__tests__/notification-decision-fallback.test.ts +2 -0
- package/src/__tests__/notification-decision-strategy.test.ts +61 -0
- package/src/__tests__/notification-guardian-path.test.ts +2 -0
- 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__/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__/send-endpoint-busy.test.ts +4 -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 +50 -12
- 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 +7 -7
- 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/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/relay-server.ts +216 -27
- 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/vellum-skills/trusted-contacts/SKILL.md +139 -16
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +19 -0
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/session-agent-loop.ts +5 -5
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +1 -20
- package/src/daemon/session-runtime-assembly.ts +28 -22
- package/src/daemon/session-tool-setup.ts +2 -2
- package/src/daemon/session.ts +3 -3
- package/src/memory/canonical-guardian-store.ts +63 -1
- package/src/memory/channel-guardian-store.ts +1 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- 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/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema.ts +4 -0
- package/src/notifications/copy-composer.ts +15 -0
- package/src/runtime/access-request-helper.ts +43 -7
- package/src/runtime/actor-trust-resolver.ts +46 -50
- 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 -96
- package/src/runtime/guardian-reply-router.ts +31 -1
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- 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 +2 -2
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +41 -10
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/tool-approval-handler.ts +11 -11
- package/src/tools/types.ts +2 -2
- 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
|
@@ -1,27 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
2
3
|
|
|
3
4
|
import { getLogger } from '../util/logger.js';
|
|
5
|
+
import { getEmbeddingModelsDir, getRootDir } from '../util/platform.js';
|
|
4
6
|
import { PromiseGuard } from '../util/promise-guard.js';
|
|
5
7
|
import type { EmbeddingBackend, EmbeddingRequestOptions } from './embedding-backend.js';
|
|
8
|
+
import { EmbeddingRuntimeManager } from './embedding-runtime-manager.js';
|
|
6
9
|
|
|
7
10
|
const log = getLogger('memory-embedding-local');
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
interface WorkerResponse {
|
|
13
|
+
id?: number;
|
|
14
|
+
type?: string;
|
|
15
|
+
vectors?: number[][];
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Local embedding backend using @huggingface/transformers (ONNX Runtime).
|
|
16
21
|
* Runs BAAI/bge-small-en-v1.5 locally — no API calls, no network required.
|
|
17
22
|
*
|
|
18
|
-
*
|
|
23
|
+
* Embeddings run in a **separate bun process** because compiled Bun binaries
|
|
24
|
+
* cannot resolve bare specifier imports in dynamically loaded files. The embed
|
|
25
|
+
* worker communicates via JSON-lines over stdin/stdout.
|
|
26
|
+
*
|
|
27
|
+
* The embedding runtime (onnxruntime-node + transformers + bun) is downloaded
|
|
28
|
+
* post-hatch by EmbeddingRuntimeManager.
|
|
29
|
+
*
|
|
19
30
|
* Produces 384-dimensional embeddings.
|
|
20
31
|
*/
|
|
21
32
|
export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
22
33
|
readonly provider = 'local' as const;
|
|
23
34
|
readonly model: string;
|
|
24
|
-
|
|
35
|
+
|
|
36
|
+
// Subprocess — typed loosely to avoid coupling to Bun's Subprocess generics
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
private workerProc: any = null;
|
|
39
|
+
private stdoutBuffer = '';
|
|
40
|
+
private requestCounter = 0;
|
|
41
|
+
private pendingRequests = new Map<number, {
|
|
42
|
+
resolve: (response: WorkerResponse) => void;
|
|
43
|
+
}>();
|
|
44
|
+
private stdoutReaderActive = false;
|
|
45
|
+
|
|
25
46
|
private readonly initGuard = new PromiseGuard<void>();
|
|
26
47
|
|
|
27
48
|
constructor(model: string) {
|
|
@@ -35,55 +56,252 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
35
56
|
await this.ensureInitialized();
|
|
36
57
|
|
|
37
58
|
const results: number[][] = [];
|
|
38
|
-
// Process in batches of 32 to avoid OOM with large inputs
|
|
39
59
|
const batchSize = 32;
|
|
40
60
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
41
61
|
if (options?.signal?.aborted) throw new DOMException('Aborted', 'AbortError');
|
|
42
62
|
const batch = texts.slice(i, i + batchSize);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
const response = await this.sendRequest(batch);
|
|
64
|
+
if (response.error) {
|
|
65
|
+
throw new Error(`Embedding worker error: ${response.error}`);
|
|
66
|
+
}
|
|
67
|
+
if (!response.vectors) {
|
|
68
|
+
throw new Error('Embedding worker returned no vectors');
|
|
69
|
+
}
|
|
70
|
+
results.push(...response.vectors);
|
|
49
71
|
}
|
|
50
|
-
|
|
51
72
|
return results;
|
|
52
73
|
}
|
|
53
74
|
|
|
75
|
+
private sendRequest(texts: string[]): Promise<WorkerResponse> {
|
|
76
|
+
const id = ++this.requestCounter;
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
if (!this.workerProc) {
|
|
79
|
+
resolve({ id, error: 'Worker not initialized' });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.pendingRequests.set(id, { resolve });
|
|
83
|
+
this.workerProc.stdin.write(JSON.stringify({ id, texts }) + '\n');
|
|
84
|
+
this.workerProc.stdin.flush();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
54
88
|
private async ensureInitialized(): Promise<void> {
|
|
55
|
-
if (this.
|
|
89
|
+
if (this.workerProc) return;
|
|
56
90
|
await this.initGuard.run(() => this.initialize());
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
private async initialize(): Promise<void> {
|
|
60
|
-
log.info({ model: this.model }, '
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
log.info({ model: this.model }, 'Initializing local embedding backend');
|
|
95
|
+
|
|
96
|
+
const runtimeManager = new EmbeddingRuntimeManager();
|
|
97
|
+
|
|
98
|
+
// Wait for download if in progress
|
|
99
|
+
if (!runtimeManager.isReady()) {
|
|
100
|
+
log.info('Embedding runtime not yet available, waiting for download...');
|
|
101
|
+
await runtimeManager.ensureInstalled();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const bunPath = runtimeManager.getBunPath();
|
|
105
|
+
const workerPath = runtimeManager.getWorkerPath();
|
|
106
|
+
|
|
107
|
+
if (!bunPath) {
|
|
108
|
+
throw new Error('Local embedding backend unavailable: no bun binary found');
|
|
109
|
+
}
|
|
110
|
+
if (!existsSync(workerPath)) {
|
|
111
|
+
throw new Error(`Local embedding backend unavailable: worker script not found at ${workerPath}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await this.startWorker(bunPath, workerPath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async startWorker(bunPath: string, workerPath: string): Promise<void> {
|
|
118
|
+
const embeddingModelsDir = getEmbeddingModelsDir();
|
|
119
|
+
const modelCacheDir = `${embeddingModelsDir}/model-cache`;
|
|
120
|
+
|
|
121
|
+
log.info({ bunPath, workerPath, model: this.model }, 'Spawning embedding worker process');
|
|
122
|
+
|
|
123
|
+
const proc = Bun.spawn({
|
|
124
|
+
cmd: [bunPath, workerPath, this.model, modelCacheDir],
|
|
125
|
+
stdin: 'pipe',
|
|
126
|
+
stdout: 'pipe',
|
|
127
|
+
stderr: 'pipe',
|
|
128
|
+
cwd: embeddingModelsDir,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Type-compatible assignment
|
|
132
|
+
this.workerProc = proc;
|
|
133
|
+
|
|
134
|
+
// Start reading stdout for responses (needed for waitForReady)
|
|
135
|
+
this.startStdoutReader();
|
|
136
|
+
|
|
71
137
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
138
|
+
// Wait for the worker to signal it's ready (model loaded)
|
|
139
|
+
await this.waitForReady();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
// Worker failed to start — collect stderr for diagnosis
|
|
142
|
+
this.workerProc = null;
|
|
143
|
+
const exitCode = await proc.exited.catch(() => undefined);
|
|
144
|
+
const stderr = await new Response(proc.stderr).text().catch(() => '');
|
|
145
|
+
if (stderr.trim()) {
|
|
146
|
+
log.warn({ stderr: stderr.trim(), exitCode, bunPath }, 'Embedding worker stderr');
|
|
147
|
+
}
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Embedding worker exited (code ${exitCode ?? 'unknown'}): ${stderr.trim() || (err instanceof Error ? err.message : String(err))}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Worker is running — drain stderr in background for ongoing logging
|
|
154
|
+
this.drainStderr(proc.stderr);
|
|
155
|
+
|
|
156
|
+
// Write PID file so `vellum ps` can see the embed worker
|
|
157
|
+
this.writePidFile(proc.pid);
|
|
158
|
+
|
|
159
|
+
log.info({ pid: proc.pid, model: this.model }, 'Embedding worker process started');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private drainStderr(stderr: ReadableStream<Uint8Array>): void {
|
|
163
|
+
const reader = stderr.getReader();
|
|
164
|
+
const decoder = new TextDecoder();
|
|
165
|
+
(async () => {
|
|
75
166
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
167
|
+
while (true) {
|
|
168
|
+
const { done, value } = await reader.read();
|
|
169
|
+
if (done) break;
|
|
170
|
+
const text = decoder.decode(value, { stream: true }).trim();
|
|
171
|
+
if (text) log.debug({ workerStderr: text }, 'Embedding worker stderr');
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
// Reader cancelled or stream errored — expected on shutdown
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private startStdoutReader(): void {
|
|
180
|
+
if (this.stdoutReaderActive || !this.workerProc) return;
|
|
181
|
+
this.stdoutReaderActive = true;
|
|
182
|
+
|
|
183
|
+
const reader = this.workerProc.stdout.getReader();
|
|
184
|
+
const decoder = new TextDecoder();
|
|
185
|
+
|
|
186
|
+
(async () => {
|
|
187
|
+
try {
|
|
188
|
+
while (true) {
|
|
189
|
+
const { done, value } = await reader.read();
|
|
190
|
+
if (done) break;
|
|
191
|
+
this.stdoutBuffer += decoder.decode(value, { stream: true });
|
|
192
|
+
this.processStdoutBuffer();
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// Reader cancelled or stream errored
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Worker exited — reject all pending requests and clean up
|
|
199
|
+
for (const [, pending] of this.pendingRequests) {
|
|
200
|
+
pending.resolve({ error: 'Embedding worker process exited unexpectedly' });
|
|
201
|
+
}
|
|
202
|
+
this.pendingRequests.clear();
|
|
203
|
+
this.workerProc = null;
|
|
204
|
+
this.stdoutReaderActive = false;
|
|
205
|
+
this.removePidFile();
|
|
206
|
+
this.stdoutBuffer = '';
|
|
207
|
+
// Allow re-initialization on next embed() call
|
|
208
|
+
this.initGuard.reset();
|
|
209
|
+
})();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private readyResolve: (() => void) | null = null;
|
|
213
|
+
private readyReject: ((err: Error) => void) | null = null;
|
|
214
|
+
|
|
215
|
+
private processStdoutBuffer(): void {
|
|
216
|
+
let idx: number;
|
|
217
|
+
while ((idx = this.stdoutBuffer.indexOf('\n')) !== -1) {
|
|
218
|
+
const line = this.stdoutBuffer.slice(0, idx);
|
|
219
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(idx + 1);
|
|
220
|
+
if (!line.trim()) continue;
|
|
221
|
+
|
|
222
|
+
let msg: WorkerResponse;
|
|
223
|
+
try {
|
|
224
|
+
msg = JSON.parse(line);
|
|
225
|
+
} catch {
|
|
226
|
+
continue; // Skip malformed lines
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Handle ready/error signals during initialization
|
|
230
|
+
if (msg.type === 'ready') {
|
|
231
|
+
this.readyResolve?.();
|
|
232
|
+
this.readyResolve = null;
|
|
233
|
+
this.readyReject = null;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (msg.type === 'error' && this.readyReject) {
|
|
237
|
+
this.readyReject(new Error(msg.error ?? 'Worker initialization failed'));
|
|
238
|
+
this.readyResolve = null;
|
|
239
|
+
this.readyReject = null;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Handle embed responses
|
|
244
|
+
if (msg.id !== undefined) {
|
|
245
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
246
|
+
if (pending) {
|
|
247
|
+
this.pendingRequests.delete(msg.id);
|
|
248
|
+
pending.resolve(msg);
|
|
249
|
+
}
|
|
81
250
|
}
|
|
82
251
|
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private waitForReady(): Promise<void> {
|
|
255
|
+
return new Promise<void>((resolve, reject) => {
|
|
256
|
+
this.readyResolve = resolve;
|
|
257
|
+
this.readyReject = reject;
|
|
258
|
+
|
|
259
|
+
// Timeout after 2 minutes (first model download can be slow)
|
|
260
|
+
const timeout = setTimeout(() => {
|
|
261
|
+
this.readyResolve = null;
|
|
262
|
+
this.readyReject = null;
|
|
263
|
+
reject(new Error('Embedding worker timed out waiting for model to load'));
|
|
264
|
+
}, 120_000);
|
|
265
|
+
|
|
266
|
+
// Clear timeout when resolved
|
|
267
|
+
const originalResolve = resolve;
|
|
268
|
+
this.readyResolve = () => {
|
|
269
|
+
clearTimeout(timeout);
|
|
270
|
+
originalResolve();
|
|
271
|
+
};
|
|
272
|
+
const originalReject = reject;
|
|
273
|
+
this.readyReject = (err: Error) => {
|
|
274
|
+
clearTimeout(timeout);
|
|
275
|
+
originalReject(err);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Also handle early worker exit
|
|
279
|
+
this.workerProc?.exited.then(() => {
|
|
280
|
+
if (this.readyResolve) {
|
|
281
|
+
clearTimeout(timeout);
|
|
282
|
+
this.readyResolve = null;
|
|
283
|
+
this.readyReject = null;
|
|
284
|
+
reject(new Error('Embedding worker process exited before becoming ready'));
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private static readonly PID_FILENAME = 'embed-worker.pid';
|
|
83
291
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
292
|
+
private writePidFile(pid: number): void {
|
|
293
|
+
try {
|
|
294
|
+
writeFileSync(join(getRootDir(), LocalEmbeddingBackend.PID_FILENAME), String(pid));
|
|
295
|
+
} catch {
|
|
296
|
+
// Best-effort — doesn't affect functionality
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private removePidFile(): void {
|
|
301
|
+
try {
|
|
302
|
+
unlinkSync(join(getRootDir(), LocalEmbeddingBackend.PID_FILENAME));
|
|
303
|
+
} catch {
|
|
304
|
+
// Best-effort
|
|
305
|
+
}
|
|
88
306
|
}
|
|
89
307
|
}
|