Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

438 lines
12 KiB
TypeScript

/** Maps a stored document `Memory` into the display-card shape the documents view renders — provenance kind, title, and derived metadata. */
import type { Memory } from "@elizaos/core";
import type { DocumentVisibilityScope } from "./service-loader.js";
export type DocumentProvenanceKind =
| "import"
| "upload"
| "learned"
| "character"
| "url"
| "youtube"
| "bundled"
| "unknown";
export interface DocumentProvenance {
kind: DocumentProvenanceKind;
label: string;
detail?: string;
}
export interface PresentedDocument {
id: string;
filename: string;
contentType: string;
fileSize: number;
createdAt: number;
fragmentCount: number;
source: DocumentProvenanceKind;
scope: DocumentVisibilityScope;
scopedToEntityId?: string;
addedBy?: string;
addedByRole?: string;
addedFrom?: string;
url?: string;
/** Served URL of the original-bytes file linked to this document, so the
* detail view can offer "download original" (PR6). */
mediaUrl?: string;
/** Stored filename of the linked original-bytes file (PR6). */
mediaFileName?: string;
provenance: DocumentProvenance;
canEditText: boolean;
editabilityReason?: string;
canDelete: boolean;
deleteabilityReason?: string;
content?: { text?: string };
/** When this document is the searchable mirror of a voice Transcript, the
* original transcript record id (so the Knowledge view can link back to it)
* and its audio URL. Populated from the mirror metadata (#8789). */
transcriptId?: string;
transcriptAudioUrl?: string;
}
const BINARY_CONTENT_TYPE_PREFIXES = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument",
"application/vnd.ms-excel",
"application/vnd.ms-powerpoint",
"application/octet-stream",
"image/",
"audio/",
"video/",
];
const BINARY_FILE_EXTENSIONS = new Set([
"pdf",
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"zip",
"rar",
"7z",
"jpg",
"jpeg",
"png",
"gif",
"bmp",
"svg",
"webp",
"mp3",
"wav",
"mp4",
"mov",
"avi",
]);
const DOCUMENT_SCOPE_VALUES = new Set<DocumentVisibilityScope>([
"global",
"owner-private",
"user-private",
"agent-private",
]);
function asRecord(value: unknown): Record<string, unknown> | undefined {
return value && typeof value === "object"
? (value as Record<string, unknown>)
: undefined;
}
function asString(value: unknown): string | undefined {
return typeof value === "string" && value.trim().length > 0
? value.trim()
: undefined;
}
function asNumber(value: unknown): number | undefined {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string") {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return undefined;
}
function truncateLabel(value: string, maxLength = 80): string {
if (value.length <= maxLength) return value;
// Back off one code unit when the cut would land between the halves of a
// surrogate pair — otherwise the label ends in a lone high surrogate that
// renders as U+FFFD in the documents view.
let end = maxLength - 1;
const lastKept = value.charCodeAt(end - 1);
if (lastKept >= 0xd800 && lastKept <= 0xdbff) end -= 1;
return `${value.slice(0, end).trimEnd()}...`;
}
function stripMarkdownPrefix(line: string): string {
return line
.replace(/^#+\s*/, "")
.replace(/^[-*]\s+/, "")
.replace(/^\d+[.)]\s+/, "")
.trim();
}
function deriveTitleFromText(
text: string | undefined,
fallback = "Untitled",
): string {
if (!text) return fallback;
const lines = text
.replace(/\r\n/g, "\n")
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0);
for (const line of lines) {
if (/^path:\s+/i.test(line)) continue;
const candidate = stripMarkdownPrefix(line);
if (candidate.length > 0) {
return truncateLabel(candidate);
}
}
return fallback;
}
function looksLikeBase64(text: string): boolean {
const clean = text.replace(/\s/g, "");
if (clean.length < 16 || clean.length % 4 !== 0) return false;
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(clean)) return false;
return /[a-z]/.test(clean) && (/[A-Z]/.test(clean) || /\d/.test(clean));
}
function getFilenameExtension(
filename: string | undefined,
): string | undefined {
if (!filename) return undefined;
const dotIndex = filename.lastIndexOf(".");
if (dotIndex <= 0 || dotIndex === filename.length - 1) return undefined;
return filename.slice(dotIndex + 1).toLowerCase();
}
function isBinaryLike(
contentType: string | undefined,
filename: string | undefined,
) {
const normalizedContentType = contentType?.toLowerCase();
if (
normalizedContentType &&
BINARY_CONTENT_TYPE_PREFIXES.some((prefix) =>
normalizedContentType.startsWith(prefix),
)
) {
return true;
}
const extension = getFilenameExtension(filename);
return extension ? BINARY_FILE_EXTENSIONS.has(extension) : false;
}
function isGenericFilename(
filename: string | undefined,
provenanceKind: DocumentProvenanceKind,
): boolean {
if (!filename) return true;
const normalized = filename.toLowerCase();
if (normalized === "document-note.txt") return true;
if (provenanceKind === "learned" && normalized === "user-document.txt") {
return true;
}
return false;
}
export function normalizeDocumentSource(
source: unknown,
): DocumentProvenanceKind {
switch (source) {
case "import":
return "import";
case "upload":
case "rag-service-main-upload":
return "upload";
case "learned":
return "learned";
case "character":
return "character";
case "url":
return "url";
case "youtube":
return "youtube";
case "eliza-default-documents":
return "bundled";
default:
return "unknown";
}
}
export function getDocumentProvenance(
metadata: Record<string, unknown> | undefined,
): DocumentProvenance {
const kind = normalizeDocumentSource(metadata?.source);
switch (kind) {
case "import":
return { kind, label: "Conversation import" };
case "upload":
return { kind, label: "Manual upload" };
case "learned":
return { kind, label: "Learned document" };
case "character":
return {
kind,
label: "Character document",
detail: asString(metadata?.path),
};
case "url":
return {
kind,
label: "Imported URL",
detail: asString(metadata?.url),
};
case "youtube":
return {
kind,
label: "YouTube transcript",
detail: asString(metadata?.url),
};
case "bundled":
return { kind, label: "Bundled document" };
default:
return { kind, label: "Document" };
}
}
export function getDocumentContentType(
metadata: Record<string, unknown> | undefined,
): string {
return (
asString(metadata?.fileType) ?? asString(metadata?.contentType) ?? "unknown"
);
}
export function getDocumentTitleFromMetadata(
metadata: Record<string, unknown> | undefined,
contentText?: string,
): string {
const provenance = getDocumentProvenance(metadata);
const filename = asString(metadata?.filename);
const title = asString(metadata?.title);
const originalFilename = asString(metadata?.originalFilename);
const url = asString(metadata?.url);
if (filename && !isGenericFilename(filename, provenance.kind)) {
return filename;
}
if (title) return title;
if (
originalFilename &&
!isGenericFilename(originalFilename, provenance.kind)
) {
return originalFilename;
}
if (url) return url;
return deriveTitleFromText(contentText, "Untitled");
}
export function getDocumentVisibilityScope(
metadata: Record<string, unknown> | undefined,
): DocumentVisibilityScope {
return DOCUMENT_SCOPE_VALUES.has(metadata?.scope as DocumentVisibilityScope)
? (metadata?.scope as DocumentVisibilityScope)
: "global";
}
export function isDocumentTextBacked(memory: Memory): boolean {
const metadata = asRecord(memory.metadata);
if (typeof metadata?.textBacked === "boolean") {
return metadata.textBacked;
}
const filename = getDocumentTitleFromMetadata(metadata, memory.content.text);
const contentType = getDocumentContentType(metadata);
if (isBinaryLike(contentType, filename)) {
return false;
}
const contentText = memory.content.text;
if (typeof contentText !== "string" || contentText.trim().length === 0) {
return false;
}
return !looksLikeBase64(contentText);
}
export function getDocumentPreviewText(memory: Memory): string | undefined {
const contentText = memory.content.text;
if (typeof contentText !== "string") return undefined;
const trimmed = contentText.trim();
if (trimmed.length === 0 || looksLikeBase64(trimmed)) {
return undefined;
}
return trimmed;
}
export function getDocumentEditability(memory: Memory): {
canEditText: boolean;
reason?: string;
} {
const metadata = asRecord(memory.metadata);
const provenance = getDocumentProvenance(metadata);
if (provenance.kind === "bundled") {
return {
canEditText: false,
reason: "Bundled documents are seeded by the runtime.",
};
}
if (provenance.kind === "character") {
return {
canEditText: false,
reason: "Character documents come from source files or character config.",
};
}
if (!isDocumentTextBacked(memory)) {
return {
canEditText: false,
reason: "Only text-backed documents can be edited here.",
};
}
return { canEditText: true };
}
export function getDocumentDeleteability(memory: Memory): {
canDelete: boolean;
reason?: string;
} {
const provenance = getDocumentProvenance(asRecord(memory.metadata));
if (provenance.kind === "bundled") {
return {
canDelete: false,
reason: "Bundled documents are recreated by the runtime.",
};
}
if (provenance.kind === "character") {
return {
canDelete: false,
reason: "Character documents are backed by the character source.",
};
}
return { canDelete: true };
}
export function presentDocument(
memory: Memory,
fragmentCount: number,
options?: { includeContent?: boolean },
): PresentedDocument {
const metadata = asRecord(memory.metadata);
const provenance = getDocumentProvenance(metadata);
const contentType = getDocumentContentType(metadata);
const previewText = options?.includeContent
? getDocumentPreviewText(memory)
: undefined;
const editability = getDocumentEditability(memory);
const deleteability = getDocumentDeleteability(memory);
return {
id: String(memory.id ?? ""),
filename: getDocumentTitleFromMetadata(metadata, memory.content.text),
contentType,
fileSize: asNumber(metadata?.fileSize) ?? 0,
createdAt: asNumber(memory.createdAt) ?? 0,
fragmentCount,
source: provenance.kind,
scope: getDocumentVisibilityScope(metadata),
scopedToEntityId: asString(metadata?.scopedToEntityId),
addedBy: asString(metadata?.addedBy),
addedByRole: asString(metadata?.addedByRole),
addedFrom: asString(metadata?.addedFrom),
url: asString(metadata?.url),
...(asString(metadata?.mediaUrl)
? { mediaUrl: asString(metadata?.mediaUrl) }
: {}),
...(asString(metadata?.mediaFileName)
? { mediaFileName: asString(metadata?.mediaFileName) }
: {}),
provenance,
canEditText: editability.canEditText,
editabilityReason: editability.reason,
canDelete: deleteability.canDelete,
deleteabilityReason: deleteability.reason,
...(previewText ? { content: { text: previewText } } : {}),
...(asString(metadata?.transcriptId)
? { transcriptId: asString(metadata?.transcriptId) }
: {}),
...(asString(metadata?.audioUrl)
? { transcriptAudioUrl: asString(metadata?.audioUrl) }
: {}),
};
}