@poolzin/pool-bot 2026.2.0 → 2026.2.2
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/CHANGELOG.md +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/image-tool.js +1 -1
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +74 -82
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/mentions.js +1 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/index.html +28 -28
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/auth-choice.apply.oauth.js +1 -1
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.registry.js +1 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/compat/legacy-names.js +2 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +172 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.mocks.js +11 -7
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media/store.js +2 -0
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +3 -3
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +54 -17
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { retryAsync } from "../infra/retry.js";
|
|
4
|
+
import { hashText, runWithConcurrency } from "./internal.js";
|
|
5
|
+
export const VOYAGE_BATCH_ENDPOINT = "/v1/embeddings";
|
|
6
|
+
const VOYAGE_BATCH_COMPLETION_WINDOW = "12h";
|
|
7
|
+
const VOYAGE_BATCH_MAX_REQUESTS = 50000;
|
|
8
|
+
function getVoyageBaseUrl(client) {
|
|
9
|
+
return client.baseUrl?.replace(/\/$/, "") ?? "";
|
|
10
|
+
}
|
|
11
|
+
function getVoyageHeaders(client, params) {
|
|
12
|
+
const headers = client.headers ? { ...client.headers } : {};
|
|
13
|
+
if (params.json) {
|
|
14
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
15
|
+
headers["Content-Type"] = "application/json";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
delete headers["Content-Type"];
|
|
20
|
+
delete headers["content-type"];
|
|
21
|
+
}
|
|
22
|
+
return headers;
|
|
23
|
+
}
|
|
24
|
+
function splitVoyageBatchRequests(requests) {
|
|
25
|
+
if (requests.length <= VOYAGE_BATCH_MAX_REQUESTS) {
|
|
26
|
+
return [requests];
|
|
27
|
+
}
|
|
28
|
+
const groups = [];
|
|
29
|
+
for (let i = 0; i < requests.length; i += VOYAGE_BATCH_MAX_REQUESTS) {
|
|
30
|
+
groups.push(requests.slice(i, i + VOYAGE_BATCH_MAX_REQUESTS));
|
|
31
|
+
}
|
|
32
|
+
return groups;
|
|
33
|
+
}
|
|
34
|
+
async function submitVoyageBatch(params) {
|
|
35
|
+
const baseUrl = getVoyageBaseUrl(params.client);
|
|
36
|
+
const jsonl = params.requests.map((request) => JSON.stringify(request)).join("\n");
|
|
37
|
+
const form = new FormData();
|
|
38
|
+
form.append("purpose", "batch");
|
|
39
|
+
form.append("file", new Blob([jsonl], { type: "application/jsonl" }), `memory-embeddings.${hashText(String(Date.now()))}.jsonl`);
|
|
40
|
+
// 1. Upload file using Voyage Files API
|
|
41
|
+
const fileRes = await fetch(`${baseUrl}/files`, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: getVoyageHeaders(params.client, { json: false }),
|
|
44
|
+
body: form,
|
|
45
|
+
});
|
|
46
|
+
if (!fileRes.ok) {
|
|
47
|
+
const text = await fileRes.text();
|
|
48
|
+
throw new Error(`voyage batch file upload failed: ${fileRes.status} ${text}`);
|
|
49
|
+
}
|
|
50
|
+
const filePayload = (await fileRes.json());
|
|
51
|
+
if (!filePayload.id) {
|
|
52
|
+
throw new Error("voyage batch file upload failed: missing file id");
|
|
53
|
+
}
|
|
54
|
+
// 2. Create batch job using Voyage Batches API
|
|
55
|
+
const batchRes = await retryAsync(async () => {
|
|
56
|
+
const res = await fetch(`${baseUrl}/batches`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: getVoyageHeaders(params.client, { json: true }),
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
input_file_id: filePayload.id,
|
|
61
|
+
endpoint: VOYAGE_BATCH_ENDPOINT,
|
|
62
|
+
completion_window: VOYAGE_BATCH_COMPLETION_WINDOW,
|
|
63
|
+
request_params: {
|
|
64
|
+
model: params.client.model,
|
|
65
|
+
input_type: "document",
|
|
66
|
+
},
|
|
67
|
+
metadata: {
|
|
68
|
+
source: "clawdbot-memory",
|
|
69
|
+
agent: params.agentId,
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const text = await res.text();
|
|
75
|
+
const err = new Error(`voyage batch create failed: ${res.status} ${text}`);
|
|
76
|
+
err.status = res.status;
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
return res;
|
|
80
|
+
}, {
|
|
81
|
+
attempts: 3,
|
|
82
|
+
minDelayMs: 300,
|
|
83
|
+
maxDelayMs: 2000,
|
|
84
|
+
jitter: 0.2,
|
|
85
|
+
shouldRetry: (err) => {
|
|
86
|
+
const status = err.status;
|
|
87
|
+
return status === 429 || (typeof status === "number" && status >= 500);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
return (await batchRes.json());
|
|
91
|
+
}
|
|
92
|
+
async function fetchVoyageBatchStatus(params) {
|
|
93
|
+
const baseUrl = getVoyageBaseUrl(params.client);
|
|
94
|
+
const res = await fetch(`${baseUrl}/batches/${params.batchId}`, {
|
|
95
|
+
headers: getVoyageHeaders(params.client, { json: true }),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
const text = await res.text();
|
|
99
|
+
throw new Error(`voyage batch status failed: ${res.status} ${text}`);
|
|
100
|
+
}
|
|
101
|
+
return (await res.json());
|
|
102
|
+
}
|
|
103
|
+
async function readVoyageBatchError(params) {
|
|
104
|
+
try {
|
|
105
|
+
const baseUrl = getVoyageBaseUrl(params.client);
|
|
106
|
+
const res = await fetch(`${baseUrl}/files/${params.errorFileId}/content`, {
|
|
107
|
+
headers: getVoyageHeaders(params.client, { json: true }),
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
const text = await res.text();
|
|
111
|
+
throw new Error(`voyage batch error file content failed: ${res.status} ${text}`);
|
|
112
|
+
}
|
|
113
|
+
const text = await res.text();
|
|
114
|
+
if (!text.trim()) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
const lines = text
|
|
118
|
+
.split("\n")
|
|
119
|
+
.map((line) => line.trim())
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.map((line) => JSON.parse(line));
|
|
122
|
+
const first = lines.find((line) => line.error?.message || line.response?.body?.error);
|
|
123
|
+
const message = first?.error?.message ??
|
|
124
|
+
(typeof first?.response?.body?.error?.message === "string"
|
|
125
|
+
? first?.response?.body?.error?.message
|
|
126
|
+
: undefined);
|
|
127
|
+
return message;
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
131
|
+
return message ? `error file unavailable: ${message}` : undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function waitForVoyageBatch(params) {
|
|
135
|
+
const start = Date.now();
|
|
136
|
+
let current = params.initial;
|
|
137
|
+
while (true) {
|
|
138
|
+
const status = current ??
|
|
139
|
+
(await fetchVoyageBatchStatus({
|
|
140
|
+
client: params.client,
|
|
141
|
+
batchId: params.batchId,
|
|
142
|
+
}));
|
|
143
|
+
const state = status.status ?? "unknown";
|
|
144
|
+
if (state === "completed") {
|
|
145
|
+
if (!status.output_file_id) {
|
|
146
|
+
throw new Error(`voyage batch ${params.batchId} completed without output file`);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
outputFileId: status.output_file_id,
|
|
150
|
+
errorFileId: status.error_file_id ?? undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (["failed", "expired", "cancelled", "canceled"].includes(state)) {
|
|
154
|
+
const detail = status.error_file_id
|
|
155
|
+
? await readVoyageBatchError({ client: params.client, errorFileId: status.error_file_id })
|
|
156
|
+
: undefined;
|
|
157
|
+
const suffix = detail ? `: ${detail}` : "";
|
|
158
|
+
throw new Error(`voyage batch ${params.batchId} ${state}${suffix}`);
|
|
159
|
+
}
|
|
160
|
+
if (!params.wait) {
|
|
161
|
+
throw new Error(`voyage batch ${params.batchId} still ${state}; wait disabled`);
|
|
162
|
+
}
|
|
163
|
+
if (Date.now() - start > params.timeoutMs) {
|
|
164
|
+
throw new Error(`voyage batch ${params.batchId} timed out after ${params.timeoutMs}ms`);
|
|
165
|
+
}
|
|
166
|
+
params.debug?.(`voyage batch ${params.batchId} ${state}; waiting ${params.pollIntervalMs}ms`);
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, params.pollIntervalMs));
|
|
168
|
+
current = undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export async function runVoyageEmbeddingBatches(params) {
|
|
172
|
+
if (params.requests.length === 0) {
|
|
173
|
+
return new Map();
|
|
174
|
+
}
|
|
175
|
+
const groups = splitVoyageBatchRequests(params.requests);
|
|
176
|
+
const byCustomId = new Map();
|
|
177
|
+
const tasks = groups.map((group, groupIndex) => async () => {
|
|
178
|
+
const batchInfo = await submitVoyageBatch({
|
|
179
|
+
client: params.client,
|
|
180
|
+
requests: group,
|
|
181
|
+
agentId: params.agentId,
|
|
182
|
+
});
|
|
183
|
+
if (!batchInfo.id) {
|
|
184
|
+
throw new Error("voyage batch create failed: missing batch id");
|
|
185
|
+
}
|
|
186
|
+
params.debug?.("memory embeddings: voyage batch created", {
|
|
187
|
+
batchId: batchInfo.id,
|
|
188
|
+
status: batchInfo.status,
|
|
189
|
+
group: groupIndex + 1,
|
|
190
|
+
groups: groups.length,
|
|
191
|
+
requests: group.length,
|
|
192
|
+
});
|
|
193
|
+
if (!params.wait && batchInfo.status !== "completed") {
|
|
194
|
+
throw new Error(`voyage batch ${batchInfo.id} submitted; enable remote.batch.wait to await completion`);
|
|
195
|
+
}
|
|
196
|
+
const completed = batchInfo.status === "completed"
|
|
197
|
+
? {
|
|
198
|
+
outputFileId: batchInfo.output_file_id ?? "",
|
|
199
|
+
errorFileId: batchInfo.error_file_id ?? undefined,
|
|
200
|
+
}
|
|
201
|
+
: await waitForVoyageBatch({
|
|
202
|
+
client: params.client,
|
|
203
|
+
batchId: batchInfo.id,
|
|
204
|
+
wait: params.wait,
|
|
205
|
+
pollIntervalMs: params.pollIntervalMs,
|
|
206
|
+
timeoutMs: params.timeoutMs,
|
|
207
|
+
debug: params.debug,
|
|
208
|
+
initial: batchInfo,
|
|
209
|
+
});
|
|
210
|
+
if (!completed.outputFileId) {
|
|
211
|
+
throw new Error(`voyage batch ${batchInfo.id} completed without output file`);
|
|
212
|
+
}
|
|
213
|
+
const baseUrl = getVoyageBaseUrl(params.client);
|
|
214
|
+
const contentRes = await fetch(`${baseUrl}/files/${completed.outputFileId}/content`, {
|
|
215
|
+
headers: getVoyageHeaders(params.client, { json: true }),
|
|
216
|
+
});
|
|
217
|
+
if (!contentRes.ok) {
|
|
218
|
+
const text = await contentRes.text();
|
|
219
|
+
throw new Error(`voyage batch file content failed: ${contentRes.status} ${text}`);
|
|
220
|
+
}
|
|
221
|
+
const errors = [];
|
|
222
|
+
const remaining = new Set(group.map((request) => request.custom_id));
|
|
223
|
+
if (contentRes.body) {
|
|
224
|
+
const reader = createInterface({
|
|
225
|
+
input: Readable.fromWeb(contentRes.body),
|
|
226
|
+
terminal: false,
|
|
227
|
+
});
|
|
228
|
+
for await (const rawLine of reader) {
|
|
229
|
+
if (!rawLine.trim()) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const line = JSON.parse(rawLine);
|
|
233
|
+
const customId = line.custom_id;
|
|
234
|
+
if (!customId) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
remaining.delete(customId);
|
|
238
|
+
if (line.error?.message) {
|
|
239
|
+
errors.push(`${customId}: ${line.error.message}`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const response = line.response;
|
|
243
|
+
const statusCode = response?.status_code ?? 0;
|
|
244
|
+
if (statusCode >= 400) {
|
|
245
|
+
const message = response?.body?.error?.message ??
|
|
246
|
+
(typeof response?.body === "string" ? response.body : undefined) ??
|
|
247
|
+
"unknown error";
|
|
248
|
+
errors.push(`${customId}: ${message}`);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const data = response?.body?.data ?? [];
|
|
252
|
+
const embedding = data[0]?.embedding ?? [];
|
|
253
|
+
if (embedding.length === 0) {
|
|
254
|
+
errors.push(`${customId}: empty embedding`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
byCustomId.set(customId, embedding);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (errors.length > 0) {
|
|
261
|
+
throw new Error(`voyage batch ${batchInfo.id} failed: ${errors.join("; ")}`);
|
|
262
|
+
}
|
|
263
|
+
if (remaining.size > 0) {
|
|
264
|
+
throw new Error(`voyage batch ${batchInfo.id} missing ${remaining.size} embedding responses`);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
params.debug?.("memory embeddings: voyage batch submit", {
|
|
268
|
+
requests: params.requests.length,
|
|
269
|
+
groups: groups.length,
|
|
270
|
+
wait: params.wait,
|
|
271
|
+
concurrency: params.concurrency,
|
|
272
|
+
pollIntervalMs: params.pollIntervalMs,
|
|
273
|
+
timeoutMs: params.timeoutMs,
|
|
274
|
+
});
|
|
275
|
+
await runWithConcurrency(tasks, params.concurrency);
|
|
276
|
+
return byCustomId;
|
|
277
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js";
|
|
2
|
+
export const DEFAULT_VOYAGE_EMBEDDING_MODEL = "voyage-4-large";
|
|
3
|
+
const DEFAULT_VOYAGE_BASE_URL = "https://api.voyageai.com/v1";
|
|
4
|
+
export function normalizeVoyageModel(model) {
|
|
5
|
+
const trimmed = model.trim();
|
|
6
|
+
if (!trimmed) {
|
|
7
|
+
return DEFAULT_VOYAGE_EMBEDDING_MODEL;
|
|
8
|
+
}
|
|
9
|
+
if (trimmed.startsWith("voyage/")) {
|
|
10
|
+
return trimmed.slice("voyage/".length);
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
export async function createVoyageEmbeddingProvider(options) {
|
|
15
|
+
const client = await resolveVoyageEmbeddingClient(options);
|
|
16
|
+
const url = `${client.baseUrl.replace(/\/$/, "")}/embeddings`;
|
|
17
|
+
const embed = async (input, input_type) => {
|
|
18
|
+
if (input.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const body = {
|
|
22
|
+
model: client.model,
|
|
23
|
+
input,
|
|
24
|
+
};
|
|
25
|
+
if (input_type) {
|
|
26
|
+
body.input_type = input_type;
|
|
27
|
+
}
|
|
28
|
+
const res = await fetch(url, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: client.headers,
|
|
31
|
+
body: JSON.stringify(body),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const text = await res.text();
|
|
35
|
+
throw new Error(`voyage embeddings failed: ${res.status} ${text}`);
|
|
36
|
+
}
|
|
37
|
+
const payload = (await res.json());
|
|
38
|
+
const data = payload.data ?? [];
|
|
39
|
+
return data.map((entry) => entry.embedding ?? []);
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
provider: {
|
|
43
|
+
id: "voyage",
|
|
44
|
+
model: client.model,
|
|
45
|
+
embedQuery: async (text) => {
|
|
46
|
+
const [vec] = await embed([text], "query");
|
|
47
|
+
return vec ?? [];
|
|
48
|
+
},
|
|
49
|
+
embedBatch: async (texts) => embed(texts, "document"),
|
|
50
|
+
},
|
|
51
|
+
client,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export async function resolveVoyageEmbeddingClient(options) {
|
|
55
|
+
const remote = options.remote;
|
|
56
|
+
const remoteApiKey = remote?.apiKey?.trim();
|
|
57
|
+
const remoteBaseUrl = remote?.baseUrl?.trim();
|
|
58
|
+
const apiKey = remoteApiKey
|
|
59
|
+
? remoteApiKey
|
|
60
|
+
: requireApiKey(await resolveApiKeyForProvider({
|
|
61
|
+
provider: "voyage",
|
|
62
|
+
cfg: options.config,
|
|
63
|
+
agentDir: options.agentDir,
|
|
64
|
+
}), "voyage");
|
|
65
|
+
const providerConfig = options.config.models?.providers?.voyage;
|
|
66
|
+
const baseUrl = remoteBaseUrl || providerConfig?.baseUrl?.trim() || DEFAULT_VOYAGE_BASE_URL;
|
|
67
|
+
const headerOverrides = Object.assign({}, providerConfig?.headers, remote?.headers);
|
|
68
|
+
const headers = {
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
Authorization: `Bearer ${apiKey}`,
|
|
71
|
+
...headerOverrides,
|
|
72
|
+
};
|
|
73
|
+
const model = normalizeVoyageModel(options.model);
|
|
74
|
+
return { baseUrl, headers, model };
|
|
75
|
+
}
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
+
import { formatErrorMessage } from "../infra/errors.js";
|
|
2
3
|
import { resolveUserPath } from "../utils.js";
|
|
3
4
|
import { createGeminiEmbeddingProvider } from "./embeddings-gemini.js";
|
|
4
5
|
import { createOpenAiEmbeddingProvider } from "./embeddings-openai.js";
|
|
6
|
+
import { createVoyageEmbeddingProvider } from "./embeddings-voyage.js";
|
|
5
7
|
import { importNodeLlamaCpp } from "./node-llama.js";
|
|
8
|
+
function sanitizeAndNormalizeEmbedding(vec) {
|
|
9
|
+
const sanitized = vec.map((value) => (Number.isFinite(value) ? value : 0));
|
|
10
|
+
const magnitude = Math.sqrt(sanitized.reduce((sum, value) => sum + value * value, 0));
|
|
11
|
+
if (magnitude < 1e-10) {
|
|
12
|
+
return sanitized;
|
|
13
|
+
}
|
|
14
|
+
return sanitized.map((value) => value / magnitude);
|
|
15
|
+
}
|
|
6
16
|
const DEFAULT_LOCAL_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
|
|
7
17
|
function canAutoSelectLocal(options) {
|
|
8
18
|
const modelPath = options.local?.modelPath?.trim();
|
|
9
|
-
if (!modelPath)
|
|
19
|
+
if (!modelPath) {
|
|
10
20
|
return false;
|
|
11
|
-
|
|
21
|
+
}
|
|
22
|
+
if (/^(hf:|https?:)/i.test(modelPath)) {
|
|
12
23
|
return false;
|
|
24
|
+
}
|
|
13
25
|
const resolved = resolveUserPath(modelPath);
|
|
14
26
|
try {
|
|
15
27
|
return fsSync.statSync(resolved).isFile();
|
|
@@ -19,7 +31,7 @@ function canAutoSelectLocal(options) {
|
|
|
19
31
|
}
|
|
20
32
|
}
|
|
21
33
|
function isMissingApiKeyError(err) {
|
|
22
|
-
const message =
|
|
34
|
+
const message = formatErrorMessage(err);
|
|
23
35
|
return message.includes("No API key found for provider");
|
|
24
36
|
}
|
|
25
37
|
async function createLocalEmbeddingProvider(options) {
|
|
@@ -49,13 +61,13 @@ async function createLocalEmbeddingProvider(options) {
|
|
|
49
61
|
embedQuery: async (text) => {
|
|
50
62
|
const ctx = await ensureContext();
|
|
51
63
|
const embedding = await ctx.getEmbeddingFor(text);
|
|
52
|
-
return Array.from(embedding.vector);
|
|
64
|
+
return sanitizeAndNormalizeEmbedding(Array.from(embedding.vector));
|
|
53
65
|
},
|
|
54
66
|
embedBatch: async (texts) => {
|
|
55
67
|
const ctx = await ensureContext();
|
|
56
68
|
const embeddings = await Promise.all(texts.map(async (text) => {
|
|
57
69
|
const embedding = await ctx.getEmbeddingFor(text);
|
|
58
|
-
return Array.from(embedding.vector);
|
|
70
|
+
return sanitizeAndNormalizeEmbedding(Array.from(embedding.vector));
|
|
59
71
|
}));
|
|
60
72
|
return embeddings;
|
|
61
73
|
},
|
|
@@ -73,10 +85,14 @@ export async function createEmbeddingProvider(options) {
|
|
|
73
85
|
const { provider, client } = await createGeminiEmbeddingProvider(options);
|
|
74
86
|
return { provider, gemini: client };
|
|
75
87
|
}
|
|
88
|
+
if (id === "voyage") {
|
|
89
|
+
const { provider, client } = await createVoyageEmbeddingProvider(options);
|
|
90
|
+
return { provider, voyage: client };
|
|
91
|
+
}
|
|
76
92
|
const { provider, client } = await createOpenAiEmbeddingProvider(options);
|
|
77
93
|
return { provider, openAi: client };
|
|
78
94
|
};
|
|
79
|
-
const formatPrimaryError = (err, provider) => provider === "local" ? formatLocalSetupError(err) :
|
|
95
|
+
const formatPrimaryError = (err, provider) => provider === "local" ? formatLocalSetupError(err) : formatErrorMessage(err);
|
|
80
96
|
if (requestedProvider === "auto") {
|
|
81
97
|
const missingKeyErrors = [];
|
|
82
98
|
let localError = null;
|
|
@@ -89,7 +105,7 @@ export async function createEmbeddingProvider(options) {
|
|
|
89
105
|
localError = formatLocalSetupError(err);
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
|
-
for (const provider of ["openai", "gemini"]) {
|
|
108
|
+
for (const provider of ["openai", "gemini", "voyage"]) {
|
|
93
109
|
try {
|
|
94
110
|
const result = await createProvider(provider);
|
|
95
111
|
return { ...result, requestedProvider };
|
|
@@ -100,7 +116,7 @@ export async function createEmbeddingProvider(options) {
|
|
|
100
116
|
missingKeyErrors.push(message);
|
|
101
117
|
continue;
|
|
102
118
|
}
|
|
103
|
-
throw new Error(message);
|
|
119
|
+
throw new Error(message, { cause: err });
|
|
104
120
|
}
|
|
105
121
|
}
|
|
106
122
|
const details = [...missingKeyErrors, localError].filter(Boolean);
|
|
@@ -126,17 +142,12 @@ export async function createEmbeddingProvider(options) {
|
|
|
126
142
|
};
|
|
127
143
|
}
|
|
128
144
|
catch (fallbackErr) {
|
|
129
|
-
throw new Error(`${reason}\n\nFallback to ${fallback} failed: ${
|
|
145
|
+
throw new Error(`${reason}\n\nFallback to ${fallback} failed: ${formatErrorMessage(fallbackErr)}`, { cause: fallbackErr });
|
|
130
146
|
}
|
|
131
147
|
}
|
|
132
|
-
throw new Error(reason);
|
|
148
|
+
throw new Error(reason, { cause: primaryErr });
|
|
133
149
|
}
|
|
134
150
|
}
|
|
135
|
-
function formatError(err) {
|
|
136
|
-
if (err instanceof Error)
|
|
137
|
-
return err.message;
|
|
138
|
-
return String(err);
|
|
139
|
-
}
|
|
140
151
|
function isNodeLlamaCppMissing(err) {
|
|
141
152
|
if (!(err instanceof Error))
|
|
142
153
|
return false;
|
|
@@ -147,7 +158,7 @@ function isNodeLlamaCppMissing(err) {
|
|
|
147
158
|
return false;
|
|
148
159
|
}
|
|
149
160
|
function formatLocalSetupError(err) {
|
|
150
|
-
const detail =
|
|
161
|
+
const detail = formatErrorMessage(err);
|
|
151
162
|
const missing = isNodeLlamaCppMissing(err);
|
|
152
163
|
return [
|
|
153
164
|
"Local embeddings unavailable.",
|
|
@@ -164,6 +175,7 @@ function formatLocalSetupError(err) {
|
|
|
164
175
|
: null,
|
|
165
176
|
"3) If you use pnpm: pnpm approve-builds (select node-llama-cpp), then pnpm rebuild node-llama-cpp",
|
|
166
177
|
'Or set agents.defaults.memorySearch.provider = "openai" (remote).',
|
|
178
|
+
'Or set agents.defaults.memorySearch.provider = "voyage" (remote).',
|
|
167
179
|
]
|
|
168
180
|
.filter(Boolean)
|
|
169
181
|
.join("\n");
|
package/dist/memory/internal.js
CHANGED
|
@@ -13,6 +13,16 @@ export function normalizeRelPath(value) {
|
|
|
13
13
|
const trimmed = value.trim().replace(/^[./]+/, "");
|
|
14
14
|
return trimmed.replace(/\\/g, "/");
|
|
15
15
|
}
|
|
16
|
+
export function normalizeExtraMemoryPaths(workspaceDir, extraPaths) {
|
|
17
|
+
if (!extraPaths?.length) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const resolved = extraPaths
|
|
21
|
+
.map((value) => value.trim())
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.map((value) => path.isAbsolute(value) ? path.resolve(value) : path.resolve(workspaceDir, value));
|
|
24
|
+
return Array.from(new Set(resolved));
|
|
25
|
+
}
|
|
16
26
|
export function isMemoryPath(relPath) {
|
|
17
27
|
const normalized = normalizeRelPath(relPath);
|
|
18
28
|
if (!normalized)
|
|
@@ -21,15 +31,6 @@ export function isMemoryPath(relPath) {
|
|
|
21
31
|
return true;
|
|
22
32
|
return normalized.startsWith("memory/");
|
|
23
33
|
}
|
|
24
|
-
async function exists(filePath) {
|
|
25
|
-
try {
|
|
26
|
-
await fs.access(filePath);
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
34
|
async function walkDir(dir, files) {
|
|
34
35
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
35
36
|
for (const entry of entries) {
|
|
@@ -45,20 +46,55 @@ async function walkDir(dir, files) {
|
|
|
45
46
|
files.push(full);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
export async function listMemoryFiles(workspaceDir) {
|
|
49
|
+
export async function listMemoryFiles(workspaceDir, extraPaths) {
|
|
49
50
|
const result = [];
|
|
50
51
|
const memoryFile = path.join(workspaceDir, "MEMORY.md");
|
|
51
52
|
const altMemoryFile = path.join(workspaceDir, "memory.md");
|
|
52
|
-
if (await exists(memoryFile))
|
|
53
|
-
result.push(memoryFile);
|
|
54
|
-
if (await exists(altMemoryFile))
|
|
55
|
-
result.push(altMemoryFile);
|
|
56
53
|
const memoryDir = path.join(workspaceDir, "memory");
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const addMarkdownFile = async (absPath) => {
|
|
55
|
+
try {
|
|
56
|
+
const stat = await fs.lstat(absPath);
|
|
57
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!absPath.endsWith(".md")) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
result.push(absPath);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
};
|
|
67
|
+
await addMarkdownFile(memoryFile);
|
|
68
|
+
await addMarkdownFile(altMemoryFile);
|
|
69
|
+
try {
|
|
70
|
+
const dirStat = await fs.lstat(memoryDir);
|
|
71
|
+
if (!dirStat.isSymbolicLink() && dirStat.isDirectory()) {
|
|
72
|
+
await walkDir(memoryDir, result);
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
|
-
|
|
75
|
+
catch { }
|
|
76
|
+
const normalizedExtraPaths = normalizeExtraMemoryPaths(workspaceDir, extraPaths);
|
|
77
|
+
if (normalizedExtraPaths.length > 0) {
|
|
78
|
+
for (const inputPath of normalizedExtraPaths) {
|
|
79
|
+
try {
|
|
80
|
+
const stat = await fs.lstat(inputPath);
|
|
81
|
+
if (stat.isSymbolicLink()) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (stat.isDirectory()) {
|
|
85
|
+
await walkDir(inputPath, result);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (stat.isFile() && inputPath.endsWith(".md")) {
|
|
89
|
+
result.push(inputPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (result.length <= 1) {
|
|
61
96
|
return result;
|
|
97
|
+
}
|
|
62
98
|
const seen = new Set();
|
|
63
99
|
const deduped = [];
|
|
64
100
|
for (const entry of result) {
|
|
@@ -67,8 +103,9 @@ export async function listMemoryFiles(workspaceDir) {
|
|
|
67
103
|
key = await fs.realpath(entry);
|
|
68
104
|
}
|
|
69
105
|
catch { }
|
|
70
|
-
if (seen.has(key))
|
|
106
|
+
if (seen.has(key)) {
|
|
71
107
|
continue;
|
|
108
|
+
}
|
|
72
109
|
seen.add(key);
|
|
73
110
|
deduped.push(entry);
|
|
74
111
|
}
|
|
@@ -187,3 +224,49 @@ export function cosineSimilarity(a, b) {
|
|
|
187
224
|
return 0;
|
|
188
225
|
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
189
226
|
}
|
|
227
|
+
export async function runWithConcurrency(tasks, limit) {
|
|
228
|
+
if (tasks.length === 0) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const resolvedLimit = Math.max(1, Math.min(limit, tasks.length));
|
|
232
|
+
const results = Array.from({ length: tasks.length });
|
|
233
|
+
let next = 0;
|
|
234
|
+
let firstError = null;
|
|
235
|
+
const workers = Array.from({ length: resolvedLimit }, async () => {
|
|
236
|
+
while (true) {
|
|
237
|
+
if (firstError) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const index = next;
|
|
241
|
+
next += 1;
|
|
242
|
+
if (index >= tasks.length) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
results[index] = await tasks[index]();
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
firstError = err;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
await Promise.allSettled(workers);
|
|
255
|
+
if (firstError) {
|
|
256
|
+
throw firstError;
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Remap chunk startLine/endLine using a lineMap from session file building.
|
|
262
|
+
*/
|
|
263
|
+
export function remapChunkLines(chunks, lineMap) {
|
|
264
|
+
if (!lineMap || lineMap.length === 0) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
for (const chunk of chunks) {
|
|
268
|
+
// startLine/endLine are 1-indexed; lineMap is 0-indexed by content line
|
|
269
|
+
chunk.startLine = lineMap[chunk.startLine - 1] ?? chunk.startLine;
|
|
270
|
+
chunk.endLine = lineMap[chunk.endLine - 1] ?? chunk.endLine;
|
|
271
|
+
}
|
|
272
|
+
}
|