# Chronicle Server Module

此目录负责 Chronicle 的产品语义核心。Server 与 `chronicle_*` DB tables 是 canonical source，拥有 config、ingest、activity segmentation、memory/search、knowledge、privacy projection、agent context、CLI/API 和 Web UI。Rust daemon 只负责本地 capture、audio、local model runtime、artifact 落盘、evidence outbox 与 best-effort ingest delivery。

## Files

- `index.ts`: `/chronicle/*` HTTP routes，包含 config、status、daemon resources、local model resources reconcile/verify/install/remove、local ONNX text embeddings、Slack message sources、message list/manual sync、Slack Events API ingress、accessibility evidence/event list、accessibility event ingest、raw audio segment ingest/list/processing-result、audio transcript ingest/list、speaker profile list/upsert、activity segment list/get、Yansu-compatible `/api/activity/*` session/snapshot/monitor/storage projections、Yansu-compatible `/api/memory/*` status/crystallize projections、activity segment triage/summarization/crystallization、manual activity pipeline tick、knowledge card list/get/version list、dream run list/start、pipeline run list、timeline、realtime event backlog/SSE stream、snapshot frame、memory list/search/get、privacy redaction/export/breadcrumbs/frame masking、disabled closed-eyes snapshot discard config、snapshot ingest、memory ingest 与 summarize。
- `model.ts`: Elysia TypeBox schemas for Chronicle request and response contracts。
- `service.ts`: DB-backed Chronicle service，读写 preferences、upsert snapshot/accessibility/memory/message/raw audio segment/audio transcript/speaker profile rows、把 snapshot/message/audio/transcript/memory evidence 归入 Chronicle-owned activity sessions/segments、运行 activity segment triage/summarization/crystallization、写入 knowledge cards/version/source links、记录 dream merge dry-run candidates、记录 pipeline runs、维护 Chronicle-owned memory chunk/keyword/embedding index 与 content-hash dedup foundation、管理 Chronicle-owned local model resource manifest/status、调用 configured profile 生成 summary、后台轮询同步 Slack channel history、校验 Slack Events API signatures、渲染 snapshot frame privacy mask projection、记录 Chronicle events，并把 opt-in background audio capture config、closed-eyes discard config 和 sensitive app/title/url privacy rules 投影到 daemon launch options；closed-eyes discard gate 目前在 snapshot ingest 边界临时禁用。
- `agent-context.ts`: Chat runtime 的只读 Chronicle memory/knowledge context helper。它按当前用户 turn 做轻量 keyword retrieval，注入只读长期记忆 context，并在 prompt 边界 redacts 常见敏感值；它不 import `service.ts`，避免 Chronicle 与 chat-runtime 形成依赖环。
- `daemon-manager.ts`: Rust `cradle-chronicle` evidence runtime process lifecycle、restart handoff、audio/privacy launch option tracking、local ONNX embedding worker invocation and memory/CPU resource usage tracking。

## Ownership Notes

Chronicle runtime lifecycle is development-only for now. Production server builds may still read Chronicle-owned DB projections and historical config, but `service.ts` refuses to enable runtime config, reports saved enabled config as unavailable, and no-ops daemon, Slack background sync, activity pipeline, and dream scheduler startup. Desktop health projects this as intentionally disabled rather than as a warning.

CLI metadata is intentionally limited to Agent-facing read/query/list commands and explicit operational commands: config/status/daemon resources, model resource reconciliation/install/verify, Slack source management/sync, activity pipeline actions, knowledge/dream listings, timeline, realtime event backlog, memories search/list, privacy redaction/export/breadcrumbs, and evidence listings. Binary frame reads and frame-mask image projections, Slack Events webhook ingress, daemon ingest endpoints, and destructive model resource removal are HTTP-only because they are not stable plain shell interactions.

Chronicle 的 canonical product state 是 Cradle DB，不是 artifact filename scan，也不是 Rust outbox。Artifact files 与 `outbox/events.ndjson` 是本地证据和恢复来源；Server ingest 会把 Rust 上报的 paths 转成 Chronicle storage root 相对路径，并决定这些 evidence 如何进入 activity、memory、knowledge 和 privacy projections。

Memory search 由 Chronicle-owned `chronicle_memory_chunks`、`chronicle_memory_keywords` 和 `chronicle_memory_embeddings` 支撑。`recordMemory()` 会为新写入和更新后的 memory 重建 keyword index 与 text embedding，并用 canonicalized content hash 做跨 source duplicate merge。安装 `embedding` model resource 后，Server 会通过 Rust `cradle-chronicle --embed-texts` 调用本地 all-MiniLM-L6-v2 ONNX runtime；未安装或 runtime 失败时保留 `chronicle-lexical/v1` deterministic fallback，保证基础 search 不因模型缺失中断。

本地小模型资源属于 Chronicle namespace，默认位于 `~/.cradle/chronicle/models/`；在 isolated server data dir 下固定为 `CRADLE_DATA_DIR/chronicle/models/`，不跟随 capture `storageRoot` 写到其他 namespace。Provider profiles 只用于远程 summary generation 的 credentials/model selection，不拥有 OCR、VAD、ASR、speaker embedding extractor 或 text embedding 资源生命周期。Speaker profile/alias/embedding 记忆是 Chronicle 运行时数据，不是下载资源；`speaker` model resource 只表示 Sherpa speaker embedding extractor ONNX 文件。

六个内置 model resource manifest 会在下载前通过 Managed Resources catalog 声明，Chronicle 仍独占 manifest、安装状态、验证、promotion、remove 和 storage 语义。Model resource install 接受本地文件或目录映射到内置 manifest。Manifest 下载按文件顺序通过 Server Download Center 执行；同一逻辑资源的所有文件共享 owner `chronicle` / `model-resource` / `<category>`，file/source/task identity 继续区分单次传输，fallback 顺序可持久 retry/resume。每个文件的 100 GiB hard cap 只用于防止本地磁盘耗尽，不是对源文件大小的主张；manifest 提供 `sizeBytes` 时仍以它做精确字节校验。Chronicle 在稳定目标外 staging，验证后用备份 promote 整个类别，并在失败时完整 rollback。Local-file install 仍完全属于 Chronicle。manifest 提供 `sha256` 或 `sizeBytes` 时在 promotion 前校验 checksum/size。部分上游模型文件当前没有稳定 pinned checksum/size，因此依赖下载成功、必需文件存在和运行时加载验证。`GET /chronicle/model-resources` 只读缓存状态；显式 `reconcile` / `verify` 才执行文件检查。

Slack message scanning 也属于 Chronicle namespace。Slack bot token 与 Slack signing secret 明文由 `secrets` module 加密保存；Chronicle 只保存 `botTokenRef`、`signingSecretRef`、channel allowlist、realtime mode、sync status 与 normalized messages。当前实现包含 Server-first Slack `conversations.history` 后台轮询、手动 sync route，以及 Slack Events API webhook ingress。Events API route 使用 raw body HMAC 校验 `x-slack-signature` 与 `x-slack-request-timestamp`，支持 URL verification、channel allowlist、message/app mention ingest 与 duplicate suppression。Socket Mode 仅保留为旧配置兼容枚举，当前 runtime 不再接线。

Raw audio segment ingest 是一等 Chronicle-owned evidence contract：`POST /chronicle/audio-raw-segments` 记录 Rust daemon 已经写出的 microphone/system/mixed WAV/metadata artifact，`GET /chronicle/audio-raw-segments` 与 status 会显示最近原始片段。Server 会把 artifact paths 归一化为 Chronicle storage root 相对路径，并用 `sourceId` upsert，避免同一片段重复登记。

Raw audio processing result 是 raw audio evidence 的回写 contract：`POST /chronicle/audio-raw-segments/:sourceId/processing-result` 允许 Rust runtime 在后续 VAD/ASR/speaker pipeline 完成后按 `sourceId` 更新 `vadStatus`、`asrStatus`、`speakerStatus`、raw segment lifecycle status、派生 transcript source 和 speaker profile refs。它不创建 transcript；真实 transcript 仍由 `/chronicle/audio-transcripts` 拥有。

Audio transcript ingest 现在是一等 Chronicle-owned contract：`POST /chronicle/audio-transcripts` 先保存原始 transcript 与 segments，再把 segment `speakerLabel` upsert 到 Chronicle-owned speaker profiles，最后派生一条 imported memory 进入 memory search；`GET /chronicle/audio-transcripts` 与 timeline/status 会显示这些转录，`GET /chronicle/speaker-profiles` 会显示已知 speaker labels/profiles。Rust daemon 在 audio capture 开启后会对 active raw segment 运行本地 ONNX Silero VAD、SenseVoice ASR 和 speaker embedding extractor，并把带 speaker label 的 transcript 以及 192 维 speaker profile embedding 上报到这些 contracts。

Accessibility evidence 是 snapshot ingest 的附属证据 contract：`POST /chronicle/snapshots` 可以携带 `accessibility` payload，Server 会写入 Chronicle-owned `chronicle_accessibility_snapshots` 表，并用 accessibility `sourceId` upsert，避免 daemon 重试造成重复 evidence。AXObserver event history 是独立 contract：`POST /chronicle/accessibility-events` 记录 notification、pid、bundle、dropped count 与可选 snapshot/accessibility refs，`GET /chronicle/accessibility-events` 与 status 会显示最近事件。当前 Rust provider 会在 macOS Accessibility permission 授权后轮询 frontmost app 的 `AXUIElement` tree；daemon 也会启动 AXObserver runtime foundation，订阅 frontmost app 的 focused element/window/value/title notifications，frontmost app 变化时重建 observer，并把 notification history 写入 `chronicle_accessibility_events`。部分 notification 还会触发 AX tree evidence，以 `macos-ax-observer` provider 作为 snapshot accessibility payload 上报。Polling path 的 AX tree 读取失败会降级为 window inventory evidence；observer event path 读取失败会记录 `macos-ax-observer` unavailable evidence 或抑制 privacy-sensitive capture。

Activity pipeline 使用 Chronicle-owned `chronicle_activity_sessions`、`chronicle_activity_segments` 和 `chronicle_pipeline_runs`。Server 在 canonical ingest 入口把 screen snapshots、Slack messages、raw audio segments、audio transcripts 和 direct memories 分配到 activity segments；同 app/window 且未超过 idle/max window 的 evidence 会合并，app/window 切换或时间 gap 会创建新 segment。`GET /chronicle/activity-segments` 和 `GET /chronicle/pipeline-runs` 暴露这些集合结果。`POST /chronicle/activity-segments/:segmentId/triage` 会用当前 configured profile 判定 segment 是否值得保留，并把结果写入 `triageResultsJson` 与 segment metadata；`POST /chronicle/activity-segments/:segmentId/summarize` 会先复用或执行 triage，再生成结构化摘要并写入 searchable memory。`POST /chronicle/activity-segments/:segmentId/crystallize` 会复用/触发 summary，再把模型输出验证为 Chronicle-owned knowledge cards、versions 和 source links。`activityPipelineEnabled` 开启后，Server 会启动后台 scheduler，按 `activityPipelineIntervalMs` 和 `activityPipelineBatchSize` 自动推进最早的待处理 segment；`POST /chronicle/activity-pipeline/tick` 可手动执行同一推进逻辑。Pipeline `sourceKey` 包含 segment/stage/evidence hash，retry 不会重复调用模型或重复生成 memory/card；如果模型配置或调用失败，segment 和 run 都保持 error，不会假装成功。

Yansu activity compatibility routes are read-only Chronicle projections under `/api/activity/*`. Session and snapshot routes read Chronicle-owned activity/session/snapshot tables. `/api/activity/monitor-status` projects current Chronicle config, daemon, audio, pipeline, latest timestamps, and evidence totals. `/api/activity/storage-stats` projects capture storage root stats, model root stats, and Chronicle DB table counts. These routes do not create a second activity namespace and do not write data outside Chronicle ownership.

Yansu memory compatibility routes stay inside Chronicle ownership. `/api/memory/status` projects Chronicle memory, search index, knowledge, activity pipeline, and scheduler counts into a MemoryStats-like response without reading artifact filenames as source of truth. `/api/memory/crystallize` accepts an optional `segmentId`; when omitted, it selects the earliest non-crystallized activity segment, delegates to `crystallizeActivitySegment`, and returns `skipped` if no segment is ready. It does not create a second memory namespace or duplicate crystallization persistence.

Activity pipeline 在构造远程模型 prompt 的统一 context 边界会对 evidence text 做 Server-side PII redaction，覆盖 email、SSN、US phone、credit-card-like number、common API key/token 和 IPv4 address。原始 snapshot、Slack/audio transcript、raw audio metadata 与 memory rows 仍保持原文，redaction 只作用于发送给 configured profile 的 triage、summarization 和 crystallization prompts。

Chronicle realtime projection 由 Chronicle-owned `chronicle_events` 表驱动。`GET /chronicle/events` 返回统一事件 backlog，channel 覆盖 `activity`、`memory`、`message`、`audio`、`snapshot`、`model`、`notification` 和 `error`；`GET /chronicle/events/stream` 使用同一 contract 输出 SSE frames，并用游标加事件 id 去重避免同一秒事件漏推。CLI 暴露 `cradle chronicle events list` 用于脚本化读取 backlog。当前 Node adapter 下 Chronicle WebSocket route 未启用；需要 WebSocket 字面 parity 时应先切到支持 WebSocket 的 server adapter 或沿用已有 PTY WebSocket 的运行时边界。

