mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 06:31:11 +00:00
refactor(memory-host): own package contract surface
This commit is contained in:
parent
6fadc56802
commit
dc3df62e67
56 changed files with 1111 additions and 602 deletions
|
|
@ -1 +1,73 @@
|
|||
export * from "../../../src/memory-host-sdk/engine-embeddings.js";
|
||||
// Real workspace contract for memory embedding providers and batch helpers.
|
||||
|
||||
export {
|
||||
getMemoryEmbeddingProvider,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
listMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviderAdapters,
|
||||
} from "../../../src/plugins/memory-embedding-provider-runtime.js";
|
||||
export type {
|
||||
MemoryEmbeddingBatchChunk,
|
||||
MemoryEmbeddingBatchOptions,
|
||||
MemoryEmbeddingProvider,
|
||||
MemoryEmbeddingProviderAdapter,
|
||||
MemoryEmbeddingProviderCreateOptions,
|
||||
MemoryEmbeddingProviderCreateResult,
|
||||
MemoryEmbeddingProviderRuntime,
|
||||
} from "../../../src/plugins/memory-embedding-providers.js";
|
||||
export { createLocalEmbeddingProvider, DEFAULT_LOCAL_MODEL } from "./host/embeddings.js";
|
||||
export { extractBatchErrorMessage, formatUnavailableBatchError } from "./host/batch-error-utils.js";
|
||||
export { postJsonWithRetry } from "./host/batch-http.js";
|
||||
export { applyEmbeddingBatchOutputLine } from "./host/batch-output.js";
|
||||
export {
|
||||
EMBEDDING_BATCH_ENDPOINT,
|
||||
type EmbeddingBatchStatus,
|
||||
type ProviderBatchOutputLine,
|
||||
} from "./host/batch-provider-common.js";
|
||||
export {
|
||||
buildEmbeddingBatchGroupOptions,
|
||||
runEmbeddingBatchGroups,
|
||||
type EmbeddingBatchExecutionParams,
|
||||
} from "./host/batch-runner.js";
|
||||
export {
|
||||
resolveBatchCompletionFromStatus,
|
||||
resolveCompletedBatchResult,
|
||||
throwIfBatchTerminalFailure,
|
||||
type BatchCompletionResult,
|
||||
} from "./host/batch-status.js";
|
||||
export { uploadBatchJsonlFile } from "./host/batch-upload.js";
|
||||
export {
|
||||
buildBatchHeaders,
|
||||
normalizeBatchBaseUrl,
|
||||
type BatchHttpClientConfig,
|
||||
} from "./host/batch-utils.js";
|
||||
export { enforceEmbeddingMaxInputTokens } from "./host/embedding-chunk-limits.js";
|
||||
export {
|
||||
isMissingEmbeddingApiKeyError,
|
||||
mapBatchEmbeddingsByIndex,
|
||||
sanitizeEmbeddingCacheHeaders,
|
||||
} from "./host/embedding-provider-adapter-utils.js";
|
||||
export { sanitizeAndNormalizeEmbedding } from "./host/embedding-vectors.js";
|
||||
export { debugEmbeddingsLog } from "./host/embeddings-debug.js";
|
||||
export { normalizeEmbeddingModelWithPrefixes } from "./host/embeddings-model-normalize.js";
|
||||
export {
|
||||
resolveRemoteEmbeddingBearerClient,
|
||||
type RemoteEmbeddingProviderId,
|
||||
} from "./host/embeddings-remote-client.js";
|
||||
export {
|
||||
createRemoteEmbeddingProvider,
|
||||
resolveRemoteEmbeddingClient,
|
||||
type RemoteEmbeddingClient,
|
||||
} from "./host/embeddings-remote-provider.js";
|
||||
export { fetchRemoteEmbeddingVectors } from "./host/embeddings-remote-fetch.js";
|
||||
export {
|
||||
estimateStructuredEmbeddingInputBytes,
|
||||
estimateUtf8Bytes,
|
||||
} from "./host/embedding-input-limits.js";
|
||||
export { hasNonTextEmbeddingParts, type EmbeddingInput } from "./host/embedding-inputs.js";
|
||||
export { buildRemoteBaseUrlPolicy, withRemoteHttpResponse } from "./host/remote-http.js";
|
||||
export {
|
||||
buildCaseInsensitiveExtensionGlob,
|
||||
classifyMemoryMultimodalPath,
|
||||
getMemoryMultimodalExtensions,
|
||||
} from "./host/multimodal.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,48 @@
|
|||
export * from "../../../src/memory-host-sdk/engine-foundation.js";
|
||||
// Real workspace contract for memory engine foundation concerns.
|
||||
|
||||
export {
|
||||
resolveAgentContextLimits,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
resolveSessionAgentId,
|
||||
} from "../../../src/agents/agent-scope.js";
|
||||
export {
|
||||
resolveMemorySearchConfig,
|
||||
resolveMemorySearchSyncConfig,
|
||||
type ResolvedMemorySearchConfig,
|
||||
type ResolvedMemorySearchSyncConfig,
|
||||
} from "../../../src/agents/memory-search.js";
|
||||
export { parseDurationMs } from "../../../src/cli/parse-duration.js";
|
||||
export { loadConfig } from "../../../src/config/config.js";
|
||||
export { resolveStateDir } from "../../../src/config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../../../src/config/sessions/paths.js";
|
||||
export {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
} from "../../../src/config/types.secrets.js";
|
||||
export { writeFileWithinRoot } from "../../../src/infra/fs-safe.js";
|
||||
export { createSubsystemLogger } from "../../../src/logging/subsystem.js";
|
||||
export { detectMime } from "../../../src/media/mime.js";
|
||||
export { resolveGlobalSingleton } from "../../../src/shared/global-singleton.js";
|
||||
export { onSessionTranscriptUpdate } from "../../../src/sessions/transcript-events.js";
|
||||
export { splitShellArgs } from "../../../src/utils/shell-argv.js";
|
||||
export { runTasksWithConcurrency } from "../../../src/utils/run-with-concurrency.js";
|
||||
export {
|
||||
shortenHomeInString,
|
||||
shortenHomePath,
|
||||
resolveUserPath,
|
||||
truncateUtf16Safe,
|
||||
} from "../../../src/utils.js";
|
||||
export type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
export type { SessionSendPolicyConfig } from "../../../src/config/types.base.js";
|
||||
export type { SecretInput } from "../../../src/config/types.secrets.js";
|
||||
export type {
|
||||
MemoryBackend,
|
||||
MemoryCitationsMode,
|
||||
MemoryQmdConfig,
|
||||
MemoryQmdIndexPath,
|
||||
MemoryQmdMcporterConfig,
|
||||
MemoryQmdSearchMode,
|
||||
} from "../../../src/config/types.memory.js";
|
||||
export type { MemorySearchConfig } from "../../../src/config/types.tools.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,26 @@
|
|||
export * from "../../../src/memory-host-sdk/engine-qmd.js";
|
||||
// Real workspace contract for QMD/session/query helpers used by the memory engine.
|
||||
|
||||
export { extractKeywords, isQueryStopWordToken } from "./host/query-expansion.js";
|
||||
export {
|
||||
buildSessionEntry,
|
||||
listSessionFilesForAgent,
|
||||
loadDreamingNarrativeTranscriptPathSetForAgent,
|
||||
loadSessionTranscriptClassificationForAgent,
|
||||
normalizeSessionTranscriptPathForComparison,
|
||||
sessionPathForFile,
|
||||
type BuildSessionEntryOptions,
|
||||
type SessionFileEntry,
|
||||
type SessionTranscriptClassification,
|
||||
} from "./host/session-files.js";
|
||||
export { parseUsageCountedSessionIdFromFileName } from "../../../src/config/sessions/artifacts.js";
|
||||
export { parseQmdQueryJson, type QmdQueryResult } from "./host/qmd-query-parser.js";
|
||||
export {
|
||||
deriveQmdScopeChannel,
|
||||
deriveQmdScopeChatType,
|
||||
isQmdScopeAllowed,
|
||||
} from "./host/qmd-scope.js";
|
||||
export {
|
||||
checkQmdBinaryAvailability,
|
||||
resolveCliSpawnInvocation,
|
||||
runCliCommand,
|
||||
} from "./host/qmd-process.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,48 @@
|
|||
export * from "../../../src/memory-host-sdk/engine-storage.js";
|
||||
// Real workspace contract for memory engine storage/index helpers.
|
||||
|
||||
export {
|
||||
buildFileEntry,
|
||||
buildMultimodalChunkForIndexing,
|
||||
chunkMarkdown,
|
||||
cosineSimilarity,
|
||||
ensureDir,
|
||||
hashText,
|
||||
listMemoryFiles,
|
||||
normalizeExtraMemoryPaths,
|
||||
parseEmbedding,
|
||||
remapChunkLines,
|
||||
runWithConcurrency,
|
||||
type MemoryChunk,
|
||||
type MemoryFileEntry,
|
||||
} from "./host/internal.js";
|
||||
export { readMemoryFile } from "./host/read-file.js";
|
||||
export {
|
||||
buildMemoryReadResult,
|
||||
buildMemoryReadResultFromSlice,
|
||||
DEFAULT_MEMORY_READ_LINES,
|
||||
DEFAULT_MEMORY_READ_MAX_CHARS,
|
||||
type MemoryReadResult,
|
||||
} from "./host/read-file-shared.js";
|
||||
export { resolveMemoryBackendConfig } from "./host/backend-config.js";
|
||||
export type {
|
||||
ResolvedMemoryBackendConfig,
|
||||
ResolvedQmdConfig,
|
||||
ResolvedQmdMcporterConfig,
|
||||
} from "./host/backend-config.js";
|
||||
export type {
|
||||
MemoryEmbeddingProbeResult,
|
||||
MemoryProviderStatus,
|
||||
MemorySearchManager,
|
||||
MemorySearchRuntimeDebug,
|
||||
MemorySearchResult,
|
||||
MemorySource,
|
||||
MemorySyncProgressUpdate,
|
||||
} from "./host/types.js";
|
||||
export { ensureMemoryIndexSchema } from "./host/memory-schema.js";
|
||||
export { loadSqliteVecExtension } from "./host/sqlite-vec.js";
|
||||
export {
|
||||
closeMemorySqliteWalMaintenance,
|
||||
configureMemorySqliteWalMaintenance,
|
||||
requireNodeSqlite,
|
||||
} from "./host/sqlite.js";
|
||||
export { isFileMissingError, statRegularFile } from "./host/fs-utils.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
export * from "../../../src/memory-host-sdk/engine.js";
|
||||
// Aggregate workspace contract for the memory engine surface.
|
||||
// Keep focused subpaths preferred for new code.
|
||||
|
||||
export * from "./engine-foundation.js";
|
||||
export * from "./engine-storage.js";
|
||||
export * from "./engine-embeddings.js";
|
||||
export * from "./engine-qmd.js";
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import type {
|
|||
} from "../../../../src/config/types.memory.js";
|
||||
import { CANONICAL_ROOT_MEMORY_FILENAME } from "../../../../src/memory/root-memory-files.js";
|
||||
import { normalizeAgentId } from "../../../../src/routing/session-key.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../../../src/shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../../../../src/utils.js";
|
||||
import { splitShellArgs } from "../../../../src/utils/shell-argv.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string-utils.js";
|
||||
|
||||
export type ResolvedMemoryBackendConfig = {
|
||||
backend: MemoryBackend;
|
||||
|
|
|
|||
|
|
@ -1 +1,33 @@
|
|||
export * from "../../../../src/memory-host-sdk/host/batch-error-utils.js";
|
||||
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
||||
|
||||
type BatchOutputErrorLike = {
|
||||
error?: { message?: string };
|
||||
response?: {
|
||||
body?:
|
||||
| string
|
||||
| {
|
||||
error?: { message?: string };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function getResponseErrorMessage(line: BatchOutputErrorLike | undefined): string | undefined {
|
||||
const body = line?.response?.body;
|
||||
if (typeof body === "string") {
|
||||
return body || undefined;
|
||||
}
|
||||
if (!body || typeof body !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return typeof body.error?.message === "string" ? body.error.message : undefined;
|
||||
}
|
||||
|
||||
export function extractBatchErrorMessage(lines: BatchOutputErrorLike[]): string | undefined {
|
||||
const first = lines.find((line) => line.error?.message || getResponseErrorMessage(line));
|
||||
return first?.error?.message ?? getResponseErrorMessage(first);
|
||||
}
|
||||
|
||||
export function formatUnavailableBatchError(err: unknown): string | undefined {
|
||||
const message = formatErrorMessage(err);
|
||||
return message ? `error file unavailable: ${message}` : undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export async function postJsonWithRetry<T>(params: {
|
|||
url: string;
|
||||
headers: Record<string, string>;
|
||||
ssrfPolicy?: SsrFPolicy;
|
||||
fetchImpl?: typeof fetch;
|
||||
body: unknown;
|
||||
errorPrefix: string;
|
||||
}): Promise<T> {
|
||||
|
|
@ -15,6 +16,7 @@ export async function postJsonWithRetry<T>(params: {
|
|||
url: params.url,
|
||||
headers: params.headers,
|
||||
ssrfPolicy: params.ssrfPolicy,
|
||||
fetchImpl: params.fetchImpl,
|
||||
body: params.body,
|
||||
errorPrefix: params.errorPrefix,
|
||||
attachStatus: true,
|
||||
|
|
|
|||
2
packages/memory-host-sdk/src/host/embedding-defaults.ts
Normal file
2
packages/memory-host-sdk/src/host/embedding-defaults.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const DEFAULT_LOCAL_MODEL =
|
||||
"hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf";
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { normalizeLowercaseStringOrEmpty } from "./string-utils.js";
|
||||
|
||||
export function isMissingEmbeddingApiKeyError(err: unknown): boolean {
|
||||
return err instanceof Error && err.message.includes("No API key found for provider");
|
||||
}
|
||||
|
||||
export function sanitizeEmbeddingCacheHeaders(
|
||||
headers: Record<string, string>,
|
||||
excludedHeaderNames: string[],
|
||||
): Array<[string, string]> {
|
||||
const excluded = new Set(
|
||||
excludedHeaderNames.map((name) => normalizeLowercaseStringOrEmpty(name)),
|
||||
);
|
||||
return Object.entries(headers)
|
||||
.filter(([key]) => !excluded.has(normalizeLowercaseStringOrEmpty(key)))
|
||||
.toSorted(([a], [b]) => a.localeCompare(b))
|
||||
.map(([key, value]) => [key, value]);
|
||||
}
|
||||
|
||||
export function mapBatchEmbeddingsByIndex(
|
||||
byCustomId: Map<string, number[]>,
|
||||
count: number,
|
||||
): number[][] {
|
||||
const embeddings: number[][] = [];
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
embeddings.push(byCustomId.get(String(index)) ?? []);
|
||||
}
|
||||
return embeddings;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { requireApiKey, resolveApiKeyForProvider } from "../../../../src/agents/model-auth.js";
|
||||
import type { SsrFPolicy } from "../../../../src/infra/net/ssrf.js";
|
||||
import type { EmbeddingProviderOptions } from "./embeddings.js";
|
||||
import { normalizeOptionalString } from "../../../../src/shared/string-coerce.js";
|
||||
import type { EmbeddingProviderOptions } from "./embeddings.types.js";
|
||||
import { buildRemoteBaseUrlPolicy } from "./remote-http.js";
|
||||
import { resolveMemorySecretInputString } from "./secret-input.js";
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ export async function resolveRemoteEmbeddingBearerClient(params: {
|
|||
value: remote?.apiKey,
|
||||
path: "agents.*.memorySearch.remote.apiKey",
|
||||
});
|
||||
const remoteBaseUrl = remote?.baseUrl?.trim();
|
||||
const remoteBaseUrl = normalizeOptionalString(remote?.baseUrl);
|
||||
const providerConfig = params.options.config.models?.providers?.[params.provider];
|
||||
const apiKey = remoteApiKey
|
||||
? remoteApiKey
|
||||
|
|
@ -28,7 +29,8 @@ export async function resolveRemoteEmbeddingBearerClient(params: {
|
|||
}),
|
||||
params.provider,
|
||||
);
|
||||
const baseUrl = remoteBaseUrl || providerConfig?.baseUrl?.trim() || params.defaultBaseUrl;
|
||||
const baseUrl =
|
||||
remoteBaseUrl || normalizeOptionalString(providerConfig?.baseUrl) || params.defaultBaseUrl;
|
||||
const headerOverrides = Object.assign({}, providerConfig?.headers, remote?.headers);
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export async function fetchRemoteEmbeddingVectors(params: {
|
|||
url: string;
|
||||
headers: Record<string, string>;
|
||||
ssrfPolicy?: SsrFPolicy;
|
||||
fetchImpl?: typeof fetch;
|
||||
body: unknown;
|
||||
errorPrefix: string;
|
||||
}): Promise<number[][]> {
|
||||
|
|
@ -12,6 +13,7 @@ export async function fetchRemoteEmbeddingVectors(params: {
|
|||
url: params.url,
|
||||
headers: params.headers,
|
||||
ssrfPolicy: params.ssrfPolicy,
|
||||
fetchImpl: params.fetchImpl,
|
||||
body: params.body,
|
||||
errorPrefix: params.errorPrefix,
|
||||
parse: (payload) => {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import {
|
|||
type RemoteEmbeddingProviderId,
|
||||
} from "./embeddings-remote-client.js";
|
||||
import { fetchRemoteEmbeddingVectors } from "./embeddings-remote-fetch.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.types.js";
|
||||
|
||||
export type RemoteEmbeddingClient = {
|
||||
baseUrl: string;
|
||||
headers: Record<string, string>;
|
||||
ssrfPolicy?: SsrFPolicy;
|
||||
fetchImpl?: typeof fetch;
|
||||
model: string;
|
||||
};
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ export function createRemoteEmbeddingProvider(params: {
|
|||
url,
|
||||
headers: client.headers,
|
||||
ssrfPolicy: client.ssrfPolicy,
|
||||
fetchImpl: client.fetchImpl,
|
||||
body: { model: client.model, input },
|
||||
errorPrefix: params.errorPrefix,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1 +1,85 @@
|
|||
export * from "../../../../src/memory-host-sdk/host/embeddings.js";
|
||||
import { DEFAULT_LOCAL_MODEL } from "./embedding-defaults.js";
|
||||
import { sanitizeAndNormalizeEmbedding } from "./embedding-vectors.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.types.js";
|
||||
import {
|
||||
importNodeLlamaCpp,
|
||||
type Llama,
|
||||
type LlamaEmbeddingContext,
|
||||
type LlamaModel,
|
||||
} from "./node-llama.js";
|
||||
import { normalizeOptionalString } from "./string-utils.js";
|
||||
|
||||
export type {
|
||||
EmbeddingProvider,
|
||||
EmbeddingProviderFallback,
|
||||
EmbeddingProviderId,
|
||||
EmbeddingProviderOptions,
|
||||
EmbeddingProviderRequest,
|
||||
GeminiTaskType,
|
||||
} from "./embeddings.types.js";
|
||||
|
||||
export { DEFAULT_LOCAL_MODEL } from "./embedding-defaults.js";
|
||||
|
||||
export async function createLocalEmbeddingProvider(
|
||||
options: EmbeddingProviderOptions,
|
||||
): Promise<EmbeddingProvider> {
|
||||
const modelPath = normalizeOptionalString(options.local?.modelPath) || DEFAULT_LOCAL_MODEL;
|
||||
const modelCacheDir = normalizeOptionalString(options.local?.modelCacheDir);
|
||||
const contextSize: number | "auto" = options.local?.contextSize ?? 4096;
|
||||
|
||||
// Lazy-load node-llama-cpp to keep startup light unless local is enabled.
|
||||
const { getLlama, resolveModelFile, LlamaLogLevel } = await importNodeLlamaCpp();
|
||||
|
||||
let llama: Llama | null = null;
|
||||
let embeddingModel: LlamaModel | null = null;
|
||||
let embeddingContext: LlamaEmbeddingContext | null = null;
|
||||
let initPromise: Promise<LlamaEmbeddingContext> | null = null;
|
||||
|
||||
const ensureContext = async (): Promise<LlamaEmbeddingContext> => {
|
||||
if (embeddingContext) {
|
||||
return embeddingContext;
|
||||
}
|
||||
if (initPromise) {
|
||||
return initPromise;
|
||||
}
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
if (!llama) {
|
||||
llama = await getLlama({ logLevel: LlamaLogLevel.error });
|
||||
}
|
||||
if (!embeddingModel) {
|
||||
const resolved = await resolveModelFile(modelPath, modelCacheDir || undefined);
|
||||
embeddingModel = await llama.loadModel({ modelPath: resolved });
|
||||
}
|
||||
if (!embeddingContext) {
|
||||
embeddingContext = await embeddingModel.createEmbeddingContext({ contextSize });
|
||||
}
|
||||
return embeddingContext;
|
||||
} catch (err) {
|
||||
initPromise = null;
|
||||
throw err;
|
||||
}
|
||||
})();
|
||||
return initPromise;
|
||||
};
|
||||
|
||||
return {
|
||||
id: "local",
|
||||
model: modelPath,
|
||||
embedQuery: async (text) => {
|
||||
const ctx = await ensureContext();
|
||||
const embedding = await ctx.getEmbeddingFor(text);
|
||||
return sanitizeAndNormalizeEmbedding(Array.from(embedding.vector));
|
||||
},
|
||||
embedBatch: async (texts) => {
|
||||
const ctx = await ensureContext();
|
||||
const embeddings = await Promise.all(
|
||||
texts.map(async (text) => {
|
||||
const embedding = await ctx.getEmbeddingFor(text);
|
||||
return sanitizeAndNormalizeEmbedding(Array.from(embedding.vector));
|
||||
}),
|
||||
);
|
||||
return embeddings;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
56
packages/memory-host-sdk/src/host/embeddings.types.ts
Normal file
56
packages/memory-host-sdk/src/host/embeddings.types.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import type { OpenClawConfig, SecretInput } from "../engine-foundation.js";
|
||||
import type { EmbeddingInput } from "./embedding-inputs.js";
|
||||
|
||||
export type EmbeddingProvider = {
|
||||
id: string;
|
||||
model: string;
|
||||
maxInputTokens?: number;
|
||||
embedQuery: (text: string) => Promise<number[]>;
|
||||
embedBatch: (texts: string[]) => Promise<number[][]>;
|
||||
embedBatchInputs?: (inputs: EmbeddingInput[]) => Promise<number[][]>;
|
||||
};
|
||||
|
||||
export type EmbeddingProviderId = string;
|
||||
export type EmbeddingProviderRequest = string;
|
||||
export type EmbeddingProviderFallback = string;
|
||||
|
||||
export type GeminiTaskType =
|
||||
| "RETRIEVAL_QUERY"
|
||||
| "RETRIEVAL_DOCUMENT"
|
||||
| "SEMANTIC_SIMILARITY"
|
||||
| "CLASSIFICATION"
|
||||
| "CLUSTERING"
|
||||
| "QUESTION_ANSWERING"
|
||||
| "FACT_VERIFICATION";
|
||||
|
||||
export type EmbeddingProviderOptions = {
|
||||
config: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
provider?: EmbeddingProviderRequest;
|
||||
remote?: {
|
||||
baseUrl?: string;
|
||||
apiKey?: SecretInput;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
model: string;
|
||||
inputType?: string;
|
||||
queryInputType?: string;
|
||||
documentInputType?: string;
|
||||
fallback?: EmbeddingProviderFallback;
|
||||
local?: {
|
||||
modelPath?: string;
|
||||
modelCacheDir?: string;
|
||||
/**
|
||||
* Context size passed to node-llama-cpp `createEmbeddingContext`.
|
||||
* Default: 4096, chosen to cover typical memory-search chunks (128–512 tokens)
|
||||
* while keeping non-weight VRAM bounded.
|
||||
* Set `"auto"` to let node-llama-cpp use the model's trained maximum — not
|
||||
* recommended for 8B+ models (e.g. Qwen3-Embedding-8B: up to 40 960 tokens → ~32 GB VRAM).
|
||||
*/
|
||||
contextSize?: number | "auto";
|
||||
};
|
||||
/** Provider-specific output vector dimensions for supported embedding families. */
|
||||
outputDimensionality?: number;
|
||||
/** Gemini: override the default task type sent with embedding requests. */
|
||||
taskType?: GeminiTaskType;
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { normalizeLowercaseStringOrEmpty } from "../../../../src/shared/string-coerce.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string-utils.js";
|
||||
|
||||
const MEMORY_MULTIMODAL_SPECS = {
|
||||
image: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ export type LlamaEmbeddingContext = {
|
|||
};
|
||||
|
||||
export type LlamaModel = {
|
||||
createEmbeddingContext: () => Promise<LlamaEmbeddingContext>;
|
||||
createEmbeddingContext: (options?: {
|
||||
contextSize?: number | "auto";
|
||||
}) => Promise<LlamaEmbeddingContext>;
|
||||
};
|
||||
|
||||
export type Llama = {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export async function postJson<T>(params: {
|
|||
url: string;
|
||||
headers: Record<string, string>;
|
||||
ssrfPolicy?: SsrFPolicy;
|
||||
fetchImpl?: typeof fetch;
|
||||
body: unknown;
|
||||
errorPrefix: string;
|
||||
attachStatus?: boolean;
|
||||
|
|
@ -13,6 +14,7 @@ export async function postJson<T>(params: {
|
|||
return await withRemoteHttpResponse({
|
||||
url: params.url,
|
||||
ssrfPolicy: params.ssrfPolicy,
|
||||
fetchImpl: params.fetchImpl,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: params.headers,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
||||
import { createSubsystemLogger } from "../../../../src/logging/subsystem.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../../../src/shared/string-coerce.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string-utils.js";
|
||||
|
||||
const log = createSubsystemLogger("memory");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { parseAgentSessionKey } from "../../../../src/sessions/session-key-utils.js";
|
||||
import type { ResolvedQmdConfig } from "./backend-config.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "../../../../src/shared/string-coerce.js";
|
||||
import type { ResolvedQmdConfig } from "./backend-config.js";
|
||||
} from "./string-utils.js";
|
||||
|
||||
type ParsedQmdSessionScope = {
|
||||
channel?: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { normalizeLowercaseStringOrEmpty } from "../../../../src/shared/string-coerce.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string-utils.js";
|
||||
|
||||
/**
|
||||
* Query expansion for FTS-only search mode.
|
||||
|
|
|
|||
114
packages/memory-host-sdk/src/host/read-file-shared.ts
Normal file
114
packages/memory-host-sdk/src/host/read-file-shared.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import type { MemoryReadResult } from "./types.js";
|
||||
|
||||
export const DEFAULT_MEMORY_READ_LINES = 120;
|
||||
export const DEFAULT_MEMORY_READ_MAX_CHARS = 12_000;
|
||||
|
||||
export type { MemoryReadResult } from "./types.js";
|
||||
|
||||
function buildContinuationNotice(params: {
|
||||
nextFrom: number | undefined;
|
||||
suggestReadFallback?: boolean;
|
||||
}): string {
|
||||
const base =
|
||||
typeof params.nextFrom === "number"
|
||||
? `[More content available. Use from=${params.nextFrom} to continue.]`
|
||||
: "[More content available. Requested excerpt exceeded the default maxChars budget.]";
|
||||
const fallback = params.suggestReadFallback
|
||||
? " If you need the full raw line, use read on the source file."
|
||||
: "";
|
||||
return `\n\n${base.slice(0, -1)}${fallback}]`;
|
||||
}
|
||||
|
||||
function fitLinesToCharBudget(params: { lines: string[]; maxChars: number }): {
|
||||
text: string;
|
||||
includedLines: number;
|
||||
hardTruncatedSingleLine: boolean;
|
||||
} {
|
||||
const { lines, maxChars } = params;
|
||||
if (lines.length === 0) {
|
||||
return { text: "", includedLines: 0, hardTruncatedSingleLine: false };
|
||||
}
|
||||
|
||||
let includedLines = lines.length;
|
||||
let text = lines.join("\n");
|
||||
while (includedLines > 1 && text.length > maxChars) {
|
||||
includedLines -= 1;
|
||||
text = lines.slice(0, includedLines).join("\n");
|
||||
}
|
||||
|
||||
if (text.length <= maxChars) {
|
||||
return { text, includedLines, hardTruncatedSingleLine: false };
|
||||
}
|
||||
|
||||
return {
|
||||
text: text.slice(0, maxChars),
|
||||
includedLines: 1,
|
||||
hardTruncatedSingleLine: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMemoryReadResultFromSlice(params: {
|
||||
selectedLines: string[];
|
||||
relPath: string;
|
||||
startLine: number;
|
||||
moreSourceLinesRemain?: boolean;
|
||||
maxChars?: number;
|
||||
suggestReadFallback?: boolean;
|
||||
}): MemoryReadResult {
|
||||
const start = Math.max(1, params.startLine);
|
||||
const fitted = fitLinesToCharBudget({
|
||||
lines: params.selectedLines,
|
||||
maxChars: Math.max(1, params.maxChars ?? DEFAULT_MEMORY_READ_MAX_CHARS),
|
||||
});
|
||||
const moreSourceLinesRemain = params.moreSourceLinesRemain ?? false;
|
||||
const charCapTruncated =
|
||||
fitted.hardTruncatedSingleLine || fitted.includedLines < params.selectedLines.length;
|
||||
const nextFrom =
|
||||
!fitted.hardTruncatedSingleLine &&
|
||||
(moreSourceLinesRemain || fitted.includedLines < params.selectedLines.length)
|
||||
? start + fitted.includedLines
|
||||
: undefined;
|
||||
const truncated = charCapTruncated || moreSourceLinesRemain;
|
||||
const text =
|
||||
truncated && fitted.text
|
||||
? `${fitted.text}${buildContinuationNotice({
|
||||
nextFrom,
|
||||
suggestReadFallback: fitted.hardTruncatedSingleLine && params.suggestReadFallback,
|
||||
})}`
|
||||
: fitted.text;
|
||||
return {
|
||||
text,
|
||||
path: params.relPath,
|
||||
from: start,
|
||||
lines: fitted.includedLines,
|
||||
...(truncated ? { truncated: true } : {}),
|
||||
...(typeof nextFrom === "number" ? { nextFrom } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMemoryReadResult(params: {
|
||||
content: string;
|
||||
relPath: string;
|
||||
from?: number;
|
||||
lines?: number;
|
||||
defaultLines?: number;
|
||||
maxChars?: number;
|
||||
suggestReadFallback?: boolean;
|
||||
}): MemoryReadResult {
|
||||
const fileLines = params.content.split("\n");
|
||||
const start = Math.max(1, params.from ?? 1);
|
||||
const requestedCount = Math.max(
|
||||
1,
|
||||
params.lines ?? params.defaultLines ?? DEFAULT_MEMORY_READ_LINES,
|
||||
);
|
||||
const selectedLines = fileLines.slice(start - 1, start - 1 + requestedCount);
|
||||
const moreSourceLinesRemain = start - 1 + selectedLines.length < fileLines.length;
|
||||
return buildMemoryReadResultFromSlice({
|
||||
selectedLines,
|
||||
relPath: params.relPath,
|
||||
startLine: start,
|
||||
moreSourceLinesRemain,
|
||||
maxChars: params.maxChars,
|
||||
suggestReadFallback: params.suggestReadFallback,
|
||||
});
|
||||
}
|
||||
|
|
@ -6,13 +6,13 @@ import {
|
|||
} from "../../../../src/agents/agent-scope.js";
|
||||
import { resolveMemorySearchConfig } from "../../../../src/agents/memory-search.js";
|
||||
import type { OpenClawConfig } from "../../../../src/config/config.js";
|
||||
import { isFileMissingError, statRegularFile } from "./fs-utils.js";
|
||||
import { isMemoryPath, normalizeExtraMemoryPaths } from "./internal.js";
|
||||
import {
|
||||
buildMemoryReadResult,
|
||||
DEFAULT_MEMORY_READ_LINES,
|
||||
type MemoryReadResult,
|
||||
} from "../../../../src/memory-host-sdk/host/read-file-shared.js";
|
||||
import { isFileMissingError, statRegularFile } from "./fs-utils.js";
|
||||
import { isMemoryPath, normalizeExtraMemoryPaths } from "./internal.js";
|
||||
} from "./read-file-shared.js";
|
||||
|
||||
export async function readMemoryFile(params: {
|
||||
workspaceDir: string;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,17 @@
|
|||
import { fetchWithSsrFGuard, GUARDED_FETCH_MODE } from "../../../../src/infra/net/fetch-guard.js";
|
||||
import { shouldUseEnvHttpProxyForUrl } from "../../../../src/infra/net/proxy-env.js";
|
||||
import type { SsrFPolicy } from "../../../../src/infra/net/ssrf.js";
|
||||
import {
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
type SsrFPolicy,
|
||||
} from "../../../../src/infra/net/ssrf.js";
|
||||
|
||||
export function buildRemoteBaseUrlPolicy(baseUrl: string): SsrFPolicy | undefined {
|
||||
const trimmed = baseUrl.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(trimmed);
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
return undefined;
|
||||
}
|
||||
// Keep policy tied to the configured host so private operator endpoints
|
||||
// continue to work, while cross-host redirects stay blocked.
|
||||
return { allowedHostnames: [parsed.hostname] };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
export const buildRemoteBaseUrlPolicy = ssrfPolicyFromHttpBaseUrlAllowedHostname;
|
||||
|
||||
export async function withRemoteHttpResponse<T>(params: {
|
||||
url: string;
|
||||
init?: RequestInit;
|
||||
ssrfPolicy?: SsrFPolicy;
|
||||
fetchImpl?: typeof fetch;
|
||||
fetchWithSsrFGuardImpl?: typeof fetchWithSsrFGuard;
|
||||
shouldUseEnvHttpProxyForUrlImpl?: typeof shouldUseEnvHttpProxyForUrl;
|
||||
auditContext?: string;
|
||||
|
|
@ -33,6 +21,7 @@ export async function withRemoteHttpResponse<T>(params: {
|
|||
const shouldUseEnvProxy = params.shouldUseEnvHttpProxyForUrlImpl ?? shouldUseEnvHttpProxyForUrl;
|
||||
const { response, release } = await guardedFetch({
|
||||
url: params.url,
|
||||
fetchImpl: params.fetchImpl,
|
||||
init: params.init,
|
||||
policy: params.ssrfPolicy,
|
||||
auditContext: params.auditContext ?? "memory-remote",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,31 @@
|
|||
import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { stripInternalRuntimeContext } from "../../../../src/agents/internal-runtime-context.js";
|
||||
import { isHeartbeatUserMessage } from "../../../../src/auto-reply/heartbeat-filter.js";
|
||||
import { HEARTBEAT_PROMPT } from "../../../../src/auto-reply/heartbeat.js";
|
||||
import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js";
|
||||
import { HEARTBEAT_TOKEN, isSilentReplyPayloadText } from "../../../../src/auto-reply/tokens.js";
|
||||
import {
|
||||
isCompactionCheckpointTranscriptFileName,
|
||||
isSessionArchiveArtifactName,
|
||||
isUsageCountedSessionTranscriptFileName,
|
||||
} from "../../../../src/config/sessions/artifacts.js";
|
||||
import { resolveSessionTranscriptsDirForAgent } from "../../../../src/config/sessions/paths.js";
|
||||
import { isExecCompletionEvent } from "../../../../src/infra/heartbeat-events-filter.js";
|
||||
import { redactSensitiveText } from "../../../../src/logging/redact.js";
|
||||
import { hasInterSessionUserProvenance } from "../../../../src/sessions/input-provenance.js";
|
||||
import { isCronRunSessionKey } from "../../../../src/sessions/session-key-utils.js";
|
||||
import { hashText } from "./hash.js";
|
||||
|
||||
const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-";
|
||||
// Keep the historical one-line-per-message export shape for normal turns, but
|
||||
// wrap pathological long messages so downstream indexers never ingest a single
|
||||
// toxic line. Wrapped continuation lines still map back to the same JSONL line.
|
||||
// This limit applies to content only; the role label adds up to 11 chars.
|
||||
const SESSION_EXPORT_CONTENT_WRAP_CHARS = 800;
|
||||
const DIRECT_CRON_PROMPT_RE = /^\[cron:[^\]]+\]\s*/;
|
||||
|
||||
export type SessionFileEntry = {
|
||||
path: string;
|
||||
absPath: string;
|
||||
|
|
@ -20,10 +35,38 @@ export type SessionFileEntry = {
|
|||
content: string;
|
||||
/** Maps each content line (0-indexed) to its 1-indexed JSONL source line. */
|
||||
lineMap: number[];
|
||||
/** Maps each content line (0-indexed) to epoch ms; 0 means unknown timestamp. */
|
||||
messageTimestampsMs: number[];
|
||||
/** True when this transcript belongs to an internal dreaming narrative run. */
|
||||
generatedByDreamingNarrative?: boolean;
|
||||
/** True when this transcript belongs to an isolated cron run session. */
|
||||
generatedByCronRun?: boolean;
|
||||
};
|
||||
|
||||
export type BuildSessionEntryOptions = {
|
||||
/** Optional preclassification from a caller-managed dreaming transcript lookup. */
|
||||
generatedByDreamingNarrative?: boolean;
|
||||
/** Optional preclassification from a caller-managed cron transcript lookup. */
|
||||
generatedByCronRun?: boolean;
|
||||
};
|
||||
|
||||
export type SessionTranscriptClassification = {
|
||||
dreamingNarrativeTranscriptPaths: ReadonlySet<string>;
|
||||
cronRunTranscriptPaths: ReadonlySet<string>;
|
||||
};
|
||||
|
||||
type SessionTranscriptStoreEntry = {
|
||||
sessionFile?: unknown;
|
||||
sessionId?: unknown;
|
||||
};
|
||||
|
||||
function shouldSkipTranscriptFileForDreaming(absPath: string): boolean {
|
||||
const fileName = path.basename(absPath);
|
||||
return (
|
||||
isSessionArchiveArtifactName(fileName) || isCompactionCheckpointTranscriptFileName(fileName)
|
||||
);
|
||||
}
|
||||
|
||||
function isDreamingNarrativeBootstrapRecord(record: unknown): boolean {
|
||||
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
||||
return false;
|
||||
|
|
@ -43,16 +86,155 @@ function isDreamingNarrativeBootstrapRecord(record: unknown): boolean {
|
|||
return false;
|
||||
}
|
||||
const runId = (candidate.data as { runId?: unknown }).runId;
|
||||
return typeof runId === "string" && runId.startsWith("dreaming-narrative-");
|
||||
return typeof runId === "string" && runId.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
|
||||
}
|
||||
|
||||
function shouldSkipTranscriptFileForDreaming(absPath: string): boolean {
|
||||
const fileName = path.basename(absPath);
|
||||
return (
|
||||
isSessionArchiveArtifactName(fileName) || isCompactionCheckpointTranscriptFileName(fileName)
|
||||
function hasDreamingNarrativeRunId(value: unknown): boolean {
|
||||
return typeof value === "string" && value.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
|
||||
}
|
||||
|
||||
function isDreamingNarrativeGeneratedRecord(record: unknown): boolean {
|
||||
if (isDreamingNarrativeBootstrapRecord(record)) {
|
||||
return true;
|
||||
}
|
||||
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
||||
return false;
|
||||
}
|
||||
const candidate = record as {
|
||||
runId?: unknown;
|
||||
sessionKey?: unknown;
|
||||
data?: unknown;
|
||||
};
|
||||
if (
|
||||
hasDreamingNarrativeRunId(candidate.runId) ||
|
||||
hasDreamingNarrativeRunId(candidate.sessionKey)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (!candidate.data || typeof candidate.data !== "object" || Array.isArray(candidate.data)) {
|
||||
return false;
|
||||
}
|
||||
const nested = candidate.data as {
|
||||
runId?: unknown;
|
||||
sessionKey?: unknown;
|
||||
};
|
||||
return hasDreamingNarrativeRunId(nested.runId) || hasDreamingNarrativeRunId(nested.sessionKey);
|
||||
}
|
||||
|
||||
function isDreamingNarrativeSessionStoreKey(sessionKey: string): boolean {
|
||||
const trimmed = sessionKey.trim();
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
const firstSeparator = trimmed.indexOf(":");
|
||||
if (firstSeparator < 0) {
|
||||
return trimmed.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
|
||||
}
|
||||
const secondSeparator = trimmed.indexOf(":", firstSeparator + 1);
|
||||
const sessionSegment = secondSeparator < 0 ? trimmed : trimmed.slice(secondSeparator + 1);
|
||||
return sessionSegment.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
|
||||
}
|
||||
|
||||
function normalizeComparablePath(pathname: string): string {
|
||||
const resolved = path.resolve(pathname);
|
||||
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
||||
}
|
||||
|
||||
export function normalizeSessionTranscriptPathForComparison(pathname: string): string {
|
||||
return normalizeComparablePath(pathname);
|
||||
}
|
||||
|
||||
function resolveSessionStoreTranscriptPath(
|
||||
sessionsDir: string,
|
||||
entry: { sessionFile?: unknown; sessionId?: unknown } | undefined,
|
||||
): string | null {
|
||||
if (typeof entry?.sessionFile === "string" && entry.sessionFile.trim().length > 0) {
|
||||
const sessionFile = entry.sessionFile.trim();
|
||||
const resolved = path.isAbsolute(sessionFile)
|
||||
? sessionFile
|
||||
: path.resolve(sessionsDir, sessionFile);
|
||||
return normalizeComparablePath(resolved);
|
||||
}
|
||||
if (typeof entry?.sessionId === "string" && entry.sessionId.trim().length > 0) {
|
||||
return normalizeComparablePath(path.join(sessionsDir, `${entry.sessionId.trim()}.jsonl`));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadDreamingNarrativeTranscriptPathSetForSessionsDir(
|
||||
sessionsDir: string,
|
||||
): ReadonlySet<string> {
|
||||
return loadSessionTranscriptClassificationForSessionsDir(sessionsDir)
|
||||
.dreamingNarrativeTranscriptPaths;
|
||||
}
|
||||
|
||||
export function loadSessionTranscriptClassificationForSessionsDir(
|
||||
sessionsDir: string,
|
||||
): SessionTranscriptClassification {
|
||||
const storePath = path.join(sessionsDir, "sessions.json");
|
||||
const store = readSessionTranscriptClassificationStore(storePath);
|
||||
const dreamingTranscriptPaths = new Set<string>();
|
||||
const cronRunTranscriptPaths = new Set<string>();
|
||||
for (const [sessionKey, entry] of Object.entries(store)) {
|
||||
const transcriptPath = resolveSessionStoreTranscriptPath(sessionsDir, entry);
|
||||
if (!transcriptPath) {
|
||||
continue;
|
||||
}
|
||||
if (isDreamingNarrativeSessionStoreKey(sessionKey)) {
|
||||
dreamingTranscriptPaths.add(transcriptPath);
|
||||
}
|
||||
if (isCronRunSessionKey(sessionKey)) {
|
||||
cronRunTranscriptPaths.add(transcriptPath);
|
||||
}
|
||||
}
|
||||
return {
|
||||
dreamingNarrativeTranscriptPaths: dreamingTranscriptPaths,
|
||||
cronRunTranscriptPaths,
|
||||
};
|
||||
}
|
||||
|
||||
function readSessionTranscriptClassificationStore(
|
||||
storePath: string,
|
||||
): Record<string, SessionTranscriptStoreEntry> {
|
||||
try {
|
||||
const parsed = JSON.parse(fsSync.readFileSync(storePath, "utf-8")) as unknown;
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return {};
|
||||
}
|
||||
return parsed as Record<string, SessionTranscriptStoreEntry>;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function loadDreamingNarrativeTranscriptPathSetForAgent(
|
||||
agentId: string,
|
||||
): ReadonlySet<string> {
|
||||
return loadSessionTranscriptClassificationForAgent(agentId).dreamingNarrativeTranscriptPaths;
|
||||
}
|
||||
|
||||
export function loadSessionTranscriptClassificationForAgent(
|
||||
agentId: string,
|
||||
): SessionTranscriptClassification {
|
||||
return loadSessionTranscriptClassificationForSessionsDir(
|
||||
resolveSessionTranscriptsDirForAgent(agentId),
|
||||
);
|
||||
}
|
||||
|
||||
function classifySessionTranscriptFromSessionStore(absPath: string): {
|
||||
generatedByDreamingNarrative: boolean;
|
||||
generatedByCronRun: boolean;
|
||||
} {
|
||||
const sessionsDir = path.dirname(absPath);
|
||||
const normalizedAbsPath = normalizeComparablePath(absPath);
|
||||
const classification = loadSessionTranscriptClassificationForSessionsDir(sessionsDir);
|
||||
return {
|
||||
generatedByDreamingNarrative:
|
||||
classification.dreamingNarrativeTranscriptPaths.has(normalizedAbsPath),
|
||||
generatedByCronRun: classification.cronRunTranscriptPaths.has(normalizedAbsPath),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listSessionFilesForAgent(agentId: string): Promise<string[]> {
|
||||
const dir = resolveSessionTranscriptsDirForAgent(agentId);
|
||||
try {
|
||||
|
|
@ -103,11 +285,77 @@ function collectRawSessionText(content: unknown): string | null {
|
|||
return parts.length > 0 ? parts.join("\n") : null;
|
||||
}
|
||||
|
||||
function isHighSurrogate(code: number): boolean {
|
||||
return code >= 0xd800 && code <= 0xdbff;
|
||||
}
|
||||
|
||||
function isLowSurrogate(code: number): boolean {
|
||||
return code >= 0xdc00 && code <= 0xdfff;
|
||||
}
|
||||
|
||||
function splitLongSessionLine(
|
||||
text: string,
|
||||
maxChars: number = SESSION_EXPORT_CONTENT_WRAP_CHARS,
|
||||
): string[] {
|
||||
const normalized = text.trim();
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
if (normalized.length <= maxChars) {
|
||||
return [normalized];
|
||||
}
|
||||
|
||||
const segments: string[] = [];
|
||||
let cursor = 0;
|
||||
while (cursor < normalized.length) {
|
||||
const remaining = normalized.length - cursor;
|
||||
if (remaining <= maxChars) {
|
||||
segments.push(normalized.slice(cursor).trim());
|
||||
break;
|
||||
}
|
||||
|
||||
const limit = cursor + maxChars;
|
||||
let splitAt = limit;
|
||||
for (let index = limit; index > cursor; index -= 1) {
|
||||
if (normalized[index] === " ") {
|
||||
splitAt = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (
|
||||
splitAt < normalized.length &&
|
||||
splitAt > cursor &&
|
||||
isHighSurrogate(normalized.charCodeAt(splitAt - 1)) &&
|
||||
isLowSurrogate(normalized.charCodeAt(splitAt))
|
||||
) {
|
||||
splitAt -= 1;
|
||||
}
|
||||
segments.push(normalized.slice(cursor, splitAt).trim());
|
||||
cursor = splitAt;
|
||||
while (cursor < normalized.length && normalized[cursor] === " ") {
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return segments.filter(Boolean);
|
||||
}
|
||||
|
||||
function renderSessionExportLines(label: string, text: string): string[] {
|
||||
return splitLongSessionLine(text).map((segment) => `${label}: ${segment}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip OpenClaw-injected inbound metadata envelopes from a raw text block
|
||||
* on user-role messages before normalization. See the authoritative
|
||||
* implementation in `src/memory-host-sdk/host/session-files.ts` for the
|
||||
* full rationale; duplicated here to keep this parallel copy bug-free.
|
||||
* Strip OpenClaw-injected inbound metadata envelopes from a raw text block.
|
||||
*
|
||||
* User-role messages arriving from external channels (Telegram, Discord,
|
||||
* Slack, …) are stored with a multi-line prefix containing Conversation info,
|
||||
* Sender info, and other AI-facing metadata blocks. These envelopes must be
|
||||
* removed BEFORE normalization, because `stripInboundMetadata` relies on
|
||||
* newline structure and fenced `json` code fences to locate sentinels; once
|
||||
* `normalizeSessionText` collapses newlines into spaces, stripping is
|
||||
* impossible.
|
||||
*
|
||||
* See: https://github.com/openclaw/openclaw/issues/63921
|
||||
*/
|
||||
function stripInboundMetadataForUserRole(text: string, role: "user" | "assistant"): string {
|
||||
if (role !== "user") {
|
||||
|
|
@ -116,6 +364,59 @@ function stripInboundMetadataForUserRole(text: string, role: "user" | "assistant
|
|||
return stripInboundMetadata(text);
|
||||
}
|
||||
|
||||
const GENERATED_SYSTEM_MESSAGE_RE = /^System(?: \(untrusted\))?: \[[^\]]+\]\s*/;
|
||||
|
||||
function isGeneratedSystemWrapperMessage(text: string, role: "user" | "assistant"): boolean {
|
||||
if (role !== "user") {
|
||||
return false;
|
||||
}
|
||||
return GENERATED_SYSTEM_MESSAGE_RE.test(text);
|
||||
}
|
||||
|
||||
function isGeneratedCronPromptMessage(text: string, role: "user" | "assistant"): boolean {
|
||||
if (role !== "user") {
|
||||
return false;
|
||||
}
|
||||
return DIRECT_CRON_PROMPT_RE.test(text);
|
||||
}
|
||||
|
||||
function isGeneratedHeartbeatPromptMessage(text: string, role: "user" | "assistant"): boolean {
|
||||
return role === "user" && isHeartbeatUserMessage({ role, content: text }, HEARTBEAT_PROMPT);
|
||||
}
|
||||
|
||||
function sanitizeSessionText(text: string, role: "user" | "assistant"): string | null {
|
||||
const strippedInbound = stripInboundMetadataForUserRole(text, role);
|
||||
const strippedInternal = stripInternalRuntimeContext(strippedInbound);
|
||||
const normalized = normalizeSessionText(strippedInternal);
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (isGeneratedSystemWrapperMessage(normalized, role)) {
|
||||
return null;
|
||||
}
|
||||
if (isGeneratedCronPromptMessage(normalized, role)) {
|
||||
return null;
|
||||
}
|
||||
if (isGeneratedHeartbeatPromptMessage(normalized, role)) {
|
||||
return null;
|
||||
}
|
||||
if (isSilentReplyPayloadText(normalized)) {
|
||||
return null;
|
||||
}
|
||||
// Assistant-side machinery acks: HEARTBEAT_OK is the canonical "all clear,
|
||||
// nothing to do" reply to a heartbeat tick. Drop on the assistant side
|
||||
// directly so we do not have to rely on cross-message coupling with the
|
||||
// preceding user message (which a real user could spoof).
|
||||
if (role === "assistant" && normalized === HEARTBEAT_TOKEN) {
|
||||
return null;
|
||||
}
|
||||
const withoutSystemEnvelope = normalized.replace(GENERATED_SYSTEM_MESSAGE_RE, "").trim();
|
||||
if (isExecCompletionEvent(withoutSystemEnvelope)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function extractSessionText(
|
||||
content: unknown,
|
||||
role: "user" | "assistant" = "assistant",
|
||||
|
|
@ -124,12 +425,35 @@ export function extractSessionText(
|
|||
if (rawText === null) {
|
||||
return null;
|
||||
}
|
||||
const stripped = stripInboundMetadataForUserRole(rawText, role);
|
||||
const normalized = normalizeSessionText(stripped);
|
||||
return normalized ? normalized : null;
|
||||
return sanitizeSessionText(rawText, role);
|
||||
}
|
||||
|
||||
export async function buildSessionEntry(absPath: string): Promise<SessionFileEntry | null> {
|
||||
function parseSessionTimestampMs(
|
||||
record: { timestamp?: unknown },
|
||||
message: { timestamp?: unknown },
|
||||
): number {
|
||||
const candidates = [message.timestamp, record.timestamp];
|
||||
for (const value of candidates) {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
const ms = value > 0 && value < 1e11 ? value * 1000 : value;
|
||||
if (Number.isFinite(ms) && ms > 0) {
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const parsed = Date.parse(value);
|
||||
if (Number.isFinite(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export async function buildSessionEntry(
|
||||
absPath: string,
|
||||
opts: BuildSessionEntryOptions = {},
|
||||
): Promise<SessionFileEntry | null> {
|
||||
try {
|
||||
const stat = await fs.stat(absPath);
|
||||
if (shouldSkipTranscriptFileForDreaming(absPath)) {
|
||||
|
|
@ -141,14 +465,24 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
|
|||
hash: hashText("\n\n"),
|
||||
content: "",
|
||||
lineMap: [],
|
||||
generatedByDreamingNarrative: false,
|
||||
messageTimestampsMs: [],
|
||||
};
|
||||
}
|
||||
const raw = await fs.readFile(absPath, "utf-8");
|
||||
const lines = raw.split("\n");
|
||||
const collected: string[] = [];
|
||||
const lineMap: number[] = [];
|
||||
let generatedByDreamingNarrative = false;
|
||||
const messageTimestampsMs: number[] = [];
|
||||
const sessionStoreClassification =
|
||||
opts.generatedByDreamingNarrative === undefined || opts.generatedByCronRun === undefined
|
||||
? classifySessionTranscriptFromSessionStore(absPath)
|
||||
: null;
|
||||
let generatedByDreamingNarrative =
|
||||
opts.generatedByDreamingNarrative ??
|
||||
sessionStoreClassification?.generatedByDreamingNarrative ??
|
||||
false;
|
||||
const generatedByCronRun =
|
||||
opts.generatedByCronRun ?? sessionStoreClassification?.generatedByCronRun ?? false;
|
||||
for (let jsonlIdx = 0; jsonlIdx < lines.length; jsonlIdx++) {
|
||||
const line = lines[jsonlIdx];
|
||||
if (!line.trim()) {
|
||||
|
|
@ -160,7 +494,7 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
|
|||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (!generatedByDreamingNarrative && isDreamingNarrativeBootstrapRecord(record)) {
|
||||
if (!generatedByDreamingNarrative && isDreamingNarrativeGeneratedRecord(record)) {
|
||||
generatedByDreamingNarrative = true;
|
||||
}
|
||||
if (
|
||||
|
|
@ -182,14 +516,34 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
|
|||
if (message.role === "user" && hasInterSessionUserProvenance(message)) {
|
||||
continue;
|
||||
}
|
||||
const text = extractSessionText(message.content, message.role);
|
||||
const rawText = collectRawSessionText(message.content);
|
||||
if (rawText === null) {
|
||||
continue;
|
||||
}
|
||||
const text = sanitizeSessionText(rawText, message.role);
|
||||
if (!text) {
|
||||
// Assistant-side machinery (silent replies, system wrappers) is already
|
||||
// dropped by sanitizeSessionText. We deliberately do NOT use the prior
|
||||
// user message's pattern-match to drop the next assistant message:
|
||||
// user-typed text can match those same patterns (`[cron:...]`,
|
||||
// `System (untrusted): ...`) and a cross-message drop would let users
|
||||
// exfiltrate real assistant replies from the dreaming corpus by
|
||||
// prefixing their own prompt. See PR #70737 review (aisle-research-bot).
|
||||
continue;
|
||||
}
|
||||
if (generatedByDreamingNarrative || generatedByCronRun) {
|
||||
continue;
|
||||
}
|
||||
const safe = redactSensitiveText(text, { mode: "tools" });
|
||||
const label = message.role === "user" ? "User" : "Assistant";
|
||||
collected.push(`${label}: ${safe}`);
|
||||
lineMap.push(jsonlIdx + 1);
|
||||
const renderedLines = renderSessionExportLines(label, safe);
|
||||
const timestampMs = parseSessionTimestampMs(
|
||||
record as { timestamp?: unknown },
|
||||
message as { timestamp?: unknown },
|
||||
);
|
||||
collected.push(...renderedLines);
|
||||
lineMap.push(...renderedLines.map(() => jsonlIdx + 1));
|
||||
messageTimestampsMs.push(...renderedLines.map(() => timestampMs));
|
||||
}
|
||||
const content = collected.join("\n");
|
||||
return {
|
||||
|
|
@ -197,10 +551,12 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
|
|||
absPath,
|
||||
mtimeMs: stat.mtimeMs,
|
||||
size: stat.size,
|
||||
hash: hashText(content + "\n" + lineMap.join(",")),
|
||||
hash: hashText(content + "\n" + lineMap.join(",") + "\n" + messageTimestampsMs.join(",")),
|
||||
content,
|
||||
lineMap,
|
||||
messageTimestampsMs,
|
||||
...(generatedByDreamingNarrative ? { generatedByDreamingNarrative: true } : {}),
|
||||
...(generatedByCronRun ? { generatedByCronRun: true } : {}),
|
||||
};
|
||||
} catch (err) {
|
||||
void logSessionFileReadFailure(absPath, err);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
||||
import { normalizeOptionalString } from "../../../../src/shared/string-coerce.js";
|
||||
import { normalizeOptionalString } from "./string-utils.js";
|
||||
|
||||
type SqliteVecModule = {
|
||||
getLoadablePath: () => string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import { createRequire } from "node:module";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
||||
import {
|
||||
configureSqliteWalMaintenance,
|
||||
type SqliteWalMaintenance,
|
||||
type SqliteWalMaintenanceOptions,
|
||||
} from "../../../../src/infra/sqlite-wal.js";
|
||||
import { installProcessWarningFilter } from "../../../../src/infra/warning-filter.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const sqliteWalMaintenanceByDb = new WeakMap<DatabaseSync, SqliteWalMaintenance>();
|
||||
|
||||
export function requireNodeSqlite(): typeof import("node:sqlite") {
|
||||
installProcessWarningFilter();
|
||||
|
|
@ -18,3 +25,25 @@ export function requireNodeSqlite(): typeof import("node:sqlite") {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function configureMemorySqliteWalMaintenance(
|
||||
db: DatabaseSync,
|
||||
options?: SqliteWalMaintenanceOptions,
|
||||
): SqliteWalMaintenance {
|
||||
const existing = sqliteWalMaintenanceByDb.get(db);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const maintenance = configureSqliteWalMaintenance(db, options);
|
||||
sqliteWalMaintenanceByDb.set(db, maintenance);
|
||||
return maintenance;
|
||||
}
|
||||
|
||||
export function closeMemorySqliteWalMaintenance(db: DatabaseSync): boolean {
|
||||
const maintenance = sqliteWalMaintenanceByDb.get(db);
|
||||
if (!maintenance) {
|
||||
return true;
|
||||
}
|
||||
sqliteWalMaintenanceByDb.delete(db);
|
||||
return maintenance.close();
|
||||
}
|
||||
|
|
|
|||
19
packages/memory-host-sdk/src/host/string-utils.ts
Normal file
19
packages/memory-host-sdk/src/host/string-utils.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
export function normalizeNullableString(value: unknown): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : null;
|
||||
}
|
||||
|
||||
export function normalizeOptionalString(value: unknown): string | undefined {
|
||||
return normalizeNullableString(value) ?? undefined;
|
||||
}
|
||||
|
||||
export function normalizeOptionalLowercaseString(value: unknown): string | undefined {
|
||||
return normalizeOptionalString(value)?.toLowerCase();
|
||||
}
|
||||
|
||||
export function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return normalizeOptionalLowercaseString(value) ?? "";
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { vi } from "vitest";
|
||||
import * as ssrf from "../../../../../src/infra/net/ssrf.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../../../../src/shared/string-coerce.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../string-utils.js";
|
||||
|
||||
export function mockPublicPinnedHostname() {
|
||||
return vi.spyOn(ssrf, "resolvePinnedHostnameWithPolicy").mockImplementation(async (hostname) => {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,22 @@ export type MemorySyncProgressUpdate = {
|
|||
label?: string;
|
||||
};
|
||||
|
||||
export type MemorySearchRuntimeDebug = {
|
||||
backend: "builtin" | "qmd";
|
||||
configuredMode?: string;
|
||||
effectiveMode?: string;
|
||||
fallback?: string;
|
||||
};
|
||||
|
||||
export type MemoryReadResult = {
|
||||
text: string;
|
||||
path: string;
|
||||
truncated?: boolean;
|
||||
from?: number;
|
||||
lines?: number;
|
||||
nextFrom?: number;
|
||||
};
|
||||
|
||||
export type MemoryProviderStatus = {
|
||||
backend: "builtin" | "qmd";
|
||||
provider: string;
|
||||
|
|
@ -71,14 +87,12 @@ export interface MemorySearchManager {
|
|||
maxResults?: number;
|
||||
minScore?: number;
|
||||
sessionKey?: string;
|
||||
qmdSearchModeOverride?: "query" | "search" | "vsearch";
|
||||
onDebug?: (debug: MemorySearchRuntimeDebug) => void;
|
||||
sources?: MemorySource[];
|
||||
},
|
||||
): Promise<MemorySearchResult[]>;
|
||||
readFile(params: {
|
||||
relPath: string;
|
||||
from?: number;
|
||||
lines?: number;
|
||||
}): Promise<{ text: string; path: string }>;
|
||||
readFile(params: { relPath: string; from?: number; lines?: number }): Promise<MemoryReadResult>;
|
||||
status(): MemoryProviderStatus;
|
||||
sync?(params?: {
|
||||
reason?: string;
|
||||
|
|
|
|||
|
|
@ -1 +1,5 @@
|
|||
export * from "../../../src/memory-host-sdk/multimodal.js";
|
||||
export {
|
||||
isMemoryMultimodalEnabled,
|
||||
normalizeMemoryMultimodalSettings,
|
||||
type MemoryMultimodalSettings,
|
||||
} from "./host/multimodal.js";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "../../../src/memory-host-sdk/query.js";
|
||||
export { extractKeywords, isQueryStopWordToken } from "./host/query-expansion.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
export * from "../../../src/memory-host-sdk/runtime-cli.js";
|
||||
// Focused runtime contract for memory CLI/UI helpers.
|
||||
|
||||
export { formatErrorMessage, withManager } from "../../../src/cli/cli-utils.js";
|
||||
export { formatHelpExamples } from "../../../src/cli/help-format.js";
|
||||
export { resolveCommandSecretRefsViaGateway } from "../../../src/cli/command-secret-gateway.js";
|
||||
export { withProgress, withProgressTotals } from "../../../src/cli/progress.js";
|
||||
export { defaultRuntime } from "../../../src/runtime.js";
|
||||
export { formatDocsLink } from "../../../src/terminal/links.js";
|
||||
export { colorize, isRich, theme } from "../../../src/terminal/theme.js";
|
||||
export { isVerbose, setVerbose } from "../../../src/globals.js";
|
||||
export { shortenHomeInString, shortenHomePath } from "../../../src/utils.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,41 @@
|
|||
export * from "../../../src/memory-host-sdk/runtime-core.js";
|
||||
// Focused runtime contract for memory plugin config/state/helpers.
|
||||
|
||||
export type { AnyAgentTool } from "../../../src/agents/tools/common.js";
|
||||
export { resolveCronStyleNow } from "../../../src/agents/current-time.js";
|
||||
export { DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } from "../../../src/agents/pi-settings.js";
|
||||
export { resolveDefaultAgentId, resolveSessionAgentId } from "../../../src/agents/agent-scope.js";
|
||||
export { resolveMemorySearchConfig } from "../../../src/agents/memory-search.js";
|
||||
export {
|
||||
asToolParamsRecord,
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readStringParam,
|
||||
} from "../../../src/agents/tools/common.js";
|
||||
export { SILENT_REPLY_TOKEN } from "../../../src/auto-reply/tokens.js";
|
||||
export { parseNonNegativeByteSize } from "../../../src/config/byte-size.js";
|
||||
export {
|
||||
getRuntimeConfig,
|
||||
/** @deprecated Use getRuntimeConfig(), or pass the already loaded config through the call path. */
|
||||
loadConfig,
|
||||
} from "../../../src/config/config.js";
|
||||
export { resolveStateDir } from "../../../src/config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../../../src/config/sessions/paths.js";
|
||||
export { emptyPluginConfigSchema } from "../../../src/plugins/config-schema.js";
|
||||
export {
|
||||
buildMemoryPromptSection as buildActiveMemoryPromptSection,
|
||||
listActiveMemoryPublicArtifacts,
|
||||
getMemoryCapabilityRegistration,
|
||||
} from "../../../src/plugins/memory-state.js";
|
||||
export { parseAgentSessionKey } from "../../../src/routing/session-key.js";
|
||||
export type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
export type { MemoryCitationsMode } from "../../../src/config/types.memory.js";
|
||||
export type {
|
||||
MemoryFlushPlan,
|
||||
MemoryFlushPlanResolver,
|
||||
MemoryPluginCapability,
|
||||
MemoryPluginPublicArtifact,
|
||||
MemoryPluginPublicArtifactsProvider,
|
||||
MemoryPluginRuntime,
|
||||
MemoryPromptSectionBuilder,
|
||||
} from "../../../src/plugins/memory-state.js";
|
||||
export type { OpenClawPluginApi } from "../../../src/plugins/types.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,10 @@
|
|||
export * from "../../../src/memory-host-sdk/runtime-files.js";
|
||||
// Focused runtime contract for memory file/backend access.
|
||||
|
||||
export { listMemoryFiles, normalizeExtraMemoryPaths } from "./host/internal.js";
|
||||
export { readAgentMemoryFile } from "./host/read-file.js";
|
||||
export { resolveMemoryBackendConfig } from "./host/backend-config.js";
|
||||
export type {
|
||||
MemorySearchManager,
|
||||
MemorySearchRuntimeDebug,
|
||||
MemorySearchResult,
|
||||
} from "./host/types.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,6 @@
|
|||
export * from "../../../src/memory-host-sdk/runtime.js";
|
||||
// Aggregate workspace contract for memory runtime/helper seams.
|
||||
// Keep focused subpaths preferred for new code.
|
||||
|
||||
export * from "./runtime-core.js";
|
||||
export * from "./runtime-cli.js";
|
||||
export * from "./runtime-files.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
export * from "../../../src/memory-host-sdk/secret.js";
|
||||
export {
|
||||
hasConfiguredMemorySecretInput,
|
||||
resolveMemorySecretInputString,
|
||||
} from "./host/secret-input.js";
|
||||
|
|
|
|||
|
|
@ -1 +1,6 @@
|
|||
export * from "../../../src/memory-host-sdk/status.js";
|
||||
export {
|
||||
resolveMemoryCacheSummary,
|
||||
resolveMemoryFtsState,
|
||||
resolveMemoryVectorState,
|
||||
type Tone,
|
||||
} from "./host/status-format.js";
|
||||
|
|
|
|||
|
|
@ -106,7 +106,11 @@ type BoundaryReportSummary = {
|
|||
exportedSubpathCount: number;
|
||||
sourceBridgeFileCount: number;
|
||||
packageCoreReferenceFileCount: number;
|
||||
implementation: "private-core-bridge" | "package-owned" | "mixed";
|
||||
implementation:
|
||||
| "private-core-bridge"
|
||||
| "private-package-core-integrated"
|
||||
| "package-owned"
|
||||
| "mixed";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -365,10 +369,13 @@ function countByOwner(records: readonly CompatDebtRecord[]): Record<string, numb
|
|||
function resolveMemoryHostImplementation(
|
||||
memoryHostSdk: BoundaryReport["memoryHostSdk"],
|
||||
): BoundaryReportSummary["memoryHostSdk"]["implementation"] {
|
||||
if (memoryHostSdk.privatePackage && memoryHostSdk.packageCoreReferenceFiles.length > 0) {
|
||||
if (memoryHostSdk.privatePackage && memoryHostSdk.sourceBridgeFiles.length > 0) {
|
||||
return "private-core-bridge";
|
||||
}
|
||||
if (!memoryHostSdk.privatePackage && memoryHostSdk.packageCoreReferenceFiles.length === 0) {
|
||||
if (memoryHostSdk.privatePackage && memoryHostSdk.packageCoreReferenceFiles.length > 0) {
|
||||
return "private-package-core-integrated";
|
||||
}
|
||||
if (memoryHostSdk.packageCoreReferenceFiles.length === 0) {
|
||||
return "package-owned";
|
||||
}
|
||||
return "mixed";
|
||||
|
|
|
|||
|
|
@ -1,73 +1 @@
|
|||
// Real workspace contract for memory embedding providers and batch helpers.
|
||||
|
||||
export {
|
||||
getMemoryEmbeddingProvider,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
listMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviderAdapters,
|
||||
} from "../plugins/memory-embedding-provider-runtime.js";
|
||||
export type {
|
||||
MemoryEmbeddingBatchChunk,
|
||||
MemoryEmbeddingBatchOptions,
|
||||
MemoryEmbeddingProvider,
|
||||
MemoryEmbeddingProviderAdapter,
|
||||
MemoryEmbeddingProviderCreateOptions,
|
||||
MemoryEmbeddingProviderCreateResult,
|
||||
MemoryEmbeddingProviderRuntime,
|
||||
} from "../plugins/memory-embedding-providers.js";
|
||||
export { createLocalEmbeddingProvider, DEFAULT_LOCAL_MODEL } from "./host/embeddings.js";
|
||||
export { extractBatchErrorMessage, formatUnavailableBatchError } from "./host/batch-error-utils.js";
|
||||
export { postJsonWithRetry } from "./host/batch-http.js";
|
||||
export { applyEmbeddingBatchOutputLine } from "./host/batch-output.js";
|
||||
export {
|
||||
EMBEDDING_BATCH_ENDPOINT,
|
||||
type EmbeddingBatchStatus,
|
||||
type ProviderBatchOutputLine,
|
||||
} from "./host/batch-provider-common.js";
|
||||
export {
|
||||
buildEmbeddingBatchGroupOptions,
|
||||
runEmbeddingBatchGroups,
|
||||
type EmbeddingBatchExecutionParams,
|
||||
} from "./host/batch-runner.js";
|
||||
export {
|
||||
resolveBatchCompletionFromStatus,
|
||||
resolveCompletedBatchResult,
|
||||
throwIfBatchTerminalFailure,
|
||||
type BatchCompletionResult,
|
||||
} from "./host/batch-status.js";
|
||||
export { uploadBatchJsonlFile } from "./host/batch-upload.js";
|
||||
export {
|
||||
buildBatchHeaders,
|
||||
normalizeBatchBaseUrl,
|
||||
type BatchHttpClientConfig,
|
||||
} from "./host/batch-utils.js";
|
||||
export { enforceEmbeddingMaxInputTokens } from "./host/embedding-chunk-limits.js";
|
||||
export {
|
||||
isMissingEmbeddingApiKeyError,
|
||||
mapBatchEmbeddingsByIndex,
|
||||
sanitizeEmbeddingCacheHeaders,
|
||||
} from "./host/embedding-provider-adapter-utils.js";
|
||||
export { sanitizeAndNormalizeEmbedding } from "./host/embedding-vectors.js";
|
||||
export { debugEmbeddingsLog } from "./host/embeddings-debug.js";
|
||||
export { normalizeEmbeddingModelWithPrefixes } from "./host/embeddings-model-normalize.js";
|
||||
export {
|
||||
resolveRemoteEmbeddingBearerClient,
|
||||
type RemoteEmbeddingProviderId,
|
||||
} from "./host/embeddings-remote-client.js";
|
||||
export {
|
||||
createRemoteEmbeddingProvider,
|
||||
resolveRemoteEmbeddingClient,
|
||||
type RemoteEmbeddingClient,
|
||||
} from "./host/embeddings-remote-provider.js";
|
||||
export { fetchRemoteEmbeddingVectors } from "./host/embeddings-remote-fetch.js";
|
||||
export {
|
||||
estimateStructuredEmbeddingInputBytes,
|
||||
estimateUtf8Bytes,
|
||||
} from "./host/embedding-input-limits.js";
|
||||
export { hasNonTextEmbeddingParts, type EmbeddingInput } from "./host/embedding-inputs.js";
|
||||
export { buildRemoteBaseUrlPolicy, withRemoteHttpResponse } from "./host/remote-http.js";
|
||||
export {
|
||||
buildCaseInsensitiveExtensionGlob,
|
||||
classifyMemoryMultimodalPath,
|
||||
getMemoryMultimodalExtensions,
|
||||
} from "./host/multimodal.js";
|
||||
export * from "../../packages/memory-host-sdk/src/engine-embeddings.js";
|
||||
|
|
|
|||
|
|
@ -1,48 +1 @@
|
|||
// Real workspace contract for memory engine foundation concerns.
|
||||
|
||||
export {
|
||||
resolveAgentContextLimits,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
resolveSessionAgentId,
|
||||
} from "../agents/agent-scope.js";
|
||||
export {
|
||||
resolveMemorySearchConfig,
|
||||
resolveMemorySearchSyncConfig,
|
||||
type ResolvedMemorySearchConfig,
|
||||
type ResolvedMemorySearchSyncConfig,
|
||||
} from "../agents/memory-search.js";
|
||||
export { parseDurationMs } from "../cli/parse-duration.js";
|
||||
export { loadConfig } from "../config/config.js";
|
||||
export { resolveStateDir } from "../config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
|
||||
export {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
} from "../config/types.secrets.js";
|
||||
export { writeFileWithinRoot } from "../infra/fs-safe.js";
|
||||
export { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
export { detectMime } from "../media/mime.js";
|
||||
export { resolveGlobalSingleton } from "../shared/global-singleton.js";
|
||||
export { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
||||
export { splitShellArgs } from "../utils/shell-argv.js";
|
||||
export { runTasksWithConcurrency } from "../utils/run-with-concurrency.js";
|
||||
export {
|
||||
shortenHomeInString,
|
||||
shortenHomePath,
|
||||
resolveUserPath,
|
||||
truncateUtf16Safe,
|
||||
} from "../utils.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { SessionSendPolicyConfig } from "../config/types.base.js";
|
||||
export type { SecretInput } from "../config/types.secrets.js";
|
||||
export type {
|
||||
MemoryBackend,
|
||||
MemoryCitationsMode,
|
||||
MemoryQmdConfig,
|
||||
MemoryQmdIndexPath,
|
||||
MemoryQmdMcporterConfig,
|
||||
MemoryQmdSearchMode,
|
||||
} from "../config/types.memory.js";
|
||||
export type { MemorySearchConfig } from "../config/types.tools.js";
|
||||
export * from "../../packages/memory-host-sdk/src/engine-foundation.js";
|
||||
|
|
|
|||
|
|
@ -1,26 +1 @@
|
|||
// Real workspace contract for QMD/session/query helpers used by the memory engine.
|
||||
|
||||
export { extractKeywords, isQueryStopWordToken } from "./host/query-expansion.js";
|
||||
export {
|
||||
buildSessionEntry,
|
||||
listSessionFilesForAgent,
|
||||
loadDreamingNarrativeTranscriptPathSetForAgent,
|
||||
loadSessionTranscriptClassificationForAgent,
|
||||
normalizeSessionTranscriptPathForComparison,
|
||||
sessionPathForFile,
|
||||
type BuildSessionEntryOptions,
|
||||
type SessionFileEntry,
|
||||
type SessionTranscriptClassification,
|
||||
} from "./host/session-files.js";
|
||||
export { parseUsageCountedSessionIdFromFileName } from "../config/sessions/artifacts.js";
|
||||
export { parseQmdQueryJson, type QmdQueryResult } from "./host/qmd-query-parser.js";
|
||||
export {
|
||||
deriveQmdScopeChannel,
|
||||
deriveQmdScopeChatType,
|
||||
isQmdScopeAllowed,
|
||||
} from "./host/qmd-scope.js";
|
||||
export {
|
||||
checkQmdBinaryAvailability,
|
||||
resolveCliSpawnInvocation,
|
||||
runCliCommand,
|
||||
} from "./host/qmd-process.js";
|
||||
export * from "../../packages/memory-host-sdk/src/engine-qmd.js";
|
||||
|
|
|
|||
|
|
@ -1,48 +1 @@
|
|||
// Real workspace contract for memory engine storage/index helpers.
|
||||
|
||||
export {
|
||||
buildFileEntry,
|
||||
buildMultimodalChunkForIndexing,
|
||||
chunkMarkdown,
|
||||
cosineSimilarity,
|
||||
ensureDir,
|
||||
hashText,
|
||||
listMemoryFiles,
|
||||
normalizeExtraMemoryPaths,
|
||||
parseEmbedding,
|
||||
remapChunkLines,
|
||||
runWithConcurrency,
|
||||
type MemoryChunk,
|
||||
type MemoryFileEntry,
|
||||
} from "./host/internal.js";
|
||||
export { readMemoryFile } from "./host/read-file.js";
|
||||
export {
|
||||
buildMemoryReadResult,
|
||||
buildMemoryReadResultFromSlice,
|
||||
DEFAULT_MEMORY_READ_LINES,
|
||||
DEFAULT_MEMORY_READ_MAX_CHARS,
|
||||
type MemoryReadResult,
|
||||
} from "./host/read-file-shared.js";
|
||||
export { resolveMemoryBackendConfig } from "./host/backend-config.js";
|
||||
export type {
|
||||
ResolvedMemoryBackendConfig,
|
||||
ResolvedQmdConfig,
|
||||
ResolvedQmdMcporterConfig,
|
||||
} from "./host/backend-config.js";
|
||||
export type {
|
||||
MemoryEmbeddingProbeResult,
|
||||
MemoryProviderStatus,
|
||||
MemorySearchManager,
|
||||
MemorySearchRuntimeDebug,
|
||||
MemorySearchResult,
|
||||
MemorySource,
|
||||
MemorySyncProgressUpdate,
|
||||
} from "./host/types.js";
|
||||
export { ensureMemoryIndexSchema } from "./host/memory-schema.js";
|
||||
export { loadSqliteVecExtension } from "./host/sqlite-vec.js";
|
||||
export {
|
||||
closeMemorySqliteWalMaintenance,
|
||||
configureMemorySqliteWalMaintenance,
|
||||
requireNodeSqlite,
|
||||
} from "./host/sqlite.js";
|
||||
export { isFileMissingError, statRegularFile } from "./host/fs-utils.js";
|
||||
export * from "../../packages/memory-host-sdk/src/engine-storage.js";
|
||||
|
|
|
|||
|
|
@ -1,7 +1 @@
|
|||
// Aggregate workspace contract for the memory engine surface.
|
||||
// Keep focused subpaths preferred for new code.
|
||||
|
||||
export * from "./engine-foundation.js";
|
||||
export * from "./engine-storage.js";
|
||||
export * from "./engine-embeddings.js";
|
||||
export * from "./engine-qmd.js";
|
||||
export * from "../../packages/memory-host-sdk/src/engine.js";
|
||||
|
|
|
|||
|
|
@ -1,33 +1 @@
|
|||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
|
||||
type BatchOutputErrorLike = {
|
||||
error?: { message?: string };
|
||||
response?: {
|
||||
body?:
|
||||
| string
|
||||
| {
|
||||
error?: { message?: string };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function getResponseErrorMessage(line: BatchOutputErrorLike | undefined): string | undefined {
|
||||
const body = line?.response?.body;
|
||||
if (typeof body === "string") {
|
||||
return body || undefined;
|
||||
}
|
||||
if (!body || typeof body !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return typeof body.error?.message === "string" ? body.error.message : undefined;
|
||||
}
|
||||
|
||||
export function extractBatchErrorMessage(lines: BatchOutputErrorLike[]): string | undefined {
|
||||
const first = lines.find((line) => line.error?.message || getResponseErrorMessage(line));
|
||||
return first?.error?.message ?? getResponseErrorMessage(first);
|
||||
}
|
||||
|
||||
export function formatUnavailableBatchError(err: unknown): string | undefined {
|
||||
const message = formatErrorMessage(err);
|
||||
return message ? `error file unavailable: ${message}` : undefined;
|
||||
}
|
||||
export * from "../../../packages/memory-host-sdk/src/host/batch-error-utils.js";
|
||||
|
|
|
|||
|
|
@ -1,85 +1 @@
|
|||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { DEFAULT_LOCAL_MODEL } from "./embedding-defaults.js";
|
||||
import { sanitizeAndNormalizeEmbedding } from "./embedding-vectors.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.types.js";
|
||||
import {
|
||||
importNodeLlamaCpp,
|
||||
type Llama,
|
||||
type LlamaEmbeddingContext,
|
||||
type LlamaModel,
|
||||
} from "./node-llama.js";
|
||||
|
||||
export type {
|
||||
EmbeddingProvider,
|
||||
EmbeddingProviderFallback,
|
||||
EmbeddingProviderId,
|
||||
EmbeddingProviderOptions,
|
||||
EmbeddingProviderRequest,
|
||||
GeminiTaskType,
|
||||
} from "./embeddings.types.js";
|
||||
|
||||
export { DEFAULT_LOCAL_MODEL } from "./embedding-defaults.js";
|
||||
|
||||
export async function createLocalEmbeddingProvider(
|
||||
options: EmbeddingProviderOptions,
|
||||
): Promise<EmbeddingProvider> {
|
||||
const modelPath = normalizeOptionalString(options.local?.modelPath) || DEFAULT_LOCAL_MODEL;
|
||||
const modelCacheDir = normalizeOptionalString(options.local?.modelCacheDir);
|
||||
const contextSize: number | "auto" = options.local?.contextSize ?? 4096;
|
||||
|
||||
// Lazy-load node-llama-cpp to keep startup light unless local is enabled.
|
||||
const { getLlama, resolveModelFile, LlamaLogLevel } = await importNodeLlamaCpp();
|
||||
|
||||
let llama: Llama | null = null;
|
||||
let embeddingModel: LlamaModel | null = null;
|
||||
let embeddingContext: LlamaEmbeddingContext | null = null;
|
||||
let initPromise: Promise<LlamaEmbeddingContext> | null = null;
|
||||
|
||||
const ensureContext = async (): Promise<LlamaEmbeddingContext> => {
|
||||
if (embeddingContext) {
|
||||
return embeddingContext;
|
||||
}
|
||||
if (initPromise) {
|
||||
return initPromise;
|
||||
}
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
if (!llama) {
|
||||
llama = await getLlama({ logLevel: LlamaLogLevel.error });
|
||||
}
|
||||
if (!embeddingModel) {
|
||||
const resolved = await resolveModelFile(modelPath, modelCacheDir || undefined);
|
||||
embeddingModel = await llama.loadModel({ modelPath: resolved });
|
||||
}
|
||||
if (!embeddingContext) {
|
||||
embeddingContext = await embeddingModel.createEmbeddingContext({ contextSize });
|
||||
}
|
||||
return embeddingContext;
|
||||
} catch (err) {
|
||||
initPromise = null;
|
||||
throw err;
|
||||
}
|
||||
})();
|
||||
return initPromise;
|
||||
};
|
||||
|
||||
return {
|
||||
id: "local",
|
||||
model: modelPath,
|
||||
embedQuery: async (text) => {
|
||||
const ctx = await ensureContext();
|
||||
const embedding = await ctx.getEmbeddingFor(text);
|
||||
return sanitizeAndNormalizeEmbedding(Array.from(embedding.vector));
|
||||
},
|
||||
embedBatch: async (texts) => {
|
||||
const ctx = await ensureContext();
|
||||
const embeddings = await Promise.all(
|
||||
texts.map(async (text) => {
|
||||
const embedding = await ctx.getEmbeddingFor(text);
|
||||
return sanitizeAndNormalizeEmbedding(Array.from(embedding.vector));
|
||||
}),
|
||||
);
|
||||
return embeddings;
|
||||
},
|
||||
};
|
||||
}
|
||||
export * from "../../../packages/memory-host-sdk/src/host/embeddings.js";
|
||||
|
|
|
|||
|
|
@ -1,114 +1 @@
|
|||
import type { MemoryReadResult } from "./types.js";
|
||||
|
||||
export const DEFAULT_MEMORY_READ_LINES = 120;
|
||||
export const DEFAULT_MEMORY_READ_MAX_CHARS = 12_000;
|
||||
|
||||
export type { MemoryReadResult } from "./types.js";
|
||||
|
||||
function buildContinuationNotice(params: {
|
||||
nextFrom: number | undefined;
|
||||
suggestReadFallback?: boolean;
|
||||
}): string {
|
||||
const base =
|
||||
typeof params.nextFrom === "number"
|
||||
? `[More content available. Use from=${params.nextFrom} to continue.]`
|
||||
: "[More content available. Requested excerpt exceeded the default maxChars budget.]";
|
||||
const fallback = params.suggestReadFallback
|
||||
? " If you need the full raw line, use read on the source file."
|
||||
: "";
|
||||
return `\n\n${base.slice(0, -1)}${fallback}]`;
|
||||
}
|
||||
|
||||
function fitLinesToCharBudget(params: { lines: string[]; maxChars: number }): {
|
||||
text: string;
|
||||
includedLines: number;
|
||||
hardTruncatedSingleLine: boolean;
|
||||
} {
|
||||
const { lines, maxChars } = params;
|
||||
if (lines.length === 0) {
|
||||
return { text: "", includedLines: 0, hardTruncatedSingleLine: false };
|
||||
}
|
||||
|
||||
let includedLines = lines.length;
|
||||
let text = lines.join("\n");
|
||||
while (includedLines > 1 && text.length > maxChars) {
|
||||
includedLines -= 1;
|
||||
text = lines.slice(0, includedLines).join("\n");
|
||||
}
|
||||
|
||||
if (text.length <= maxChars) {
|
||||
return { text, includedLines, hardTruncatedSingleLine: false };
|
||||
}
|
||||
|
||||
return {
|
||||
text: text.slice(0, maxChars),
|
||||
includedLines: 1,
|
||||
hardTruncatedSingleLine: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMemoryReadResultFromSlice(params: {
|
||||
selectedLines: string[];
|
||||
relPath: string;
|
||||
startLine: number;
|
||||
moreSourceLinesRemain?: boolean;
|
||||
maxChars?: number;
|
||||
suggestReadFallback?: boolean;
|
||||
}): MemoryReadResult {
|
||||
const start = Math.max(1, params.startLine);
|
||||
const fitted = fitLinesToCharBudget({
|
||||
lines: params.selectedLines,
|
||||
maxChars: Math.max(1, params.maxChars ?? DEFAULT_MEMORY_READ_MAX_CHARS),
|
||||
});
|
||||
const moreSourceLinesRemain = params.moreSourceLinesRemain ?? false;
|
||||
const charCapTruncated =
|
||||
fitted.hardTruncatedSingleLine || fitted.includedLines < params.selectedLines.length;
|
||||
const nextFrom =
|
||||
!fitted.hardTruncatedSingleLine &&
|
||||
(moreSourceLinesRemain || fitted.includedLines < params.selectedLines.length)
|
||||
? start + fitted.includedLines
|
||||
: undefined;
|
||||
const truncated = charCapTruncated || moreSourceLinesRemain;
|
||||
const text =
|
||||
truncated && fitted.text
|
||||
? `${fitted.text}${buildContinuationNotice({
|
||||
nextFrom,
|
||||
suggestReadFallback: fitted.hardTruncatedSingleLine && params.suggestReadFallback,
|
||||
})}`
|
||||
: fitted.text;
|
||||
return {
|
||||
text,
|
||||
path: params.relPath,
|
||||
from: start,
|
||||
lines: fitted.includedLines,
|
||||
...(truncated ? { truncated: true } : {}),
|
||||
...(typeof nextFrom === "number" ? { nextFrom } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMemoryReadResult(params: {
|
||||
content: string;
|
||||
relPath: string;
|
||||
from?: number;
|
||||
lines?: number;
|
||||
defaultLines?: number;
|
||||
maxChars?: number;
|
||||
suggestReadFallback?: boolean;
|
||||
}): MemoryReadResult {
|
||||
const fileLines = params.content.split("\n");
|
||||
const start = Math.max(1, params.from ?? 1);
|
||||
const requestedCount = Math.max(
|
||||
1,
|
||||
params.lines ?? params.defaultLines ?? DEFAULT_MEMORY_READ_LINES,
|
||||
);
|
||||
const selectedLines = fileLines.slice(start - 1, start - 1 + requestedCount);
|
||||
const moreSourceLinesRemain = start - 1 + selectedLines.length < fileLines.length;
|
||||
return buildMemoryReadResultFromSlice({
|
||||
selectedLines,
|
||||
relPath: params.relPath,
|
||||
startLine: start,
|
||||
moreSourceLinesRemain,
|
||||
maxChars: params.maxChars,
|
||||
suggestReadFallback: params.suggestReadFallback,
|
||||
});
|
||||
}
|
||||
export * from "../../../packages/memory-host-sdk/src/host/read-file-shared.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1 @@
|
|||
export {
|
||||
isMemoryMultimodalEnabled,
|
||||
normalizeMemoryMultimodalSettings,
|
||||
type MemoryMultimodalSettings,
|
||||
} from "./host/multimodal.js";
|
||||
export * from "../../packages/memory-host-sdk/src/multimodal.js";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { extractKeywords, isQueryStopWordToken } from "./host/query-expansion.js";
|
||||
export * from "../../packages/memory-host-sdk/src/query.js";
|
||||
|
|
|
|||
|
|
@ -1,11 +1 @@
|
|||
// Focused runtime contract for memory CLI/UI helpers.
|
||||
|
||||
export { formatErrorMessage, withManager } from "../cli/cli-utils.js";
|
||||
export { formatHelpExamples } from "../cli/help-format.js";
|
||||
export { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
|
||||
export { withProgress, withProgressTotals } from "../cli/progress.js";
|
||||
export { defaultRuntime } from "../runtime.js";
|
||||
export { formatDocsLink } from "../terminal/links.js";
|
||||
export { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
export { isVerbose, setVerbose } from "../globals.js";
|
||||
export { shortenHomeInString, shortenHomePath } from "../utils.js";
|
||||
export * from "../../packages/memory-host-sdk/src/runtime-cli.js";
|
||||
|
|
|
|||
|
|
@ -1,41 +1 @@
|
|||
// Focused runtime contract for memory plugin config/state/helpers.
|
||||
|
||||
export type { AnyAgentTool } from "../agents/tools/common.js";
|
||||
export { resolveCronStyleNow } from "../agents/current-time.js";
|
||||
export { DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } from "../agents/pi-settings.js";
|
||||
export { resolveDefaultAgentId, resolveSessionAgentId } from "../agents/agent-scope.js";
|
||||
export { resolveMemorySearchConfig } from "../agents/memory-search.js";
|
||||
export {
|
||||
asToolParamsRecord,
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readStringParam,
|
||||
} from "../agents/tools/common.js";
|
||||
export { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
export { parseNonNegativeByteSize } from "../config/byte-size.js";
|
||||
export {
|
||||
getRuntimeConfig,
|
||||
/** @deprecated Use getRuntimeConfig(), or pass the already loaded config through the call path. */
|
||||
loadConfig,
|
||||
} from "../config/config.js";
|
||||
export { resolveStateDir } from "../config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export {
|
||||
buildMemoryPromptSection as buildActiveMemoryPromptSection,
|
||||
listActiveMemoryPublicArtifacts,
|
||||
getMemoryCapabilityRegistration,
|
||||
} from "../plugins/memory-state.js";
|
||||
export { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { MemoryCitationsMode } from "../config/types.memory.js";
|
||||
export type {
|
||||
MemoryFlushPlan,
|
||||
MemoryFlushPlanResolver,
|
||||
MemoryPluginCapability,
|
||||
MemoryPluginPublicArtifact,
|
||||
MemoryPluginPublicArtifactsProvider,
|
||||
MemoryPluginRuntime,
|
||||
MemoryPromptSectionBuilder,
|
||||
} from "../plugins/memory-state.js";
|
||||
export type { OpenClawPluginApi } from "../plugins/types.js";
|
||||
export * from "../../packages/memory-host-sdk/src/runtime-core.js";
|
||||
|
|
|
|||
|
|
@ -1,10 +1 @@
|
|||
// Focused runtime contract for memory file/backend access.
|
||||
|
||||
export { listMemoryFiles, normalizeExtraMemoryPaths } from "./host/internal.js";
|
||||
export { readAgentMemoryFile } from "./host/read-file.js";
|
||||
export { resolveMemoryBackendConfig } from "./host/backend-config.js";
|
||||
export type {
|
||||
MemorySearchManager,
|
||||
MemorySearchRuntimeDebug,
|
||||
MemorySearchResult,
|
||||
} from "./host/types.js";
|
||||
export * from "../../packages/memory-host-sdk/src/runtime-files.js";
|
||||
|
|
|
|||
|
|
@ -1,6 +1 @@
|
|||
// Aggregate workspace contract for memory runtime/helper seams.
|
||||
// Keep focused subpaths preferred for new code.
|
||||
|
||||
export * from "./runtime-core.js";
|
||||
export * from "./runtime-cli.js";
|
||||
export * from "./runtime-files.js";
|
||||
export * from "../../packages/memory-host-sdk/src/runtime.js";
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
export {
|
||||
hasConfiguredMemorySecretInput,
|
||||
resolveMemorySecretInputString,
|
||||
} from "./host/secret-input.js";
|
||||
export * from "../../packages/memory-host-sdk/src/secret.js";
|
||||
|
|
|
|||
|
|
@ -1,6 +1 @@
|
|||
export {
|
||||
resolveMemoryCacheSummary,
|
||||
resolveMemoryFtsState,
|
||||
resolveMemoryVectorState,
|
||||
type Tone,
|
||||
} from "./host/status-format.js";
|
||||
export * from "../../packages/memory-host-sdk/src/status.js";
|
||||
|
|
|
|||
|
|
@ -129,9 +129,6 @@ describe("opt-in extension package boundaries", () => {
|
|||
expect(packageJson.exports?.["./acp-runtime"]?.types).toBe(
|
||||
"./dist/src/plugin-sdk/acp-runtime.d.ts",
|
||||
);
|
||||
expect(packageJson.exports?.["./browser-config"]?.types).toBe(
|
||||
"./dist/src/plugin-sdk/browser-config.d.ts",
|
||||
);
|
||||
expect(packageJson.exports?.["./channel-secret-runtime"]?.types).toBe(
|
||||
"./dist/src/plugin-sdk/channel-secret-runtime.d.ts",
|
||||
);
|
||||
|
|
@ -193,7 +190,7 @@ describe("opt-in extension package boundaries", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("keeps memory-host-sdk as a private package bridge over the core-owned implementation", () => {
|
||||
it("keeps memory-host-sdk as a private package-owned contract surface", () => {
|
||||
const packageJson = readJsonFile<PackageJson>("packages/memory-host-sdk/package.json");
|
||||
const packageExports = packageJson.exports as unknown as Record<string, string>;
|
||||
|
||||
|
|
@ -210,9 +207,7 @@ describe("opt-in extension package boundaries", () => {
|
|||
throw new Error(`Missing memory-host-sdk export target for ${exportPath}`);
|
||||
}
|
||||
const source = readFileSync(resolve(REPO_ROOT, "packages/memory-host-sdk", target), "utf8");
|
||||
expect(source.trim(), target).toBe(
|
||||
`export * from "../../../src/memory-host-sdk/${exportPath.slice(2)}.js";`,
|
||||
);
|
||||
expect(source, target).not.toContain("src/memory-host-sdk/");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue