mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 07:07:27 +00:00
refactor: extract shared install and embedding utilities
This commit is contained in:
@@ -1,54 +1,13 @@
|
|||||||
|
import type {
|
||||||
|
NodeListNode,
|
||||||
|
PairedNode,
|
||||||
|
PairingList,
|
||||||
|
PendingRequest,
|
||||||
|
} from "../../shared/node-list-types.js";
|
||||||
import { resolveNodeIdFromCandidates } from "../../shared/node-match.js";
|
import { resolveNodeIdFromCandidates } from "../../shared/node-match.js";
|
||||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||||
|
|
||||||
export type NodeListNode = {
|
export type { NodeListNode };
|
||||||
nodeId: string;
|
|
||||||
displayName?: string;
|
|
||||||
platform?: string;
|
|
||||||
version?: string;
|
|
||||||
coreVersion?: string;
|
|
||||||
uiVersion?: string;
|
|
||||||
remoteIp?: string;
|
|
||||||
deviceFamily?: string;
|
|
||||||
modelIdentifier?: string;
|
|
||||||
caps?: string[];
|
|
||||||
commands?: string[];
|
|
||||||
permissions?: Record<string, boolean>;
|
|
||||||
paired?: boolean;
|
|
||||||
connected?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PendingRequest = {
|
|
||||||
requestId: string;
|
|
||||||
nodeId: string;
|
|
||||||
displayName?: string;
|
|
||||||
platform?: string;
|
|
||||||
version?: string;
|
|
||||||
coreVersion?: string;
|
|
||||||
uiVersion?: string;
|
|
||||||
remoteIp?: string;
|
|
||||||
isRepair?: boolean;
|
|
||||||
ts: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PairedNode = {
|
|
||||||
nodeId: string;
|
|
||||||
token?: string;
|
|
||||||
displayName?: string;
|
|
||||||
platform?: string;
|
|
||||||
version?: string;
|
|
||||||
coreVersion?: string;
|
|
||||||
uiVersion?: string;
|
|
||||||
remoteIp?: string;
|
|
||||||
permissions?: Record<string, boolean>;
|
|
||||||
createdAtMs?: number;
|
|
||||||
approvedAtMs?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PairingList = {
|
|
||||||
pending: PendingRequest[];
|
|
||||||
paired: PairedNode[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function parseNodeList(value: unknown): NodeListNode[] {
|
function parseNodeList(value: unknown): NodeListNode[] {
|
||||||
const obj = typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
|
const obj = typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
|
||||||
|
|||||||
@@ -43,54 +43,9 @@ export type NodesRpcOpts = {
|
|||||||
audio?: boolean;
|
audio?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeListNode = {
|
export type {
|
||||||
nodeId: string;
|
NodeListNode,
|
||||||
displayName?: string;
|
PairedNode,
|
||||||
platform?: string;
|
PairingList,
|
||||||
version?: string;
|
PendingRequest,
|
||||||
coreVersion?: string;
|
} from "../../shared/node-list-types.js";
|
||||||
uiVersion?: string;
|
|
||||||
remoteIp?: string;
|
|
||||||
deviceFamily?: string;
|
|
||||||
modelIdentifier?: string;
|
|
||||||
pathEnv?: string;
|
|
||||||
caps?: string[];
|
|
||||||
commands?: string[];
|
|
||||||
permissions?: Record<string, boolean>;
|
|
||||||
paired?: boolean;
|
|
||||||
connected?: boolean;
|
|
||||||
connectedAtMs?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PendingRequest = {
|
|
||||||
requestId: string;
|
|
||||||
nodeId: string;
|
|
||||||
displayName?: string;
|
|
||||||
platform?: string;
|
|
||||||
version?: string;
|
|
||||||
coreVersion?: string;
|
|
||||||
uiVersion?: string;
|
|
||||||
remoteIp?: string;
|
|
||||||
isRepair?: boolean;
|
|
||||||
ts: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PairedNode = {
|
|
||||||
nodeId: string;
|
|
||||||
token?: string;
|
|
||||||
displayName?: string;
|
|
||||||
platform?: string;
|
|
||||||
version?: string;
|
|
||||||
coreVersion?: string;
|
|
||||||
uiVersion?: string;
|
|
||||||
remoteIp?: string;
|
|
||||||
permissions?: Record<string, boolean>;
|
|
||||||
createdAtMs?: number;
|
|
||||||
approvedAtMs?: number;
|
|
||||||
lastConnectedAtMs?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PairingList = {
|
|
||||||
pending: PendingRequest[];
|
|
||||||
paired: PairedNode[];
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||||
import {
|
import {
|
||||||
@@ -11,8 +10,12 @@ import {
|
|||||||
} from "../infra/archive.js";
|
} from "../infra/archive.js";
|
||||||
import { installPackageDir } from "../infra/install-package-dir.js";
|
import { installPackageDir } from "../infra/install-package-dir.js";
|
||||||
import { resolveSafeInstallDir, unscopedPackageName } from "../infra/install-safe-path.js";
|
import { resolveSafeInstallDir, unscopedPackageName } from "../infra/install-safe-path.js";
|
||||||
|
import {
|
||||||
|
packNpmSpecToArchive,
|
||||||
|
resolveArchiveSourcePath,
|
||||||
|
withTempDir,
|
||||||
|
} from "../infra/install-source-utils.js";
|
||||||
import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
|
||||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||||
import { parseFrontmatter } from "./frontmatter.js";
|
import { parseFrontmatter } from "./frontmatter.js";
|
||||||
|
|
||||||
@@ -105,15 +108,6 @@ function resolveTimedHookInstallModeOptions(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function withTempDir<T>(prefix: string, fn: (tmpDir: string) => Promise<T>): Promise<T> {
|
|
||||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
||||||
try {
|
|
||||||
return await fn(tmpDir);
|
|
||||||
} finally {
|
|
||||||
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveInstallTargetDir(
|
async function resolveInstallTargetDir(
|
||||||
id: string,
|
id: string,
|
||||||
hooksDir?: string,
|
hooksDir?: string,
|
||||||
@@ -325,15 +319,11 @@ export async function installHooksFromArchive(params: {
|
|||||||
}): Promise<InstallHooksResult> {
|
}): Promise<InstallHooksResult> {
|
||||||
const logger = params.logger ?? defaultLogger;
|
const logger = params.logger ?? defaultLogger;
|
||||||
const timeoutMs = params.timeoutMs ?? 120_000;
|
const timeoutMs = params.timeoutMs ?? 120_000;
|
||||||
|
const archivePathResult = await resolveArchiveSourcePath(params.archivePath);
|
||||||
const archivePath = resolveUserPath(params.archivePath);
|
if (!archivePathResult.ok) {
|
||||||
if (!(await fileExists(archivePath))) {
|
return archivePathResult;
|
||||||
return { ok: false, error: `archive not found: ${archivePath}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resolveArchiveKind(archivePath)) {
|
|
||||||
return { ok: false, error: `unsupported archive: ${archivePath}` };
|
|
||||||
}
|
}
|
||||||
|
const archivePath = archivePathResult.path;
|
||||||
|
|
||||||
return await withTempDir("openclaw-hook-", async (tmpDir) => {
|
return await withTempDir("openclaw-hook-", async (tmpDir) => {
|
||||||
const extractDir = path.join(tmpDir, "extract");
|
const extractDir = path.join(tmpDir, "extract");
|
||||||
@@ -396,30 +386,17 @@ export async function installHooksFromNpmSpec(params: {
|
|||||||
|
|
||||||
return await withTempDir("openclaw-hook-pack-", async (tmpDir) => {
|
return await withTempDir("openclaw-hook-pack-", async (tmpDir) => {
|
||||||
logger.info?.(`Downloading ${spec}…`);
|
logger.info?.(`Downloading ${spec}…`);
|
||||||
const res = await runCommandWithTimeout(["npm", "pack", spec, "--ignore-scripts"], {
|
const packedResult = await packNpmSpecToArchive({
|
||||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
spec,
|
||||||
|
timeoutMs,
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
env: {
|
|
||||||
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
||||||
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (res.code !== 0) {
|
if (!packedResult.ok) {
|
||||||
return { ok: false, error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}` };
|
return packedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packed = (res.stdout || "")
|
|
||||||
.split("\n")
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.pop();
|
|
||||||
if (!packed) {
|
|
||||||
return { ok: false, error: "npm pack produced no archive" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const archivePath = path.join(tmpDir, packed);
|
|
||||||
return await installHooksFromArchive({
|
return await installHooksFromArchive({
|
||||||
archivePath,
|
archivePath: packedResult.archivePath,
|
||||||
hooksDir: params.hooksDir,
|
hooksDir: params.hooksDir,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
78
src/infra/install-source-utils.ts
Normal file
78
src/infra/install-source-utils.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
|
import { resolveUserPath } from "../utils.js";
|
||||||
|
import { fileExists, resolveArchiveKind } from "./archive.js";
|
||||||
|
|
||||||
|
export async function withTempDir<T>(
|
||||||
|
prefix: string,
|
||||||
|
fn: (tmpDir: string) => Promise<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||||
|
try {
|
||||||
|
return await fn(tmpDir);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveArchiveSourcePath(archivePath: string): Promise<
|
||||||
|
| {
|
||||||
|
ok: true;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
ok: false;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
const resolved = resolveUserPath(archivePath);
|
||||||
|
if (!(await fileExists(resolved))) {
|
||||||
|
return { ok: false, error: `archive not found: ${resolved}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolveArchiveKind(resolved)) {
|
||||||
|
return { ok: false, error: `unsupported archive: ${resolved}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, path: resolved };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function packNpmSpecToArchive(params: {
|
||||||
|
spec: string;
|
||||||
|
timeoutMs: number;
|
||||||
|
cwd: string;
|
||||||
|
}): Promise<
|
||||||
|
| {
|
||||||
|
ok: true;
|
||||||
|
archivePath: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
ok: false;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
const res = await runCommandWithTimeout(["npm", "pack", params.spec, "--ignore-scripts"], {
|
||||||
|
timeoutMs: Math.max(params.timeoutMs, 300_000),
|
||||||
|
cwd: params.cwd,
|
||||||
|
env: {
|
||||||
|
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
||||||
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (res.code !== 0) {
|
||||||
|
return { ok: false, error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const packed = (res.stdout || "")
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.pop();
|
||||||
|
if (!packed) {
|
||||||
|
return { ok: false, error: "npm pack produced no archive" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, archivePath: path.join(params.cwd, packed) };
|
||||||
|
}
|
||||||
@@ -366,40 +366,49 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async embedChunksWithVoyageBatch(
|
private async embedChunksWithProviderBatch<TRequest extends { custom_id: string }>(params: {
|
||||||
chunks: MemoryChunk[],
|
chunks: MemoryChunk[];
|
||||||
entry: MemoryFileEntry | SessionFileEntry,
|
entry: MemoryFileEntry | SessionFileEntry;
|
||||||
source: MemorySource,
|
source: MemorySource;
|
||||||
): Promise<number[][]> {
|
provider: "voyage" | "openai" | "gemini";
|
||||||
const voyage = this.voyage;
|
enabled: boolean;
|
||||||
if (!voyage) {
|
buildRequest: (chunk: MemoryChunk) => Omit<TRequest, "custom_id">;
|
||||||
return this.embedChunksInBatches(chunks);
|
runBatch: (runnerOptions: {
|
||||||
|
agentId: string;
|
||||||
|
requests: TRequest[];
|
||||||
|
wait: boolean;
|
||||||
|
concurrency: number;
|
||||||
|
pollIntervalMs: number;
|
||||||
|
timeoutMs: number;
|
||||||
|
debug: (message: string, data?: Record<string, unknown>) => void;
|
||||||
|
}) => Promise<Map<string, number[]> | number[][]>;
|
||||||
|
}): Promise<number[][]> {
|
||||||
|
if (!params.enabled) {
|
||||||
|
return this.embedChunksInBatches(params.chunks);
|
||||||
}
|
}
|
||||||
if (chunks.length === 0) {
|
if (params.chunks.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { embeddings, missing } = this.collectCachedEmbeddings(chunks);
|
const { embeddings, missing } = this.collectCachedEmbeddings(params.chunks);
|
||||||
if (missing.length === 0) {
|
if (missing.length === 0) {
|
||||||
return embeddings;
|
return embeddings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { requests, mapping } = this.buildBatchRequests<VoyageBatchRequest>({
|
const { requests, mapping } = this.buildBatchRequests<TRequest>({
|
||||||
missing,
|
missing,
|
||||||
entry,
|
entry: params.entry,
|
||||||
source,
|
source: params.source,
|
||||||
build: (chunk) => ({
|
build: params.buildRequest,
|
||||||
body: { input: chunk.text },
|
});
|
||||||
}),
|
const runnerOptions = this.buildEmbeddingBatchRunnerOptions({
|
||||||
|
requests,
|
||||||
|
chunks: params.chunks,
|
||||||
|
source: params.source,
|
||||||
});
|
});
|
||||||
const runnerOptions = this.buildEmbeddingBatchRunnerOptions({ requests, chunks, source });
|
|
||||||
const batchResult = await this.runBatchWithFallback({
|
const batchResult = await this.runBatchWithFallback({
|
||||||
provider: "voyage",
|
provider: params.provider,
|
||||||
run: async () =>
|
run: async () => await params.runBatch(runnerOptions),
|
||||||
await runVoyageEmbeddingBatches({
|
fallback: async () => await this.embedChunksInBatches(params.chunks),
|
||||||
client: voyage,
|
|
||||||
...runnerOptions,
|
|
||||||
}),
|
|
||||||
fallback: async () => await this.embedChunksInBatches(chunks),
|
|
||||||
});
|
});
|
||||||
if (Array.isArray(batchResult)) {
|
if (Array.isArray(batchResult)) {
|
||||||
return batchResult;
|
return batchResult;
|
||||||
@@ -408,51 +417,55 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
|
|||||||
return embeddings;
|
return embeddings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async embedChunksWithVoyageBatch(
|
||||||
|
chunks: MemoryChunk[],
|
||||||
|
entry: MemoryFileEntry | SessionFileEntry,
|
||||||
|
source: MemorySource,
|
||||||
|
): Promise<number[][]> {
|
||||||
|
const voyage = this.voyage;
|
||||||
|
return await this.embedChunksWithProviderBatch<VoyageBatchRequest>({
|
||||||
|
chunks,
|
||||||
|
entry,
|
||||||
|
source,
|
||||||
|
provider: "voyage",
|
||||||
|
enabled: Boolean(voyage),
|
||||||
|
buildRequest: (chunk) => ({
|
||||||
|
body: { input: chunk.text },
|
||||||
|
}),
|
||||||
|
runBatch: async (runnerOptions) =>
|
||||||
|
await runVoyageEmbeddingBatches({
|
||||||
|
client: voyage!,
|
||||||
|
...runnerOptions,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async embedChunksWithOpenAiBatch(
|
private async embedChunksWithOpenAiBatch(
|
||||||
chunks: MemoryChunk[],
|
chunks: MemoryChunk[],
|
||||||
entry: MemoryFileEntry | SessionFileEntry,
|
entry: MemoryFileEntry | SessionFileEntry,
|
||||||
source: MemorySource,
|
source: MemorySource,
|
||||||
): Promise<number[][]> {
|
): Promise<number[][]> {
|
||||||
const openAi = this.openAi;
|
const openAi = this.openAi;
|
||||||
if (!openAi) {
|
return await this.embedChunksWithProviderBatch<OpenAiBatchRequest>({
|
||||||
return this.embedChunksInBatches(chunks);
|
chunks,
|
||||||
}
|
|
||||||
if (chunks.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const { embeddings, missing } = this.collectCachedEmbeddings(chunks);
|
|
||||||
if (missing.length === 0) {
|
|
||||||
return embeddings;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requests, mapping } = this.buildBatchRequests<OpenAiBatchRequest>({
|
|
||||||
missing,
|
|
||||||
entry,
|
entry,
|
||||||
source,
|
source,
|
||||||
build: (chunk) => ({
|
provider: "openai",
|
||||||
|
enabled: Boolean(openAi),
|
||||||
|
buildRequest: (chunk) => ({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: OPENAI_BATCH_ENDPOINT,
|
url: OPENAI_BATCH_ENDPOINT,
|
||||||
body: {
|
body: {
|
||||||
model: this.openAi?.model ?? this.provider?.model ?? "text-embedding-3-small",
|
model: openAi?.model ?? this.provider?.model ?? "text-embedding-3-small",
|
||||||
input: chunk.text,
|
input: chunk.text,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
runBatch: async (runnerOptions) =>
|
||||||
const runnerOptions = this.buildEmbeddingBatchRunnerOptions({ requests, chunks, source });
|
|
||||||
const batchResult = await this.runBatchWithFallback({
|
|
||||||
provider: "openai",
|
|
||||||
run: async () =>
|
|
||||||
await runOpenAiEmbeddingBatches({
|
await runOpenAiEmbeddingBatches({
|
||||||
openAi,
|
openAi: openAi!,
|
||||||
...runnerOptions,
|
...runnerOptions,
|
||||||
}),
|
}),
|
||||||
fallback: async () => await this.embedChunksInBatches(chunks),
|
|
||||||
});
|
});
|
||||||
if (Array.isArray(batchResult)) {
|
|
||||||
return batchResult;
|
|
||||||
}
|
|
||||||
this.applyBatchEmbeddings({ byCustomId: batchResult, mapping, embeddings });
|
|
||||||
return embeddings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async embedChunksWithGeminiBatch(
|
private async embedChunksWithGeminiBatch(
|
||||||
@@ -461,42 +474,22 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
|
|||||||
source: MemorySource,
|
source: MemorySource,
|
||||||
): Promise<number[][]> {
|
): Promise<number[][]> {
|
||||||
const gemini = this.gemini;
|
const gemini = this.gemini;
|
||||||
if (!gemini) {
|
return await this.embedChunksWithProviderBatch<GeminiBatchRequest>({
|
||||||
return this.embedChunksInBatches(chunks);
|
chunks,
|
||||||
}
|
|
||||||
if (chunks.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const { embeddings, missing } = this.collectCachedEmbeddings(chunks);
|
|
||||||
if (missing.length === 0) {
|
|
||||||
return embeddings;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requests, mapping } = this.buildBatchRequests<GeminiBatchRequest>({
|
|
||||||
missing,
|
|
||||||
entry,
|
entry,
|
||||||
source,
|
source,
|
||||||
build: (chunk) => ({
|
provider: "gemini",
|
||||||
|
enabled: Boolean(gemini),
|
||||||
|
buildRequest: (chunk) => ({
|
||||||
content: { parts: [{ text: chunk.text }] },
|
content: { parts: [{ text: chunk.text }] },
|
||||||
taskType: "RETRIEVAL_DOCUMENT",
|
taskType: "RETRIEVAL_DOCUMENT",
|
||||||
}),
|
}),
|
||||||
});
|
runBatch: async (runnerOptions) =>
|
||||||
const runnerOptions = this.buildEmbeddingBatchRunnerOptions({ requests, chunks, source });
|
|
||||||
|
|
||||||
const batchResult = await this.runBatchWithFallback({
|
|
||||||
provider: "gemini",
|
|
||||||
run: async () =>
|
|
||||||
await runGeminiEmbeddingBatches({
|
await runGeminiEmbeddingBatches({
|
||||||
gemini,
|
gemini: gemini!,
|
||||||
...runnerOptions,
|
...runnerOptions,
|
||||||
}),
|
}),
|
||||||
fallback: async () => await this.embedChunksInBatches(chunks),
|
|
||||||
});
|
});
|
||||||
if (Array.isArray(batchResult)) {
|
|
||||||
return batchResult;
|
|
||||||
}
|
|
||||||
this.applyBatchEmbeddings({ byCustomId: batchResult, mapping, embeddings });
|
|
||||||
return embeddings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async embedBatchWithRetry(texts: string[]): Promise<number[][]> {
|
protected async embedBatchWithRetry(texts: string[]): Promise<number[][]> {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||||
import {
|
import {
|
||||||
@@ -15,8 +14,12 @@ import {
|
|||||||
safeDirName,
|
safeDirName,
|
||||||
unscopedPackageName,
|
unscopedPackageName,
|
||||||
} from "../infra/install-safe-path.js";
|
} from "../infra/install-safe-path.js";
|
||||||
|
import {
|
||||||
|
packNpmSpecToArchive,
|
||||||
|
resolveArchiveSourcePath,
|
||||||
|
withTempDir,
|
||||||
|
} from "../infra/install-source-utils.js";
|
||||||
import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
|
||||||
import { extensionUsesSkippedScannerPath, isPathInside } from "../security/scan-paths.js";
|
import { extensionUsesSkippedScannerPath, isPathInside } from "../security/scan-paths.js";
|
||||||
import * as skillScanner from "../security/skill-scanner.js";
|
import * as skillScanner from "../security/skill-scanner.js";
|
||||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||||
@@ -298,18 +301,13 @@ export async function installPluginFromArchive(params: {
|
|||||||
const logger = params.logger ?? defaultLogger;
|
const logger = params.logger ?? defaultLogger;
|
||||||
const timeoutMs = params.timeoutMs ?? 120_000;
|
const timeoutMs = params.timeoutMs ?? 120_000;
|
||||||
const mode = params.mode ?? "install";
|
const mode = params.mode ?? "install";
|
||||||
|
const archivePathResult = await resolveArchiveSourcePath(params.archivePath);
|
||||||
const archivePath = resolveUserPath(params.archivePath);
|
if (!archivePathResult.ok) {
|
||||||
if (!(await fileExists(archivePath))) {
|
return archivePathResult;
|
||||||
return { ok: false, error: `archive not found: ${archivePath}` };
|
|
||||||
}
|
}
|
||||||
|
const archivePath = archivePathResult.path;
|
||||||
|
|
||||||
if (!resolveArchiveKind(archivePath)) {
|
return await withTempDir("openclaw-plugin-", async (tmpDir) => {
|
||||||
return { ok: false, error: `unsupported archive: ${archivePath}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-plugin-"));
|
|
||||||
try {
|
|
||||||
const extractDir = path.join(tmpDir, "extract");
|
const extractDir = path.join(tmpDir, "extract");
|
||||||
await fs.mkdir(extractDir, { recursive: true });
|
await fs.mkdir(extractDir, { recursive: true });
|
||||||
|
|
||||||
@@ -341,9 +339,7 @@ export async function installPluginFromArchive(params: {
|
|||||||
dryRun: params.dryRun,
|
dryRun: params.dryRun,
|
||||||
expectedPluginId: params.expectedPluginId,
|
expectedPluginId: params.expectedPluginId,
|
||||||
});
|
});
|
||||||
} finally {
|
});
|
||||||
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installPluginFromDir(params: {
|
export async function installPluginFromDir(params: {
|
||||||
@@ -433,36 +429,19 @@ export async function installPluginFromNpmSpec(params: {
|
|||||||
return { ok: false, error: specError };
|
return { ok: false, error: specError };
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-npm-pack-"));
|
return await withTempDir("openclaw-npm-pack-", async (tmpDir) => {
|
||||||
try {
|
|
||||||
logger.info?.(`Downloading ${spec}…`);
|
logger.info?.(`Downloading ${spec}…`);
|
||||||
const res = await runCommandWithTimeout(["npm", "pack", spec, "--ignore-scripts"], {
|
const packedResult = await packNpmSpecToArchive({
|
||||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
spec,
|
||||||
|
timeoutMs,
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
env: {
|
|
||||||
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
||||||
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (res.code !== 0) {
|
if (!packedResult.ok) {
|
||||||
return {
|
return packedResult;
|
||||||
ok: false,
|
|
||||||
error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const packed = (res.stdout || "")
|
|
||||||
.split("\n")
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.pop();
|
|
||||||
if (!packed) {
|
|
||||||
return { ok: false, error: "npm pack produced no archive" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const archivePath = path.join(tmpDir, packed);
|
|
||||||
return await installPluginFromArchive({
|
return await installPluginFromArchive({
|
||||||
archivePath,
|
archivePath: packedResult.archivePath,
|
||||||
extensionsDir: params.extensionsDir,
|
extensionsDir: params.extensionsDir,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
logger,
|
logger,
|
||||||
@@ -470,9 +449,7 @@ export async function installPluginFromNpmSpec(params: {
|
|||||||
dryRun,
|
dryRun,
|
||||||
expectedPluginId,
|
expectedPluginId,
|
||||||
});
|
});
|
||||||
} finally {
|
});
|
||||||
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installPluginFromPath(params: {
|
export async function installPluginFromPath(params: {
|
||||||
|
|||||||
51
src/shared/node-list-types.ts
Normal file
51
src/shared/node-list-types.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
export type NodeListNode = {
|
||||||
|
nodeId: string;
|
||||||
|
displayName?: string;
|
||||||
|
platform?: string;
|
||||||
|
version?: string;
|
||||||
|
coreVersion?: string;
|
||||||
|
uiVersion?: string;
|
||||||
|
remoteIp?: string;
|
||||||
|
deviceFamily?: string;
|
||||||
|
modelIdentifier?: string;
|
||||||
|
pathEnv?: string;
|
||||||
|
caps?: string[];
|
||||||
|
commands?: string[];
|
||||||
|
permissions?: Record<string, boolean>;
|
||||||
|
paired?: boolean;
|
||||||
|
connected?: boolean;
|
||||||
|
connectedAtMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PendingRequest = {
|
||||||
|
requestId: string;
|
||||||
|
nodeId: string;
|
||||||
|
displayName?: string;
|
||||||
|
platform?: string;
|
||||||
|
version?: string;
|
||||||
|
coreVersion?: string;
|
||||||
|
uiVersion?: string;
|
||||||
|
remoteIp?: string;
|
||||||
|
isRepair?: boolean;
|
||||||
|
ts: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PairedNode = {
|
||||||
|
nodeId: string;
|
||||||
|
token?: string;
|
||||||
|
displayName?: string;
|
||||||
|
platform?: string;
|
||||||
|
version?: string;
|
||||||
|
coreVersion?: string;
|
||||||
|
uiVersion?: string;
|
||||||
|
remoteIp?: string;
|
||||||
|
permissions?: Record<string, boolean>;
|
||||||
|
createdAtMs?: number;
|
||||||
|
approvedAtMs?: number;
|
||||||
|
lastConnectedAtMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PairingList = {
|
||||||
|
pending: PendingRequest[];
|
||||||
|
paired: PairedNode[];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user