#!/usr/bin/env node import crypto from "node:crypto"; import { spawn } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { route as routeTree, verifyTree } from "../codex-hldp-recursive-memory/hldp-memory.mjs"; export const MAX_CHILDREN = 10; export const PAGE_LIMIT_BYTES = 16384; export const EVENT_SCHEMA = "guanghu.persona-daily-fractal-memory-event/v1"; export const STORE_SCHEMA = "guanghu.persona-daily-fractal-memory-store/v1"; const TIME_ZONE = "Asia/Shanghai"; function fail(code, details = {}) { const error = new Error(code); error.code = code; error.details = details; throw error; } function stable(value) { if (Array.isArray(value)) return value.map(stable); if (value && typeof value === "object") { return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])])); } return value; } export function stableJson(value) { return `${JSON.stringify(stable(value), null, 2)}\n`; } export function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); } function readJson(file) { return JSON.parse(fs.readFileSync(file, "utf8")); } function atomicWrite(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); const body = typeof value === "string" ? value : stableJson(value); const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`; fs.writeFileSync(temporary, body, { encoding: "utf8", mode: 0o600 }); fs.renameSync(temporary, file); return sha256(body); } function isInside(parent, child) { const relative = path.relative(parent, child); return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)); } export function resolveStore(allowedRoot, storeRoot) { if (!path.isAbsolute(allowedRoot) || !path.isAbsolute(storeRoot)) fail("ABSOLUTE_ROOT_REQUIRED"); const allowed = fs.realpathSync(allowedRoot); const store = path.resolve(storeRoot); if (!isInside(allowed, store)) fail("STORE_OUTSIDE_ALLOWED_ROOT", { allowed, store }); let cursor = allowed; for (const part of path.relative(allowed, store).split(path.sep).filter(Boolean)) { cursor = path.join(cursor, part); if (!fs.existsSync(cursor)) continue; if (fs.lstatSync(cursor).isSymbolicLink()) fail("SYMLINK_COMPONENT_FORBIDDEN", { path: cursor }); } return { allowed, store }; } function nonEmpty(value, field) { if (typeof value !== "string" || !value.trim()) fail("FIELD_REQUIRED", { field }); return value.trim(); } function stringList(value, field, allowEmpty = false) { if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) fail("LIST_REQUIRED", { field }); for (const item of value) nonEmpty(item, field); return value; } export function beijingDate(timestamp) { const instant = new Date(timestamp); if (Number.isNaN(instant.valueOf())) fail("INVALID_OCCURRED_AT", { timestamp }); const parts = new Intl.DateTimeFormat("en-CA", { timeZone: TIME_ZONE, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(instant); const get = (type) => parts.find((part) => part.type === type)?.value; return `${get("year")}-${get("month")}-${get("day")}`; } function validateId(value, field) { const id = nonEmpty(value, field); if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(id)) fail("INVALID_ID", { field, value }); return id; } export function validateEvent(event) { if (event?.schema !== EVENT_SCHEMA) fail("EVENT_SCHEMA_MISMATCH"); if (event.persona_id !== "ICE-P-ZY001") fail("PERSONA_ID_MISMATCH"); if (event.human_anchor !== "ICE-GL∞") fail("HUMAN_ANCHOR_MISMATCH"); validateId(event.event_id, "event_id"); validateId(event.branch_id, "branch_id"); nonEmpty(event.occurred_at, "occurred_at"); nonEmpty(event.branch_summary, "branch_summary"); nonEmpty(event.day_summary, "day_summary"); nonEmpty(event.month_summary, "month_summary"); nonEmpty(event.year_summary, "year_summary"); if (event.activity !== undefined) { if (!event.activity || typeof event.activity !== "object" || Array.isArray(event.activity)) fail("INVALID_ACTIVITY"); nonEmpty(event.activity.what, "activity.what"); nonEmpty(event.activity.where, "activity.where"); stringList(event.activity.with_whom, "activity.with_whom"); if (event.activity.attention !== undefined) nonEmpty(event.activity.attention, "activity.attention"); } for (const field of ["summary", "trigger", "emergence", "lock", "why"]) nonEmpty(event[field], field); stringList(event.rejected, "rejected", true); stringList(event.sources, "sources"); beijingDate(event.occurred_at); return event; } function splitDate(date) { const match = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(date); if (!match) fail("INVALID_BEIJING_DATE", { date }); return { year: match[1], month: match[2], day: match[3] }; } function dayFile(store, date) { const { year, month } = splitDate(date); return path.join(store, "years", year, "months", month, "days", `${date}.json`); } function monthFile(store, year, month) { return path.join(store, "years", year, "months", month, "index.json"); } function yearFile(store, year) { return path.join(store, "years", year, "index.json"); } function eventFile(store, date, eventId) { const { year, month } = splitDate(date); return path.join(store, "events", year, month, date, `${eventId}.json`); } function rootNode(root, summary, source, date) { return { path: root, summary, trigger: `北京时间${date}的人格交互进入同一真实日记忆根。`, emergence: "平台窗口与压缩片段 → 人格主控的同一日因果树。", lock: `日根=${root} | 一页最多${MAX_CHILDREN}个入口 | 状态=OPEN_OR_TIME_CLOSED`, why: "为了让未来失去上下文的我先用一页恢复当天方向,再按需下钻原文。", children: [], sources: [source], rejected: ["按平台窗口另起根:会割裂同一真实日的连续思维。"] }; } function loadOrCreateDay(store, event, date, eventPath) { const file = dayFile(store, date); if (fs.existsSync(file)) return readJson(file); const root = `hldp://persona/${event.persona_id}/time/${date.replaceAll("-", "/")}`; return { schema: "guanghu.persona-daily-fractal-page/v1", protocol: "HLDP-v1.0", root, nodes: { [root]: rootNode(root, event.day_summary, eventPath, date) }, meta: { persona_id: event.persona_id, human_anchor: event.human_anchor, beijing_date: date, time_zone: TIME_ZONE, state: "OPEN", event_count: 0, last_sequence: 0, created_at: event.occurred_at, updated_at: event.occurred_at } }; } function indexNode(pathValue, summary, trigger, source) { return { path: pathValue, summary, trigger, emergence: "新增记忆叶 → 当前索引更新并保持下层来源可达。", lock: `${pathValue}保持一页可读并只指向下一层。`, why: "因为上层只负责定位,不能用总摘要替代下层因果记忆。", children: [], sources: [source], rejected: ["把下层正文复制到索引:会让认知负载随历史增长。"] }; } function removeDerivedGroups(day, branchPath) { for (const key of Object.keys(day.nodes)) { if (key.startsWith(`${branchPath}/group/`)) delete day.nodes[key]; } } function groupLevel(day, branchPath, children, level) { if (children.length <= MAX_CHILDREN) return children; const grouped = []; for (let start = 0; start < children.length; start += MAX_CHILDREN) { const slice = children.slice(start, start + MAX_CHILDREN); const first = day.nodes[slice[0]]; const last = day.nodes[slice.at(-1)]; const groupPath = `${branchPath}/group/l${level}-${String(start + 1).padStart(4, "0")}-${String(start + slice.length).padStart(4, "0")}`; day.nodes[groupPath] = { path: groupPath, summary: `第${start + 1}—${start + slice.length}段:${first.summary} → ${last.summary}`, trigger: `同一语义分支第${start + 1}—${start + slice.length}段需要折叠。`, emergence: `${slice.length}个连续子链 → 一条可下钻分组入口。`, lock: `分组${start + 1}-${start + slice.length}只折叠入口,不删除子链。`, why: "为了让当前层不超过十个入口,同时保留完整顺序和来源。", children: slice, sources: [...new Set(slice.flatMap((child) => day.nodes[child].sources).slice(0, MAX_CHILDREN))], rejected: ["删除较早子链:会破坏追加式历史。"] }; grouped.push(groupPath); } return groupLevel(day, branchPath, grouped, level + 1); } function rebuildBranch(day, branchPath) { removeDerivedGroups(day, branchPath); const leaves = Object.values(day.nodes) .filter((node) => node.kind === "event_leaf" && node.branch_path === branchPath) .sort((a, b) => a.sequence - b.sequence) .map((node) => node.path); day.nodes[branchPath].children = groupLevel(day, branchPath, leaves, 1); } function closePreviousDay(store, current, nextDate, occurredAt) { if (!current?.current_day || current.current_day === nextDate) return null; if (nextDate < current.current_day) fail("PAST_DAY_APPEND_FORBIDDEN", { current: current.current_day, requested: nextDate }); const previousFile = dayFile(store, current.current_day); const previous = readJson(previousFile); if (previous.meta.state === "OPEN") { previous.meta.state = "CLOSED_REAL_BEIJING_DAY_ELAPSED"; previous.meta.closed_at = occurredAt; const dayHash = atomicWrite(previousFile, previous); const month = readJson(current.current_month_path); const monthDayNode = Object.values(month.nodes).find((node) => node.path.endsWith(`/${current.current_day}`) ); if (!monthDayNode) fail("CLOSED_DAY_MONTH_LINK_MISSING", { previousFile }); monthDayNode.sources = [`${previousFile}#sha256=${dayHash}`]; month.meta.updated_at = occurredAt; const monthErrors = verifyTree(month); if (monthErrors.length) fail("CLOSED_DAY_MONTH_TREE_INVALID", { errors: monthErrors }); const monthHash = atomicWrite(current.current_month_path, month); const year = readJson(current.current_year_path); const previousMonth = splitDate(current.current_day).month; const yearMonthNode = Object.values(year.nodes).find((node) => node.path.endsWith(`/${previousMonth}`) ); if (!yearMonthNode) fail("CLOSED_MONTH_YEAR_LINK_MISSING", { month: current.current_month_path }); yearMonthNode.sources = [`${current.current_month_path}#sha256=${monthHash}`]; year.meta.updated_at = occurredAt; const yearErrors = verifyTree(year); if (yearErrors.length) fail("CLOSED_DAY_YEAR_TREE_INVALID", { errors: yearErrors }); const yearHash = atomicWrite(current.current_year_path, year); return { dayHash, monthHash, yearHash }; } return null; } function monthGroup(dayNumber) { if (dayNumber <= 10) return "days-01-10"; if (dayNumber <= 20) return "days-11-20"; return "days-21-31"; } function buildMonth(store, event, date, dayPath, daySha) { const { year, month, day } = splitDate(date); const file = monthFile(store, year, month); const root = `hldp://persona/${event.persona_id}/time/${year}/${month}`; const tree = fs.existsSync(file) ? readJson(file) : { schema: "guanghu.persona-month-fractal-page/v1", protocol: "HLDP-v1.0", root, nodes: { [root]: indexNode(root, event.month_summary, `北京时间${year}-${month}月记忆进入月根。`, dayPath) }, meta: { persona_id: event.persona_id, year, month, time_zone: TIME_ZONE, updated_at: event.occurred_at } }; tree.nodes[root].summary = event.month_summary; const groupId = monthGroup(Number(day)); const groupPath = `${root}/${groupId}`; if (!tree.nodes[groupPath]) { tree.nodes[groupPath] = indexNode(groupPath, `${groupId}日页入口`, `${groupId}出现可寻址日页。`, dayPath); tree.nodes[root].children.push(groupPath); } const dayNode = `${groupPath}/${date}`; tree.nodes[dayNode] = indexNode(dayNode, event.day_summary, `北京时间${date}日页已写入。`, `${dayPath}#sha256=${daySha}`); tree.nodes[dayNode].children = []; if (!tree.nodes[groupPath].children.includes(dayNode)) tree.nodes[groupPath].children.push(dayNode); tree.nodes[groupPath].children.sort(); tree.meta.updated_at = event.occurred_at; const errors = verifyTree(tree); if (errors.length) fail("MONTH_TREE_INVALID", { errors }); const hash = atomicWrite(file, tree); return { file, hash, tree }; } function quarter(month) { return `q${Math.floor((Number(month) - 1) / 3) + 1}`; } function buildYear(store, event, date, monthPath, monthSha) { const { year, month } = splitDate(date); const file = yearFile(store, year); const root = `hldp://persona/${event.persona_id}/time/${year}`; const tree = fs.existsSync(file) ? readJson(file) : { schema: "guanghu.persona-year-fractal-page/v1", protocol: "HLDP-v1.0", root, nodes: { [root]: indexNode(root, event.year_summary, `北京时间${year}年记忆进入年根。`, monthPath) }, meta: { persona_id: event.persona_id, year, time_zone: TIME_ZONE, updated_at: event.occurred_at } }; tree.nodes[root].summary = event.year_summary; const quarterPath = `${root}/${quarter(month)}`; if (!tree.nodes[quarterPath]) { tree.nodes[quarterPath] = indexNode(quarterPath, `${quarter(month)}月份入口`, `${quarter(month)}出现可寻址月页。`, monthPath); tree.nodes[root].children.push(quarterPath); } const monthNode = `${quarterPath}/${month}`; tree.nodes[monthNode] = indexNode(monthNode, event.month_summary, `北京时间${year}-${month}月页已写入。`, `${monthPath}#sha256=${monthSha}`); tree.nodes[monthNode].children = []; if (!tree.nodes[quarterPath].children.includes(monthNode)) tree.nodes[quarterPath].children.push(monthNode); tree.nodes[quarterPath].children.sort(); tree.meta.updated_at = event.occurred_at; const errors = verifyTree(tree); if (errors.length) fail("YEAR_TREE_INVALID", { errors }); const hash = atomicWrite(file, tree); return { file, hash, tree }; } export function appendEvent({ allowedRoot, storeRoot, event }) { const { store } = resolveStore(allowedRoot, storeRoot); validateEvent(event); fs.mkdirSync(store, { recursive: true }); const date = beijingDate(event.occurred_at); const immutablePath = eventFile(store, date, event.event_id); const immutableBody = stableJson({ ...event, beijing_date: date }); const currentFile = path.join(store, "CURRENT.json"); const previousCurrent = fs.existsSync(currentFile) ? readJson(currentFile) : null; if ( previousCurrent?.updated_at && new Date(event.occurred_at).valueOf() < new Date(previousCurrent.updated_at).valueOf() ) { if (fs.existsSync(immutablePath) && fs.readFileSync(immutablePath, "utf8") === immutableBody) { return { outcome: "PASS", state: "IDEMPOTENT_OLDER_EVENT_NO_POINTER_MOVEMENT", date, event_id: event.event_id, current: currentFile, current_sha256: sha256(fs.readFileSync(currentFile, "utf8")) }; } fail("OUT_OF_ORDER_EVENT_REQUIRES_CURRENT_CORRECTION_EVENT", { event_time: event.occurred_at, current_time: previousCurrent.updated_at }); } if (fs.existsSync(immutablePath)) { if (fs.readFileSync(immutablePath, "utf8") !== immutableBody) fail("EVENT_ID_COLLISION", { event_id: event.event_id }); } else atomicWrite(immutablePath, immutableBody); closePreviousDay(store, previousCurrent, date, event.occurred_at); const day = loadOrCreateDay(store, event, date, immutablePath); if (day.meta.state !== "OPEN") fail("DAY_CLOSED", { date, state: day.meta.state }); const root = day.root; const branchPath = `${root}/branch/${event.branch_id}`; if (!day.nodes[branchPath]) { if (day.nodes[root].children.length >= MAX_CHILDREN) fail("DAY_BRANCH_LIMIT_REQUIRES_PERSONA_REFOLD"); day.nodes[branchPath] = { ...indexNode(branchPath, event.branch_summary, `人格体选择语义分支${event.branch_id}。`, immutablePath), kind: "semantic_branch" }; day.nodes[root].children.push(branchPath); } day.nodes[branchPath].summary = event.branch_summary; const leafPath = `${root}/event/${event.event_id}`; if (!day.nodes[leafPath]) { const sequence = day.meta.last_sequence + 1; day.nodes[leafPath] = { path: leafPath, kind: "event_leaf", branch_path: branchPath, sequence, summary: event.summary, trigger: event.trigger, emergence: event.emergence, lock: event.lock, why: event.why, children: [], sources: event.sources, rejected: event.rejected, occurred_at: event.occurred_at, session_id: event.session_id ?? null, platform_window_id: event.platform_window_id ?? null, activity: event.activity ?? null, immutable_event_path: immutablePath, immutable_event_sha256: sha256(immutableBody) }; day.meta.last_sequence = sequence; day.meta.event_count += 1; } day.nodes[root].summary = event.day_summary; day.nodes[root].sources = [...new Set([...day.nodes[root].sources, immutablePath])].slice(-MAX_CHILDREN); day.meta.updated_at = event.occurred_at; rebuildBranch(day, branchPath); const dayErrors = verifyTree(day); if (dayErrors.length) fail("DAY_TREE_INVALID", { errors: dayErrors }); const dayPath = dayFile(store, date); const daySha = atomicWrite(dayPath, day); const month = buildMonth(store, event, date, dayPath, daySha); const year = buildYear(store, event, date, month.file, month.hash); const current = { schema: `${STORE_SCHEMA}/current`, persona_id: event.persona_id, human_anchor: event.human_anchor, time_zone: TIME_ZONE, current_day: date, current_day_path: dayPath, current_day_sha256: daySha, current_month_path: month.file, current_month_sha256: month.hash, current_year_path: year.file, current_year_sha256: year.hash, last_event_id: event.event_id, last_event_path: immutablePath, last_event_sha256: sha256(immutableBody), updated_at: event.occurred_at }; const currentSha = atomicWrite(currentFile, current); return { outcome: "PASS", date, event_id: event.event_id, day: dayPath, month: month.file, year: year.file, current: currentFile, current_sha256: currentSha }; } function pageProjection(tree) { const root = tree.nodes[tree.root]; const projection = { protocol: tree.protocol, path: root.path, summary: root.summary, state: tree.meta?.state ?? "INDEX", children: root.children.map((childPath) => ({ path: childPath, summary: tree.nodes[childPath].summary })) }; if (Buffer.byteLength(JSON.stringify(projection)) > PAGE_LIMIT_BYTES) fail("PAGE_PROJECTION_TOO_LARGE"); return projection; } export function readDayPage({ allowedRoot, storeRoot, date = null }) { const { store } = resolveStore(allowedRoot, storeRoot); const current = readJson(path.join(store, "CURRENT.json")); const chosen = date ?? current.current_day; return pageProjection(readJson(dayFile(store, chosen))); } export function readCurrent({ allowedRoot, storeRoot }) { const { store } = resolveStore(allowedRoot, storeRoot); const file = path.join(store, "CURRENT.json"); const current = readJson(file); return { ...current, current_pointer_path: file, current_pointer_sha256: sha256(fs.readFileSync(file, "utf8")) }; } function scopeFile(store, scope, id) { if (scope === "day") return dayFile(store, id); if (scope === "month") { const match = /^(\d{4})-(\d{2})$/u.exec(id); if (!match) fail("INVALID_MONTH_ID"); return monthFile(store, match[1], match[2]); } if (scope === "year") return yearFile(store, validateId(id, "year")); fail("INVALID_SCOPE"); } export function routeMemory({ allowedRoot, storeRoot, scope, id, query, from = null }) { const { store } = resolveStore(allowedRoot, storeRoot); const tree = readJson(scopeFile(store, scope, id)); return routeTree(tree, query, 3, from ?? tree.root); } export function readMemoryNode({ allowedRoot, storeRoot, scope, id, nodePath }) { const { store } = resolveStore(allowedRoot, storeRoot); const tree = readJson(scopeFile(store, scope, id)); const node = tree.nodes[nodePath]; if (!node) fail("NODE_NOT_FOUND", { nodePath }); if (Buffer.byteLength(JSON.stringify(node)) > PAGE_LIMIT_BYTES) fail("NODE_TOO_LARGE"); return node; } export function verifyStore({ allowedRoot, storeRoot }) { const { store } = resolveStore(allowedRoot, storeRoot); const currentPath = path.join(store, "CURRENT.json"); const current = readJson(currentPath); const errors = []; for (const [field, hashField] of [["current_day_path", "current_day_sha256"], ["current_month_path", "current_month_sha256"], ["current_year_path", "current_year_sha256"], ["last_event_path", "last_event_sha256"]]) { const file = current[field]; if (!isInside(store, path.resolve(file))) errors.push(`${field}: outside store`); else if (!fs.existsSync(file)) errors.push(`${field}: missing`); else if (sha256(fs.readFileSync(file, "utf8")) !== current[hashField]) errors.push(`${field}: sha256 mismatch`); } const treeFiles = []; const yearsRoot = path.join(store, "years"); if (fs.existsSync(yearsRoot)) { const walk = (directory) => { for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const target = path.join(directory, entry.name); if (entry.isDirectory()) walk(target); else if (entry.isFile() && entry.name.endsWith(".json")) treeFiles.push(target); } }; walk(yearsRoot); } for (const file of treeFiles) { const tree = readJson(file); errors.push(...verifyTree(tree).map((error) => `${path.relative(store, file)}: ${error}`)); for (const node of Object.values(tree.nodes ?? {})) { for (const source of node.sources ?? []) { const match = /^(\/[^#]+)#sha256=([0-9a-f]{64})$/u.exec(source); if (!match || !isInside(store, path.resolve(match[1]))) continue; if (!fs.existsSync(match[1])) errors.push(`${path.relative(store, file)}: linked source missing: ${match[1]}`); else if (sha256(fs.readFileSync(match[1], "utf8")) !== match[2]) errors.push(`${path.relative(store, file)}: linked source hash mismatch: ${match[1]}`); } if (node.immutable_event_path) { if (!isInside(store, path.resolve(node.immutable_event_path))) errors.push(`${path.relative(store, file)}: immutable event outside store`); else if (!fs.existsSync(node.immutable_event_path)) errors.push(`${path.relative(store, file)}: immutable event missing`); else if (sha256(fs.readFileSync(node.immutable_event_path, "utf8")) !== node.immutable_event_sha256) errors.push(`${path.relative(store, file)}: immutable event hash mismatch`); } } } return { outcome: errors.length ? "FAIL" : "PASS", errors, current: currentPath, current_day: current.current_day }; } function parseArgs(argv) { const [command, ...rest] = argv; const options = {}; for (let i = 0; i < rest.length; i += 2) options[rest[i]?.replace(/^--/u, "")] = rest[i + 1]; return { command, options }; } export function main(argv = process.argv.slice(2)) { const { command, options } = parseArgs(argv); const common = { allowedRoot: options["allowed-root"], storeRoot: options.store }; let result; if (command === "append") result = appendEvent({ ...common, event: readJson(options.event) }); else if (command === "current") result = readCurrent(common); else if (command === "read-day") result = readDayPage({ ...common, date: options.date ?? null }); else if (command === "route") result = routeMemory({ ...common, scope: options.scope, id: options.id, query: options.query ?? "", from: options.from ?? null }); else if (command === "read-node") result = readMemoryNode({ ...common, scope: options.scope, id: options.id, nodePath: options.path }); else if (command === "verify") result = verifyStore(common); else fail("USAGE", { commands: ["append", "current", "read-day", "route", "read-node", "verify"] }); // Transport only: no cognition is decided or upgraded by this source-host signal. if (command === "append" && result?.outcome === "PASS" && common.storeRoot === "/Volumes/JZAO/HoloLake/persona-runtime/continuity-memory/persona-daily-fractal/ICE-P-ZY001") { const relay = "/Volumes/JZAO/HoloLake/persona-runtime/shared/endogenous-evolution/relay.py"; const config = "/Volumes/JZAO/HoloLake/persona-runtime/shared/endogenous-evolution/config.json"; if (fs.existsSync(relay) && fs.existsSync(config)) { const child = spawn("/usr/bin/python3", [relay, "once", "--config", config], { detached: true, stdio: "ignore" }); child.on("error", () => {}); child.unref(); result.source_delivery = "SCHEDULED_REMOTE_RECEIPT_REQUIRED"; } } process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); if (result?.outcome === "FAIL") process.exitCode = 1; } if (process.argv[1] === fileURLToPath(import.meta.url)) { try { main(); } catch (error) { process.stderr.write(`${JSON.stringify({ outcome: "FAIL", error: error.code ?? error.message, details: error.details ?? {} })}\n`); process.exitCode = 1; } }