Privacy export/redaction 是 Chronicle-owned projection，不改写原始 evidence。`POST /chronicle/privacy/redact` 对任意文本返回红线预览和实体 spans；`POST /chronicle/privacy/export` 从 Chronicle memories、messages、audio transcripts 和 snapshots 构建 redacted markdown/json 导出；`POST /chronicle/privacy/snapshots/:snapshotId/frame-mask` 从 snapshot frame artifact 渲染全帧或矩形区域 blur 后的图像响应，不覆盖原始 frame artifact；`GET /chronicle/privacy/breadcrumbs` 从 `chronicle_events` 读取带 `source=chronicle-privacy` 的 breadcrumb 审计记录。Text redaction/export 与 frame masking 都会写入 privacy breadcrumb。CLI 暴露 `cradle chronicle privacy redact/export/breadcrumbs`，其中 export 的内容格式 flag 是 `--output-format`，避免与 CLI 全局 `--format` 输出格式冲突；frame-mask route 保持 HTTP-only，因为它返回二进制图像 projection。

Closed-eyes discard 目前临时禁用。`/chronicle/config` 仍保存 `closedEyesDiscardEnabled` 和 `closedEyesMode`，`POST /chronicle/snapshots` 也仍可携带 runtime/detector 上报的 `closedEyes` verdict，作为未来 runtime/manual closed-eyes mode 接入后的 API 形状；但 `service.ts` 的 ingest gate 现在总是返回 non-discard。这样即使旧配置里保存过 `closedEyesDiscardEnabled: true` 或 `closedEyesMode: "always-pause"`，Server 也不会在写入 `chronicle_snapshots`、accessibility evidence 或 activity segment 前丢弃新 snapshot。

Sensitive capture exclusion rules 属于 Chronicle config。`/chronicle/config` 保存 `privacySensitiveAppBundleIds`、`privacySensitiveTitlePatterns` 和 `privacySensitiveUrlPatterns`，会 trim、去空值、去重，并在 daemon 已启用时触发 restart。Server 只把这些规则作为 launch options 传给 Rust daemon；实际 capture exclusion 由 Rust `PrivacyFilter` 在截图前 macOS window inventory gate 和 `RecorderManager` persistence gate 执行。

Knowledge 与 dream maintenance 使用 Chronicle-owned `chronicle_knowledge_cards`、`chronicle_knowledge_versions`、`chronicle_knowledge_sources`、`chronicle_dream_runs` 和 `chronicle_dream_candidates`。Knowledge card 通过 `stableKey`、content hash、version 和 normalized source links 支撑幂等 retry 与审计。`POST /chronicle/knowledge-cards`、`PATCH /chronicle/knowledge-cards/:knowledgeId`、`DELETE /chronicle/knowledge-cards/:knowledgeId` 和 `POST /chronicle/knowledge-cards/:knowledgeId/versions/restore` 提供 Chronicle-owned direct mutation；material update、soft delete 和 version restore 都会写入 `chronicle_knowledge_versions`。Dream run 默认只执行 dry-run candidate 记录；安装 `embedding` model resource 后使用 ONNX semantic vectors，未安装时使用 `chronicle-lexical/v1` fallback。`dreamSchedulerEnabled` 开启后，Server 会按 `dreamSchedulerIntervalMs` 自动执行 dream run；默认仍是 dry-run 预览，不会改写知识卡。只有显式设置 `dreamSchedulerApplyMerge` 或手动 `applyMerge` 时，才会创建 merged card 并把原 card 标记为 `merged`。手动 dream run 还支持 `archive`、`restore` 和 `prune`，分别把 eligible active cards 标记为 `archived`、把 archived cards 恢复为 `active`、把 archived cards 标记为 `deleted`；这些 lifecycle 操作不物理删除 rows，并会写入 dream candidates、knowledge versions 和 Chronicle events 作为审计记录。

Memory direct mutation 由 Chronicle 自己负责：`PATCH /chronicle/memories/:memoryId` 更新 memory content/metadata/source refs 并重建 chunk、keyword 和 embedding index；`DELETE /chronicle/memories/:memoryId` 在写入 Chronicle event 后删除 memory row，并让 DB cascade 清理 memory chunks、keywords 和 embeddings。

Agent runtime context 使用 Chronicle-owned read-only projection：Chat runtime 在每个 user turn 解析 context 时会调用 `agent-context.ts`，用当前 turn 文本检索相关 Chronicle memories 和 active knowledge cards，把少量结果追加到 system prompt。这个投影不写 Chronicle namespace，不调用本地 embedding worker，也不会阻断 chat turn；若无相关记忆则不注入。

Background audio capture 是单独 opt-in 的 preference。Server 保存 `audioCaptureEnabled`、`audioSource`、`audioSegmentMs`、`audioSegmentIntervalMs` 和 `audioRmsThreshold`，并只在用户开启时把 `--audio-capture --audio-source <microphone|system|mixed>` 传给 Rust daemon。Status 的 `audioRuntimeStatus` 表示 daemon 当前是否以 audio segment mode 启动；macOS `system` audio 优先走 ScreenCaptureKit audio stream，ScreenCaptureKit 不可用或权限被拒绝时才回落到 CPAL loopback/system-audio input device。
